• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
main(int argc,char ** argv)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
sigusr2(int signo)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
dummy1(void)138 dummy1 (void)
139 {
140   asm volatile ("");
141 }
142 
143 #ifdef RAISE_JMP_PATCHING
144 static NOINLINE_NOCLONE USED void
jmp(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
dummy2(void)153 dummy2 (void)
154 {
155   asm volatile ("");
156 }
157 
158 static NOINLINE_NOCLONE NORETURN void
stdarg(int f UNUSED,...)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
dummy3(void)182 dummy3 (void)
183 {
184   asm volatile ("");
185 }
186 
187 static NOINLINE_NOCLONE void
backtracegen(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
dummy4(void)197 dummy4 (void)
198 {
199   asm volatile ("");
200 }
201 
202 static void *
start(void * arg UNUSED)203 start (void *arg UNUSED)
204 {
205   backtracegen ();
206   /* Not reached.  */
207   abort ();
208 }
209 
210 int
main(int argc UNUSED,char ** argv)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