Brian Silverman | 8649792 | 2018-02-10 19:28:39 -0500 | [diff] [blame] | 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 <signal.h> |
| 85 | #include <errno.h> |
| 86 | #include <string.h> |
| 87 | #include <pthread.h> |
| 88 | #include <stdio.h> |
| 89 | #include <unistd.h> |
| 90 | |
| 91 | #ifndef __linux__ |
| 92 | |
| 93 | int |
| 94 | main (int argc __attribute__ ((unused)), char **argv) |
| 95 | { |
| 96 | fprintf (stderr, "%s: Unwinding not supported for this architecture\n", |
| 97 | argv[0]); |
| 98 | return 77; |
| 99 | } |
| 100 | |
| 101 | #else /* __linux__ */ |
| 102 | #include <sys/ptrace.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 (errno == 0); |
| 168 | assert (l == 0); |
| 169 | } |
| 170 | #ifdef RAISE_JMP_PATCHING |
| 171 | if (! gencore) |
| 172 | { |
| 173 | /* Execution will get PC patched into function jmp. */ |
| 174 | raise (SIGUSR1); |
| 175 | } |
| 176 | #endif |
| 177 | sigusr2 (SIGUSR2); |
| 178 | /* Not reached. */ |
| 179 | abort (); |
| 180 | } |
| 181 | |
| 182 | static NOINLINE_NOCLONE void |
| 183 | dummy3 (void) |
| 184 | { |
| 185 | asm volatile (""); |
| 186 | } |
| 187 | |
| 188 | static NOINLINE_NOCLONE void |
| 189 | backtracegen (void) |
| 190 | { |
| 191 | stdarg (1); |
| 192 | /* Here should be no instruction after the stdarg call as it is noreturn |
| 193 | function. It must be stdarg so that it is a call and not jump (jump as |
| 194 | a tail-call). */ |
| 195 | } |
| 196 | |
| 197 | static NOINLINE_NOCLONE void |
| 198 | dummy4 (void) |
| 199 | { |
| 200 | asm volatile (""); |
| 201 | } |
| 202 | |
| 203 | static void * |
| 204 | start (void *arg UNUSED) |
| 205 | { |
| 206 | backtracegen (); |
| 207 | /* Not reached. */ |
| 208 | abort (); |
| 209 | } |
| 210 | |
| 211 | int |
| 212 | main (int argc UNUSED, char **argv) |
| 213 | { |
| 214 | setbuf (stdout, NULL); |
| 215 | assert (*argv++); |
| 216 | ptraceme = (*argv && strcmp (*argv, "--ptraceme") == 0); |
| 217 | argv += ptraceme; |
| 218 | gencore = (*argv && strcmp (*argv, "--gencore") == 0); |
| 219 | argv += gencore; |
| 220 | assert (!*argv); |
| 221 | /* These dummy* functions are there so that each of their surrounding |
| 222 | functions has some unrelated code around. The purpose of some of the |
| 223 | tests is verify unwinding the very first / after the very last instruction |
| 224 | does not inappropriately slip into the unrelated code around. */ |
| 225 | dummy1 (); |
| 226 | dummy2 (); |
| 227 | dummy3 (); |
| 228 | dummy4 (); |
| 229 | if (gencore) |
| 230 | printf ("%ld\n", (long) getpid ()); |
| 231 | pthread_t thread; |
| 232 | int i = pthread_create (&thread, NULL, start, NULL); |
| 233 | // pthread_* functions do not set errno. |
| 234 | assert (i == 0); |
| 235 | if (ptraceme) |
| 236 | { |
| 237 | errno = 0; |
| 238 | long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); |
| 239 | assert (errno == 0); |
| 240 | assert (l == 0); |
| 241 | } |
| 242 | if (gencore) |
| 243 | pthread_join (thread, NULL); |
| 244 | else |
| 245 | raise (SIGUSR2); |
| 246 | return 0; |
| 247 | } |
| 248 | |
| 249 | #endif /* ! __linux__ */ |
| 250 | |