1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | /*++ |
6 | |
7 | |
8 | |
9 | Module Name: |
10 | |
11 | debug.c |
12 | |
13 | Abstract: |
14 | |
15 | Implementation of Win32 debugging API functions. |
16 | |
17 | Revision History: |
18 | |
19 | |
20 | |
21 | --*/ |
22 | |
23 | #ifndef BIT64 |
24 | #undef _LARGEFILE64_SOURCE |
25 | #undef _FILE_OFFSET_BITS |
26 | #endif |
27 | |
28 | #include "pal/dbgmsg.h" |
29 | SET_DEFAULT_DEBUG_CHANNEL(DEBUG); // some headers have code with asserts, so do this first |
30 | |
31 | #include "pal/thread.hpp" |
32 | #include "pal/procobj.hpp" |
33 | #include "pal/file.hpp" |
34 | |
35 | #include "pal/palinternal.h" |
36 | #include "pal/process.h" |
37 | #include "pal/context.h" |
38 | #include "pal/debug.h" |
39 | #include "pal/environ.h" |
40 | #include "pal/malloc.hpp" |
41 | #include "pal/module.h" |
42 | #include "pal/stackstring.hpp" |
43 | #include "pal/virtual.h" |
44 | #include "pal/utils.h" |
45 | |
46 | #include <signal.h> |
47 | #include <unistd.h> |
48 | #if HAVE_PROCFS_CTL |
49 | #include <unistd.h> |
50 | #elif HAVE_TTRACE // HAVE_PROCFS_CTL |
51 | #include <sys/ttrace.h> |
52 | #else // HAVE_TTRACE |
53 | #include <sys/ptrace.h> |
54 | #endif // HAVE_PROCFS_CTL |
55 | #if HAVE_VM_READ |
56 | #include <mach/mach.h> |
57 | #endif // HAVE_VM_READ |
58 | #include <errno.h> |
59 | #include <sys/types.h> |
60 | #include <sys/wait.h> |
61 | |
62 | #if HAVE_PROCFS_H |
63 | #include <procfs.h> |
64 | #endif // HAVE_PROCFS_H |
65 | |
66 | #if HAVE_MACH_EXCEPTIONS |
67 | #include "../exception/machexception.h" |
68 | #endif // HAVE_MACH_EXCEPTIONS |
69 | |
70 | using namespace CorUnix; |
71 | |
72 | extern "C" void DBG_DebugBreak_End(); |
73 | |
74 | #if HAVE_PROCFS_CTL |
75 | #define CTL_ATTACH "attach" |
76 | #define CTL_DETACH "detach" |
77 | #define CTL_WAIT "wait" |
78 | #endif // HAVE_PROCFS_CTL |
79 | |
80 | /* ------------------- Constant definitions ----------------------------------*/ |
81 | |
82 | #if !HAVE_VM_READ && !HAVE_PROCFS_CTL |
83 | const BOOL DBG_ATTACH = TRUE; |
84 | const BOOL DBG_DETACH = FALSE; |
85 | #endif |
86 | static const char PAL_OUTPUTDEBUGSTRING[] = "PAL_OUTPUTDEBUGSTRING" ; |
87 | |
88 | #ifdef _DEBUG |
89 | #define ENABLE_RUN_ON_DEBUG_BREAK 1 |
90 | #endif // _DEBUG |
91 | |
92 | #ifdef ENABLE_RUN_ON_DEBUG_BREAK |
93 | static const char PAL_RUN_ON_DEBUG_BREAK[] = "PAL_RUN_ON_DEBUG_BREAK" ; |
94 | #endif // ENABLE_RUN_ON_DEBUG_BREAK |
95 | |
96 | extern "C" { |
97 | |
98 | /*++ |
99 | Function: |
100 | FlushInstructionCache |
101 | |
102 | The FlushInstructionCache function flushes the instruction cache for |
103 | the specified process. |
104 | |
105 | Remarks |
106 | |
107 | This is a no-op for x86 architectures where the instruction and data |
108 | caches are coherent in hardware. For non-X86 architectures, this call |
109 | usually maps to a kernel API to flush the D-caches on all processors. |
110 | |
111 | --*/ |
112 | BOOL |
113 | PALAPI |
114 | FlushInstructionCache( |
115 | IN HANDLE hProcess, |
116 | IN LPCVOID lpBaseAddress, |
117 | IN SIZE_T dwSize) |
118 | { |
119 | BOOL Ret; |
120 | |
121 | PERF_ENTRY(FlushInstructionCache); |
122 | ENTRY("FlushInstructionCache (hProcess=%p, lpBaseAddress=%p dwSize=%d)\ |
123 | \n" , hProcess, lpBaseAddress, dwSize); |
124 | |
125 | if (lpBaseAddress != NULL) |
126 | { |
127 | Ret = DBG_FlushInstructionCache(lpBaseAddress, dwSize); |
128 | } |
129 | else |
130 | { |
131 | Ret = TRUE; |
132 | } |
133 | |
134 | LOGEXIT("FlushInstructionCache returns BOOL %d\n" , Ret); |
135 | PERF_EXIT(FlushInstructionCache); |
136 | return Ret; |
137 | } |
138 | |
139 | |
140 | /*++ |
141 | Function: |
142 | OutputDebugStringA |
143 | |
144 | See MSDN doc. |
145 | --*/ |
146 | VOID |
147 | PALAPI |
148 | OutputDebugStringA( |
149 | IN LPCSTR lpOutputString) |
150 | { |
151 | PERF_ENTRY(OutputDebugStringA); |
152 | ENTRY("OutputDebugStringA (lpOutputString=%p (%s))\n" , |
153 | lpOutputString ? lpOutputString : "NULL" , |
154 | lpOutputString ? lpOutputString : "NULL" ); |
155 | |
156 | // As we don't support debug events, we are going to output the debug string |
157 | // to stderr instead of generating OUT_DEBUG_STRING_EVENT. It's safe to tell |
158 | // EnvironGetenv not to make a copy of the value here since we only want to |
159 | // check whether it exists, not actually use it. |
160 | if ((lpOutputString != NULL) && |
161 | (NULL != EnvironGetenv(PAL_OUTPUTDEBUGSTRING, /* copyValue */ FALSE))) |
162 | { |
163 | fprintf(stderr, "%s" , lpOutputString); |
164 | } |
165 | |
166 | LOGEXIT("OutputDebugStringA returns\n" ); |
167 | PERF_EXIT(OutputDebugStringA); |
168 | } |
169 | |
170 | /*++ |
171 | Function: |
172 | OutputDebugStringW |
173 | |
174 | See MSDN doc. |
175 | --*/ |
176 | VOID |
177 | PALAPI |
178 | OutputDebugStringW( |
179 | IN LPCWSTR lpOutputString) |
180 | { |
181 | CHAR *lpOutputStringA; |
182 | int strLen; |
183 | |
184 | PERF_ENTRY(OutputDebugStringW); |
185 | ENTRY("OutputDebugStringW (lpOutputString=%p (%S))\n" , |
186 | lpOutputString ? lpOutputString: W16_NULLSTRING, |
187 | lpOutputString ? lpOutputString: W16_NULLSTRING); |
188 | |
189 | if (lpOutputString == NULL) |
190 | { |
191 | OutputDebugStringA("" ); |
192 | goto EXIT; |
193 | } |
194 | |
195 | if ((strLen = WideCharToMultiByte(CP_ACP, 0, lpOutputString, -1, NULL, 0, |
196 | NULL, NULL)) |
197 | == 0) |
198 | { |
199 | ASSERT("failed to get wide chars length\n" ); |
200 | SetLastError(ERROR_INTERNAL_ERROR); |
201 | goto EXIT; |
202 | } |
203 | |
204 | /* strLen includes the null terminator */ |
205 | if ((lpOutputStringA = (LPSTR) InternalMalloc((strLen * sizeof(CHAR)))) == NULL) |
206 | { |
207 | ERROR("Insufficient memory available !\n" ); |
208 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
209 | goto EXIT; |
210 | } |
211 | |
212 | if(! WideCharToMultiByte(CP_ACP, 0, lpOutputString, -1, |
213 | lpOutputStringA, strLen, NULL, NULL)) |
214 | { |
215 | ASSERT("failed to convert wide chars to multibytes\n" ); |
216 | SetLastError(ERROR_INTERNAL_ERROR); |
217 | free(lpOutputStringA); |
218 | goto EXIT; |
219 | } |
220 | |
221 | OutputDebugStringA(lpOutputStringA); |
222 | free(lpOutputStringA); |
223 | |
224 | EXIT: |
225 | LOGEXIT("OutputDebugStringW returns\n" ); |
226 | PERF_EXIT(OutputDebugStringW); |
227 | } |
228 | |
229 | #ifdef ENABLE_RUN_ON_DEBUG_BREAK |
230 | /* |
231 | When DebugBreak() is called, if PAL_RUN_ON_DEBUG_BREAK is set, |
232 | DebugBreak() will execute whatever command is in there. |
233 | |
234 | PAL_RUN_ON_DEBUG_BREAK must be no longer than 255 characters. |
235 | |
236 | This command string inherits the current process's environment, |
237 | with two additions: |
238 | PAL_EXE_PID - the process ID of the current process |
239 | PAL_EXE_NAME - the name of the executable of the current process |
240 | |
241 | When DebugBreak() runs this string, it periodically polls the child process |
242 | and blocks until it finishes. If you use this mechanism to start a |
243 | debugger, you can break this poll loop by setting the "spin" variable in |
244 | run_debug_command()'s frame to 0, and then the parent process can |
245 | continue. |
246 | |
247 | suggested values for PAL_RUN_ON_DEBUG_BREAK: |
248 | to halt the process for later inspection: |
249 | 'echo stopping $PAL_EXE_PID; kill -STOP $PAL_EXE_PID; sleep 10' |
250 | |
251 | to print out the stack trace: |
252 | 'pstack $PAL_EXE_PID' |
253 | |
254 | to invoke the gdb debugger on the process: |
255 | 'set -x; gdb $PAL_EXE_NAME $PAL_EXE_PID' |
256 | |
257 | to invoke the ddd debugger on the process (requires X11): |
258 | 'set -x; ddd $PAL_EXE_NAME $PAL_EXE_PID' |
259 | */ |
260 | |
261 | static |
262 | int |
263 | run_debug_command (const char *command) |
264 | { |
265 | int pid; |
266 | Volatile<int> spin = 1; |
267 | |
268 | if (!command) { |
269 | return 1; |
270 | } |
271 | |
272 | printf("Spawning command: %s\n" , command); |
273 | |
274 | pid = fork(); |
275 | if (pid == -1) { |
276 | return -1; |
277 | } |
278 | if (pid == 0) { |
279 | const char *argv[4] = { "sh" , "-c" , command, 0 }; |
280 | execv("/bin/sh" , (char **)argv); |
281 | exit(127); |
282 | } |
283 | |
284 | /* We continue either when the spawned process has stopped, or when |
285 | an attached debugger sets spin to 0 */ |
286 | while (spin != 0) { |
287 | int status = 0; |
288 | int ret = waitpid(pid, &status, WNOHANG); |
289 | if (ret == 0) { |
290 | int i; |
291 | /* I tried to use sleep for this, and that works everywhere except |
292 | FreeBSD. The problem on FreeBSD is that if the process gets a |
293 | signal while blocked in sleep(), gdb is confused by the stack */ |
294 | for (i = 0; i < 1000000; i++) |
295 | ; |
296 | } |
297 | else if (ret == -1) { |
298 | if (errno != EINTR) { |
299 | return -1; |
300 | } |
301 | } |
302 | else if (WIFEXITED(status)) { |
303 | return WEXITSTATUS(status); |
304 | } |
305 | else { |
306 | fprintf (stderr, "unexpected return from waitpid\n" ); |
307 | return -1; |
308 | } |
309 | }; |
310 | return 0; |
311 | } |
312 | #endif // ENABLE_RUN_ON_DEBUG_BREAK |
313 | |
314 | #define PID_TEXT "PAL_EXE_PID=" |
315 | #define EXE_TEXT "PAL_EXE_NAME=" |
316 | |
317 | static |
318 | int |
319 | DebugBreakCommand() |
320 | { |
321 | #ifdef ENABLE_RUN_ON_DEBUG_BREAK |
322 | extern MODSTRUCT exe_module; |
323 | |
324 | char *command_string = EnvironGetenv(PAL_RUN_ON_DEBUG_BREAK); |
325 | if (command_string) |
326 | { |
327 | char pid_buf[sizeof (PID_TEXT) + 32]; |
328 | PathCharString exe_bufString; |
329 | int libNameLength = 10; |
330 | |
331 | if (exe_module.lib_name != NULL) |
332 | { |
333 | libNameLength = PAL_wcslen(exe_module.lib_name); |
334 | } |
335 | |
336 | SIZE_T dwexe_buf = strlen(EXE_TEXT) + libNameLength + 1; |
337 | CHAR * exe_buf = exe_bufString.OpenStringBuffer(dwexe_buf); |
338 | |
339 | if (NULL == exe_buf) |
340 | { |
341 | goto FAILED; |
342 | } |
343 | |
344 | if (snprintf (pid_buf, sizeof (pid_buf), PID_TEXT "%d" , getpid()) <= 0) |
345 | { |
346 | goto FAILED; |
347 | } |
348 | |
349 | if (snprintf (exe_buf, sizeof (CHAR) * (dwexe_buf + 1), EXE_TEXT "%ls" , (wchar_t *)exe_module.lib_name) <= 0) |
350 | { |
351 | goto FAILED; |
352 | } |
353 | |
354 | exe_bufString.CloseBuffer(dwexe_buf); |
355 | /* strictly speaking, we might want to only set these environment |
356 | variables in the child process, but if we do that we can't check |
357 | for errors. putenv/setenv can fail when out of memory */ |
358 | |
359 | if (!EnvironPutenv (pid_buf, FALSE) || !EnvironPutenv (exe_buf, FALSE)) |
360 | { |
361 | goto FAILED; |
362 | } |
363 | |
364 | if (run_debug_command (command_string)) |
365 | { |
366 | goto FAILED; |
367 | } |
368 | |
369 | free(command_string); |
370 | return 1; |
371 | } |
372 | |
373 | return 0; |
374 | |
375 | FAILED: |
376 | if (command_string) |
377 | { |
378 | free(command_string); |
379 | } |
380 | |
381 | fprintf (stderr, "Failed to execute command: '%s'\n" , command_string); |
382 | return -1; |
383 | #else // ENABLE_RUN_ON_DEBUG_BREAK |
384 | return 0; |
385 | #endif // ENABLE_RUN_ON_DEBUG_BREAK |
386 | } |
387 | |
388 | /*++ |
389 | Function: |
390 | DebugBreak |
391 | |
392 | See MSDN doc. |
393 | --*/ |
394 | VOID |
395 | PALAPI |
396 | DebugBreak( |
397 | VOID) |
398 | { |
399 | PERF_ENTRY(DebugBreak); |
400 | ENTRY("DebugBreak()\n" ); |
401 | |
402 | if (DebugBreakCommand() <= 0) { |
403 | // either didn't do anything, or failed |
404 | TRACE("Calling DBG_DebugBreak\n" ); |
405 | DBG_DebugBreak(); |
406 | } |
407 | |
408 | LOGEXIT("DebugBreak returns\n" ); |
409 | PERF_EXIT(DebugBreak); |
410 | } |
411 | |
412 | /*++ |
413 | Function: |
414 | IsInDebugBreak(addr) |
415 | |
416 | Returns true if the address is in DBG_DebugBreak. |
417 | |
418 | --*/ |
419 | BOOL |
420 | IsInDebugBreak(void *addr) |
421 | { |
422 | return (addr >= (void *)DBG_DebugBreak) && (addr <= (void *)DBG_DebugBreak_End); |
423 | } |
424 | |
425 | /*++ |
426 | Function: |
427 | GetThreadContext |
428 | |
429 | See MSDN doc. |
430 | --*/ |
431 | BOOL |
432 | PALAPI |
433 | GetThreadContext( |
434 | IN HANDLE hThread, |
435 | IN OUT LPCONTEXT lpContext) |
436 | { |
437 | PAL_ERROR palError; |
438 | CPalThread *pThread; |
439 | CPalThread *pTargetThread; |
440 | IPalObject *pobjThread = NULL; |
441 | BOOL ret = FALSE; |
442 | |
443 | PERF_ENTRY(GetThreadContext); |
444 | ENTRY("GetThreadContext (hThread=%p, lpContext=%p)\n" ,hThread,lpContext); |
445 | |
446 | pThread = InternalGetCurrentThread(); |
447 | |
448 | palError = InternalGetThreadDataFromHandle( |
449 | pThread, |
450 | hThread, |
451 | 0, // THREAD_GET_CONTEXT |
452 | &pTargetThread, |
453 | &pobjThread |
454 | ); |
455 | |
456 | if (NO_ERROR == palError) |
457 | { |
458 | if (!pTargetThread->IsDummy()) |
459 | { |
460 | ret = CONTEXT_GetThreadContext( |
461 | GetCurrentProcessId(), |
462 | pTargetThread->GetPThreadSelf(), |
463 | lpContext |
464 | ); |
465 | } |
466 | else |
467 | { |
468 | ASSERT("Dummy thread handle passed to GetThreadContext\n" ); |
469 | pThread->SetLastError(ERROR_INVALID_HANDLE); |
470 | } |
471 | } |
472 | else |
473 | { |
474 | pThread->SetLastError(palError); |
475 | } |
476 | |
477 | if (NULL != pobjThread) |
478 | { |
479 | pobjThread->ReleaseReference(pThread); |
480 | } |
481 | |
482 | LOGEXIT("GetThreadContext returns ret:%d\n" , ret); |
483 | PERF_EXIT(GetThreadContext); |
484 | return ret; |
485 | } |
486 | |
487 | /*++ |
488 | Function: |
489 | SetThreadContext |
490 | |
491 | See MSDN doc. |
492 | --*/ |
493 | BOOL |
494 | PALAPI |
495 | SetThreadContext( |
496 | IN HANDLE hThread, |
497 | IN CONST CONTEXT *lpContext) |
498 | { |
499 | PAL_ERROR palError; |
500 | CPalThread *pThread; |
501 | CPalThread *pTargetThread; |
502 | IPalObject *pobjThread = NULL; |
503 | BOOL ret = FALSE; |
504 | |
505 | PERF_ENTRY(SetThreadContext); |
506 | ENTRY("SetThreadContext (hThread=%p, lpContext=%p)\n" ,hThread,lpContext); |
507 | |
508 | pThread = InternalGetCurrentThread(); |
509 | |
510 | palError = InternalGetThreadDataFromHandle( |
511 | pThread, |
512 | hThread, |
513 | 0, // THREAD_SET_CONTEXT |
514 | &pTargetThread, |
515 | &pobjThread |
516 | ); |
517 | |
518 | if (NO_ERROR == palError) |
519 | { |
520 | if (!pTargetThread->IsDummy()) |
521 | { |
522 | ret = CONTEXT_SetThreadContext( |
523 | GetCurrentProcessId(), |
524 | pTargetThread->GetPThreadSelf(), |
525 | lpContext |
526 | ); |
527 | } |
528 | else |
529 | { |
530 | ASSERT("Dummy thread handle passed to SetThreadContext\n" ); |
531 | pThread->SetLastError(ERROR_INVALID_HANDLE); |
532 | } |
533 | } |
534 | else |
535 | { |
536 | pThread->SetLastError(palError); |
537 | } |
538 | |
539 | if (NULL != pobjThread) |
540 | { |
541 | pobjThread->ReleaseReference(pThread); |
542 | } |
543 | |
544 | return ret; |
545 | } |
546 | |
547 | /*++ |
548 | Function: |
549 | PAL_ProbeMemory |
550 | |
551 | Abstract |
552 | |
553 | Parameter |
554 | pBuffer : address of memory to validate |
555 | cbBuffer : size of memory region to validate |
556 | fWriteAccess : if true, validate writable access, else just readable. |
557 | |
558 | Return |
559 | true if memory is valid, false if not. |
560 | --*/ |
561 | BOOL |
562 | PALAPI |
563 | PAL_ProbeMemory( |
564 | PVOID pBuffer, |
565 | DWORD cbBuffer, |
566 | BOOL fWriteAccess) |
567 | { |
568 | int fds[2]; |
569 | |
570 | if (pipe(fds) != 0) |
571 | { |
572 | ASSERT("pipe failed: errno is %d (%s)\n" , errno, strerror(errno)); |
573 | return FALSE; |
574 | } |
575 | |
576 | fcntl(fds[0], O_NONBLOCK); |
577 | fcntl(fds[1], O_NONBLOCK); |
578 | |
579 | PVOID pEnd = (PBYTE)pBuffer + cbBuffer; |
580 | BOOL result = TRUE; |
581 | |
582 | // Validate the first byte in the buffer, then validate the first byte on each page after that. |
583 | while (pBuffer < pEnd) |
584 | { |
585 | int written = write(fds[1], pBuffer, 1); |
586 | if (written == -1) |
587 | { |
588 | if (errno != EFAULT) |
589 | { |
590 | ASSERT("write failed: errno is %d (%s)\n" , errno, strerror(errno)); |
591 | } |
592 | result = FALSE; |
593 | break; |
594 | } |
595 | else |
596 | { |
597 | if (fWriteAccess) |
598 | { |
599 | int rd = read(fds[0], pBuffer, 1); |
600 | if (rd == -1) |
601 | { |
602 | if (errno != EFAULT) |
603 | { |
604 | ASSERT("read failed: errno is %d (%s)\n" , errno, strerror(errno)); |
605 | } |
606 | result = FALSE; |
607 | break; |
608 | } |
609 | } |
610 | } |
611 | |
612 | // Round to the beginning of the next page |
613 | pBuffer = PVOID(ALIGN_DOWN((SIZE_T)pBuffer, GetVirtualPageSize()) + GetVirtualPageSize()); |
614 | } |
615 | |
616 | close(fds[0]); |
617 | close(fds[1]); |
618 | |
619 | return result; |
620 | } |
621 | |
622 | } // extern "C" |
623 | |