From 4b9e1433d2272f5f68b3227abdd9cf6817a0afd3 Mon Sep 17 00:00:00 2001 From: Mark Wielaard Date: Tue, 4 Mar 2014 11:27:15 +0100 Subject: [PATCH] libdwfl: dwfl_linux_proc_find_elf use elf_from_remote_memory for (deleted). If a module has a "(deleted)" main ELF file, then try to read it from remote memory if the Dwfl has process state attached by reusing the ptrace mechanism from linux-pid-attach. Signed-off-by: Mark Wielaard --- libdwfl/ChangeLog | 20 ++++++++++++++ libdwfl/dwfl_frame.c | 16 ++++++++---- libdwfl/libdwflP.h | 36 ++++++++++++++++++++++++- libdwfl/linux-pid-attach.c | 65 +++++++++++++++++++++++++--------------------- libdwfl/linux-proc-maps.c | 54 ++++++++++++++++++++++++++++---------- 5 files changed, 143 insertions(+), 48 deletions(-) diff --git a/libdwfl/ChangeLog b/libdwfl/ChangeLog index e371393..12938f3 100644 --- a/libdwfl/ChangeLog +++ b/libdwfl/ChangeLog @@ -1,3 +1,23 @@ +2014-03-04 Mark Wielaard + + * libdwflP.h (struct __libdwfl_pid_arg): Moved here and renamed from + linux-pid-attach.c (struct pid_arg). + (__libdwfl_get_pid_arg): New internal function declaration. + (__libdwfl_ptrace_attach): Likewise. + (__libdwfl_ptrace_detach): Likewise. + * dwfl_frame.c (dwfl_attach_state): Add "(deleted)" files to the + special exception modules that cannot be checked at this point. + * linux-pid-attach.c (struct pid_arg): Moved to libdwflP.h + (ptrace_attach): Renamed to... + (__libdwfl_ptrace_attach): New internal function. + (__libdwfl_ptrace_detach): Likewise. Extracted from ... + (pid_thread_detach): Call __libdwfl_ptrace_detach now. + (__libdwfl_get_pid_arg): New internal function. + * linux-proc-maps.c (dwfl_linux_proc_find_elf): Check if special + module name contains "(deleted)" and dwfl_pid gives an attached + pid. If pid is set and try to (re)use ptrace attach state of + process before reading memory. + 2014-03-03 Mark Wielaard * elf-from-memory.c (elf_from_remote_memory): Take pagesize as diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c index e45cf14..fd0b9ae 100644 --- a/libdwfl/dwfl_frame.c +++ b/libdwfl/dwfl_frame.c @@ -157,11 +157,17 @@ dwfl_attach_state (Dwfl *dwfl, Elf *elf, pid_t pid, ebl = NULL; for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) { - /* Reading of the vDSO module may fail as /proc/PID/mem is unreadable - without PTRACE_ATTACH and we may not be PTRACE_ATTACH-ed now. - MOD would not be re-read later to unwind it when we are already - PTRACE_ATTACH-ed to PID. */ - if (strncmp (mod->name, "[vdso: ", 7) == 0) + /* Reading of the vDSO or (deleted) modules may fail as + /proc/PID/mem is unreadable without PTRACE_ATTACH and + we may not be PTRACE_ATTACH-ed now. MOD would not be + re-read later to unwind it when we are already + PTRACE_ATTACH-ed to PID. This happens when this function + is called from dwfl_linux_proc_attach with elf == NULL. + __libdwfl_module_getebl will call __libdwfl_getelf which + will call the find_elf callback. */ + if (strncmp (mod->name, "[vdso: ", 7) == 0 + || strcmp (strrchr (mod->name, ' ') ?: "", + " (deleted)") == 0) continue; Dwfl_Error error = __libdwfl_module_getebl (mod); if (error != DWFL_E_NOERROR) diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h index 710e699..3a28ac7 100644 --- a/libdwfl/libdwflP.h +++ b/libdwfl/libdwflP.h @@ -1,5 +1,5 @@ /* Internal definitions for libdwfl. - Copyright (C) 2005-2013 Red Hat, Inc. + Copyright (C) 2005-2014 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -387,6 +388,39 @@ struct dwfl_arange }; +/* Structure used for keeping track of ptrace attaching a thread. + Shared by linux-pid-attach and linux-proc-maps. If it has been setup + then get the instance through __libdwfl_get_pid_arg. */ +struct __libdwfl_pid_arg +{ + DIR *dir; + /* It is 0 if not used. */ + pid_t tid_attached; + /* Valid only if TID_ATTACHED is not zero. */ + bool tid_was_stopped; + /* True if threads are ptrace stopped by caller. */ + bool assume_ptrace_stopped; +}; + +/* If DWfl is not NULL and a Dwfl_Process has been setup that has + Dwfl_Thread_Callbacks set to pid_thread_callbacks, then return the + callbacks_arg, which will be a struct __libdwfl_pid_arg. Otherwise + returns NULL. */ +extern struct __libdwfl_pid_arg *__libdwfl_get_pid_arg (Dwfl *dwfl) + internal_function; + +/* Makes sure the given tid is attached. On success returns true and + sets tid_was_stopped. */ +extern bool __libdwfl_ptrace_attach (pid_t tid, bool *tid_was_stoppedp) + internal_function; + +/* Detaches a tid that was attached through + __libdwfl_ptrace_attach. Must be given the tid_was_stopped as set + by __libdwfl_ptrace_attach. */ +extern void __libdwfl_ptrace_detach (pid_t tid, bool tid_was_stopped) + internal_function; + + /* Internal wrapper for old dwfl_module_getsym and new dwfl_module_getsym_info. adjust_st_value set to true returns adjusted SYM st_value, set to false it will not adjust SYM at all, but does match against resolved *ADDR. */ diff --git a/libdwfl/linux-pid-attach.c b/libdwfl/linux-pid-attach.c index 58d6942..6be578b 100644 --- a/libdwfl/linux-pid-attach.c +++ b/libdwfl/linux-pid-attach.c @@ -1,5 +1,5 @@ /* Get Dwarf Frame state for target live PID process. - Copyright (C) 2013 Red Hat, Inc. + Copyright (C) 2013, 2014 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -37,16 +37,6 @@ # define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif -struct pid_arg -{ - DIR *dir; - /* It is 0 if not used. */ - pid_t tid_attached; - /* Valid only if TID_ATTACHED is not zero. */ - bool tid_was_stopped; - /* True if threads are ptrace stopped by caller. */ - bool assume_ptrace_stopped; -}; static bool linux_proc_pid_is_stopped (pid_t pid) @@ -72,8 +62,9 @@ linux_proc_pid_is_stopped (pid_t pid) return retval; } -static bool -ptrace_attach (pid_t tid, bool *tid_was_stoppedp) +bool +internal_function +__libdwfl_ptrace_attach (pid_t tid, bool *tid_was_stoppedp) { if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0) { @@ -121,7 +112,7 @@ ptrace_attach (pid_t tid, bool *tid_was_stoppedp) static bool pid_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg) { - struct pid_arg *pid_arg = arg; + struct __libdwfl_pid_arg *pid_arg = arg; pid_t tid = pid_arg->tid_attached; assert (tid > 0); Dwfl_Process *process = dwfl->process; @@ -164,7 +155,7 @@ static pid_t pid_next_thread (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg, void **thread_argp) { - struct pid_arg *pid_arg = dwfl_arg; + struct __libdwfl_pid_arg *pid_arg = dwfl_arg; struct dirent *dirent; /* Start fresh on first traversal. */ if (*thread_argp == NULL) @@ -238,11 +229,11 @@ pid_thread_state_registers_cb (int firstreg, unsigned nregs, static bool pid_set_initial_registers (Dwfl_Thread *thread, void *thread_arg) { - struct pid_arg *pid_arg = thread_arg; + struct __libdwfl_pid_arg *pid_arg = thread_arg; assert (pid_arg->tid_attached == 0); pid_t tid = INTUSE(dwfl_thread_tid) (thread); if (! pid_arg->assume_ptrace_stopped - && ! ptrace_attach (tid, &pid_arg->tid_was_stopped)) + && ! __libdwfl_ptrace_attach (tid, &pid_arg->tid_was_stopped)) return false; pid_arg->tid_attached = tid; Dwfl_Process *process = thread->process; @@ -254,28 +245,33 @@ pid_set_initial_registers (Dwfl_Thread *thread, void *thread_arg) static void pid_detach (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg) { - struct pid_arg *pid_arg = dwfl_arg; + struct __libdwfl_pid_arg *pid_arg = dwfl_arg; closedir (pid_arg->dir); free (pid_arg); } +void +internal_function +__libdwfl_ptrace_detach (pid_t tid, bool tid_was_stopped) +{ + /* This handling is needed only on older Linux kernels such as + 2.6.32-358.23.2.el6.ppc64. Later kernels such as + 3.11.7-200.fc19.x86_64 remember the T (stopped) state + themselves and no longer need to pass SIGSTOP during + PTRACE_DETACH. */ + ptrace (PTRACE_DETACH, tid, NULL, + (void *) (intptr_t) (tid_was_stopped ? SIGSTOP : 0)); +} + static void pid_thread_detach (Dwfl_Thread *thread, void *thread_arg) { - struct pid_arg *pid_arg = thread_arg; + struct __libdwfl_pid_arg *pid_arg = thread_arg; pid_t tid = INTUSE(dwfl_thread_tid) (thread); assert (pid_arg->tid_attached == tid); pid_arg->tid_attached = 0; if (! pid_arg->assume_ptrace_stopped) - { - /* This handling is needed only on older Linux kernels such as - 2.6.32-358.23.2.el6.ppc64. Later kernels such as - 3.11.7-200.fc19.x86_64 remember the T (stopped) state - themselves and no longer need to pass SIGSTOP during - PTRACE_DETACH. */ - ptrace (PTRACE_DETACH, tid, NULL, - (void *) (intptr_t) (pid_arg->tid_was_stopped ? SIGSTOP : 0)); - } + __libdwfl_ptrace_detach (tid, pid_arg->tid_was_stopped); } static const Dwfl_Thread_Callbacks pid_thread_callbacks = @@ -328,7 +324,7 @@ dwfl_linux_proc_attach (Dwfl *dwfl, pid_t pid, bool assume_ptrace_stopped) DIR *dir = opendir (dirname); if (dir == NULL) return errno; - struct pid_arg *pid_arg = malloc (sizeof *pid_arg); + struct __libdwfl_pid_arg *pid_arg = malloc (sizeof *pid_arg); if (pid_arg == NULL) { closedir (dir); @@ -347,3 +343,14 @@ dwfl_linux_proc_attach (Dwfl *dwfl, pid_t pid, bool assume_ptrace_stopped) return 0; } INTDEF (dwfl_linux_proc_attach) + +struct __libdwfl_pid_arg * +internal_function +__libdwfl_get_pid_arg (Dwfl *dwfl) +{ + if (dwfl != NULL && dwfl->process != NULL + && dwfl->process->callbacks == &pid_thread_callbacks) + return (struct __libdwfl_pid_arg *) dwfl->process->callbacks_arg; + + return NULL; +} diff --git a/libdwfl/linux-proc-maps.c b/libdwfl/linux-proc-maps.c index 3384403..b6620ac 100644 --- a/libdwfl/linux-proc-maps.c +++ b/libdwfl/linux-proc-maps.c @@ -339,42 +339,66 @@ dwfl_linux_proc_find_elf (Dwfl_Module *mod __attribute__ ((unused)), const char *module_name, Dwarf_Addr base, char **file_name, Elf **elfp) { + int pid = -1; if (module_name[0] == '/') { /* When this callback is used together with dwfl_linux_proc_report then we might see mappings of special character devices. Make sure we only open and return regular files. Special devices - might hang on open or read. */ + might hang on open or read. (deleted) files are super special. + The image might come from memory if we are attached. */ struct stat sb; if (stat (module_name, &sb) == -1 || (sb.st_mode & S_IFMT) != S_IFREG) - return -1; + { + if (strcmp (strrchr (module_name, ' ') ?: "", " (deleted)") == 0) + pid = INTUSE(dwfl_pid) (mod->dwfl); + else + return -1; + } - int fd = open64 (module_name, O_RDONLY); - if (fd >= 0) + if (pid == -1) { - *file_name = strdup (module_name); - if (*file_name == NULL) + int fd = open64 (module_name, O_RDONLY); + if (fd >= 0) { - close (fd); - return ENOMEM; + *file_name = strdup (module_name); + if (*file_name == NULL) + { + close (fd); + return ENOMEM; + } } + return fd; } - return fd; } - int pid; - if (sscanf (module_name, "[vdso: %d]", &pid) == 1) + if (pid != -1 || sscanf (module_name, "[vdso: %d]", &pid) == 1) { /* Special case for in-memory ELF image. */ + bool detach = false; + bool tid_was_stopped = false; + struct __libdwfl_pid_arg *pid_arg = __libdwfl_get_pid_arg (mod->dwfl); + if (pid_arg != NULL && ! pid_arg->assume_ptrace_stopped) + { + /* If any thread is already attached we are fine. Read + through that thread. It doesn't have to be the main + thread pid. */ + pid_t tid = pid_arg->tid_attached; + if (tid != 0) + pid = tid; + else + detach = __libdwfl_ptrace_attach (pid, &tid_was_stopped); + } + char *fname; if (asprintf (&fname, PROCMEMFMT, pid) < 0) - return -1; + goto detach; int fd = open64 (fname, O_RDONLY); free (fname); if (fd < 0) - return -1; + goto detach; *elfp = elf_from_remote_memory (base, getpagesize (), NULL, &read_proc_memory, &fd); @@ -382,6 +406,10 @@ dwfl_linux_proc_find_elf (Dwfl_Module *mod __attribute__ ((unused)), close (fd); *file_name = NULL; + + detach: + if (detach) + __libdwfl_ptrace_detach (pid, tid_was_stopped); return -1; } -- 2.7.4