diff options
Diffstat (limited to 'src/libdwfl/linux-proc-maps.c')
-rw-r--r-- | src/libdwfl/linux-proc-maps.c | 276 |
1 files changed, 175 insertions, 101 deletions
diff --git a/src/libdwfl/linux-proc-maps.c b/src/libdwfl/linux-proc-maps.c index 8504a5f3..d0858342 100644 --- a/src/libdwfl/linux-proc-maps.c +++ b/src/libdwfl/linux-proc-maps.c @@ -1,55 +1,35 @@ /* Standard libdwfl callbacks for debugging a live Linux process. - Copyright (C) 2005-2010 Red Hat, Inc. - This file is part of Red Hat elfutils. + Copyright (C) 2005-2010, 2013, 2014 Red Hat, Inc. + This file is part of elfutils. - Red Hat elfutils is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by the - Free Software Foundation; version 2 of the License. + This file is free software; you can redistribute it and/or modify + it under the terms of either - Red Hat elfutils is distributed in the hope that it will be useful, but + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along - with Red Hat elfutils; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA. - - In addition, as a special exception, Red Hat, Inc. gives You the - additional right to link the code of Red Hat elfutils with code licensed - under any Open Source Initiative certified open source license - (http://www.opensource.org/licenses/index.php) which requires the - distribution of source code with any binary distribution and to - distribute linked combinations of the two. Non-GPL Code permitted under - this exception must only link to the code of Red Hat elfutils through - those well defined interfaces identified in the file named EXCEPTION - found in the source code files (the "Approved Interfaces"). The files - of Non-GPL Code may instantiate templates or use macros or inline - functions from the Approved Interfaces without causing the resulting - work to be covered by the GNU General Public License. Only Red Hat, - Inc. may make changes or additions to the list of Approved Interfaces. - Red Hat's grant of this exception is conditioned upon your not adding - any new exceptions. If you wish to add a new Approved Interface or - exception, please contact Red Hat. You must obey the GNU General Public - License in all respects for all of the Red Hat elfutils code and other - code used in conjunction with Red Hat elfutils except the Non-GPL Code - covered by this exception. If you modify this file, you may extend this - exception to your version of the file, but you are not obligated to do - so. If you do not wish to provide this exception without modification, - you must delete this exception statement from your version and license - this file solely under the GPL without exception. - - Red Hat elfutils is an included package of the Open Invention Network. - An included package of the Open Invention Network is a package for which - Open Invention Network licensees cross-license their patents. No patent - license is granted, either expressly or impliedly, by designation as an - included package. Should you wish to participate in the Open Invention - Network licensing program, please visit www.openinventionnetwork.com - <http://www.openinventionnetwork.com>. */ + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see <http://www.gnu.org/licenses/>. */ #include "libdwflP.h" #include <inttypes.h> #include <sys/types.h> +#include <sys/stat.h> #include <errno.h> #include <stdio.h> #include <stdio_ext.h> @@ -60,14 +40,53 @@ #include <unistd.h> #include <assert.h> #include <endian.h> +#include "system.h" #define PROCMAPSFMT "/proc/%d/maps" #define PROCMEMFMT "/proc/%d/mem" #define PROCAUXVFMT "/proc/%d/auxv" +#define PROCEXEFMT "/proc/%d/exe" + + +/* Return ELFCLASS64 or ELFCLASS32 for the main ELF executable. Return + ELFCLASSNONE for an error. */ + +static unsigned char +get_pid_class (pid_t pid) +{ + char *fname; + if (asprintf (&fname, PROCEXEFMT, pid) < 0) + return ELFCLASSNONE; + + int fd = open64 (fname, O_RDONLY); + free (fname); + if (fd < 0) + return ELFCLASSNONE; + + unsigned char buf[EI_CLASS + 1]; + ssize_t nread = pread_retry (fd, &buf, sizeof buf, 0); + close (fd); + if (nread != sizeof buf || buf[EI_MAG0] != ELFMAG0 + || buf[EI_MAG1] != ELFMAG1 || buf[EI_MAG2] != ELFMAG2 + || buf[EI_MAG3] != ELFMAG3 + || (buf[EI_CLASS] != ELFCLASS64 && buf[EI_CLASS] != ELFCLASS32)) + return ELFCLASSNONE; + return buf[EI_CLASS]; +} + +/* Search /proc/PID/auxv for the AT_SYSINFO_EHDR tag. + + It would be easiest to call get_pid_class and parse everything according to + the 32-bit or 64-bit class. But this would bring the overhead of syscalls + to open and read the "/proc/%d/exe" file. -/* Search /proc/PID/auxv for the AT_SYSINFO_EHDR tag. */ + Therefore this function tries to parse the "/proc/%d/auxv" content both + ways, as if it were the 32-bit format and also if it were the 64-bit format. + Only if it gives some valid data in both cases get_pid_class gets called. + In most cases only one of the format bit sizes gives valid data and the + get_pid_class call overhead can be saved. */ static int grovel_auxv (pid_t pid, Dwfl *dwfl, GElf_Addr *sysinfo_ehdr) @@ -81,61 +100,79 @@ grovel_auxv (pid_t pid, Dwfl *dwfl, GElf_Addr *sysinfo_ehdr) if (fd < 0) return errno == ENOENT ? 0 : errno; + GElf_Addr sysinfo_ehdr64 = 0; + GElf_Addr sysinfo_ehdr32 = 0; + GElf_Addr segment_align64 = dwfl->segment_align; + GElf_Addr segment_align32 = dwfl->segment_align; + off_t offset = 0; ssize_t nread; + union + { + Elf64_auxv_t a64[64]; + Elf32_auxv_t a32[128]; + } d; do { - union - { - char buffer[sizeof (long int) * 2 * 64]; - Elf64_auxv_t a64[sizeof (long int) * 2 * 64 / sizeof (Elf64_auxv_t)]; - Elf32_auxv_t a32[sizeof (long int) * 2 * 32 / sizeof (Elf32_auxv_t)]; - } d; - nread = read (fd, &d, sizeof d); - if (nread > 0) + eu_static_assert (sizeof d.a64 == sizeof d.a32); + nread = pread_retry (fd, d.a64, sizeof d.a64, offset); + if (nread < 0) { - switch (sizeof (long int)) - { - case 4: - for (size_t i = 0; (char *) &d.a32[i] < &d.buffer[nread]; ++i) - if (d.a32[i].a_type == AT_SYSINFO_EHDR) - { - *sysinfo_ehdr = d.a32[i].a_un.a_val; - if (dwfl->segment_align > 1) - { - nread = 0; - break; - } - } - else if (d.a32[i].a_type == AT_PAGESZ - && dwfl->segment_align <= 1) - dwfl->segment_align = d.a32[i].a_un.a_val; + int ret = errno; + close (fd); + return ret; + } + for (size_t a32i = 0; a32i < nread / sizeof d.a32[0]; a32i++) + { + const Elf32_auxv_t *a32 = d.a32 + a32i; + switch (a32->a_type) + { + case AT_SYSINFO_EHDR: + sysinfo_ehdr32 = a32->a_un.a_val; break; - case 8: - for (size_t i = 0; (char *) &d.a64[i] < &d.buffer[nread]; ++i) - if (d.a64[i].a_type == AT_SYSINFO_EHDR) - { - *sysinfo_ehdr = d.a64[i].a_un.a_val; - if (dwfl->segment_align > 1) - { - nread = 0; - break; - } - } - else if (d.a64[i].a_type == AT_PAGESZ - && dwfl->segment_align <= 1) - dwfl->segment_align = d.a64[i].a_un.a_val; + case AT_PAGESZ: + segment_align32 = a32->a_un.a_val; break; - default: - abort (); + } + } + for (size_t a64i = 0; a64i < nread / sizeof d.a64[0]; a64i++) + { + const Elf64_auxv_t *a64 = d.a64 + a64i; + switch (a64->a_type) + { + case AT_SYSINFO_EHDR: + sysinfo_ehdr64 = a64->a_un.a_val; break; - } + case AT_PAGESZ: + segment_align64 = a64->a_un.a_val; + break; + } } + offset += nread; } - while (nread > 0); + while (nread == sizeof d.a64); close (fd); - return nread < 0 ? errno : 0; + bool valid64 = sysinfo_ehdr64 != 0 || segment_align64 != dwfl->segment_align; + bool valid32 = sysinfo_ehdr32 != 0 || segment_align32 != dwfl->segment_align; + + unsigned char pid_class = ELFCLASSNONE; + if (valid64 && valid32) + pid_class = get_pid_class (pid); + + if (pid_class == ELFCLASS64 || (valid64 && ! valid32)) + { + *sysinfo_ehdr = sysinfo_ehdr64; + dwfl->segment_align = segment_align64; + return 0; + } + if (pid_class == ELFCLASS32 || (! valid64 && valid32)) + { + *sysinfo_ehdr = sysinfo_ehdr32; + dwfl->segment_align = segment_align32; + return 0; + } + return ENOEXEC; } static int @@ -189,7 +226,6 @@ proc_maps_report (Dwfl *dwfl, FILE *f, GElf_Addr sysinfo_ehdr, pid_t pid) { bad_report: free (line); - fclose (f); return -1; } @@ -201,7 +237,7 @@ proc_maps_report (Dwfl *dwfl, FILE *f, GElf_Addr sysinfo_ehdr, pid_t pid) } char *file = line + nread + strspn (line + nread, " \t"); - if (file[0] == '\0' || (ino == 0 && dmajor == 0 && dminor == 0)) + if (file[0] != '/' || (ino == 0 && dmajor == 0 && dminor == 0)) /* This line doesn't indicate a file mapping. */ continue; @@ -209,7 +245,8 @@ proc_maps_report (Dwfl *dwfl, FILE *f, GElf_Addr sysinfo_ehdr, pid_t pid) && ino == last_ino && dmajor == last_dmajor && dminor == last_dminor) { /* This is another portion of the same file's mapping. */ - assert (!strcmp (last_file, file)); + if (strcmp (last_file, file) != 0) + goto bad_report; high = end; } else @@ -288,6 +325,7 @@ read_proc_memory (void *arg, void *data, GElf_Addr address, } extern Elf *elf_from_remote_memory (GElf_Addr ehdr_vma, + GElf_Xword pagesize, GElf_Addr *loadbasep, ssize_t (*read_memory) (void *arg, void *data, @@ -305,44 +343,80 @@ 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] == '/') { - int fd = open64 (module_name, O_RDONLY); - if (fd >= 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. (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) + { + if (strcmp (strrchr (module_name, ' ') ?: "", " (deleted)") == 0) + pid = INTUSE(dwfl_pid) (mod->dwfl); + else + return -1; + } + + 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, NULL, &read_proc_memory, &fd); + *elfp = elf_from_remote_memory (base, getpagesize (), NULL, + &read_proc_memory, &fd); close (fd); *file_name = NULL; + + detach: + if (detach) + __libdwfl_ptrace_detach (pid, tid_was_stopped); return -1; } - abort (); return -1; } INTDEF (dwfl_linux_proc_find_elf) |