Retro68/elfutils/tests/backtrace-child.c
Wolfgang Thaller d21be3b4e1 Add elfutils-0.170
(from https://sourceware.org/elfutils/, GPL/LGPL licensed)
2017-09-21 21:04:42 +02:00

251 lines
6.8 KiB
C

/* Test child for parent backtrace test.
Copyright (C) 2013, 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 <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.
There used to be a difference between x86_64 and other architectures.
To test getting a signal at the very first instruction of a function:
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
But the raise jmp patching was unreliable. It depends on the CFI for the raise()
function in glibc to be the same as for the jmp() function. This is not always
the case. Some newer glibc versions rewrote raise() and now the CFA is calculated
differently. So we disable raise jmp patching everywhere.
*/
#ifdef __x86_64__
/* #define RAISE_JMP_PATCHING 1 */
#endif
#include <config.h>
#include <assert.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.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__ */
#include <sys/ptrace.h>
#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 RAISE_JMP_PATCHING
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 RAISE_JMP_PATCHING
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__ */