Home | History | Annotate | Download | only in tests
      1 /* Test child for parent backtrace test.
      2    Copyright (C) 2013, 2016 Red Hat, Inc.
      3    This file is part of elfutils.
      4 
      5    This file is free software; you can redistribute it and/or modify
      6    it under the terms of the GNU General Public License as published by
      7    the Free Software Foundation; either version 3 of the License, or
      8    (at your option) any later version.
      9 
     10    elfutils is distributed in the hope that it will be useful, but
     11    WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13    GNU General Public License for more details.
     14 
     15    You should have received a copy of the GNU General Public License
     16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
     17 
     18 /* Command line syntax: ./backtrace-child [--ptraceme|--gencore]
     19    --ptraceme will call ptrace (PTRACE_TRACEME) in the two threads.
     20    --gencore will call abort () at its end.
     21    Main thread will signal SIGUSR2.  Other thread will signal SIGUSR1.
     22    There used to be a difference between x86_64 and other architectures.
     23    To test getting a signal at the very first instruction of a function:
     24      PC will get changed to function 'jmp' by backtrace.c function
     25      prepare_thread.  Then SIGUSR2 will be signalled to backtrace-child
     26      which will invoke function sigusr2.
     27      This is all done so that signal interrupts execution of the very first
     28      instruction of a function.  Properly handled unwind should not slip into
     29      the previous unrelated function.
     30      The tested functionality is arch-independent but the code reproducing it
     31      has to be arch-specific.
     32    On non-x86_64:
     33      sigusr2 gets called by normal function call from function stdarg.
     34    On any arch then sigusr2 calls raise (SIGUSR1) for --ptraceme.
     35    abort () is called otherwise, expected for --gencore core dump.
     36 
     37    Expected x86_64 output:
     38    TID 10276:
     39    # 0 0x7f7ab61e9e6b      raise
     40    # 1 0x7f7ab661af47 - 1  main
     41    # 2 0x7f7ab5e3bb45 - 1  __libc_start_main
     42    # 3 0x7f7ab661aa09 - 1  _start
     43    TID 10278:
     44    # 0 0x7f7ab61e9e6b      raise
     45    # 1 0x7f7ab661ab3c - 1  sigusr2
     46    # 2 0x7f7ab5e4fa60      __restore_rt
     47    # 3 0x7f7ab661ab47      jmp
     48    # 4 0x7f7ab661ac92 - 1  stdarg
     49    # 5 0x7f7ab661acba - 1  backtracegen
     50    # 6 0x7f7ab661acd1 - 1  start
     51    # 7 0x7f7ab61e2c53 - 1  start_thread
     52    # 8 0x7f7ab5f0fdbd - 1  __clone
     53 
     54    Expected non-x86_64 (i386) output; __kernel_vsyscall are skipped if found:
     55    TID 10408:
     56    # 0 0xf779f430          __kernel_vsyscall
     57    # 1 0xf7771466 - 1      raise
     58    # 2 0xf77c1d07 - 1      main
     59    # 3 0xf75bd963 - 1      __libc_start_main
     60    # 4 0xf77c1761 - 1      _start
     61    TID 10412:
     62    # 0 0xf779f430          __kernel_vsyscall
     63    # 1 0xf7771466 - 1      raise
     64    # 2 0xf77c18f4 - 1      sigusr2
     65    # 3 0xf77c1a10 - 1      stdarg
     66    # 4 0xf77c1a2c - 1      backtracegen
     67    # 5 0xf77c1a48 - 1      start
     68    # 6 0xf77699da - 1      start_thread
     69    # 7 0xf769bbfe - 1      __clone
     70 
     71    But the raise jmp patching was unreliable. It depends on the CFI for the raise()
     72    function in glibc to be the same as for the jmp() function. This is not always
     73    the case. Some newer glibc versions rewrote raise() and now the CFA is calculated
     74    differently. So we disable raise jmp patching everywhere.
     75    */
     76 
     77 #ifdef __x86_64__
     78 /* #define RAISE_JMP_PATCHING 1 */
     79 #endif
     80 
     81 #include <config.h>
     82 #include <assert.h>
     83 #include <stdlib.h>
     84 #include <errno.h>
     85 #include <string.h>
     86 #include <pthread.h>
     87 #include <stdio.h>
     88 #include <unistd.h>
     89 
     90 #ifndef __linux__
     91 
     92 int
     93 main (int argc __attribute__ ((unused)), char **argv)
     94 {
     95   fprintf (stderr, "%s: Unwinding not supported for this architecture\n",
     96            argv[0]);
     97   return 77;
     98 }
     99 
    100 #else /* __linux__ */
    101 #include <sys/ptrace.h>
    102 #include <signal.h>
    103 
    104 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
    105 #define NOINLINE_NOCLONE __attribute__ ((noinline, noclone))
    106 #else
    107 #define NOINLINE_NOCLONE __attribute__ ((noinline))
    108 #endif
    109 
    110 #define NORETURN __attribute__ ((noreturn))
    111 #define UNUSED __attribute__ ((unused))
    112 #define USED __attribute__ ((used))
    113 
    114 static int ptraceme, gencore;
    115 
    116 /* Execution will arrive here from jmp by an artificial ptrace-spawn signal.  */
    117 
    118 static NOINLINE_NOCLONE void
    119 sigusr2 (int signo)
    120 {
    121   assert (signo == SIGUSR2);
    122   if (! gencore)
    123     {
    124       raise (SIGUSR1);
    125       /* Do not return as stack may be invalid due to ptrace-patched PC to the
    126 	 jmp function.  */
    127       pthread_exit (NULL);
    128       /* Not reached.  */
    129       abort ();
    130     }
    131   /* Here we dump the core for --gencore.  */
    132   raise (SIGABRT);
    133   /* Avoid tail call optimization for the raise call.  */
    134   asm volatile ("");
    135 }
    136 
    137 static NOINLINE_NOCLONE void
    138 dummy1 (void)
    139 {
    140   asm volatile ("");
    141 }
    142 
    143 #ifdef RAISE_JMP_PATCHING
    144 static NOINLINE_NOCLONE USED void
    145 jmp (void)
    146 {
    147   /* Not reached, signal will get ptrace-spawn to jump into sigusr2.  */
    148   abort ();
    149 }
    150 #endif
    151 
    152 static NOINLINE_NOCLONE void
    153 dummy2 (void)
    154 {
    155   asm volatile ("");
    156 }
    157 
    158 static NOINLINE_NOCLONE NORETURN void
    159 stdarg (int f UNUSED, ...)
    160 {
    161   sighandler_t sigusr2_orig = signal (SIGUSR2, sigusr2);
    162   assert (sigusr2_orig == SIG_DFL);
    163   errno = 0;
    164   if (ptraceme)
    165     {
    166       long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
    167       assert (l == 0);
    168     }
    169 #ifdef RAISE_JMP_PATCHING
    170   if (! gencore)
    171     {
    172       /* Execution will get PC patched into function jmp.  */
    173       raise (SIGUSR1);
    174     }
    175 #endif
    176   sigusr2 (SIGUSR2);
    177   /* Not reached.  */
    178   abort ();
    179 }
    180 
    181 static NOINLINE_NOCLONE void
    182 dummy3 (void)
    183 {
    184   asm volatile ("");
    185 }
    186 
    187 static NOINLINE_NOCLONE void
    188 backtracegen (void)
    189 {
    190   stdarg (1);
    191   /* Here should be no instruction after the stdarg call as it is noreturn
    192      function.  It must be stdarg so that it is a call and not jump (jump as
    193      a tail-call).  */
    194 }
    195 
    196 static NOINLINE_NOCLONE void
    197 dummy4 (void)
    198 {
    199   asm volatile ("");
    200 }
    201 
    202 static void *
    203 start (void *arg UNUSED)
    204 {
    205   backtracegen ();
    206   /* Not reached.  */
    207   abort ();
    208 }
    209 
    210 int
    211 main (int argc UNUSED, char **argv)
    212 {
    213   setbuf (stdout, NULL);
    214   assert (*argv++);
    215   ptraceme = (*argv && strcmp (*argv, "--ptraceme") == 0);
    216   argv += ptraceme;
    217   gencore = (*argv && strcmp (*argv, "--gencore") == 0);
    218   argv += gencore;
    219   assert (!*argv);
    220   /* These dummy* functions are there so that each of their surrounding
    221      functions has some unrelated code around.  The purpose of some of the
    222      tests is verify unwinding the very first / after the very last instruction
    223      does not inappropriately slip into the unrelated code around.  */
    224   dummy1 ();
    225   dummy2 ();
    226   dummy3 ();
    227   dummy4 ();
    228   if (gencore)
    229     printf ("%ld\n", (long) getpid ());
    230   pthread_t thread;
    231   int i = pthread_create (&thread, NULL, start, NULL);
    232   // pthread_* functions do not set errno.
    233   assert (i == 0);
    234   if (ptraceme)
    235     {
    236       errno = 0;
    237       long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
    238       assert (l == 0);
    239     }
    240   if (gencore)
    241     pthread_join (thread, NULL);
    242   else
    243     raise (SIGUSR2);
    244   return 0;
    245 }
    246 
    247 #endif /* ! __linux__ */
    248 
    249