mirror of
https://github.com/libunwind/libunwind.git
synced 2026-01-12 00:04:03 +08:00
unw_init_local2 (&cursor, (unw_context_t*)context, UNW_INIT_SIGNAL_FRAME) with a context that comes from the signal handler is not implemented on ARM.
425 lines
13 KiB
C
425 lines
13 KiB
C
/**
|
|
* @file tests/Gtest-trace.c
|
|
*
|
|
* Exercise the unw_backtrace*() functions and ensire they produce expected
|
|
* results.
|
|
*/
|
|
/*
|
|
* Copyright (C) 2010, 2011 by FERMI NATIONAL ACCELERATOR LABORATORY
|
|
* Copyright 2024 Stephen M. Webb <stephen.webb@bregmasoft.ca>
|
|
*
|
|
* This file is part of libunwind.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "compiler.h"
|
|
|
|
#include <errno.h>
|
|
#if HAVE_EXECINFO_H
|
|
# include <execinfo.h>
|
|
#else
|
|
extern int backtrace (void **, int);
|
|
#endif
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ucontext.h>
|
|
#include <unistd.h>
|
|
#include <libunwind.h>
|
|
|
|
#include "ident.h"
|
|
|
|
#define panic(...) \
|
|
{ fprintf (stderr, __VA_ARGS__); exit (-1); }
|
|
|
|
#define SIG_STACK_SIZE 0x100000
|
|
|
|
int verbose; /**< enable verbose mode? */
|
|
int repeat_count = 9; /**< number of times to exercise the signal handler */
|
|
int num_errors = 0; /**< cumulatiove error count */
|
|
|
|
/* These variables are global because they
|
|
* can cause the signal stack to overflow */
|
|
enum test_id {
|
|
TEST_UNW_STEP,
|
|
TEST_BACKTRACE,
|
|
TEST_UNW_BACKTRACE,
|
|
TEST_UNW_BACKTRACE2
|
|
};
|
|
enum { MAX_BACKTRACE_SIZE = 128 };
|
|
|
|
struct {
|
|
char const *name;
|
|
void *addresses[MAX_BACKTRACE_SIZE];
|
|
} trace[] = {
|
|
{ "unw_step", {0} },
|
|
{ "backtrace", {0} },
|
|
{ "unw_backtrace", {0} },
|
|
{ "unw_backtrace2", {0} }
|
|
};
|
|
|
|
unw_cursor_t cursor;
|
|
unw_context_t uc;
|
|
|
|
/**
|
|
* Dump a backtrace (in verbose mode).
|
|
*/
|
|
static void
|
|
dump_backtrace(int bt_count, enum test_id id)
|
|
{
|
|
if (verbose)
|
|
{
|
|
printf ("\t%s() [depth is %d]:\n", trace[id].name, bt_count);
|
|
for (int i = 0; i < bt_count; ++i)
|
|
printf ("\t #%-3d ip=%p\n", i, trace[id].addresses[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compare a backtrace from a backtrace call with a backtrace from unw_step().
|
|
*/
|
|
static void
|
|
compare_backtraces(int step_count, int backtrace_count, enum test_id id)
|
|
{
|
|
if (step_count != backtrace_count)
|
|
{
|
|
printf ("FAILURE: unw_step() loop and %s() depths differ: %d vs. %d\n",
|
|
trace[id].name,
|
|
step_count,
|
|
backtrace_count);
|
|
++num_errors;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 1; i < step_count; ++i)
|
|
{
|
|
if (labs (trace[TEST_UNW_STEP].addresses[i] - trace[id].addresses[i]) > 1)
|
|
{
|
|
printf ("FAILURE: unw_step() loop and %s() addresses differ at %d: %p vs. %p\n",
|
|
trace[id].name, i,
|
|
trace[TEST_UNW_STEP].addresses[i],
|
|
trace[id].addresses[i]);
|
|
++num_errors;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exercises `unw_backtrace()`. Compares the result of a raw `unw_step()` loop
|
|
* with the result of calling the system-supplied `backtrace()` (if any) with the
|
|
* result of the `unw_backtrace()` call, which may have taken the "fast path"
|
|
* (using cached data).
|
|
*
|
|
* If there is no system-supplied `backtrace()` call its just an alias for
|
|
* `unw_backtrace()` so the result better match.
|
|
*
|
|
* Also exercises `unw_backtrace2()` using the same context, since the code
|
|
* differs from `unw_backtrace()`.
|
|
*/
|
|
static void
|
|
do_backtrace (void)
|
|
{
|
|
unw_word_t ip;
|
|
int ret = -UNW_ENOINFO;
|
|
int unw_step_depth = 0;
|
|
int backtrace_depth = 0;
|
|
int unw_backtrace_depth = 0;
|
|
|
|
if (verbose)
|
|
{
|
|
printf ("\nBacktrace with implicit context:\n");
|
|
}
|
|
|
|
/**
|
|
* Perform a naked `unw_step()` loop on the current context and save the IPs.
|
|
*/
|
|
ret = unw_getcontext (&uc);
|
|
if (ret != 0)
|
|
panic ("unw_getcontext failed\n");
|
|
if (unw_init_local (&cursor, &uc) < 0)
|
|
panic ("unw_init_local failed!\n");
|
|
|
|
do
|
|
{
|
|
unw_get_reg (&cursor, UNW_REG_IP, &ip);
|
|
trace[TEST_UNW_STEP].addresses[unw_step_depth++] = (void *) ip;
|
|
}
|
|
while ((ret = unw_step (&cursor)) > 0 && unw_step_depth < MAX_BACKTRACE_SIZE);
|
|
#ifdef UNW_TARGET_ARM
|
|
if (ret == -UNW_ESTOPUNWIND)
|
|
ret = 0;
|
|
#endif
|
|
if (ret < 0)
|
|
{
|
|
unw_get_reg (&cursor, UNW_REG_IP, &ip);
|
|
printf ("FAILURE: unw_step() returned %d for ip=%#010lx\n", ret, (long) ip);
|
|
++num_errors;
|
|
}
|
|
dump_backtrace(unw_step_depth, TEST_UNW_STEP);
|
|
|
|
/*
|
|
* Call the system-supplied `backtrace()` (maybe) and compare with the
|
|
* `unw_step()`results. They should be identical.
|
|
*/
|
|
backtrace_depth = backtrace (trace[TEST_BACKTRACE].addresses, MAX_BACKTRACE_SIZE);
|
|
dump_backtrace(backtrace_depth, TEST_BACKTRACE);
|
|
compare_backtraces(unw_step_depth, backtrace_depth, TEST_BACKTRACE);
|
|
|
|
/*
|
|
* Call `unw_backtrace()` and compare with the `unw_step()`results. They
|
|
* should be identical.
|
|
*/
|
|
unw_backtrace_depth = unw_backtrace (trace[TEST_UNW_BACKTRACE].addresses,
|
|
MAX_BACKTRACE_SIZE);
|
|
dump_backtrace(unw_backtrace_depth, TEST_UNW_BACKTRACE);
|
|
compare_backtraces(unw_step_depth, unw_backtrace_depth, TEST_UNW_BACKTRACE);
|
|
|
|
/*
|
|
* Call `unw_backtrace2()` on the current context and compare with the
|
|
* `unw_step()`results. They should be identical.
|
|
*/
|
|
int unw_backtrace2_depth = unw_backtrace2 (trace[TEST_UNW_BACKTRACE2].addresses,
|
|
MAX_BACKTRACE_SIZE,
|
|
&uc,
|
|
0);
|
|
dump_backtrace(unw_backtrace2_depth, TEST_UNW_BACKTRACE2);
|
|
compare_backtraces(unw_step_depth, unw_backtrace2_depth, TEST_UNW_BACKTRACE2);
|
|
}
|
|
|
|
/**
|
|
* Exercises `unw_backtrace2()` using the context passed in to a signal
|
|
* handler. Compares the result of a raw `unw_step()` loop with the result of
|
|
* the `unw_backtrace2()` call, which may have taken the "fast path" (using
|
|
* cached data).
|
|
*/
|
|
void
|
|
do_backtrace_with_context(void *context)
|
|
{
|
|
unw_word_t ip;
|
|
int ret = -UNW_ENOINFO;
|
|
int unw_step_depth = 0;
|
|
int unw_backtrace2_depth;
|
|
|
|
if (verbose)
|
|
{
|
|
printf ("\nbacktrace with explicit context:\n");
|
|
}
|
|
|
|
/**
|
|
* Perform a naked `unw_step()` loop on the passed context and save the IPs.
|
|
*/
|
|
if (unw_init_local2 (&cursor, (unw_context_t*)context, UNW_INIT_SIGNAL_FRAME) < 0)
|
|
panic ("unw_init_local2 failed!\n");
|
|
|
|
do
|
|
{
|
|
unw_get_reg (&cursor, UNW_REG_IP, &ip);
|
|
trace[TEST_UNW_STEP].addresses[unw_step_depth++] = (void *) ip;
|
|
}
|
|
while ((ret = unw_step (&cursor)) > 0 && unw_step_depth < MAX_BACKTRACE_SIZE);
|
|
#ifdef UNW_TARGET_ARM
|
|
if (ret == -UNW_ESTOPUNWIND)
|
|
ret = 0;
|
|
#endif
|
|
if (ret < 0)
|
|
{
|
|
unw_get_reg (&cursor, UNW_REG_IP, &ip);
|
|
printf ("FAILURE: unw_step() returned %d for ip=%#010lx\n", ret, (long) ip);
|
|
++num_errors;
|
|
}
|
|
dump_backtrace(unw_step_depth, TEST_UNW_STEP);
|
|
|
|
/*
|
|
* Call `unw_backtrace2()` on the passed context and compare with the
|
|
* `unw_step()`results. They should be identical.
|
|
*/
|
|
unw_backtrace2_depth = unw_backtrace2 (trace[TEST_UNW_BACKTRACE2].addresses,
|
|
MAX_BACKTRACE_SIZE,
|
|
(unw_context_t*)context,
|
|
UNW_INIT_SIGNAL_FRAME);
|
|
dump_backtrace(unw_backtrace2_depth, TEST_UNW_BACKTRACE2);
|
|
compare_backtraces(unw_step_depth, unw_backtrace2_depth, TEST_UNW_BACKTRACE2);
|
|
}
|
|
|
|
void
|
|
foo (long val UNUSED)
|
|
{
|
|
do_backtrace ();
|
|
}
|
|
|
|
void
|
|
bar (long v)
|
|
{
|
|
int arr[v];
|
|
arr[0] = 0;
|
|
|
|
/* This is a vain attempt to use up lots of registers to force
|
|
the frame-chain info to be saved on the memory stack on ia64.
|
|
It happens to work with gcc v3.3.4 and gcc v3.4.1 but perhaps
|
|
not with any other compiler. */
|
|
foo (f (arr[0]) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v)
|
|
+ (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + (f (v) + f (v))
|
|
))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
|
|
)))))))))))))))))))))))))))))))))))))))))))))))))))))));
|
|
}
|
|
|
|
void
|
|
sighandler (int signal, siginfo_t *siginfo UNUSED, void *context)
|
|
{
|
|
if (verbose)
|
|
{
|
|
int sp;
|
|
ucontext_t *ctxt UNUSED = context;
|
|
|
|
printf ("sighandler: got signal %d, sp=%p", signal, (void *)&sp);
|
|
#if UNW_TARGET_IA64
|
|
# if defined(__linux__)
|
|
printf (" @ %#010lx", ctxt->uc_mcontext.sc_ip);
|
|
# else
|
|
{
|
|
uint16_t reason;
|
|
uint64_t ip;
|
|
|
|
__uc_get_reason (ctxt, &reason);
|
|
__uc_get_ip (ctxt, &ip);
|
|
printf (" @ %#010lx (reason=%d)", ip, reason);
|
|
}
|
|
# endif
|
|
#elif UNW_TARGET_X86
|
|
#if defined __linux__
|
|
printf (" @ %#010lx", (unsigned long) ctxt->uc_mcontext.gregs[REG_EIP]);
|
|
#elif defined __FreeBSD__
|
|
printf (" @ %#010lx", (unsigned long) ctxt->uc_mcontext.mc_eip);
|
|
#endif
|
|
#elif UNW_TARGET_X86_64
|
|
#if defined __linux__ || defined __sun
|
|
printf (" @ %#010lx", (unsigned long) ctxt->uc_mcontext.gregs[REG_RIP]);
|
|
#elif defined __FreeBSD__
|
|
printf (" @ %#010lx", (unsigned long) ctxt->uc_mcontext.mc_rip);
|
|
#endif
|
|
#elif defined UNW_TARGET_ARM
|
|
#if defined __linux__
|
|
printf (" @ %#010lx", (unsigned long) ctxt->uc_mcontext.arm_pc);
|
|
#elif defined __FreeBSD__
|
|
printf (" @ %#010lx", (unsigned long) ctxt->uc_mcontext.__gregs[_REG_PC]);
|
|
#endif
|
|
#endif
|
|
printf ("\n");
|
|
}
|
|
|
|
do_backtrace();
|
|
#ifndef UNW_TARGET_ARM
|
|
/* UNW_INIT_SIGNAL_FRAME is not implemented on ARM. */
|
|
do_backtrace_with_context(context);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv UNUSED)
|
|
{
|
|
struct sigaction act;
|
|
#ifdef HAVE_SIGALTSTACK
|
|
stack_t stk;
|
|
#endif /* HAVE_SIGALTSTACK */
|
|
|
|
verbose = (argc > 1);
|
|
|
|
if (verbose)
|
|
printf ("Normal backtrace:\n");
|
|
|
|
bar (1);
|
|
|
|
memset (&act, 0, sizeof (act));
|
|
act.sa_sigaction = sighandler;
|
|
act.sa_flags = SA_SIGINFO;
|
|
if (sigaction (SIGTERM, &act, NULL) < 0)
|
|
panic ("sigaction: %s\n", strerror (errno));
|
|
|
|
/*
|
|
* Repeatedly invoke the signal hander to make sure any global cache does not
|
|
* get corrupted.
|
|
*/
|
|
for (int i = 0; i < repeat_count; ++i)
|
|
{
|
|
if (verbose)
|
|
printf ("\nBacktrace across signal handler (iteration %d):\n", i+1);
|
|
kill (getpid (), SIGTERM);
|
|
}
|
|
|
|
#ifdef HAVE_SIGALTSTACK
|
|
if (verbose)
|
|
printf ("\nBacktrace across signal handler on alternate stack:\n");
|
|
stk.ss_sp = malloc (SIG_STACK_SIZE);
|
|
if (!stk.ss_sp)
|
|
panic ("failed to allocate %u bytes\n", SIG_STACK_SIZE);
|
|
stk.ss_size = SIG_STACK_SIZE;
|
|
stk.ss_flags = 0;
|
|
if (sigaltstack (&stk, NULL) < 0)
|
|
panic ("sigaltstack: %s\n", strerror (errno));
|
|
|
|
memset (&act, 0, sizeof (act));
|
|
act.sa_sigaction = sighandler;
|
|
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
|
if (sigaction (SIGTERM, &act, NULL) < 0)
|
|
panic ("sigaction: %s\n", strerror (errno));
|
|
|
|
kill (getpid (), SIGTERM);
|
|
#endif /* HAVE_SIGALTSTACK */
|
|
|
|
if (num_errors > 0)
|
|
{
|
|
fprintf (stderr, "FAILURE: detected %d errors\n", num_errors);
|
|
exit (-1);
|
|
}
|
|
|
|
if (verbose)
|
|
printf ("SUCCESS.\n");
|
|
|
|
signal (SIGTERM, SIG_DFL);
|
|
#ifdef HAVE_SIGALTSTACK
|
|
stk.ss_flags = SS_DISABLE;
|
|
sigaltstack (&stk, NULL);
|
|
free (stk.ss_sp);
|
|
#endif /* HAVE_SIGALTSTACK */
|
|
|
|
return 0;
|
|
}
|