github.com/undoio/delve@v1.9.0/pkg/proc/native/proc_windows.go (about) 1 package native 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "syscall" 8 "unsafe" 9 10 sys "golang.org/x/sys/windows" 11 12 "github.com/undoio/delve/pkg/proc" 13 "github.com/undoio/delve/pkg/proc/internal/ebpf" 14 "github.com/undoio/delve/pkg/proc/winutil" 15 ) 16 17 // osProcessDetails holds Windows specific information. 18 type osProcessDetails struct { 19 hProcess syscall.Handle 20 breakThread int 21 entryPoint uint64 22 running bool 23 } 24 25 func (os *osProcessDetails) Close() {} 26 27 // Launch creates and begins debugging a new process. 28 func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, redirects [3]string) (*proc.Target, error) { 29 argv0Go, err := filepath.Abs(cmd[0]) 30 if err != nil { 31 return nil, err 32 } 33 34 env := proc.DisableAsyncPreemptEnv() 35 36 stdin, stdout, stderr, closefn, err := openRedirects(redirects, true) 37 if err != nil { 38 return nil, err 39 } 40 41 creationFlags := uint32(_DEBUG_ONLY_THIS_PROCESS) 42 if flags&proc.LaunchForeground == 0 { 43 creationFlags |= syscall.CREATE_NEW_PROCESS_GROUP 44 } 45 46 var p *os.Process 47 dbp := newProcess(0) 48 dbp.execPtraceFunc(func() { 49 attr := &os.ProcAttr{ 50 Dir: wd, 51 Files: []*os.File{stdin, stdout, stderr}, 52 Sys: &syscall.SysProcAttr{ 53 CreationFlags: creationFlags, 54 }, 55 Env: env, 56 } 57 p, err = os.StartProcess(argv0Go, cmd, attr) 58 }) 59 closefn() 60 if err != nil { 61 return nil, err 62 } 63 defer p.Release() 64 65 dbp.pid = p.Pid 66 dbp.childProcess = true 67 68 tgt, err := dbp.initialize(argv0Go, []string{}) 69 if err != nil { 70 dbp.Detach(true) 71 return nil, err 72 } 73 return tgt, nil 74 } 75 76 func initialize(dbp *nativeProcess) error { 77 // It should not actually be possible for the 78 // call to waitForDebugEvent to fail, since Windows 79 // will always fire a CREATE_PROCESS_DEBUG_EVENT event 80 // immediately after launching under DEBUG_ONLY_THIS_PROCESS. 81 // Attaching with DebugActiveProcess has similar effect. 82 var err error 83 var tid, exitCode int 84 dbp.execPtraceFunc(func() { 85 tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking) 86 }) 87 if err != nil { 88 return err 89 } 90 if tid == 0 { 91 dbp.postExit() 92 return proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode} 93 } 94 // Suspend all threads so that the call to _ContinueDebugEvent will 95 // not resume the target. 96 for _, thread := range dbp.threads { 97 if !thread.os.dbgUiRemoteBreakIn { 98 _, err := _SuspendThread(thread.os.hThread) 99 if err != nil { 100 return err 101 } 102 } 103 } 104 105 dbp.execPtraceFunc(func() { 106 err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE) 107 }) 108 return err 109 } 110 111 // findExePath searches for process pid, and returns its executable path. 112 func findExePath(pid int) (string, error) { 113 // Original code suggested different approach (see below). 114 // Maybe it could be useful in the future. 115 // 116 // Find executable path from PID/handle on Windows: 117 // https://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx 118 119 p, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(pid)) 120 if err != nil { 121 return "", err 122 } 123 defer syscall.CloseHandle(p) 124 125 n := uint32(128) 126 for { 127 buf := make([]uint16, int(n)) 128 err = _QueryFullProcessImageName(p, 0, &buf[0], &n) 129 switch err { 130 case syscall.ERROR_INSUFFICIENT_BUFFER: 131 // try bigger buffer 132 n *= 2 133 // but stop if it gets too big 134 if n > 10000 { 135 return "", err 136 } 137 case nil: 138 return syscall.UTF16ToString(buf[:n]), nil 139 default: 140 return "", err 141 } 142 } 143 } 144 145 // Attach to an existing process with the given PID. 146 func Attach(pid int, _ []string) (*proc.Target, error) { 147 dbp := newProcess(pid) 148 var err error 149 dbp.execPtraceFunc(func() { 150 // TODO: Probably should have SeDebugPrivilege before starting here. 151 err = _DebugActiveProcess(uint32(pid)) 152 }) 153 if err != nil { 154 return nil, err 155 } 156 exepath, err := findExePath(pid) 157 if err != nil { 158 return nil, err 159 } 160 tgt, err := dbp.initialize(exepath, []string{}) 161 if err != nil { 162 dbp.Detach(true) 163 return nil, err 164 } 165 return tgt, nil 166 } 167 168 // kill kills the process. 169 func (dbp *nativeProcess) kill() error { 170 if dbp.exited { 171 return nil 172 } 173 174 p, err := os.FindProcess(dbp.pid) 175 if err != nil { 176 return err 177 } 178 defer p.Release() 179 180 // TODO: Should not have to ignore failures here, 181 // but some tests appear to Kill twice causing 182 // this to fail on second attempt. 183 _ = syscall.TerminateProcess(dbp.os.hProcess, 1) 184 185 dbp.execPtraceFunc(func() { 186 dbp.waitForDebugEvent(waitBlocking | waitDontHandleExceptions) 187 }) 188 189 p.Wait() 190 191 dbp.postExit() 192 return nil 193 } 194 195 func (dbp *nativeProcess) requestManualStop() error { 196 if !dbp.os.running { 197 return nil 198 } 199 dbp.os.running = false 200 return _DebugBreakProcess(dbp.os.hProcess) 201 } 202 203 func (dbp *nativeProcess) updateThreadList() error { 204 // We ignore this request since threads are being 205 // tracked as they are created/killed in waitForDebugEvent. 206 return nil 207 } 208 209 func (dbp *nativeProcess) addThread(hThread syscall.Handle, threadID int, attach, suspendNewThreads bool, dbgUiRemoteBreakIn bool) (*nativeThread, error) { 210 if thread, ok := dbp.threads[threadID]; ok { 211 return thread, nil 212 } 213 thread := &nativeThread{ 214 ID: threadID, 215 dbp: dbp, 216 os: new(osSpecificDetails), 217 } 218 thread.os.dbgUiRemoteBreakIn = dbgUiRemoteBreakIn 219 thread.os.hThread = hThread 220 dbp.threads[threadID] = thread 221 if dbp.memthread == nil { 222 dbp.memthread = dbp.threads[threadID] 223 } 224 if suspendNewThreads && !dbgUiRemoteBreakIn { 225 _, err := _SuspendThread(thread.os.hThread) 226 if err != nil { 227 return nil, err 228 } 229 } 230 231 for _, bp := range dbp.Breakpoints().M { 232 if bp.WatchType != 0 { 233 err := thread.writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex) 234 if err != nil { 235 return nil, err 236 } 237 } 238 } 239 240 return thread, nil 241 } 242 243 type waitForDebugEventFlags int 244 245 const ( 246 waitBlocking waitForDebugEventFlags = 1 << iota 247 waitSuspendNewThreads 248 waitDontHandleExceptions 249 ) 250 251 const _MS_VC_EXCEPTION = 0x406D1388 // part of VisualC protocol to set thread names 252 253 func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) { 254 var debugEvent _DEBUG_EVENT 255 shouldExit := false 256 for { 257 continueStatus := uint32(_DBG_CONTINUE) 258 var milliseconds uint32 = 0 259 if flags&waitBlocking != 0 { 260 milliseconds = syscall.INFINITE 261 } 262 // Wait for a debug event... 263 err := _WaitForDebugEvent(&debugEvent, milliseconds) 264 if err != nil { 265 return 0, 0, err 266 } 267 268 // ... handle each event kind ... 269 unionPtr := unsafe.Pointer(&debugEvent.U[0]) 270 switch debugEvent.DebugEventCode { 271 case _CREATE_PROCESS_DEBUG_EVENT: 272 debugInfo := (*_CREATE_PROCESS_DEBUG_INFO)(unionPtr) 273 hFile := debugInfo.File 274 if hFile != 0 && hFile != syscall.InvalidHandle { 275 err = syscall.CloseHandle(hFile) 276 if err != nil { 277 return 0, 0, err 278 } 279 } 280 dbp.os.entryPoint = uint64(debugInfo.BaseOfImage) 281 dbp.os.hProcess = debugInfo.Process 282 _, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, 283 flags&waitSuspendNewThreads != 0, debugInfo.StartAddress == dbgUiRemoteBreakin.Addr()) 284 if err != nil { 285 return 0, 0, err 286 } 287 break 288 case _CREATE_THREAD_DEBUG_EVENT: 289 debugInfo := (*_CREATE_THREAD_DEBUG_INFO)(unionPtr) 290 _, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, 291 flags&waitSuspendNewThreads != 0, debugInfo.StartAddress == dbgUiRemoteBreakin.Addr()) 292 if err != nil { 293 return 0, 0, err 294 } 295 break 296 case _EXIT_THREAD_DEBUG_EVENT: 297 delete(dbp.threads, int(debugEvent.ThreadId)) 298 break 299 case _OUTPUT_DEBUG_STRING_EVENT: 300 //TODO: Handle debug output strings 301 break 302 case _LOAD_DLL_DEBUG_EVENT: 303 debugInfo := (*_LOAD_DLL_DEBUG_INFO)(unionPtr) 304 hFile := debugInfo.File 305 if hFile != 0 && hFile != syscall.InvalidHandle { 306 err = syscall.CloseHandle(hFile) 307 if err != nil { 308 return 0, 0, err 309 } 310 } 311 break 312 case _UNLOAD_DLL_DEBUG_EVENT: 313 break 314 case _RIP_EVENT: 315 break 316 case _EXCEPTION_DEBUG_EVENT: 317 if flags&waitDontHandleExceptions != 0 { 318 continueStatus = _DBG_EXCEPTION_NOT_HANDLED 319 break 320 } 321 exception := (*_EXCEPTION_DEBUG_INFO)(unionPtr) 322 tid := int(debugEvent.ThreadId) 323 324 switch code := exception.ExceptionRecord.ExceptionCode; code { 325 case _EXCEPTION_BREAKPOINT: 326 327 // check if the exception address really is a breakpoint instruction, if 328 // it isn't we already removed that breakpoint and we can't deal with 329 // this exception anymore. 330 atbp := true 331 if thread, found := dbp.threads[tid]; found { 332 data := make([]byte, dbp.bi.Arch.BreakpointSize()) 333 if _, err := thread.ReadMemory(data, uint64(exception.ExceptionRecord.ExceptionAddress)); err == nil { 334 instr := dbp.bi.Arch.BreakpointInstruction() 335 for i := range instr { 336 if data[i] != instr[i] { 337 atbp = false 338 break 339 } 340 } 341 } 342 if !atbp { 343 thread.setPC(uint64(exception.ExceptionRecord.ExceptionAddress)) 344 } 345 } 346 347 if atbp { 348 dbp.os.breakThread = tid 349 if th := dbp.threads[tid]; th != nil { 350 th.os.setbp = true 351 } 352 return tid, 0, nil 353 } else { 354 continueStatus = _DBG_CONTINUE 355 } 356 case _EXCEPTION_SINGLE_STEP: 357 dbp.os.breakThread = tid 358 return tid, 0, nil 359 case _MS_VC_EXCEPTION: 360 // This exception is sent to set the thread name in VisualC, we should 361 // mask it or it might crash the program. 362 continueStatus = _DBG_CONTINUE 363 default: 364 continueStatus = _DBG_EXCEPTION_NOT_HANDLED 365 } 366 case _EXIT_PROCESS_DEBUG_EVENT: 367 debugInfo := (*_EXIT_PROCESS_DEBUG_INFO)(unionPtr) 368 exitCode = int(debugInfo.ExitCode) 369 shouldExit = true 370 default: 371 return 0, 0, fmt.Errorf("unknown debug event code: %d", debugEvent.DebugEventCode) 372 } 373 374 // .. and then continue unless we received an event that indicated we should break into debugger. 375 err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, continueStatus) 376 if err != nil { 377 return 0, 0, err 378 } 379 380 if shouldExit { 381 return 0, exitCode, nil 382 } 383 } 384 } 385 386 func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { 387 var err error 388 var tid, exitCode int 389 dbp.execPtraceFunc(func() { 390 tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking) 391 }) 392 if err != nil { 393 return nil, err 394 } 395 if tid == 0 { 396 dbp.postExit() 397 return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode} 398 } 399 th := dbp.threads[tid] 400 return th, nil 401 } 402 403 func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { 404 return 0, nil, fmt.Errorf("not implemented: wait") 405 } 406 407 func (dbp *nativeProcess) exitGuard(err error) error { 408 return err 409 } 410 411 func (dbp *nativeProcess) resume() error { 412 for _, thread := range dbp.threads { 413 if thread.CurrentBreakpoint.Breakpoint != nil { 414 if err := thread.StepInstruction(); err != nil { 415 return err 416 } 417 thread.CurrentBreakpoint.Clear() 418 } 419 } 420 421 for _, thread := range dbp.threads { 422 _, err := _ResumeThread(thread.os.hThread) 423 if err != nil { 424 return err 425 } 426 } 427 dbp.os.running = true 428 429 return nil 430 } 431 432 // stop stops all running threads threads and sets breakpoints 433 func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { 434 if dbp.exited { 435 return nil, proc.ErrProcessExited{Pid: dbp.pid} 436 } 437 438 dbp.os.running = false 439 for _, th := range dbp.threads { 440 th.os.setbp = false 441 } 442 trapthread.os.setbp = true 443 444 // While the debug event that stopped the target was being propagated 445 // other target threads could generate other debug events. 446 // After this function we need to know about all the threads 447 // stopped on a breakpoint. To do that we first suspend all target 448 // threads and then repeatedly call _ContinueDebugEvent followed by 449 // waitForDebugEvent in non-blocking mode. 450 // We need to explicitly call SuspendThread because otherwise the 451 // call to _ContinueDebugEvent will resume execution of some of the 452 // target threads. 453 454 err := trapthread.SetCurrentBreakpoint(true) 455 if err != nil { 456 return nil, err 457 } 458 459 context := winutil.NewCONTEXT() 460 461 for _, thread := range dbp.threads { 462 thread.os.delayErr = nil 463 if !thread.os.dbgUiRemoteBreakIn { 464 // Wait before reporting the error, the thread could be removed when we 465 // call waitForDebugEvent in the next loop. 466 _, thread.os.delayErr = _SuspendThread(thread.os.hThread) 467 if thread.os.delayErr == nil { 468 // This call will block until the thread has stopped. 469 _ = _GetThreadContext(thread.os.hThread, context) 470 } 471 } 472 } 473 474 for { 475 var err error 476 var tid int 477 dbp.execPtraceFunc(func() { 478 err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE) 479 if err == nil { 480 tid, _, _ = dbp.waitForDebugEvent(waitSuspendNewThreads) 481 } 482 }) 483 if err != nil { 484 return nil, err 485 } 486 if tid == 0 { 487 break 488 } 489 err = dbp.threads[tid].SetCurrentBreakpoint(true) 490 if err != nil { 491 return nil, err 492 } 493 } 494 495 // Check if trapthread still exist, if the process is dying it could have 496 // been removed while we were stopping the other threads. 497 trapthreadFound := false 498 for _, thread := range dbp.threads { 499 if thread.ID == trapthread.ID { 500 trapthreadFound = true 501 } 502 if thread.os.delayErr != nil && thread.os.delayErr != syscall.Errno(0x5) { 503 // Do not report Access is denied error, it is caused by the thread 504 // having already died but we haven't been notified about it yet. 505 return nil, thread.os.delayErr 506 } 507 } 508 509 if !trapthreadFound { 510 wasDbgUiRemoteBreakIn := trapthread.os.dbgUiRemoteBreakIn 511 // trapthread exited during stop, pick another one 512 trapthread = nil 513 for _, thread := range dbp.threads { 514 if thread.CurrentBreakpoint.Breakpoint != nil && thread.os.delayErr == nil { 515 trapthread = thread 516 break 517 } 518 } 519 if trapthread == nil && wasDbgUiRemoteBreakIn { 520 // If this was triggered by a manual stop request we should stop 521 // regardless, pick a thread. 522 for _, thread := range dbp.threads { 523 return thread, nil 524 } 525 } 526 } 527 528 return trapthread, nil 529 } 530 531 func (dbp *nativeProcess) detach(kill bool) error { 532 if !kill { 533 //TODO(aarzilli): when debug.Target exist Detach should be moved to 534 // debug.Target and the call to RestoreAsyncPreempt should be moved there. 535 for _, thread := range dbp.threads { 536 _, err := _ResumeThread(thread.os.hThread) 537 if err != nil { 538 return err 539 } 540 } 541 } 542 return _DebugActiveProcessStop(uint32(dbp.pid)) 543 } 544 545 func (dbp *nativeProcess) EntryPoint() (uint64, error) { 546 return dbp.os.entryPoint, nil 547 } 548 549 func (dbp *nativeProcess) SupportsBPF() bool { 550 return false 551 } 552 553 func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error { 554 return nil 555 } 556 557 func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams { 558 return nil 559 } 560 561 func killProcess(pid int) error { 562 p, err := os.FindProcess(pid) 563 if err != nil { 564 return err 565 } 566 defer p.Release() 567 568 return p.Kill() 569 }