/* Copyright 2007, Red Hat Inc. */ #define _GNU_SOURCE 1 #define ps_prochandle attach_state_struct #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "debugger.h" /* Insert constant delay for before each critical check? */ #if 0 #define USLEEP (1000000 / 2) #endif #if 1 /* Standard run. */ #define TIMEOUT_SECS 4 #define LOOPS_MIN 100000 #else /* QEMU? */ /* LOOPS_MIN is a safety as QEMU clock time sucks. 100000 is 4s natively and 53s in QEMU. */ #define TIMEOUT_SECS 20 #define LOOPS_MIN 500000 #endif /* QEMU? */ #define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0])) void delay (void) { #ifdef USLEEP int i; i = usleep (USLEEP); assert (i == 0); #endif } static __attribute__((__noreturn__)) void crash (void) { #if 0 void *array[0x100]; int count; #endif char command[256]; fputs (">>> CRASH START\n", stderr); #if 0 count = backtrace (array, ARRAY_SIZE (array)); backtrace_symbols_fd (array, count, STDERR_FILENO); #endif snprintf (command, sizeof (command), "echo -e \"bt\\nquit\"" " >/tmp/debugger.%d; gdb --batch -nx --command=/tmp/debugger.%d" " /proc/%d/exe %d >> CRASH FINISH\n", stderr); abort (); } enum state { /* Separate the valid `1 << enum state' and `enum state' ranges. */ STATE_FIRST = 4, STATE_INSTABLE, STATE_ENOENT, STATE_SLEEPING, STATE_RUNNING, STATE_STOPPED, STATE_PTRACED, STATE_ZOMBIE, STATE_DEAD, STATE_LAST }; static const char *state_to_name (enum state state) { switch (state) { case STATE_INSTABLE: return "STATE_INSTABLE"; case STATE_ENOENT: return "STATE_ENOENT"; case STATE_SLEEPING: return "STATE_SLEEPING"; case STATE_RUNNING: return "STATE_RUNNING"; case STATE_STOPPED: return "STATE_STOPPED"; case STATE_PTRACED: return "STATE_PTRACED"; case STATE_ZOMBIE: return "STATE_ZOMBIE"; case STATE_DEAD: return "STATE_DEAD"; default: crash (); } /* NOTREACHED */ crash (); } static enum state state_get (pid_t pid) { char status_name[32]; char line[LINE_MAX]; FILE *f; enum state found; delay (); snprintf (status_name, sizeof (status_name), "/proc/%d/status", (int) pid); f = fopen (status_name, "r"); if (f == NULL && errno == ENOENT) found = STATE_ENOENT; else if (f == NULL) { fprintf (stderr, "errno = %d\n", errno); crash (); } else { int i; found = STATE_INSTABLE; while (errno = 0, fgets (line, sizeof (line), f) != NULL) { const char *const string = "State:\t"; const size_t length = sizeof "State:\t" - 1; if (strncmp (line, string, length) != 0) continue; if (strcmp (line + length, "S (sleeping)\n") == 0) found = STATE_SLEEPING; else if (strcmp (line + length, "R (running)\n") == 0) found = STATE_RUNNING; else if (strcmp (line + length, "T (stopped)\n") == 0) found = STATE_STOPPED; else if (strcmp (line + length, "T (tracing stop)\n") == 0) found = STATE_PTRACED; else if (strcmp (line + length, "Z (zombie)\n") == 0) found = STATE_ZOMBIE; /* FIXME: What does it mean? */ else if (strcmp (line + length, "X (dead)\n") == 0) found = STATE_DEAD; else { fprintf (stderr, "Found an unknown state: %s", line + length); crash (); } } assert (found != STATE_INSTABLE || errno == ESRCH); i = fclose (f); assert (i == 0); } return found; } #define STATE(pid, expect_mask) state ((pid), (expect_mask), #expect_mask ) static enum state state (pid_t pid, unsigned expect_mask, const char *expect_mask_string) { enum state found; time_t timeout = time (NULL) + TIMEOUT_SECS; unsigned loops = 0; /* Sanity check `1 << enum state' was not misplaced with `enum state'. */ assert (1 << (STATE_FIRST + 1) >= STATE_LAST); assert (expect_mask != 0); #define MASK_UNDER_EXCLUSIVE(bit) ((1 << (bit)) - 1) #define MASK_ABOVE_INCLUSIVE(bit) (~MASK_UNDER_EXCLUSIVE (bit)) assert ((expect_mask & MASK_UNDER_EXCLUSIVE (STATE_FIRST + 1)) == 0); assert ((expect_mask & MASK_ABOVE_INCLUSIVE (STATE_LAST)) == 0); #undef MASK_ABOVE_INCLUSIVE #undef MASK_UNDER_EXCLUSIVE do { found = state_get (pid); if (((1 << found) & expect_mask) != 0) return found; } while (loops++ < LOOPS_MIN || time (NULL) < timeout); fprintf (stderr, "Found for PID %d state %s but expecting (%s)\n", (int) pid, state_to_name (found), expect_mask_string); crash (); } struct attach_state_struct { pid_t pid; Dwfl *dwfl; int stopped; td_thragent_t *thread_agent; /* Debugging only: Number of signal needing redelivery on PTRACE_ATTACH. */ int redelivered; int threads_count; }; int attach_state_redelivered_get (struct attach_state_struct *attach_state) { assert (attach_state != NULL); return attach_state->redelivered; } int attach_state_threads_count_get (struct attach_state_struct *attach_state) { assert (attach_state != NULL); return attach_state->threads_count; } int attach_state_stopped_get (struct attach_state_struct *attach_state) { assert (attach_state != NULL); return attach_state->stopped; } pid_t attach_state_pid_get (struct attach_state_struct *attach_state) { assert (attach_state != NULL); return attach_state->pid; } struct attach_state_struct *attach_single (pid_t pid) { int i; int status; struct attach_state_struct *attach_state; attach_state = malloc (sizeof (*attach_state)); assert (attach_state != NULL); attach_state->pid = pid; attach_state->dwfl = NULL; attach_state->redelivered = 0; attach_state->threads_count = -1; delay (); attach_state->stopped = (STATE (pid, (1 << STATE_SLEEPING) | (1 << STATE_RUNNING) | (1 << STATE_STOPPED)) == STATE_STOPPED); i = ptrace (PTRACE_ATTACH, pid, NULL, NULL); assert (i == 0); /* FIXME: Why it does not work? Disable also the STATE () call above. */ #if 0 delay(); i = ptrace (PTRACE_CONT, pid, (void *) 1, (void *) SIGSTOP); /* `STOPPED == 1' may be false, even if the process was not stopped. */ if (i == 0) attach_state->stopped = 1; else if (errno == ESRCH) attach_state->stopped = 0; else crash (); #endif for (;;) { delay (); i = waitpid (pid, &status, 0); assert (i == pid); if (!WIFSTOPPED (status)) { /* Process may have exited. */ fprintf (stderr, "PID %d waitpid(2) status %d\n", (int) pid, status); exit (EXIT_FAILURE); } if (WSTOPSIG (status) == SIGSTOP) break; if (attach_state->redelivered == 0) attach_state->redelivered = WSTOPSIG (status); else attach_state->redelivered = -1; delay (); /* Re-deliver the signal received before SIGSTOP. It happens with about only 1:200000 probability. */ i = ptrace (PTRACE_CONT, pid, (void *) 1, (void *) (unsigned long) WSTOPSIG (status)); assert (i == 0); } return attach_state; } void detach_single (struct attach_state_struct *attach_state) { int i; delay (); i = ptrace (PTRACE_DETACH, attach_state->pid, NULL, (void *) (unsigned long) (attach_state->stopped ? SIGSTOP : 0)); assert (i == 0); delay (); } static Dwfl *get_dwfl (struct ps_prochandle *proc_handle) { static char *debuginfo_path; static const Dwfl_Callbacks proc_callbacks = { .find_debuginfo = dwfl_standard_find_debuginfo, .debuginfo_path = &debuginfo_path, .find_elf = dwfl_linux_proc_find_elf, }; if (proc_handle->dwfl == NULL) { proc_handle->dwfl = dwfl_begin (&proc_callbacks); assert (proc_handle->dwfl != NULL); errno = 0; if (dwfl_linux_proc_report (proc_handle->dwfl, proc_handle->pid) != 0 || dwfl_report_end (proc_handle->dwfl, NULL, NULL) != 0) { fprintf (stderr, "dwfl reporting: %m\n"); abort (); } } return proc_handle->dwfl; } /* Functions in this interface return one of these status codes. */ typedef enum { PS_OK, /* Generic "call succeeded". */ PS_ERR, /* Generic error. */ PS_BADPID, /* Bad process handle. */ PS_BADLID, /* Bad LWP identifier. */ PS_BADADDR, /* Bad address. */ PS_NOSYM, /* Could not find given symbol. */ PS_NOFREGS /* FPU register set not available for given LWP. */ } ps_err_e; ps_err_e ps_pdread (struct ps_prochandle *proc_handle, psaddr_t addr, void *buffer, size_t length) { pid_t pid = proc_handle->pid; char filename[64]; int fd; if (pid == getpid ()) { memcpy (buffer, addr, length); return PS_OK; } /* Already under PTRACE_ATTACH. */ #if 0 if (ptrace (PTRACE_ATTACH, pid, NULL, NULL) != 0) abort (); if (waitpid (pid, NULL, 0) != pid) abort (); #endif snprintf (filename, sizeof (filename), "/proc/%ld/mem", (long) pid); fd = open (filename, O_RDONLY); assert (fd != -1); if (lseek64 (fd, (off64_t) addr, SEEK_SET) != (off64_t) addr) abort (); if (read (fd, buffer, length) != length) { /* It occurs for singlethreaded processes. */ if ((off64_t) addr == 0) return PS_ERR; fprintf (stderr, "read() error @0x%lx length %lu: %m\n", (unsigned long) addr, (unsigned long) length); abort (); } if (close (fd) != 0) abort (); /* Already under PTRACE_ATTACH. */ #if 0 if (ptrace (PTRACE_DETACH, pid, NULL, NULL) != 0) abort (); #endif return PS_OK; } ps_err_e ps_pdwrite (struct ps_prochandle *proc_handle, psaddr_t addr, const void *buffer, size_t length) { crash (); } ps_err_e ps_lgetregs (struct ps_prochandle *proc_handle, lwpid_t lwp, prgregset_t regs) { crash (); } ps_err_e ps_lsetregs (struct ps_prochandle *proc_handle, lwpid_t lwp, const prgregset_t regs) { crash (); } ps_err_e ps_lgetfpregs (struct ps_prochandle *proc_handle, lwpid_t lwp, prfpregset_t *fpregs) { crash (); } ps_err_e ps_lsetfpregs (struct ps_prochandle *proc_handle, lwpid_t lwp, const prfpregset_t *fpregs) { crash (); } /* Return the PID of the process. */ pid_t ps_getpid (struct ps_prochandle *proc_handle) { return proc_handle->pid; } struct getmodules_callback_arg { const char *sym_name; psaddr_t *sym_addr; ps_err_e retval; }; static int getmodules_callback (Dwfl_Module *module, void **module_userdata_pointer, const char *module_name, Dwarf_Addr module_low_addr, void *arg) { struct getmodules_callback_arg *getmodules_callback_arg = arg; int sym_count, ndx; GElf_Sym sym; sym_count = dwfl_module_getsymtab (module); if (sym_count == -1) return DWARF_CB_OK; assert (sym_count >= 0); for (ndx = 0; ndx < sym_count; ndx++) { const char *name_got; name_got = dwfl_module_getsym (module, ndx, &sym, NULL); assert (name_got != NULL); if (strcmp (name_got, getmodules_callback_arg->sym_name) == 0) break; } if (ndx == sym_count) return DWARF_CB_OK; *getmodules_callback_arg->sym_addr = (psaddr_t) sym.st_value; getmodules_callback_arg->retval = PS_OK; return DWARF_CB_OK; } /* Look up the named symbol in the named DSO in the symbol tables associated with the process being debugged, filling in *SYM_ADDR with the corresponding run-time address. */ ps_err_e ps_pglobal_lookup (struct ps_prochandle *proc_handle, const char *object_name, const char *sym_name, psaddr_t *sym_addr) { Dwfl *dwfl = get_dwfl (proc_handle); struct getmodules_callback_arg getmodules_callback_arg; ptrdiff_t err_ptrdiff; /* FIXME: `object_name' ignored due to missing unresolving of shared libraries symbolic links. */ getmodules_callback_arg.sym_name = sym_name; getmodules_callback_arg.sym_addr = sym_addr; getmodules_callback_arg.retval = PS_NOSYM; err_ptrdiff = dwfl_getmodules (dwfl, getmodules_callback, &getmodules_callback_arg, 0); assert (err_ptrdiff == 0); return getmodules_callback_arg.retval; } #ifdef __x86_64__ ps_err_e ps_get_thread_area (const struct ps_prochandle *ph, lwpid_t lwpid, int idx, void **base) { long val; assert (idx == FS || idx == GS); if (ptrace (PTRACE_ATTACH, lwpid, NULL, NULL) != 0) abort (); if (waitpid (lwpid, NULL, 0) != lwpid) abort (); val = ptrace (PTRACE_ARCH_PRCTL, lwpid, base, (idx == FS ? ARCH_GET_FS : ARCH_GET_GS)); if (ptrace (PTRACE_DETACH, lwpid, NULL, NULL) != 0) abort (); if (val != 0) { fprintf (stderr, "PTRACE_ARCH_PRCTL (%s): %m\n", (idx == FS ? "FS" : "GS")); return PS_ERR; } return PS_OK; } #else #error "Unsupported ps_get_thread_area ()!" #endif static int find_new_threads_callback (const td_thrhandle_t *th_p, void *data) { td_err_e err; td_thrinfo_t info; struct attach_state_struct *attach_state = data; err = td_thr_get_info (th_p, &info); assert (err == TD_OK); /* printf ("LWP = %ld TID = 0x%lx\n", (long) info.ti_lid, info.ti_tid); */ attach_state->threads_count++; return 0; } static void attach_multi (struct attach_state_struct *attach_state) { td_err_e err; err = td_init (); assert (err == TD_OK); err = td_ta_new (attach_state, &attach_state->thread_agent); assert (err == TD_OK || err == TD_NOLIBTHREAD); attach_state->threads_count = 0; if (err == TD_NOLIBTHREAD) { attach_state->thread_agent = NULL; return; } assert (attach_state->thread_agent != NULL); /* Multithreading test: */ err = td_ta_thr_iter (attach_state->thread_agent, find_new_threads_callback, attach_state, TD_THR_ANY_STATE, TD_THR_LOWEST_PRIORITY, TD_SIGNO_MASK, TD_THR_ANY_USER_FLAGS); if (err != TD_OK) { fprintf (stderr, "err = %d\n", err); abort (); } assert (attach_state->threads_count > 0); } struct attach_state_struct *attach (pid_t pid) { struct attach_state_struct *attach_state; attach_state = attach_single (pid); attach_multi (attach_state); return attach_state; } void detach (struct attach_state_struct *attach_state) { if (attach_state->thread_agent != NULL) { td_err_e err; err = td_ta_delete (attach_state->thread_agent); assert (err == TD_OK); } if (attach_state->dwfl != NULL); dwfl_end (attach_state->dwfl); detach_single (attach_state); } #ifndef LIBRARY int main (int argc, char **argv) { pid_t pid; struct attach_state_struct *attach_state; if (argc != 2) { fprintf (stderr, "Usage: %s \n", argv[0]); exit (EXIT_FAILURE); } pid = atoi (argv[1]); attach_state = attach (pid); detach (attach_state); return EXIT_SUCCESS; } #endif /* !LIBRARY */