./ 2013-06-23 Jan Kratochvil <[email protected]> * configure.ac: New AC_CHECK_SIZEOF for long. Call utrace_BIARCH, new AC_SUBST for CC_BIARCH.
m4/ 2013-06-23 Jan Kratochvil <[email protected]> * biarch.m4: New file. tests/ 2013-09-02 Jan Kratochvil <[email protected]> Mark Wielaard <[email protected]> * Makefile.am (check_PROGRAMS): Add backtrace, backtrace-child and backtrace-data. (BUILT_SOURCES, clean-local, backtrace-child-biarch): New. (TESTS): Add run-backtrace.sh. (backtrace_LDADD, backtrace_child_CFLAGS, backtrace_child_LDFLAGS) (backtrace_data_LDADD): New. * backtrace-child.c: New file. * backtrace-data.c: New file. * backtrace.c: New file. * run-backtrace.sh: New file. Signed-off-by: Jan Kratochvil <[email protected]> --- a/configure.ac +++ b/configure.ac @@ -317,4 +317,15 @@ esac # Round up to the next release API (x.y) version. eu_version=$(( (eu_version + 999) / 1000 )) +AC_CHECK_SIZEOF(long) + +# On a 64-bit host where can can use $CC -m32, we'll run two sets of tests. +# Likewise in a 32-bit build on a host where $CC -m64 works. +utrace_BIARCH +# `$utrace_biarch' will be `-m64' even on an uniarch i386 machine. +AS_IF([test $utrace_cv_cc_biarch = yes], + [CC_BIARCH="$CC $utrace_biarch"], + [CC_BIARCH="$CC"]) +AC_SUBST([CC_BIARCH]) + AC_OUTPUT --- /dev/null +++ b/m4/biarch.m4 @@ -0,0 +1,45 @@ +AC_DEFUN([utrace_CC_m32], [dnl +AC_CACHE_CHECK([$CC option for 32-bit word size], utrace_cv_CC_m32, [dnl +save_CC="$CC" +utrace_cv_CC_m32=none +for ut_try in -m32 -m31; do + [CC=`echo "$save_CC" | sed 's/ -m[36][241]//'`" $ut_try"] + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int foo (void) { return 1; }]])], + [utrace_cv_CC_m32=$ut_try]) + test x$utrace_cv_CC_m32 = xnone || break +done +CC="$save_CC"])]) + +AC_DEFUN([utrace_HOST64], [AC_REQUIRE([utrace_CC_m32]) +AS_IF([test x$utrace_cv_CC_m32 != xnone], [dnl +AC_CACHE_CHECK([for 64-bit host], utrace_cv_host64, [dnl +AC_EGREP_CPP([@utrace_host64@], [#include <stdint.h> +#if (UINTPTR_MAX > 0xffffffffUL) +@utrace_host64@ +#endif], + utrace_cv_host64=yes, utrace_cv_host64=no)]) +AS_IF([test $utrace_cv_host64 = no], + [utrace_biarch=-m64 utrace_thisarch=$utrace_cv_CC_m32], + [utrace_biarch=$utrace_cv_CC_m32 utrace_thisarch=-m64]) + +biarch_CC=`echo "$CC" | sed "s/ *${utrace_thisarch}//"` +biarch_CC="$biarch_CC $utrace_biarch"])]) + +AC_DEFUN([utrace_BIARCH], [AC_REQUIRE([utrace_HOST64]) +utrace_biarch_forced=no +AC_ARG_WITH([biarch], + AC_HELP_STRING([--with-biarch], + [enable biarch tests despite build problems]), + [AS_IF([test "x$with_biarch" != xno], [utrace_biarch_forced=yes])]) +AS_IF([test $utrace_biarch_forced = yes], [dnl +utrace_cv_cc_biarch=yes +AC_MSG_NOTICE([enabling biarch tests regardless using $biarch_CC])], [dnl +AS_IF([test x$utrace_cv_CC_m32 != xnone], [dnl +AC_CACHE_CHECK([whether $biarch_CC makes executables we can run], + utrace_cv_cc_biarch, [dnl +save_CC="$CC" +CC="$biarch_CC" +AC_RUN_IFELSE([AC_LANG_PROGRAM([], [])], + utrace_cv_cc_biarch=yes, utrace_cv_cc_biarch=no) +CC="$save_CC"])], [utrace_cv_cc_biarch=no])]) +AM_CONDITIONAL(BIARCH, [test $utrace_cv_cc_biarch = yes])]) --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -52,10 +52,24 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \ test-flag-nobits dwarf-getstring rerequest_tag \ alldts md5-sha1-test typeiter typeiter2 low_high_pc \ test-elf_cntl_gelf_getshdr dwflsyms dwfllines \ - dwfl-report-elf-align varlocs + dwfl-report-elf-align varlocs backtrace backtrace-child \ + backtrace-data asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \ asm-tst6 asm-tst7 asm-tst8 asm-tst9 +BUILT_SOURCES = backtrace-child-biarch + +clean-local: + $(RM) backtrace-child-biarch + +# Substitute $(COMPILE). +backtrace-child-biarch: backtrace-child.c + $(CC_BIARCH) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(backtrace_child_CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) $(backtrace_child_LDFLAGS) \ + -o $@ $< + TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \ update1 update2 update3 update4 \ run-show-die-info.sh run-get-files.sh run-get-lines.sh \ @@ -89,7 +103,7 @@ TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \ run-test-archive64.sh run-readelf-vmcoreinfo.sh \ run-readelf-mixed-corenote.sh run-dwfllines.sh \ run-dwfl-report-elf-align.sh run-addr2line-test.sh \ - run-addr2line-i-test.sh run-varlocs.sh + run-addr2line-i-test.sh run-varlocs.sh run-backtrace.sh if !STANDALONE check_PROGRAMS += msg_tst md5-sha1-test @@ -212,7 +226,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \ testfile_implicit_pointer.c testfile_implicit_pointer.bz2 \ testfile_parameter_ref.c testfile_parameter_ref.bz2 \ testfile_entry_value.c testfile_entry_value.bz2 \ - testfile_implicit_value.c testfile_implicit_value.bz2 + testfile_implicit_value.c testfile_implicit_value.bz2 \ + run-backtrace.sh if USE_VALGRIND valgrind_cmd='valgrind -q --trace-children=yes --error-exitcode=1 --run-libc-freeres=no' @@ -339,6 +354,10 @@ dwflsyms_LDADD = $(libdw) $(libelf) $(libmudflap) dwfllines_LDADD = $(libdw) $(libelf) $(libmudflap) dwfl_report_elf_align_LDADD = $(libdw) $(libmudflap) varlocs_LDADD = $(libdw) $(libelf) $(libmudflap) +backtrace_LDADD = $(libdw) $(libelf) $(libmudflap) +backtrace_child_CFLAGS = -fPIE +backtrace_child_LDFLAGS = -pie -pthread +backtrace_data_LDADD = $(libdw) $(libelf) $(libmudflap) if GCOV check: check-am coverage --- /dev/null +++ b/tests/backtrace-child.c @@ -0,0 +1,157 @@ +/* Test child for parent backtrace test. + Copyright (C) 2013 Red Hat, Inc. + This file is part of elfutils. + + This file 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; either version 3 of the License, or + (at your option) any later version. + + 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 this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> +#include <assert.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <sys/ptrace.h> +#include <string.h> +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) +#define NOINLINE_NOCLONE __attribute__ ((noinline, noclone)) +#else +#define NOINLINE_NOCLONE __attribute__ ((noinline)) +#endif + +#define NORETURN __attribute__ ((noreturn)) +#define UNUSED __attribute__ ((unused)) +#define USED __attribute__ ((used)) + +static int ptraceme, gencore; + +/* Execution will arrive here from jmp by an artificial ptrace-spawn signal. */ + +static void +sigusr2 (int signo) +{ + assert (signo == SIGUSR2); + if (! gencore) + raise (SIGUSR1); + + /* Catch the .plt jump, it will come from this abort call. */ + abort (); +} + +static NOINLINE_NOCLONE void +dummy1 (void) +{ + asm volatile (""); +} + +#ifdef __x86_64__ +static NOINLINE_NOCLONE USED void +jmp (void) +{ + /* Not reached, signal will get ptrace-spawn to jump into sigusr2. */ + abort (); +} +#endif + +static NOINLINE_NOCLONE void +dummy2 (void) +{ + asm volatile (""); +} + +static NOINLINE_NOCLONE NORETURN void +stdarg (int f UNUSED, ...) +{ + sighandler_t sigusr2_orig = signal (SIGUSR2, sigusr2); + assert (sigusr2_orig == SIG_DFL); + errno = 0; + if (ptraceme) + { + long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); + assert_perror (errno); + assert (l == 0); + } +#ifdef __x86_64__ + if (! gencore) + { + /* Execution will get PC patched into function jmp. */ + raise (SIGUSR1); + } +#endif + sigusr2 (SIGUSR2); + abort (); +} + +static NOINLINE_NOCLONE void +dummy3 (void) +{ + asm volatile (""); +} + +static NOINLINE_NOCLONE void +backtracegen (void) +{ + stdarg (1); + /* Here should be no instruction after the stdarg call as it is noreturn + function. It must be stdarg so that it is a call and not jump (jump as + a tail-call). */ +} + +static NOINLINE_NOCLONE void +dummy4 (void) +{ + asm volatile (""); +} + +static void * +start (void *arg UNUSED) +{ + backtracegen (); + abort (); +} + +int +main (int argc UNUSED, char **argv) +{ + assert (*argv++); + ptraceme = (*argv && strcmp (*argv, "--ptraceme") == 0); + argv += ptraceme; + gencore = (*argv && strcmp (*argv, "--gencore") == 0); + argv += gencore; + assert (*argv && strcmp (*argv, "--run") == 0); + dummy1 (); + dummy2 (); + dummy3 (); + dummy4 (); + if (gencore) + printf ("%ld\n", (long) getpid ()); + errno = 0; + pthread_t thread; + int i = pthread_create (&thread, NULL, start, NULL); + assert_perror (errno); + assert (i == 0); + if (ptraceme) + { + long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); + assert_perror (errno); + assert (l == 0); + } + if (gencore) + pthread_join (thread, NULL); + else + raise (SIGUSR2); + abort (); +} --- /dev/null +++ b/tests/backtrace-data.c @@ -0,0 +1,304 @@ +/* Test program for unwinding of frames. + Copyright (C) 2013 Red Hat, Inc. + This file is part of elfutils. + + This file 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; either version 3 of the License, or + (at your option) any later version. + + 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 this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> +#include <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <locale.h> +#include <dirent.h> +#include <stdlib.h> +#include <errno.h> +#include <error.h> +#include <unistd.h> +#include <dwarf.h> +#include <sys/resource.h> +#include <sys/ptrace.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/user.h> +#include <fcntl.h> +#include <string.h> +#include ELFUTILS_HEADER(dwfl) + +#ifndef __x86_64__ + +int +main (void) +{ + return 77; +} + +#else /* __x86_64__ */ + +static int +find_elf (Dwfl_Module *mod __attribute__ ((unused)), + void **userdata __attribute__ ((unused)), + const char *modname __attribute__ ((unused)), + Dwarf_Addr base __attribute__ ((unused)), + char **file_name __attribute__ ((unused)), + Elf **elfp __attribute__ ((unused))) +{ + /* Not used as modules are reported explicitly. */ + assert (0); +} + +static bool +memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, + void *dwfl_arg __attribute__ ((unused))) +{ + pid_t child = dwfl_pid (dwfl); + + errno = 0; + long l = ptrace (PTRACE_PEEKDATA, child, (void *) (uintptr_t) addr, NULL); + assert_perror (errno); + *result = l; + + /* We could also return false for failed ptrace. */ + return true; +} + +/* Return filename and VMA address *BASEP where its mapping starts which + contains ADDR. */ + +static char * +maps_lookup (pid_t pid, Dwarf_Addr addr, GElf_Addr *basep) +{ + char *fname; + int i = asprintf (&fname, "/proc/%ld/maps", (long) pid); + assert_perror (errno); + assert (i > 0); + FILE *f = fopen (fname, "r"); + assert_perror (errno); + assert (f); + free (fname); + for (;;) + { + // 37e3c22000-37e3c23000 rw-p 00022000 00:11 49532 /lib64/ld-2.14.90.so */ + unsigned long start, end, offset; + i = fscanf (f, "%lx-%lx %*s %lx %*x:%*x %*x", &start, &end, &offset); + assert_perror (errno); + assert (i == 3); + char *filename = strdup (""); + assert (filename); + size_t filename_len = 0; + for (;;) + { + int c = fgetc (f); + assert (c != EOF); + if (c == '\n') + break; + if (c == ' ' && *filename == '\0') + continue; + filename = realloc (filename, filename_len + 2); + assert (filename); + filename[filename_len++] = c; + filename[filename_len] = '\0'; + } + if (start <= addr && addr < end) + { + i = fclose (f); + assert_perror (errno); + assert (i == 0); + + *basep = start - offset; + return filename; + } + free (filename); + } +} + +/* Add module containing ADDR to the DWFL address space. */ + +static Dwfl_Module * +report_module (Dwfl *dwfl, pid_t child, Dwarf_Addr addr) +{ + GElf_Addr base; + char *long_name = maps_lookup (child, addr, &base); + Dwfl_Module *mod = dwfl_report_elf (dwfl, long_name, long_name, -1, + base, false /* add_p_vaddr */); + assert (mod); + free (long_name); + assert (dwfl_addrmodule (dwfl, addr) == mod); + return mod; +} + +static pid_t +next_thread (Dwfl *dwfl, Dwfl_Thread *nthread __attribute__ ((unused)), + void *dwfl_arg __attribute__ ((unused)), + void **thread_argp __attribute__ ((unused))) +{ + return dwfl_pid (dwfl); +} + +static bool +set_initial_registers (Dwfl_Thread *thread, + void *thread_arg __attribute__ ((unused))) +{ + pid_t child = dwfl_pid (dwfl_thread_dwfl (thread)); + + struct user_regs_struct user_regs; + long l = ptrace (PTRACE_GETREGS, child, NULL, &user_regs); + assert_perror (errno); + assert (l == 0); + + Dwarf_Word dwarf_regs[17]; + dwarf_regs[0] = user_regs.rax; + dwarf_regs[1] = user_regs.rdx; + dwarf_regs[2] = user_regs.rcx; + dwarf_regs[3] = user_regs.rbx; + dwarf_regs[4] = user_regs.rsi; + dwarf_regs[5] = user_regs.rdi; + dwarf_regs[6] = user_regs.rbp; + dwarf_regs[7] = user_regs.rsp; + dwarf_regs[8] = user_regs.r8; + dwarf_regs[9] = user_regs.r9; + dwarf_regs[10] = user_regs.r10; + dwarf_regs[11] = user_regs.r11; + dwarf_regs[12] = user_regs.r12; + dwarf_regs[13] = user_regs.r13; + dwarf_regs[14] = user_regs.r14; + dwarf_regs[15] = user_regs.r15; + dwarf_regs[16] = user_regs.rip; + bool ok = dwfl_thread_state_registers (thread, 0, 17, dwarf_regs); + assert (ok); + + /* x86_64 has PC contained in its CFI subset of DWARF register set so + elfutils will figure out the real PC value from REGS. + So no need to explicitly call dwfl_thread_state_register_pc. */ + + return true; +} + +static const Dwfl_Thread_Callbacks callbacks = +{ + next_thread, + memory_read, + set_initial_registers, + NULL, /* detach */ + NULL, /* thread_detach */ +}; + +static int +frame_callback (Dwfl_Frame *state, void *arg) +{ + unsigned *framenop = arg; + Dwarf_Addr pc; + bool isactivation; + if (! dwfl_frame_pc (state, &pc, &isactivation)) + { + error (1, 0, "%s", dwfl_errmsg (-1)); + return 1; + } + Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1); + + /* Get PC->SYMNAME. */ + Dwfl *dwfl = dwfl_thread_dwfl (dwfl_frame_thread (state)); + Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); + if (mod == NULL) + mod = report_module (dwfl, dwfl_pid (dwfl), pc_adjusted); + const char *symname = NULL; + symname = dwfl_module_addrname (mod, pc_adjusted); + + printf ("#%2u %#" PRIx64 "%4s\t%s\n", (*framenop)++, (uint64_t) pc, + ! isactivation ? "- 1" : "", symname); + return DWARF_CB_OK; +} + +int +main (int argc __attribute__ ((unused)), char **argv __attribute__ ((unused))) +{ + /* We use no threads here which can interfere with handling a stream. */ + __fsetlocking (stdin, FSETLOCKING_BYCALLER); + __fsetlocking (stdout, FSETLOCKING_BYCALLER); + __fsetlocking (stderr, FSETLOCKING_BYCALLER); + + /* Set locale. */ + (void) setlocale (LC_ALL, ""); + + pid_t child = fork (); + switch (child) + { + case -1: + assert_perror (errno); + assert (0); + case 0:; + long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); + assert_perror (errno); + assert (l == 0); + raise (SIGUSR1); + assert (0); + default: + break; + } + + int status; + pid_t pid = waitpid (child, &status, 0); + assert_perror (errno); + assert (pid == child); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR1); + + static char *debuginfo_path; + static const Dwfl_Callbacks offline_callbacks = + { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + .section_address = dwfl_offline_section_address, + .find_elf = find_elf, + }; + Dwfl *dwfl = dwfl_begin (&offline_callbacks); + assert (dwfl); + + struct user_regs_struct user_regs; + long l = ptrace (PTRACE_GETREGS, child, NULL, &user_regs); + assert_perror (errno); + assert (l == 0); + report_module (dwfl, child, user_regs.rip); + + bool ok = dwfl_attach_state (dwfl, NULL, child, &callbacks, NULL); + assert (ok); + + /* Multiple threads are not handled here. */ + Dwfl_Thread *thread = dwfl_next_thread (dwfl, NULL); + assert (thread); + + unsigned frameno = 0; + switch (dwfl_thread_getframes (thread, frame_callback, &frameno)) + { + case 0: + break; + case -1: + error (1, 0, "dwfl_thread_getframes: %s", dwfl_errmsg (-1)); + default: + abort (); + } + + dwfl_end (dwfl); + kill (child, SIGKILL); + pid = waitpid (child, &status, 0); + assert_perror (errno); + assert (pid == child); + assert (WIFSIGNALED (status)); + assert (WTERMSIG (status) == SIGKILL); + + return EXIT_SUCCESS; +} + +#endif /* x86_64 */ --- /dev/null +++ b/tests/backtrace.c @@ -0,0 +1,538 @@ +/* Test program for unwinding of frames. + Copyright (C) 2013 Red Hat, Inc. + This file is part of elfutils. + + This file 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; either version 3 of the License, or + (at your option) any later version. + + 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 this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> +#include <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <locale.h> +#include <dirent.h> +#include <stdlib.h> +#include <errno.h> +#include <error.h> +#include <unistd.h> +#include <dwarf.h> +#include <sys/resource.h> +#include <sys/ptrace.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/user.h> +#include <fcntl.h> +#include <string.h> +#include ELFUTILS_HEADER(dwfl) + +static void +report_pid (Dwfl *dwfl, pid_t pid) +{ + int result = dwfl_linux_proc_report (dwfl, pid); + if (result < 0) + error (2, 0, "dwfl_linux_proc_report: %s", dwfl_errmsg (-1)); + else if (result > 0) + error (2, result, "dwfl_linux_proc_report"); + + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1)); +} + +static Dwfl * +pid_to_dwfl (pid_t pid) +{ + 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, + }; + Dwfl *dwfl = dwfl_begin (&proc_callbacks); + if (dwfl == NULL) + error (2, 0, "dwfl_begin: %s", dwfl_errmsg (-1)); + report_pid (dwfl, pid); + return dwfl; +} + +static const char *executable; + +static int +find_elf (Dwfl_Module *mod, void **userdata, const char *modname, + Dwarf_Addr base, char **file_name, Elf **elfp) +{ + if (executable && modname != NULL + && (strcmp (modname, "[exe]") == 0 || strcmp (modname, "[pie]") == 0)) + { + char *executable_dup = strdup (executable); + if (executable_dup) + { + free (*file_name); + *file_name = executable_dup; + return -1; + } + } + return dwfl_build_id_find_elf (mod, userdata, modname, base, file_name, elfp); +} + +static Dwfl * +dwfl_offline (void) +{ + static char *debuginfo_path; + static const Dwfl_Callbacks offline_callbacks = + { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + + .section_address = dwfl_offline_section_address, + + /* We use this table for core files too. */ + .find_elf = find_elf, + }; + Dwfl *dwfl = dwfl_begin (&offline_callbacks); + if (dwfl == NULL) + error (2, 0, "dwfl_begin: %s", dwfl_errmsg (-1)); + return dwfl; +} + +static Dwfl * +report_corefile (Dwfl *dwfl, const char *corefile) +{ + int fd = open64 (corefile, O_RDONLY); + if (fd == -1) + error (2, 0, "open64: %m"); + Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL); + if (elf == NULL) + error (2, 0, "elf_begin: %s", elf_errmsg (-1)); + if (dwfl_core_file_report (dwfl, elf) < 0) + error (2, 0, "dwfl_core_file_report: %s", dwfl_errmsg (-1)); + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1)); + /* ELF and CORE are leaked. */ + return dwfl; +} + +static Dwfl * +corefile_to_dwfl (const char *corefile) +{ + return report_corefile (dwfl_offline (), corefile); +} + +static int +dump_modules (Dwfl_Module *mod, void **userdata __attribute__ ((unused)), + const char *name, Dwarf_Addr start, + void *arg __attribute__ ((unused))) +{ + Dwarf_Addr end; + dwfl_module_info (mod, NULL, NULL, &end, NULL, NULL, NULL, NULL); + printf ("%#" PRIx64 "\t%#" PRIx64 "\t%s\n", (uint64_t) start, (uint64_t) end, + name); + return DWARF_CB_OK; +} + +typedef void (callback_t) (pid_t tid, unsigned frameno, Dwarf_Addr pc, + const char *symname, Dwfl *dwfl, void *data); + +struct frame_callback +{ + unsigned frameno; + callback_t *callback; + void *callback_data; +}; + +static int +frame_callback (Dwfl_Frame *state, void *arg) +{ + struct frame_callback *data = arg; + Dwarf_Addr pc; + bool isactivation; + if (! dwfl_frame_pc (state, &pc, &isactivation)) + { + error (0, 0, "%s", dwfl_errmsg (-1)); + return 1; + } + Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1); + + /* Get PC->SYMNAME. */ + Dwfl_Thread *thread = dwfl_frame_thread (state); + Dwfl *dwfl = dwfl_thread_dwfl (thread); + Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); + const char *symname = NULL; + if (mod) + symname = dwfl_module_addrname (mod, pc_adjusted); + + printf ("#%2u %#" PRIx64 "%4s\t%s\n", data->frameno, (uint64_t) pc, + ! isactivation ? "- 1" : "", symname); + pid_t tid = dwfl_thread_tid (thread); + if (data->callback) + data->callback (tid, data->frameno, pc, symname, dwfl, data->callback_data); + data->frameno++; + + return DWARF_CB_OK; +} + + +static void +dump (pid_t pid, const char *corefile, callback_t *callback, + void *callback_data) +{ + Dwfl *dwfl; + if (pid && !corefile) + dwfl = pid_to_dwfl (pid); + else if (corefile && !pid) + dwfl = corefile_to_dwfl (corefile); + else + abort (); + ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, dump_modules, NULL, 0); + assert (ptrdiff == 0); + Dwfl_Thread *thread = NULL; + int err = 0; + for (;;) + { + thread = dwfl_next_thread (dwfl, thread); + if (thread == NULL) + { + const char *msg = dwfl_errmsg (0); + if (msg == NULL) + break; + error (2, 0, "dwfl_next_thread: %s", msg); + } + printf ("TID %ld:\n", (long) dwfl_thread_tid (thread)); + struct frame_callback frame_callback_data; + frame_callback_data.frameno = 0; + frame_callback_data.callback = callback; + frame_callback_data.callback_data = callback_data; + switch (dwfl_thread_getframes (thread, frame_callback, + &frame_callback_data)) + { + case 0: + break; + case 1: + err = 1; + break; + case -1: + error (0, 0, "dwfl_thread_getframes: %s", dwfl_errmsg (-1)); + err = 1; + break; + default: + abort (); + } + } + while (0); + if (callback) + callback (0, 0, 0, NULL, dwfl, callback_data); + dwfl_end (dwfl); + if (err) + exit (EXIT_FAILURE); +} + +struct see_exec_module +{ + Dwfl_Module *mod; + char selfpath[PATH_MAX + 1]; +}; + +static int +see_exec_module (Dwfl_Module *mod, void **userdata __attribute__ ((unused)), + const char *name __attribute__ ((unused)), + Dwarf_Addr start __attribute__ ((unused)), void *arg) +{ + struct see_exec_module *data = arg; + if (strcmp (name, data->selfpath) != 0) + return DWARF_CB_OK; + assert (data->mod == NULL); + data->mod = mod; + return DWARF_CB_OK; +} + +static void +selfdump_callback (pid_t tid, unsigned frameno, Dwarf_Addr pc, + const char *symname, Dwfl *dwfl, void *data) +{ + pid_t check_tid = (intptr_t) data; + bool disable = check_tid < 0; + if (disable) + check_tid = -check_tid; + static bool seen_main = false; + if (symname && strcmp (symname, "main") == 0) + seen_main = true; + if (pc == 0) + { + assert (seen_main); + return; + } + if (disable || tid != check_tid) + return; + Dwfl_Module *mod; + const char *symname2 = NULL; + switch (frameno) + { + case 0: + /* .plt has no symbols. */ + assert (symname == NULL); + break; + case 1: + assert (symname != NULL && strcmp (symname, "sigusr2") == 0); + break; + case 2: + /* __restore_rt - glibc maybe does not have to have this symbol. */ + break; + case 3: + /* Verify we trapped on the very first instruction of jmp. */ + assert (symname != NULL && strcmp (symname, "jmp") == 0); + mod = dwfl_addrmodule (dwfl, pc - 1); + if (mod) + symname2 = dwfl_module_addrname (mod, pc - 1); + assert (symname2 == NULL || strcmp (symname2, "jmp") != 0); + break; + case 4: + assert (symname != NULL && strcmp (symname, "stdarg") == 0); + break; + case 5: + /* Verify we trapped on the very last instruction of child. */ + assert (symname != NULL && strcmp (symname, "backtracegen") == 0); + mod = dwfl_addrmodule (dwfl, pc); + if (mod) + symname2 = dwfl_module_addrname (mod, pc); + assert (symname2 == NULL || strcmp (symname2, "backtracegen") != 0); + break; + } +} + +#ifdef __x86_64__ +static void +prepare_thread (pid_t pid2, Dwarf_Addr plt_start, Dwarf_Addr plt_end, + void (*jmp) (void)) +{ + long l; + errno = 0; + l = ptrace (PTRACE_POKEUSER, pid2, + (void *) (intptr_t) offsetof (struct user_regs_struct, rip), jmp); + assert_perror (errno); + assert (l == 0); + l = ptrace (PTRACE_CONT, pid2, NULL, (void *) (intptr_t) SIGUSR2); + int status; + pid_t got = waitpid (pid2, &status, __WALL); + assert_perror (errno); + assert (got == pid2); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR1); + for (;;) + { + errno = 0; + l = ptrace (PTRACE_PEEKUSER, pid2, + (void *) (intptr_t) offsetof (struct user_regs_struct, rip), + NULL); + assert_perror (errno); + if ((unsigned long) l >= plt_start && (unsigned long) l < plt_end) + break; + l = ptrace (PTRACE_SINGLESTEP, pid2, NULL, NULL); + assert_perror (errno); + assert (l == 0); + got = waitpid (pid2, &status, __WALL); + assert_perror (errno); + assert (got == pid2); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGTRAP); + } +} +#endif /* __x86_64__ */ + +#include <asm/unistd.h> +#include <unistd.h> +#define tgkill(pid, tid, sig) syscall (__NR_tgkill, (pid), (tid), (sig)) + +static void +ptrace_detach_stopped (pid_t pid) +{ + errno = 0; + long l = ptrace (PTRACE_DETACH, pid, NULL, (void *) (intptr_t) SIGSTOP); + assert_perror (errno); + assert (l == 0); +} + +static void +selfdump (const char *exec) +{ + pid_t pid = fork (); + switch (pid) + { + case -1: + abort (); + case 0: + execl (exec, exec, "--ptraceme", "--run", NULL); + abort (); + default: + break; + } + + /* Catch the main thread. Catch it first otherwise the /proc evaluation of + PID may have caught still ourselves before executing execl above. */ + errno = 0; + int status; + pid_t got = waitpid (pid, &status, 0); + assert_perror (errno); + assert (got == pid); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR2); + + /* Catch the spawned thread. Do not use __WCLONE as we could get racy + __WCLONE, probably despite pthread_create already had to be called the new + task is not yet alive enough for waitpid. */ + pid_t pid2 = waitpid (-1, &status, __WALL); + assert_perror (errno); + assert (pid2 > 0); + assert (pid2 != pid); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR1); + + Dwfl *dwfl = pid_to_dwfl (pid); + char *selfpathname; + int i = asprintf (&selfpathname, "/proc/%ld/exe", (long) pid); + assert (i > 0); + struct see_exec_module data; + ssize_t ssize = readlink (selfpathname, data.selfpath, + sizeof (data.selfpath)); + free (selfpathname); + assert (ssize > 0 && ssize < (ssize_t) sizeof (data.selfpath)); + data.selfpath[ssize] = '\0'; + data.mod = NULL; + ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, see_exec_module, &data, 0); + assert (ptrdiff == 0); + assert (data.mod != NULL); + GElf_Addr loadbase; + Elf *elf = dwfl_module_getelf (data.mod, &loadbase); + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + assert (ehdr != NULL); + Elf_Scn *scn = NULL, *plt = NULL; + while ((scn = elf_nextscn (elf, scn)) != NULL) + { + GElf_Shdr scn_shdr_mem, *scn_shdr = gelf_getshdr (scn, &scn_shdr_mem); + assert (scn_shdr != NULL); + if (strcmp (elf_strptr (elf, ehdr->e_shstrndx, scn_shdr->sh_name), + ".plt") != 0) + continue; + assert (plt == NULL); + plt = scn; + } + assert (plt != NULL); + GElf_Shdr scn_shdr_mem, *scn_shdr = gelf_getshdr (plt, &scn_shdr_mem); + assert (scn_shdr != NULL); + /* Make it true on x86_64 with i386 inferior. */ + int disable = ehdr->e_ident[EI_CLASS] == ELFCLASS32; +#ifdef __x86_64__ + Dwarf_Addr plt_start = scn_shdr->sh_addr + loadbase; + Dwarf_Addr plt_end = plt_start + scn_shdr->sh_size; + void (*jmp) (void); + if (! disable) + { + int nsym = dwfl_module_getsymtab (data.mod); + int symi; + for (symi = 1; symi < nsym; ++symi) + { + GElf_Sym symbol; + const char *symbol_name = dwfl_module_getsym (data.mod, symi, &symbol, NULL); + if (symbol_name == NULL) + continue; + switch (GELF_ST_TYPE (symbol.st_info)) + { + case STT_SECTION: + case STT_FILE: + case STT_TLS: + continue; + default: + if (strcmp (symbol_name, "jmp") != 0) + continue; + break; + } + /* LOADBASE is already applied here. */ + jmp = (void (*) (void)) (uintptr_t) symbol.st_value; + break; + } + assert (symi < nsym); + prepare_thread (pid2, plt_start, plt_end, jmp); + } +#endif + dwfl_end (dwfl); + ptrace_detach_stopped (pid); + ptrace_detach_stopped (pid2); + dump (pid, NULL, selfdump_callback, + (void *) (intptr_t) (disable ? -pid2 : pid2)); +} + +static bool +is_core (const char *corefile) +{ + Dwfl *dwfl = dwfl_offline (); + Dwfl_Module *mod = dwfl_report_elf (dwfl, "core", corefile, -1, 0 /* base */, + false /* add_p_vaddr */); + assert (mod != NULL); + GElf_Addr loadbase_ignore; + Elf *core = dwfl_module_getelf (mod, &loadbase_ignore); + assert (core != NULL); + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem); + assert (ehdr != NULL); + assert (ehdr->e_type == ET_CORE || ehdr->e_type == ET_EXEC + || ehdr->e_type == ET_DYN); + bool retval = ehdr->e_type == ET_CORE; + dwfl_end (dwfl); + return retval; +} + +int +main (int argc __attribute__ ((unused)), char **argv) +{ + /* We use no threads here which can interfere with handling a stream. */ + __fsetlocking (stdin, FSETLOCKING_BYCALLER); + __fsetlocking (stdout, FSETLOCKING_BYCALLER); + __fsetlocking (stderr, FSETLOCKING_BYCALLER); + + /* Set locale. */ + (void) setlocale (LC_ALL, ""); + + if (argc == 1) + { + selfdump ("./backtrace-child"); + return 0; + } + argv++; + if (argc == 2) + { + if (strcmp (*argv, "--help") == 0) + error (2, 0, "backtrace {{no args for ./backtrace-child}|<pid>|<core>|" + "<executable>|<executable core>}"); + char *end; + long l = strtol (*argv, &end, 10); + if (**argv && !*end) + dump (l, NULL, NULL, NULL); + else if (is_core (*argv)) + dump (0, *argv, NULL, NULL); + else + selfdump (*argv); + return 0; + } + if (argc == 3) + { + assert (! is_core (argv[0])); + assert (is_core (argv[1])); + executable = argv[0]; + dump (0, argv[1], NULL, NULL); + return 0; + } + assert (0); + + return 0; +} --- /dev/null +++ b/tests/run-backtrace.sh @@ -0,0 +1,83 @@ +#! /bin/bash +# Copyright (C) 2013 Red Hat, Inc. +# This file is part of elfutils. +# +# This file 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; either version 3 of the License, or +# (at your option) any later version. +# +# 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 this program. If not, see <http://www.gnu.org/licenses/>. + +. $srcdir/test-subr.sh + +if [ -z "$VERBOSE" ]; then + exec >/dev/null +else + set -x +fi + +mytestrun() +{ + echo "$*" + testrun "$@" +} + +check_main() +{ + if grep -w main $1; then + return + fi + cat >&2 $1 $3 + echo >&2 $2: no main + false +} + +check_gsignal() +{ + # Without proper ELF symbols resolution we could get inappropriate weak + # symbol "gsignal" with the same address as the correct symbol "raise". + if ! grep -w gsignal $1; then + return + fi + cat >&2 $1 + echo >&2 $2: found gsignal + false +} + +check_err() +{ + if test ! -s $1; then + return + fi + # In some cases we cannot reliably find out we got behind _start. + if cmp -s <(echo "${abs_builddir}/backtrace: dwfl_thread_getframes: No DWARF information found") <(uniq <$1); then + return + fi + cat >&2 $1 + echo >&2 $2: neither empty nor just out of DWARF + false +} + +for child in backtrace-child{,-biarch}; do + tempfiles $child{.bt,.err} + (set +ex; testrun ${abs_builddir}/backtrace ${abs_builddir}/$child 1>$child.bt 2>$child.err; true) + check_main $child.bt $child $child.err + check_gsignal $child.bt $child + check_err $child.err $child + core="core.`ulimit -c unlimited; set +ex; testrun ${abs_builddir}/$child --gencore --run; true`" + tempfiles $core{,.bt,.err} + (set +ex; testrun ${abs_builddir}/backtrace ${abs_builddir}/$child $core 1>$core.bt 2>$core.err; true) + cat $core.{bt,err} + check_main $core.bt $child-$core $core.err + check_gsignal $core.bt $child-$core + check_err $core.err $child-$core +done + +exit 0
