/* Test program for unwinding of frames. Copyright (C) 2013, 2014, 2016 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 . */ #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #include #include #include #include #include #include #include #include #include ELFUTILS_HEADER(dwfl) #endif #include "system.h" #ifndef __linux__ int main (int argc __attribute__ ((unused)), char **argv) { fprintf (stderr, "%s: Unwinding not supported for this architecture\n", argv[0]); return 77; } #else /* __linux__ */ 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; } static bool use_raise_jmp_patching; static pid_t check_tid; static void callback_verify (pid_t tid, unsigned frameno, Dwarf_Addr pc, const char *symname, Dwfl *dwfl) { static bool seen_main = false; if (symname && *symname == '.') symname++; if (symname && strcmp (symname, "main") == 0) seen_main = true; if (pc == 0) { assert (seen_main); return; } if (check_tid == 0) check_tid = tid; if (tid != check_tid) { // For the main thread we are only interested if we can unwind till // we see the "main" symbol. return; } Dwfl_Module *mod; /* See case 4. Special case to help out simple frame pointer unwinders. */ static bool duplicate_sigusr2 = false; if (duplicate_sigusr2) frameno--; static bool reduce_frameno = false; if (reduce_frameno) frameno--; if (! use_raise_jmp_patching && frameno >= 2) frameno += 2; const char *symname2 = NULL; switch (frameno) { case 0: if (! reduce_frameno && symname && (strcmp (symname, "__kernel_vsyscall") == 0 || strcmp (symname, "__libc_do_syscall") == 0)) reduce_frameno = true; else assert (symname && strcmp (symname, "raise") == 0); break; case 1: assert (symname != NULL && strcmp (symname, "sigusr2") == 0); break; case 2: // x86_64 only /* __restore_rt - glibc maybe does not have to have this symbol. */ break; case 3: // use_raise_jmp_patching if (use_raise_jmp_patching) { /* 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; } FALLTHROUGH; case 4: /* Some simple frame unwinders get this wrong and think sigusr2 is calling itself again. Allow it and just pretend there is an extra sigusr2 frame. */ if (symname != NULL && strcmp (symname, "sigusr2") == 0) { duplicate_sigusr2 = true; break; } 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); // Note that the following assert might in theory even fail on x86_64, // there is no guarantee that the compiler doesn't reorder the // instructions or even inserts some padding instructions at the end // (which apparently happens on ppc64). if (use_raise_jmp_patching) assert (symname2 == NULL || strcmp (symname2, "backtracegen") != 0); break; } } static int frame_callback (Dwfl_Frame *state, void *frame_arg) { int *framenop = frame_arg; Dwarf_Addr pc; bool isactivation; if (*framenop > 16) { error (0, 0, "Too many frames: %d\n", *framenop); return DWARF_CB_ABORT; } if (! dwfl_frame_pc (state, &pc, &isactivation)) { error (0, 0, "%s", dwfl_errmsg (-1)); return DWARF_CB_ABORT; } 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 ("#%2d %#" PRIx64 "%4s\t%s\n", *framenop, (uint64_t) pc, ! isactivation ? "- 1" : "", symname ?: ""); pid_t tid = dwfl_thread_tid (thread); callback_verify (tid, *framenop, pc, symname, dwfl); (*framenop)++; return DWARF_CB_OK; } static int thread_callback (Dwfl_Thread *thread, void *thread_arg __attribute__((unused))) { printf ("TID %ld:\n", (long) dwfl_thread_tid (thread)); int frameno = 0; switch (dwfl_thread_getframes (thread, frame_callback, &frameno)) { case 0: break; case DWARF_CB_ABORT: return DWARF_CB_ABORT; case -1: error (0, 0, "dwfl_thread_getframes: %s", dwfl_errmsg (-1)); /* All platforms do not have yet proper unwind termination. */ break; default: abort (); } return DWARF_CB_OK; } static void dump (Dwfl *dwfl) { ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, dump_modules, NULL, 0); assert (ptrdiff == 0); bool err = false; switch (dwfl_getthreads (dwfl, thread_callback, NULL)) { case 0: break; case DWARF_CB_ABORT: err = true; break; case -1: error (0, 0, "dwfl_getthreads: %s", dwfl_errmsg (-1)); err = true; break; default: abort (); } callback_verify (0, 0, 0, NULL, 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_ABORT; } /* We used to do this on x86_64 only (see backtrace-child why we now don't): PC will get changed to function 'jmp' by backtrace.c function prepare_thread. Then SIGUSR2 will be signalled to backtrace-child which will invoke function sigusr2. This is all done so that signal interrupts execution of the very first instruction of a function. Properly handled unwind should not slip into the previous unrelated function. */ #ifdef __x86_64__ /* #define RAISE_JMP_PATCHING 1 */ #endif static void prepare_thread (pid_t pid2 __attribute__ ((unused)), void (*jmp) (void) __attribute__ ((unused))) { #ifndef RAISE_JMP_PATCHING abort (); #else /* RAISE_JMP_PATCHING */ long l; struct user_regs_struct user_regs; errno = 0; l = ptrace (PTRACE_GETREGS, pid2, 0, (intptr_t) &user_regs); assert (l == 0); user_regs.rip = (intptr_t) jmp; l = ptrace (PTRACE_SETREGS, pid2, 0, (intptr_t) &user_regs); assert (l == 0); l = ptrace (PTRACE_CONT, pid2, NULL, (void *) (intptr_t) SIGUSR2); int status; pid_t got = waitpid (pid2, &status, __WALL); assert (got == pid2); assert (WIFSTOPPED (status)); assert (WSTOPSIG (status) == SIGUSR1); #endif /* RAISE_JMP_PATCHING */ } #include #include 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)); result = dwfl_linux_proc_attach (dwfl, pid, true); if (result < 0) error (2, 0, "dwfl_linux_proc_attach: %s", dwfl_errmsg (-1)); else if (result > 0) error (2, result, "dwfl_linux_proc_attach"); } 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 void exec_dump (const char *exec) { pid_t pid = fork (); switch (pid) { case -1: abort (); case 0: execl (exec, exec, "--ptraceme", 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 (got == pid); assert (WIFSTOPPED (status)); // Main thread will signal SIGUSR2. Other thread will signal SIGUSR1. 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 (pid2 > 0); assert (pid2 != pid); assert (WIFSTOPPED (status)); // Main thread will signal SIGUSR2. Other thread will signal SIGUSR1. 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; dwfl_getmodules (dwfl, see_exec_module, &data, 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); /* It is false also on x86_64 with i386 inferior. */ #ifndef RAISE_JMP_PATCHING use_raise_jmp_patching = false; #else /* RAISE_JMP_PATCHING_ */ use_raise_jmp_patching = ehdr->e_machine == EM_X86_64; #endif /* __x86_64__ */ void (*jmp) (void) = 0; if (use_raise_jmp_patching) { // Find inferior symbol named "jmp". 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, jmp); } dwfl_end (dwfl); check_tid = pid2; dwfl = pid_to_dwfl (pid); dump (dwfl); dwfl_end (dwfl); } #define OPT_BACKTRACE_EXEC 0x100 static const struct argp_option options[] = { { "backtrace-exec", OPT_BACKTRACE_EXEC, "EXEC", 0, N_("Run executable"), 0 }, { NULL, 0, NULL, 0, NULL, 0 } }; static error_t parse_opt (int key, char *arg, struct argp_state *state) { switch (key) { case ARGP_KEY_INIT: state->child_inputs[0] = state->input; break; case OPT_BACKTRACE_EXEC: exec_dump (arg); exit (0); default: return ARGP_ERR_UNKNOWN; } return 0; } 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, ""); elf_version (EV_CURRENT); Dwfl *dwfl = NULL; const struct argp_child argp_children[] = { { .argp = dwfl_standard_argp () }, { .argp = NULL } }; const struct argp argp = { options, parse_opt, NULL, NULL, argp_children, NULL, NULL }; (void) argp_parse (&argp, argc, argv, 0, NULL, &dwfl); assert (dwfl != NULL); /* We want to make sure the dwfl was properly attached. */ if (dwfl_pid (dwfl) < 0) error (2, 0, "dwfl_pid: %s", dwfl_errmsg (-1)); dump (dwfl); dwfl_end (dwfl); return 0; } #endif /* ! __linux__ */