/* 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/>. */ /* Command line syntax: ./backtrace-child [--ptraceme|--gencore] --ptraceme will call ptrace (PTRACE_TRACEME) in the two threads. --gencore will call abort () at its end. Main thread will signal SIGUSR2. Other thread will signal SIGUSR1. On x86_64 only: 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. The tested functionality is arch-independent but the code reproducing it has to be arch-specific. On non-x86_64: sigusr2 gets called by normal function call from function stdarg. On any arch then sigusr2 calls raise (SIGUSR1) for --ptraceme. abort () is called otherwise, expected for --gencore core dump. Expected x86_64 output: TID 10276: # 0 0x7f7ab61e9e6b raise # 1 0x7f7ab661af47 - 1 main # 2 0x7f7ab5e3bb45 - 1 __libc_start_main # 3 0x7f7ab661aa09 - 1 _start TID 10278: # 0 0x7f7ab61e9e6b raise # 1 0x7f7ab661ab3c - 1 sigusr2 # 2 0x7f7ab5e4fa60 __restore_rt # 3 0x7f7ab661ab47 jmp # 4 0x7f7ab661ac92 - 1 stdarg # 5 0x7f7ab661acba - 1 backtracegen # 6 0x7f7ab661acd1 - 1 start # 7 0x7f7ab61e2c53 - 1 start_thread # 8 0x7f7ab5f0fdbd - 1 __clone Expected non-x86_64 (i386) output; __kernel_vsyscall are skipped if found: TID 10408: # 0 0xf779f430 __kernel_vsyscall # 1 0xf7771466 - 1 raise # 2 0xf77c1d07 - 1 main # 3 0xf75bd963 - 1 __libc_start_main # 4 0xf77c1761 - 1 _start TID 10412: # 0 0xf779f430 __kernel_vsyscall # 1 0xf7771466 - 1 raise # 2 0xf77c18f4 - 1 sigusr2 # 3 0xf77c1a10 - 1 stdarg # 4 0xf77c1a2c - 1 backtracegen # 5 0xf77c1a48 - 1 start # 6 0xf77699da - 1 start_thread # 7 0xf769bbfe - 1 __clone */ #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> #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__ */ #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 NOINLINE_NOCLONE void sigusr2 (int signo) { assert (signo == SIGUSR2); if (! gencore) { raise (SIGUSR1); /* Do not return as stack may be invalid due to ptrace-patched PC to the jmp function. */ pthread_exit (NULL); /* Not reached. */ abort (); } /* Here we dump the core for --gencore. */ raise (SIGABRT); /* Avoid tail call optimization for the raise call. */ asm volatile (""); } 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 (errno == 0); assert (l == 0); } #ifdef __x86_64__ if (! gencore) { /* Execution will get PC patched into function jmp. */ raise (SIGUSR1); } #endif sigusr2 (SIGUSR2); /* Not reached. */ 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 (); /* Not reached. */ abort (); } int main (int argc UNUSED, char **argv) { setbuf (stdout, NULL); assert (*argv++); ptraceme = (*argv && strcmp (*argv, "--ptraceme") == 0); argv += ptraceme; gencore = (*argv && strcmp (*argv, "--gencore") == 0); argv += gencore; assert (!*argv); /* These dummy* functions are there so that each of their surrounding functions has some unrelated code around. The purpose of some of the tests is verify unwinding the very first / after the very last instruction does not inappropriately slip into the unrelated code around. */ dummy1 (); dummy2 (); dummy3 (); dummy4 (); if (gencore) printf ("%ld\n", (long) getpid ()); pthread_t thread; int i = pthread_create (&thread, NULL, start, NULL); // pthread_* functions do not set errno. assert (i == 0); if (ptraceme) { errno = 0; long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); assert (errno == 0); assert (l == 0); } if (gencore) pthread_join (thread, NULL); else raise (SIGUSR2); return 0; } #endif /* ! __linux__ */