mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2025-01-30 12:44:10 +08:00
76bdc7266a
This adds 'Innovative Computing Labs' as an external author to update-copyright.py, to cover the copyright notice in gprofng/common/opteron_pcbe.c, and uses that plus another external author 'Oracle and' to update gprofng copyright dates. I'm not going to commit 'Oracle and' as an accepted author, but that covers the string "Copyright (c) 2006, 2012, Oracle and/or its affiliates. All rights reserved." found in gprofng/testsuite/gprofng.display/jsynprog files.
4642 lines
133 KiB
C
4642 lines
133 KiB
C
/* Copyright (C) 2021-2023 Free Software Foundation, Inc.
|
|
Contributed by Oracle.
|
|
|
|
This file is part of GNU Binutils.
|
|
|
|
This program 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, or (at your option)
|
|
any later version.
|
|
|
|
This program 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, write to the Free Software
|
|
Foundation, 51 Franklin Street - Fifth Floor, Boston,
|
|
MA 02110-1301, USA. */
|
|
|
|
#include "config.h"
|
|
#include <alloca.h>
|
|
#include <dlfcn.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
|
|
#include "gp-defs.h"
|
|
#include "collector.h"
|
|
#include "gp-experiment.h"
|
|
#include "memmgr.h"
|
|
#include "tsd.h"
|
|
|
|
/* Get dynamic module interface*/
|
|
#include "collector_module.h"
|
|
|
|
/* Get definitions for SP_LEAF_CHECK_MARKER, SP_TRUNC_STACK_MARKER */
|
|
#include "data_pckts.h"
|
|
|
|
#if ARCH(SPARC)
|
|
struct frame
|
|
{
|
|
long fr_local[8]; /* saved locals */
|
|
long fr_arg[6]; /* saved arguments [0 - 5] */
|
|
struct frame *fr_savfp; /* saved frame pointer */
|
|
long fr_savpc; /* saved program counter */
|
|
#if WSIZE(32)
|
|
char *fr_stret; /* struct return addr */
|
|
#endif
|
|
long fr_argd[6]; /* arg dump area */
|
|
long fr_argx[1]; /* array of args past the sixth */
|
|
};
|
|
|
|
#elif ARCH(Intel)
|
|
struct frame
|
|
{
|
|
unsigned long fr_savfp;
|
|
unsigned long fr_savpc;
|
|
};
|
|
#endif
|
|
|
|
/* Set the debug trace level */
|
|
#define DBG_LT0 0
|
|
#define DBG_LT1 1
|
|
#define DBG_LT2 2
|
|
#define DBG_LT3 3
|
|
|
|
int (*__collector_VM_ReadByteInstruction)(unsigned char *) = NULL;
|
|
#define VM_NO_ACCESS (-1)
|
|
#define VM_NOT_VM_MEMORY (-2)
|
|
#define VM_NOT_X_SEGMENT (-3)
|
|
|
|
#define isInside(p, bgn, end) ((p) >= (bgn) && (p) < (end))
|
|
|
|
/*
|
|
* Weed through all the arch dependent stuff to get the right definition
|
|
* for 'pc' in the ucontext structure. The system header files are mess
|
|
* dealing with all the arch (just look for PC, R_PC, REG_PC).
|
|
*
|
|
*/
|
|
|
|
#if ARCH(SPARC)
|
|
|
|
#define IN_BARRIER(x) \
|
|
( barrier_hdl && \
|
|
(unsigned long)x >= barrier_hdl && \
|
|
(unsigned long)x < barrier_hdlx )
|
|
static unsigned long barrier_hdl = 0;
|
|
static unsigned long barrier_hdlx = 0;
|
|
|
|
#if WSIZE(64)
|
|
#define STACK_BIAS 2047
|
|
#define IN_TRAP_HANDLER(x) \
|
|
( misalign_hdl && \
|
|
(unsigned long)x >= misalign_hdl && \
|
|
(unsigned long)x < misalign_hdlx )
|
|
static unsigned long misalign_hdl = 0;
|
|
static unsigned long misalign_hdlx = 0;
|
|
#elif WSIZE(32)
|
|
#define STACK_BIAS 0
|
|
#endif
|
|
|
|
#if WSIZE(64)
|
|
#define GET_GREG(ctx,reg) (((ucontext_t*)ctx)->uc_mcontext.mc_gregs[(reg)])
|
|
#define GET_SP(ctx) (((ucontext_t*)ctx)->uc_mcontext.mc_gregs[MC_O6])
|
|
#define GET_PC(ctx) (((ucontext_t*)ctx)->uc_mcontext.mc_gregs[MC_PC])
|
|
#else
|
|
#define GET_GREG(ctx,reg) (((ucontext_t*)ctx)->uc_mcontext.gregs[(reg)])
|
|
#define GET_SP(ctx) (((ucontext_t*)ctx)->uc_mcontext.gregs[REG_O6])
|
|
#define GET_PC(ctx) (((ucontext_t*)ctx)->uc_mcontext.gregs[REG_PC])
|
|
#endif
|
|
|
|
#elif ARCH(Intel)
|
|
#include "opcodes/disassemble.h"
|
|
|
|
static int
|
|
fprintf_func (void *arg ATTRIBUTE_UNUSED, const char *fmt ATTRIBUTE_UNUSED, ...)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
fprintf_styled_func (void *arg ATTRIBUTE_UNUSED,
|
|
enum disassembler_style st ATTRIBUTE_UNUSED,
|
|
const char *fmt ATTRIBUTE_UNUSED, ...)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Get LENGTH bytes from info's buffer, at target address memaddr.
|
|
Transfer them to myaddr. */
|
|
static int
|
|
read_memory_func (bfd_vma memaddr, bfd_byte *myaddr, unsigned int length,
|
|
disassemble_info *info)
|
|
{
|
|
unsigned int opb = info->octets_per_byte;
|
|
size_t end_addr_offset = length / opb;
|
|
size_t max_addr_offset = info->buffer_length / opb;
|
|
size_t octets = (memaddr - info->buffer_vma) * opb;
|
|
if (memaddr < info->buffer_vma
|
|
|| memaddr - info->buffer_vma > max_addr_offset
|
|
|| memaddr - info->buffer_vma + end_addr_offset > max_addr_offset
|
|
|| (info->stop_vma && (memaddr >= info->stop_vma
|
|
|| memaddr + end_addr_offset > info->stop_vma)))
|
|
return -1;
|
|
memcpy (myaddr, info->buffer + octets, length);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
print_address_func (bfd_vma addr ATTRIBUTE_UNUSED,
|
|
disassemble_info *info ATTRIBUTE_UNUSED) { }
|
|
|
|
static asymbol *
|
|
symbol_at_address_func (bfd_vma addr ATTRIBUTE_UNUSED,
|
|
disassemble_info *info ATTRIBUTE_UNUSED)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static bfd_boolean
|
|
symbol_is_valid (asymbol *sym ATTRIBUTE_UNUSED,
|
|
disassemble_info *info ATTRIBUTE_UNUSED)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
memory_error_func (int status ATTRIBUTE_UNUSED, bfd_vma addr ATTRIBUTE_UNUSED,
|
|
disassemble_info *info ATTRIBUTE_UNUSED) { }
|
|
|
|
|
|
#if WSIZE(32)
|
|
#define GET_PC(ctx) (((ucontext_t*)ctx)->uc_mcontext.gregs[REG_EIP])
|
|
#define GET_SP(ctx) (((ucontext_t*)ctx)->uc_mcontext.gregs[REG_ESP])
|
|
#define GET_FP(ctx) (((ucontext_t*)ctx)->uc_mcontext.gregs[REG_EBP])
|
|
|
|
#elif WSIZE(64)
|
|
#define GET_PC(ctx) (((ucontext_t*)ctx)->uc_mcontext.gregs[REG_RIP])
|
|
#define GET_SP(ctx) (((ucontext_t*)ctx)->uc_mcontext.gregs[REG_RSP])
|
|
#define GET_FP(ctx) (((ucontext_t*)ctx)->uc_mcontext.gregs[REG_RBP])
|
|
#endif /* WSIZE() */
|
|
|
|
#elif ARCH(Aarch64)
|
|
#define GET_PC(ctx) (((ucontext_t*)ctx)->uc_mcontext.regs[15])
|
|
#define GET_SP(ctx) (((ucontext_t*)ctx)->uc_mcontext.regs[13])
|
|
#define GET_FP(ctx) (((ucontext_t*)ctx)->uc_mcontext.regs[14])
|
|
#endif /* ARCH() */
|
|
|
|
/*
|
|
* FILL_CONTEXT() for all platforms
|
|
* Could use getcontext() except:
|
|
* - it's not guaranteed to be async signal safe
|
|
* - it's a system call and not that lightweight
|
|
* - it's not portable as of POSIX.1-2008
|
|
* So we just use low-level mechanisms to fill in the few fields we need.
|
|
*/
|
|
#if ARCH(SPARC)
|
|
#if WSIZE(32)
|
|
#define FILL_CONTEXT(context) \
|
|
{ \
|
|
greg_t fp; \
|
|
__asm__ __volatile__( "mov %%i6, %0" : "=r" (fp) ); \
|
|
__asm__ __volatile__( "ta 3" ); \
|
|
GET_SP(context) = fp; \
|
|
GET_PC(context) = (greg_t)0; \
|
|
}
|
|
|
|
#elif WSIZE(64)
|
|
#define FILL_CONTEXT(context) \
|
|
{ \
|
|
greg_t fp; \
|
|
__asm__ __volatile__( "mov %%i6, %0" : "=r" (fp) ); \
|
|
__asm__ __volatile__( "flushw" ); \
|
|
GET_SP(context) = fp; \
|
|
GET_PC(context) = (greg_t)0; \
|
|
}
|
|
#endif /* WSIZE() */
|
|
|
|
#elif ARCH(Intel)
|
|
#define FILL_CONTEXT(context) \
|
|
{ \
|
|
context->uc_link = NULL; \
|
|
void *sp = __collector_getsp(); \
|
|
GET_SP(context) = (intptr_t)sp; \
|
|
GET_FP(context) = (intptr_t)__collector_getfp(); \
|
|
GET_PC(context) = (intptr_t)__collector_getpc(); \
|
|
context->uc_stack.ss_sp = sp; \
|
|
context->uc_stack.ss_size = 0x100000; \
|
|
}
|
|
|
|
#elif ARCH(Aarch64)
|
|
#define FILL_CONTEXT(context) \
|
|
{ CALL_UTIL (getcontext) (context); \
|
|
context->uc_mcontext.sp = (__u64) __builtin_frame_address(0); \
|
|
}
|
|
|
|
#endif /* ARCH() */
|
|
|
|
static int
|
|
getByteInstruction (unsigned char *p)
|
|
{
|
|
if (__collector_VM_ReadByteInstruction)
|
|
{
|
|
int v = __collector_VM_ReadByteInstruction (p);
|
|
if (v != VM_NOT_VM_MEMORY)
|
|
return v;
|
|
}
|
|
return *p;
|
|
}
|
|
|
|
struct DataHandle *dhndl = NULL;
|
|
|
|
static unsigned unwind_key = COLLECTOR_TSD_INVALID_KEY;
|
|
|
|
/* To support two OpenMP API's we use a pointer
|
|
* to the actual function.
|
|
*/
|
|
int (*__collector_omp_stack_trace)(char*, int, hrtime_t, void*) = NULL;
|
|
int (*__collector_mpi_stack_trace)(char*, int, hrtime_t) = NULL;
|
|
|
|
#define DEFAULT_MAX_NFRAMES 256
|
|
static int max_native_nframes = DEFAULT_MAX_NFRAMES;
|
|
static int max_java_nframes = DEFAULT_MAX_NFRAMES;
|
|
|
|
#define NATIVE_FRAME_BYTES(nframes) ( ((nframes)+1) * sizeof(long) )
|
|
#define JAVA_FRAME_BYTES(nframes) ( ((nframes)+1) * sizeof(long) * 2 + 16 )
|
|
#define OVERHEAD_BYTES ( 2 * sizeof(long) + 2 * sizeof(Stack_info) )
|
|
|
|
#define ROOT_UID 801425552975190205ULL
|
|
#define ROOT_UID_INV 92251691606677ULL
|
|
#define ROOT_IDX 13907816567264074199ULL
|
|
#define ROOT_IDX_INV 2075111ULL
|
|
#define UIDTableSize 1048576
|
|
static volatile uint64_t *UIDTable = NULL;
|
|
static volatile int seen_omp = 0;
|
|
|
|
static int stack_unwind (char *buf, int size, void *bptr, void *eptr, ucontext_t *context, int mode);
|
|
static FrameInfo compute_uid (Frame_packet *frp);
|
|
static int omp_no_walk = 0;
|
|
|
|
#if ARCH(Intel)
|
|
#define ValTableSize 1048576
|
|
#define OmpValTableSize 65536
|
|
static unsigned long *AddrTable_RA_FROMFP = NULL; // Cache for RA_FROMFP pcs
|
|
static unsigned long *AddrTable_RA_EOSTCK = NULL; // Cache for RA_EOSTCK pcs
|
|
static struct WalkContext *OmpCurCtxs = NULL;
|
|
static struct WalkContext *OmpCtxs = NULL;
|
|
static uint32_t *OmpVals = NULL;
|
|
static unsigned long *OmpRAs = NULL;
|
|
static unsigned long adjust_ret_addr (unsigned long ra, unsigned long segoff, unsigned long tend);
|
|
static int parse_x86_AVX_instruction (unsigned char *pc);
|
|
|
|
struct WalkContext
|
|
{
|
|
unsigned long pc;
|
|
unsigned long sp;
|
|
unsigned long fp;
|
|
unsigned long ln;
|
|
unsigned long sbase; /* stack boundary */
|
|
unsigned long tbgn; /* current memory segment start */
|
|
unsigned long tend; /* current memory segment end */
|
|
};
|
|
#endif
|
|
|
|
#if defined(DEBUG) && ARCH(Intel)
|
|
#include <execinfo.h>
|
|
|
|
static void
|
|
dump_stack (int nline)
|
|
{
|
|
if ((__collector_tracelevel & SP_DUMP_STACK) == 0)
|
|
return;
|
|
|
|
enum Constexpr { MAX_SIZE = 1024 };
|
|
void *array[MAX_SIZE];
|
|
size_t sz = backtrace (array, MAX_SIZE);
|
|
char **strings = backtrace_symbols (array, sz);
|
|
DprintfT (SP_DUMP_STACK, "\ndump_stack: %d size=%d\n", nline, (int) sz);
|
|
for (int i = 0; i < sz; i++)
|
|
DprintfT (SP_DUMP_STACK, " %3d: %p %s\n", i, array[i],
|
|
strings[i] ? strings[i] : "???");
|
|
}
|
|
|
|
#define dump_targets(nline, ntrg, targets) \
|
|
if ((__collector_tracelevel & SP_DUMP_UNWIND) != 0) \
|
|
for(int i = 0; i < ntrg; i++) \
|
|
DprintfT (SP_DUMP_UNWIND, " %2d: 0x%lx\n", i, (long) targets[i])
|
|
#else
|
|
#define dump_stack(x)
|
|
#define dump_targets(nline, ntrg, targets)
|
|
#endif
|
|
|
|
void
|
|
__collector_ext_unwind_key_init (int isPthread, void * stack)
|
|
{
|
|
void * ptr = __collector_tsd_get_by_key (unwind_key);
|
|
if (ptr == NULL)
|
|
{
|
|
TprintfT (DBG_LT2, "__collector_ext_unwind_key_init: cannot get tsd\n");
|
|
return;
|
|
}
|
|
if (isPthread)
|
|
{
|
|
size_t stack_size = 0;
|
|
void *stack_addr = 0;
|
|
pthread_t pthread = pthread_self ();
|
|
pthread_attr_t attr;
|
|
int err = pthread_getattr_np (pthread, &attr);
|
|
TprintfT (DBG_LT1, "__collector_ext_unwind_key_init: pthread: 0x%lx err: %d\n", pthread, err);
|
|
if (err == 0)
|
|
{
|
|
err = pthread_attr_getstack (&attr, &stack_addr, &stack_size);
|
|
if (err == 0)
|
|
stack_addr = (char*) stack_addr + stack_size;
|
|
TprintfT (DBG_LT1, "__collector_ext_unwind_key_init: stack_size=0x%lx eos=%p err=%d\n",
|
|
(long) stack_size, stack_addr, err);
|
|
err = pthread_attr_destroy (&attr);
|
|
TprintfT (DBG_LT1, "__collector_ext_unwind_key_init: destroy: %d\n", err);
|
|
}
|
|
*(void**) ptr = stack_addr;
|
|
}
|
|
else
|
|
*(void**) ptr = stack; // cloned thread
|
|
}
|
|
|
|
void
|
|
__collector_ext_unwind_init (int record)
|
|
{
|
|
int sz = UIDTableSize * sizeof (*UIDTable);
|
|
UIDTable = (uint64_t*) __collector_allocCSize (__collector_heap, sz, 1);
|
|
if (UIDTable == NULL)
|
|
{
|
|
__collector_terminate_expt ();
|
|
return;
|
|
}
|
|
CALL_UTIL (memset)((void*) UIDTable, 0, sz);
|
|
|
|
char *str = CALL_UTIL (getenv)("GPROFNG_JAVA_MAX_CALL_STACK_DEPTH");
|
|
if (str != NULL && *str != 0)
|
|
{
|
|
char *endptr;
|
|
int n = CALL_UTIL (strtol)(str, &endptr, 0);
|
|
if (endptr != str && n >= 0)
|
|
{
|
|
if (n < 5)
|
|
n = 5;
|
|
if (n > MAX_STACKDEPTH)
|
|
n = MAX_STACKDEPTH;
|
|
max_java_nframes = n;
|
|
}
|
|
}
|
|
|
|
str = CALL_UTIL (getenv)("GPROFNG_MAX_CALL_STACK_DEPTH");
|
|
if (str != NULL && *str != 0)
|
|
{
|
|
char *endptr = str;
|
|
int n = CALL_UTIL (strtol)(str, &endptr, 0);
|
|
if (endptr != str && n >= 0)
|
|
{
|
|
if (n < 5)
|
|
n = 5;
|
|
if (n > MAX_STACKDEPTH)
|
|
n = MAX_STACKDEPTH;
|
|
max_native_nframes = n;
|
|
}
|
|
}
|
|
|
|
TprintfT (DBG_LT0, "GPROFNG_MAX_CALL_STACK_DEPTH=%d GPROFNG_JAVA_MAX_CALL_STACK_DEPTH=%d\n",
|
|
max_native_nframes, max_java_nframes);
|
|
omp_no_walk = 1;
|
|
|
|
if (__collector_VM_ReadByteInstruction == NULL)
|
|
__collector_VM_ReadByteInstruction = (int(*)()) dlsym (RTLD_DEFAULT, "Async_VM_ReadByteInstruction");
|
|
|
|
#if ARCH(SPARC)
|
|
#if WSIZE(64)
|
|
misalign_hdl = (unsigned long) dlsym (RTLD_DEFAULT, "__misalign_trap_handler");
|
|
misalign_hdlx = (unsigned long) dlsym (RTLD_DEFAULT, "__misalign_trap_handler_end");
|
|
if (misalign_hdlx == 0)
|
|
misalign_hdlx = misalign_hdl + 292;
|
|
barrier_hdl = (unsigned long) dlsym (RTLD_DEFAULT, "__mt_EndOfTask_Barrier_");
|
|
barrier_hdlx = (unsigned long) dlsym (RTLD_DEFAULT, "__mt_EndOfTask_Barrier_Dummy_");
|
|
if (barrier_hdlx == 0)
|
|
barrier_hdl = 0;
|
|
#else
|
|
barrier_hdl = (unsigned long) dlsym (RTLD_DEFAULT, "__mt_EndOfTask_Barrier_");
|
|
barrier_hdlx = (unsigned long) dlsym (RTLD_DEFAULT, "__mt_EndOfTask_Barrier_Dummy_");
|
|
if (barrier_hdlx == 0)
|
|
barrier_hdl = 0;
|
|
#endif /* WSIZE() */
|
|
|
|
#elif ARCH(Intel)
|
|
sz = ValTableSize * sizeof (*AddrTable_RA_FROMFP);
|
|
AddrTable_RA_FROMFP = (unsigned long*) __collector_allocCSize (__collector_heap, sz, 1);
|
|
sz = ValTableSize * sizeof (*AddrTable_RA_EOSTCK);
|
|
AddrTable_RA_EOSTCK = (unsigned long*) __collector_allocCSize (__collector_heap, sz, 1);
|
|
if (omp_no_walk && (__collector_omp_stack_trace != NULL || __collector_mpi_stack_trace != NULL))
|
|
{
|
|
sz = OmpValTableSize * sizeof (*OmpCurCtxs);
|
|
OmpCurCtxs = (struct WalkContext *) __collector_allocCSize (__collector_heap, sz, 1);
|
|
sz = OmpValTableSize * sizeof (*OmpCtxs);
|
|
OmpCtxs = (struct WalkContext *) __collector_allocCSize (__collector_heap, sz, 1);
|
|
sz = OmpValTableSize * sizeof (*OmpVals);
|
|
OmpVals = (uint32_t*) __collector_allocCSize (__collector_heap, sz, 1);
|
|
sz = OmpValTableSize * sizeof (*OmpRAs);
|
|
OmpRAs = (unsigned long*) __collector_allocCSize (__collector_heap, sz, 1);
|
|
if (OmpCurCtxs == NULL || OmpCtxs == NULL || OmpVals == NULL || OmpRAs == NULL)
|
|
{
|
|
TprintfT (0, "unwind_init() ERROR: failed; terminating experiment\n");
|
|
__collector_terminate_expt ();
|
|
return;
|
|
}
|
|
}
|
|
#endif /* ARCH() */
|
|
|
|
if (record)
|
|
{
|
|
dhndl = __collector_create_handle (SP_FRINFO_FILE);
|
|
__collector_log_write ("<%s name=\"%s\" format=\"binary\"/>\n", SP_TAG_DATAPTR, SP_FRINFO_FILE);
|
|
}
|
|
|
|
unwind_key = __collector_tsd_create_key (sizeof (void*), NULL, NULL);
|
|
if (unwind_key == COLLECTOR_TSD_INVALID_KEY)
|
|
{
|
|
TprintfT (0, "unwind_init: ERROR: TSD key create failed.\n");
|
|
__collector_log_write ("<%s kind=\"%s\" id=\"%d\">TSD key not created</%s>\n",
|
|
SP_TAG_EVENT, SP_JCMD_CERROR, COL_ERROR_GENERAL, SP_TAG_EVENT);
|
|
return;
|
|
}
|
|
TprintfT (0, "unwind_init() completed normally\n");
|
|
return;
|
|
}
|
|
|
|
void
|
|
__collector_ext_unwind_close ()
|
|
{
|
|
__collector_delete_handle (dhndl);
|
|
dhndl = NULL;
|
|
}
|
|
|
|
void*
|
|
__collector_ext_return_address (unsigned level)
|
|
{
|
|
if (NULL == UIDTable) //unwind not initialized yet
|
|
return NULL;
|
|
unsigned size = (level + 4) * sizeof (long); // need to strip __collector_get_return_address and its caller
|
|
ucontext_t context;
|
|
FILL_CONTEXT ((&context));
|
|
char* buf = (char*) alloca (size);
|
|
if (buf == NULL)
|
|
{
|
|
TprintfT (DBG_LT0, "__collector_get_return_address: ERROR: alloca(%d) fails\n", size);
|
|
return NULL;
|
|
}
|
|
int sz = stack_unwind (buf, size, NULL, NULL, &context, 0);
|
|
if (sz < (level + 3) * sizeof (long))
|
|
{
|
|
TprintfT (DBG_LT0, "__collector_get_return_address: size=%d, but stack_unwind returns %d\n", size, sz);
|
|
return NULL;
|
|
}
|
|
long *lbuf = (long*) buf;
|
|
TprintfT (DBG_LT2, "__collector_get_return_address: return %lx\n", lbuf[level + 2]);
|
|
return (void *) (lbuf[level + 2]);
|
|
}
|
|
/*
|
|
* Collector interface method getFrameInfo
|
|
*/
|
|
FrameInfo
|
|
__collector_get_frame_info (hrtime_t ts, int mode, void *arg)
|
|
{
|
|
ucontext_t *context = NULL;
|
|
void *bptr = NULL;
|
|
CM_Array *array = NULL;
|
|
|
|
int unwind_mode = 0;
|
|
int do_walk = 1;
|
|
|
|
if (mode & FRINFO_NO_WALK)
|
|
do_walk = 0;
|
|
int bmode = mode & 0xffff;
|
|
int pseudo_context = 0;
|
|
if (bmode == FRINFO_FROM_STACK_ARG || bmode == FRINFO_FROM_STACK)
|
|
{
|
|
bptr = arg;
|
|
context = (ucontext_t*) alloca (sizeof (ucontext_t));
|
|
FILL_CONTEXT (context);
|
|
unwind_mode |= bmode;
|
|
}
|
|
else if (bmode == FRINFO_FROM_UC)
|
|
{
|
|
context = (ucontext_t*) arg;
|
|
if (context == NULL)
|
|
return (FrameInfo) 0;
|
|
if (GET_SP (context) == 0)
|
|
pseudo_context = 1;
|
|
}
|
|
else if (bmode == FRINFO_FROM_ARRAY)
|
|
{
|
|
array = (CM_Array*) arg;
|
|
if (array == NULL || array->length <= 0)
|
|
return (FrameInfo) 0;
|
|
}
|
|
else
|
|
return (FrameInfo) 0;
|
|
|
|
int max_frame_size = OVERHEAD_BYTES + NATIVE_FRAME_BYTES (max_native_nframes);
|
|
if (__collector_java_mode && __collector_java_asyncgetcalltrace_loaded && context && !pseudo_context)
|
|
max_frame_size += JAVA_FRAME_BYTES (max_java_nframes);
|
|
|
|
Frame_packet *frpckt = alloca (sizeof (Frame_packet) + max_frame_size);
|
|
frpckt->type = FRAME_PCKT;
|
|
frpckt->hsize = sizeof (Frame_packet);
|
|
|
|
char *d = (char*) (frpckt + 1);
|
|
int size = max_frame_size;
|
|
|
|
#define MIN(a,b) ((a)<(b)?(a):(b))
|
|
#if defined(GPROFNG_JAVA_PROFILING)
|
|
/* get Java info */
|
|
if (__collector_java_mode && __collector_java_asyncgetcalltrace_loaded && context && !pseudo_context)
|
|
{
|
|
/* use only 2/3 of the buffer and leave the rest for the native stack */
|
|
int tmpsz = MIN (size, JAVA_FRAME_BYTES (max_java_nframes));
|
|
if (tmpsz > 0)
|
|
{
|
|
int sz = __collector_ext_jstack_unwind (d, tmpsz, context);
|
|
d += sz;
|
|
size -= sz;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* get native stack */
|
|
if (context)
|
|
{
|
|
Stack_info *sinfo = (Stack_info*) d;
|
|
int sz = sizeof (Stack_info);
|
|
d += sz;
|
|
size -= sz;
|
|
#if ARCH(Intel)
|
|
if (omp_no_walk == 0)
|
|
do_walk = 1;
|
|
#endif
|
|
if (do_walk == 0)
|
|
unwind_mode |= FRINFO_NO_WALK;
|
|
|
|
int tmpsz = MIN (size, NATIVE_FRAME_BYTES (max_native_nframes));
|
|
if (tmpsz > 0)
|
|
{
|
|
sz = stack_unwind (d, tmpsz, bptr, NULL, context, unwind_mode);
|
|
d += sz;
|
|
size -= sz;
|
|
}
|
|
sinfo->kind = STACK_INFO;
|
|
sinfo->hsize = (d - (char*) sinfo);
|
|
}
|
|
|
|
/* create a stack image from user data */
|
|
if (array && array->length > 0)
|
|
{
|
|
Stack_info *sinfo = (Stack_info*) d;
|
|
int sz = sizeof (Stack_info);
|
|
d += sz;
|
|
size -= sz;
|
|
sz = array->length;
|
|
if (sz > size)
|
|
sz = size; // YXXX should we mark this with truncation frame?
|
|
__collector_memcpy (d, array->bytes, sz);
|
|
d += sz;
|
|
size -= sz;
|
|
sinfo->kind = STACK_INFO;
|
|
sinfo->hsize = (d - (char*) sinfo);
|
|
}
|
|
|
|
/* Compute the total size */
|
|
frpckt->tsize = d - (char*) frpckt;
|
|
FrameInfo uid = compute_uid (frpckt);
|
|
return uid;
|
|
}
|
|
|
|
FrameInfo
|
|
compute_uid (Frame_packet *frp)
|
|
{
|
|
uint64_t idxs[LAST_INFO];
|
|
uint64_t uid = ROOT_UID;
|
|
uint64_t idx = ROOT_IDX;
|
|
|
|
Common_info *cinfo = (Common_info*) ((char*) frp + frp->hsize);
|
|
char *end = (char*) frp + frp->tsize;
|
|
for (;;)
|
|
{
|
|
if ((char*) cinfo >= end || cinfo->hsize == 0 ||
|
|
(char*) cinfo + cinfo->hsize > end)
|
|
break;
|
|
|
|
/* Start with a different value to avoid matching with uid */
|
|
uint64_t uidt = 1;
|
|
uint64_t idxt = 1;
|
|
long *ptr = (long*) ((char*) cinfo + cinfo->hsize);
|
|
long *bnd = (long*) ((char*) cinfo + sizeof (Common_info));
|
|
TprintfT (DBG_LT2, "compute_uid: Cnt=%ld: ", (long) cinfo->hsize);
|
|
while (ptr > bnd)
|
|
{
|
|
long val = *(--ptr);
|
|
tprintf (DBG_LT2, "0x%8.8llx ", (unsigned long long) val);
|
|
uidt = (uidt + val) * ROOT_UID;
|
|
idxt = (idxt + val) * ROOT_IDX;
|
|
uid = (uid + val) * ROOT_UID;
|
|
idx = (idx + val) * ROOT_IDX;
|
|
}
|
|
if (cinfo->kind == STACK_INFO || cinfo->kind == JAVA_INFO)
|
|
{
|
|
cinfo->uid = uidt;
|
|
idxs[cinfo->kind] = idxt;
|
|
}
|
|
cinfo = (Common_info*) ((char*) cinfo + cinfo->hsize);
|
|
}
|
|
tprintf (DBG_LT2, "\n");
|
|
|
|
/* Check if we have already recorded that uid.
|
|
* The following fragment contains benign data races.
|
|
* It's important, though, that all reads from UIDTable
|
|
* happen before writes.
|
|
*/
|
|
int found1 = 0;
|
|
int idx1 = (int) ((idx >> 44) % UIDTableSize);
|
|
if (UIDTable[idx1] == uid)
|
|
found1 = 1;
|
|
int found2 = 0;
|
|
int idx2 = (int) ((idx >> 24) % UIDTableSize);
|
|
if (UIDTable[idx2] == uid)
|
|
found2 = 1;
|
|
int found3 = 0;
|
|
int idx3 = (int) ((idx >> 4) % UIDTableSize);
|
|
if (UIDTable[idx3] == uid)
|
|
found3 = 1;
|
|
if (!found1)
|
|
UIDTable[idx1] = uid;
|
|
if (!found2)
|
|
UIDTable[idx2] = uid;
|
|
if (!found3)
|
|
UIDTable[idx3] = uid;
|
|
|
|
if (found1 || found2 || found3)
|
|
return (FrameInfo) uid;
|
|
frp->uid = uid;
|
|
|
|
/* Compress info's */
|
|
cinfo = (Common_info*) ((char*) frp + frp->hsize);
|
|
for (;;)
|
|
{
|
|
if ((char*) cinfo >= end || cinfo->hsize == 0 ||
|
|
(char*) cinfo + cinfo->hsize > end)
|
|
break;
|
|
if (cinfo->kind == STACK_INFO || cinfo->kind == JAVA_INFO)
|
|
{
|
|
long *ptr = (long*) ((char*) cinfo + sizeof (Common_info));
|
|
long *bnd = (long*) ((char*) cinfo + cinfo->hsize);
|
|
uint64_t uidt = cinfo->uid;
|
|
uint64_t idxt = idxs[cinfo->kind];
|
|
int found = 0;
|
|
int first = 1;
|
|
while (ptr < bnd - 1)
|
|
{
|
|
int idx1 = (int) ((idxt >> 44) % UIDTableSize);
|
|
if (UIDTable[idx1] == uidt)
|
|
{
|
|
found = 1;
|
|
break;
|
|
}
|
|
else if (first)
|
|
{
|
|
first = 0;
|
|
UIDTable[idx1] = uidt;
|
|
}
|
|
long val = *ptr++;
|
|
uidt = uidt * ROOT_UID_INV - val;
|
|
idxt = idxt * ROOT_IDX_INV - val;
|
|
}
|
|
if (found)
|
|
{
|
|
char *d = (char*) ptr;
|
|
char *s = (char*) bnd;
|
|
if (!first)
|
|
{
|
|
int i;
|
|
for (i = 0; i<sizeof (uidt); i++)
|
|
{
|
|
*d++ = (char) uidt;
|
|
uidt = uidt >> 8;
|
|
}
|
|
}
|
|
int delta = s - d;
|
|
while (s < end)
|
|
*d++ = *s++;
|
|
cinfo->kind |= COMPRESSED_INFO;
|
|
cinfo->hsize -= delta;
|
|
frp->tsize -= delta;
|
|
end -= delta;
|
|
}
|
|
}
|
|
cinfo = (Common_info*) ((char*) cinfo + cinfo->hsize);
|
|
}
|
|
__collector_write_packet (dhndl, (CM_Packet*) frp);
|
|
return (FrameInfo) uid;
|
|
}
|
|
|
|
FrameInfo
|
|
__collector_getUID (CM_Array *arg, FrameInfo suid)
|
|
{
|
|
if (arg->length % sizeof (long) != 0 ||
|
|
(long) arg->bytes % sizeof (long) != 0)
|
|
return (FrameInfo) - 1;
|
|
if (arg->length == 0)
|
|
return suid;
|
|
|
|
uint64_t uid = suid ? suid : 1;
|
|
uint64_t idx = suid ? suid : 1;
|
|
long *ptr = (long*) ((char*) arg->bytes + arg->length);
|
|
long *bnd = (long*) (arg->bytes);
|
|
while (ptr > bnd)
|
|
{
|
|
long val = *(--ptr);
|
|
uid = (uid + val) * ROOT_UID;
|
|
idx = (idx + val) * ROOT_IDX;
|
|
}
|
|
|
|
/* Check if we have already recorded that uid.
|
|
* The following fragment contains benign data races.
|
|
* It's important, though, that all reads from UIDTable
|
|
* happen before writes.
|
|
*/
|
|
int found1 = 0;
|
|
int idx1 = (int) ((idx >> 44) % UIDTableSize);
|
|
if (UIDTable[idx1] == uid)
|
|
found1 = 1;
|
|
int found2 = 0;
|
|
int idx2 = (int) ((idx >> 24) % UIDTableSize);
|
|
if (UIDTable[idx2] == uid)
|
|
found2 = 1;
|
|
int found3 = 0;
|
|
int idx3 = (int) ((idx >> 4) % UIDTableSize);
|
|
if (UIDTable[idx3] == uid)
|
|
found3 = 1;
|
|
|
|
if (!found1)
|
|
UIDTable[idx1] = uid;
|
|
if (!found2)
|
|
UIDTable[idx2] = uid;
|
|
if (!found3)
|
|
UIDTable[idx3] = uid;
|
|
if (found1 || found2 || found3)
|
|
return (FrameInfo) uid;
|
|
|
|
int sz = sizeof (Uid_packet) + arg->length;
|
|
if (suid)
|
|
sz += sizeof (suid);
|
|
Uid_packet *uidp = alloca (sz);
|
|
uidp->tsize = sz;
|
|
uidp->type = UID_PCKT;
|
|
uidp->flags = 0;
|
|
uidp->uid = uid;
|
|
|
|
/* Compress */
|
|
ptr = (long*) (arg->bytes);
|
|
bnd = (long*) ((char*) arg->bytes + arg->length);
|
|
long *dst = (long*) (uidp + 1);
|
|
uint64_t uidt = uid;
|
|
uint64_t idxt = idx;
|
|
uint64_t luid = suid; /* link uid */
|
|
|
|
while (ptr < bnd)
|
|
{
|
|
|
|
long val = *ptr++;
|
|
*dst++ = val;
|
|
|
|
if ((bnd - ptr) > sizeof (uidt))
|
|
{
|
|
uidt = uidt * ROOT_UID_INV - val;
|
|
idxt = idxt * ROOT_IDX_INV - val;
|
|
int idx1 = (int) ((idxt >> 44) % UIDTableSize);
|
|
if (UIDTable[idx1] == uidt)
|
|
{
|
|
luid = uidt;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (luid)
|
|
{
|
|
char *d = (char*) dst;
|
|
for (int i = 0; i<sizeof (luid); i++)
|
|
{
|
|
*d++ = (char) luid;
|
|
luid = luid >> 8;
|
|
}
|
|
uidp->flags |= COMPRESSED_INFO;
|
|
uidp->tsize = d - (char*) uidp;
|
|
}
|
|
__collector_write_packet (dhndl, (CM_Packet*) uidp);
|
|
|
|
return (FrameInfo) uid;
|
|
}
|
|
|
|
int
|
|
__collector_getStackTrace (void *buf, int size, void *bptr, void *eptr, void *arg)
|
|
{
|
|
if (arg == (void*) __collector_omp_stack_trace)
|
|
seen_omp = 1;
|
|
int do_walk = 1;
|
|
if (arg == NULL || arg == (void*) __collector_omp_stack_trace)
|
|
{
|
|
do_walk = (arg == (void*) __collector_omp_stack_trace && omp_no_walk) ? 0 : 1;
|
|
ucontext_t *context = (ucontext_t*) alloca (sizeof (ucontext_t));
|
|
FILL_CONTEXT (context);
|
|
arg = context;
|
|
}
|
|
int unwind_mode = 0;
|
|
if (do_walk == 0)
|
|
unwind_mode |= FRINFO_NO_WALK;
|
|
return stack_unwind (buf, size, bptr, eptr, arg, unwind_mode);
|
|
}
|
|
|
|
#if ARCH(SPARC)
|
|
/*
|
|
* These are important data structures taken from the header files reg.h and
|
|
* ucontext.h. They are used for the stack trace algorithm explained below.
|
|
*
|
|
* typedef struct ucontext {
|
|
* u_long uc_flags;
|
|
* struct ucontext *uc_link;
|
|
* usigset_t uc_sigmask;
|
|
* stack_t uc_stack;
|
|
* mcontext_t uc_mcontext;
|
|
* long uc_filler[23];
|
|
* } ucontext_t;
|
|
*
|
|
* #define SPARC_MAXREGWINDOW 31
|
|
*
|
|
* struct rwindow {
|
|
* greg_t rw_local[8];
|
|
* greg_t rw_in[8];
|
|
* };
|
|
*
|
|
* #define rw_fp rw_in[6]
|
|
* #define rw_rtn rw_in[7]
|
|
*
|
|
* struct gwindows {
|
|
* int wbcnt;
|
|
* int *spbuf[SPARC_MAXREGWINDOW];
|
|
* struct rwindow wbuf[SPARC_MAXREGWINDOW];
|
|
* };
|
|
*
|
|
* typedef struct gwindows gwindows_t;
|
|
*
|
|
* typedef struct {
|
|
* gregset_t gregs;
|
|
* gwindows_t *gwins;
|
|
* fpregset_t fpregs;
|
|
* long filler[21];
|
|
* } mcontext_t;
|
|
*
|
|
* The stack would look like this when SIGPROF occurrs.
|
|
*
|
|
* ------------------------- <- high memory
|
|
* | |
|
|
* | |
|
|
* -------------------------
|
|
* | |
|
|
* ------------------------- <- fp' <-|
|
|
* | | |
|
|
* : : |
|
|
* | | |
|
|
* ------------------------- |
|
|
* | fp |----------|
|
|
* | |
|
|
* ------------------------- <- sp'
|
|
* | | | |
|
|
* | gwins | <- saved stack pointers & | |
|
|
* | | register windows | |- mcontext
|
|
* ------------------------- | |
|
|
* | gregs | <- saved registers | |
|
|
* ------------------------- |
|
|
* | | |- ucontext
|
|
* ------------------------- <- ucp (ucontext pointer) |
|
|
* | | |
|
|
* | | |- siginfo
|
|
* ------------------------- <- sip (siginfo pointer) |
|
|
* | |
|
|
* ------------------------- <- sp
|
|
*
|
|
* Then the signal handler is called with:
|
|
* handler( signo, sip, uip );
|
|
* When gwins is null, all the stack frames are saved in the user stack.
|
|
* In that case we can find sp' from gregs and walk the stack for a backtrace.
|
|
* However, if gwins is not null we will have a more complicated case.
|
|
* Wbcnt(in gwins) tells you how many saved register windows are valid.
|
|
* This is important because the kernel does not allocate the entire array.
|
|
* And the top most frame is saved in the lowest index element. The next
|
|
* paragraph explains the possible causes.
|
|
*
|
|
* There are two routines in the kernel to flush out user register windows.
|
|
* flush_user_windows and flush_user_windows_to_stack
|
|
* The first routine will not cause a page fault. Therefore if the user
|
|
* stack is not in memory, the register windows will be saved to the pcb.
|
|
* This can happen when the kernel is trying to deliver a signal and
|
|
* the user stack got swap out. The kernel will then build a new context for
|
|
* the signal handler and the saved register windows will
|
|
* be copied to the ucontext as show above. On the other hand,
|
|
* flush_user_windows_to_stack can cause a page fault, and if it failed
|
|
* then there is something wrong (stack overflow, misalign).
|
|
* The first saved register window does not necessary correspond to the
|
|
* first stack frame. So the current stack pointer must be compare with
|
|
* the stack pointers in spbuf to find a match.
|
|
*
|
|
* We will also follow the uc_link field in ucontext to trace also nested
|
|
* signal stack frames.
|
|
*
|
|
*/
|
|
|
|
/* Dealing with trap handlers.
|
|
* When a user defined trap handler is invoked the return address
|
|
* (or actually the address of an instruction that raised the trap)
|
|
* is passed to the trap handler in %l6, whereas saved %o7 contains
|
|
* garbage. First, we need to find out if a particular pc belongs
|
|
* to the trap handler, and if so, take the %l6 value from the stack rather
|
|
* than %o7 from either the stack or the register.
|
|
* There are three possible situations represented
|
|
* by the following stacks:
|
|
*
|
|
* MARKER MARKER MARKER
|
|
* trap handler pc __func pc before 'save' __func pc after 'save'
|
|
* %l6 %o7 from reg %o7 (garbage)
|
|
* ... %l6 trap handler pc
|
|
* ... %l6
|
|
* ...
|
|
* where __func is a function called from the trap handler.
|
|
*
|
|
* Currently this is implemented to only deal with __misalign_trap_handler
|
|
* set for v9 FORTRAN applications. Implementation of IN_TRAP_HANDLER
|
|
* macro shows it. A general solution is postponed.
|
|
*/
|
|
|
|
/* Special handling of unwind through the parallel loop barrier code:
|
|
*
|
|
* The library defines two symbols, __mt_EndOfTask_Barrier_ and
|
|
* __mt_EndOfTask_Barrier_Dummy_ representing the first word of
|
|
* the barrier sychronization code, and the first word following
|
|
* it. Whenever the leaf PC is between these two symbols,
|
|
* the unwind code is special-cased as follows:
|
|
* The __mt_EndOfTask_Barrier_ function is guaranteed to be a leaf
|
|
* function, so its return address is in a register, not saved on
|
|
* the stack.
|
|
*
|
|
* MARKER
|
|
* __mt_EndOfTask_Barrier_ PC -- the leaf PC
|
|
* loop body function address for the task -- implied caller of __mt_EndOfTask_Barrier_
|
|
* this address is taken from the %O0 register
|
|
* {mt_master or mt_slave} -- real caller of __mt_EndOfTask_Barrier_
|
|
* ...
|
|
*
|
|
* With this trick, the analyzer will show the time in the barrier
|
|
* attributed to the loop at the end of which the barrier synchronization
|
|
* is taking place. That loop body routine, will be shown as called
|
|
* from the function from which it was extracted, which will be shown
|
|
* as called from the real caller, either the slave or master library routine.
|
|
*/
|
|
|
|
/*
|
|
* These no-fault-load (0x82) assembly functions are courtesy of Rob Gardner.
|
|
*
|
|
* Note that 0x82 is ASI_PNF. See
|
|
* http://lxr.free-electrons.com/source/arch/sparc/include/uapi/asm/asi.h#L134
|
|
* ASI address space identifier; PNF primary no fault
|
|
*/
|
|
|
|
/* load an int from an address */
|
|
|
|
/* if the address is illegal, return a 0 */
|
|
static int
|
|
SPARC_no_fault_load_int (void *addr)
|
|
{
|
|
int val;
|
|
__asm__ __volatile__(
|
|
"lda [%1] 0x82, %0\n\t"
|
|
: "=r" (val)
|
|
: "r" (addr)
|
|
);
|
|
|
|
return val;
|
|
}
|
|
|
|
/* check if an address is invalid
|
|
*
|
|
* A no-fault load of an illegal address still faults, but it does so silently to the calling process.
|
|
* It returns a 0, but so could a load of a legal address.
|
|
* So, we time the load. A "fast" load must be a successful load.
|
|
* A "slow" load is probably a fault.
|
|
* Since it could also be a cache/TLB miss or other abnormality,
|
|
* it's safest to retry a slow load.
|
|
* The cost of trying a valid address should be some nanosecs.
|
|
* The cost of trying an invalid address up to 10 times could be some microsecs.
|
|
*/
|
|
#if 0
|
|
static
|
|
int invalid_SPARC_addr(void *addr)
|
|
{
|
|
long t1, t2;
|
|
int i;
|
|
|
|
for (i=0; i<10; i++) {
|
|
__asm__ __volatile__(
|
|
"rd %%tick, %0\n\t"
|
|
"lduba [%2] 0x82, %%g0\n\t"
|
|
"rd %%tick, %1\n\t"
|
|
: "=r" (t1), "=r" (t2)
|
|
: "r" (addr) );
|
|
if ( (t2 - t1) < 100 )
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* The standard SPARC procedure-calling convention is that the
|
|
* calling PC (for determining the return address when the procedure
|
|
* is finished) is placed in register %o7. A called procedure
|
|
* typically executes a "save" instruction that shifts the register
|
|
* window, and %o7 becomes %i7.
|
|
*
|
|
* Optimized leaf procedures do not shift the register window.
|
|
* They assume the return address will remain %o7. So when
|
|
* we process a leaf PC, we walk instructions to see if there
|
|
* is a call, restore, or other instruction that would indicate
|
|
* we can IGNORE %o7 because this is NOT a leaf procedure.
|
|
*
|
|
* If a limited instruction walk uncovers no such hint, we save
|
|
* not only the PC but the %o7 value as well... just to be safe.
|
|
* Later, in DBE post-processing of the call stacks, we decide
|
|
* whether any recorded %o7 value should be used as a caller
|
|
* frame or should be discarded.
|
|
*/
|
|
|
|
#define IS_ILLTRAP(x) (((x) & 0xc1c00000) == 0)
|
|
#define IS_SAVE(x) (((x) & 0xc1f80000) == 0x81e00000)
|
|
#define IS_MOVO7R(x) (((x) & 0xc1f8201f) == 0x8160000f)
|
|
#define IS_MOVRO7(x) (((x) & 0xfff82000) == 0x9f600000)
|
|
#define IS_ORRG0O7(x) (((x) & 0xff78201f) == 0x9e100000)
|
|
#define IS_ORG0RO7(x) (((x) & 0xff7fe000) == 0x9e100000)
|
|
#define IS_ORG0O7R(x) (((x) & 0xc17fe01f) == 0x8010000f)
|
|
#define IS_ORO7G0R(x) (((x) & 0xc17fe01f) == 0x8013c000)
|
|
#define IS_RESTORE(x) (((x) & 0xc1f80000) == 0x81e80000)
|
|
#define IS_RET(x) ((x) == 0x81c7e008)
|
|
#define IS_RETL(x) ((x) == 0x81c3e008)
|
|
#define IS_RETURN(x) (((x) & 0xc1f80000) == 0x81c80000)
|
|
#define IS_BRANCH(x) ((((x) & 0xc0000000) == 0) && (((x) & 0x01c00000) != 0x01000000))
|
|
#define IS_CALL(x) (((x) & 0xc0000000) == 0x40000000)
|
|
#define IS_LDO7(x) (((x) & 0xfff80000) == 0xde000000)
|
|
|
|
static long pagesize = 0;
|
|
|
|
static int
|
|
process_leaf (long *lbuf, int ind, int lsize, void *context)
|
|
{
|
|
greg_t pc = GET_PC (context);
|
|
greg_t o7 = GET_GREG (context, REG_O7);
|
|
|
|
/* omazur: TBR START -- not used */
|
|
if (IN_BARRIER (pc))
|
|
{
|
|
if (ind < lsize)
|
|
lbuf[ind++] = pc;
|
|
if (ind < lsize)
|
|
lbuf[ind++] = GET_GREG (context, REG_O0);
|
|
return ind;
|
|
}
|
|
/* omazur: TBR END */
|
|
#if WSIZE(64)
|
|
if (IN_TRAP_HANDLER (pc))
|
|
{
|
|
if (ind < lsize)
|
|
lbuf[ind++] = pc;
|
|
return ind;
|
|
}
|
|
#endif
|
|
unsigned *instrp = (unsigned *) pc;
|
|
unsigned *end_addr = instrp + 20;
|
|
while (instrp < end_addr)
|
|
{
|
|
unsigned instr = *instrp++;
|
|
if (IS_ILLTRAP (instr))
|
|
break;
|
|
else if (IS_SAVE (instr))
|
|
{
|
|
if (ind < lsize)
|
|
lbuf[ind++] = pc;
|
|
if (o7 && ind < lsize)
|
|
lbuf[ind++] = o7;
|
|
return ind;
|
|
}
|
|
else if (IS_MOVO7R (instr) || IS_ORG0O7R (instr) || IS_ORO7G0R (instr))
|
|
break;
|
|
else if (IS_MOVRO7 (instr) || IS_ORG0RO7 (instr))
|
|
{
|
|
int rs2 = (instr & 0x1f) + REG_G1 - 1;
|
|
o7 = (rs2 <= REG_O7) ? GET_GREG (context, rs2) : 0;
|
|
break;
|
|
}
|
|
else if (IS_ORRG0O7 (instr))
|
|
{
|
|
int rs2 = ((instr & 0x7c000) >> 14) + REG_G1 - 1;
|
|
o7 = (rs2 <= REG_O7) ? GET_GREG (context, rs2) : 0;
|
|
break;
|
|
}
|
|
else if (IS_RESTORE (instr))
|
|
{
|
|
o7 = 0;
|
|
break;
|
|
}
|
|
else if (IS_RETURN (instr))
|
|
{
|
|
o7 = 0;
|
|
break;
|
|
}
|
|
else if (IS_RET (instr))
|
|
{
|
|
o7 = 0;
|
|
break;
|
|
}
|
|
else if (IS_RETL (instr))
|
|
{
|
|
/* process delay slot */
|
|
instr = *instrp++;
|
|
if (IS_RESTORE (instr))
|
|
o7 = 0;
|
|
break;
|
|
}
|
|
else if (IS_BRANCH (instr))
|
|
{
|
|
unsigned *backbegin = ((unsigned *) pc - 1);
|
|
unsigned *backend = backbegin - 12 + (instrp - (unsigned *) pc);
|
|
while (backbegin > backend)
|
|
{
|
|
// 21920143 stack unwind: SPARC process_leaf backtracks too far
|
|
/*
|
|
* We've already dereferenced backbegin+1.
|
|
* So if backbegin is on the same page, we're fine.
|
|
* If we've gone to a different page, possibly things are not fine.
|
|
* We don't really know how to test that.
|
|
* Let's just assume the worst: that dereferencing backbegin would segv.
|
|
* We won't know if we're in a leaf function or not.
|
|
*/
|
|
if (pagesize == 0)
|
|
pagesize = CALL_UTIL (sysconf)(_SC_PAGESIZE);
|
|
if ((((long) (backbegin + 1)) & (pagesize - 1)) < sizeof (unsigned*))
|
|
break;
|
|
unsigned backinstr = *backbegin--;
|
|
if (IS_LDO7 (backinstr))
|
|
{
|
|
o7 = 0;
|
|
break;
|
|
}
|
|
else if (IS_ILLTRAP (backinstr))
|
|
break;
|
|
else if (IS_RETURN (backinstr))
|
|
break;
|
|
else if (IS_RET (backinstr))
|
|
break;
|
|
else if (IS_RETL (backinstr))
|
|
break;
|
|
else if (IS_CALL (backinstr))
|
|
break;
|
|
else if (IS_SAVE (backinstr))
|
|
{
|
|
o7 = 0;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else if (IS_CALL (instr))
|
|
o7 = 0;
|
|
}
|
|
|
|
#if WSIZE(64)
|
|
if (o7 != 0 && ((long) o7) < 32 && ((long) o7) > -32)
|
|
{
|
|
/* 20924821 SEGV in unwind code on SPARC/Linux
|
|
* We've seen this condition in some SPARC-Linux runs.
|
|
* o7 is non-zero but not a valid address.
|
|
* Values like 4 or -7 have been seen.
|
|
* Let's check if o7 is unreasonably small.
|
|
* If so, set to 0 so that it won't be recorded.
|
|
* Otherwise, there is risk of it being dereferenced in process_sigreturn().
|
|
*/
|
|
// __collector_log_write("<event kind=\"%s\" id=\"%d\">time %lld, internal debug unwind at leaf; o7 = %ld, pc = %x</event>\n",
|
|
// SP_JCMD_COMMENT, COL_COMMENT_NONE, __collector_gethrtime() - __collector_start_time, (long) o7, pc );
|
|
o7 = 0;
|
|
}
|
|
#endif
|
|
|
|
if (o7)
|
|
{
|
|
if (ind < lsize)
|
|
lbuf[ind++] = SP_LEAF_CHECK_MARKER;
|
|
if (ind < lsize)
|
|
lbuf[ind++] = pc;
|
|
if (ind < lsize)
|
|
lbuf[ind++] = o7;
|
|
}
|
|
else if (ind < lsize)
|
|
lbuf[ind++] = pc;
|
|
return ind;
|
|
}
|
|
|
|
#if WSIZE(64)
|
|
// detect signal handler
|
|
static int
|
|
process_sigreturn (long *lbuf, int ind, int lsize, unsigned char * tpc,
|
|
struct frame **pfp, void * bptr, int extra_frame)
|
|
{
|
|
// cheap checks whether tpc is obviously not an instruction address
|
|
if ((4096 > (unsigned long) tpc) // the first page is off limits
|
|
|| (3 & (unsigned long) tpc))
|
|
return ind; // the address is not aligned
|
|
|
|
// get the instruction at tpc, skipping over as many as 7 nop's (0x01000000)
|
|
int insn, i;
|
|
for (i = 0; i < 7; i++)
|
|
{
|
|
insn = SPARC_no_fault_load_int ((void *) tpc);
|
|
if (insn != 0x01000000)
|
|
break;
|
|
tpc += 4;
|
|
}
|
|
|
|
// we're not expecting 0 (and it could mean an illegal address)
|
|
if (insn == 0)
|
|
return ind;
|
|
|
|
// We are looking for __rt_sigreturn_stub with the instruction
|
|
// 0x82102065 : mov 0x65 /* __NR_rt_sigreturn */, %g1
|
|
if (insn == 0x82102065)
|
|
{
|
|
/*
|
|
* according to linux kernel source code,
|
|
* syscall(_NR_rt_sigreturn) uses the following data in stack:
|
|
* struct rt_signal_frame {
|
|
* struct sparc_stackf ss;
|
|
* siginfo_t info;
|
|
* struct pt_regs regs;
|
|
* ....};
|
|
* sizeof(struct sparc_stackf) is 192;
|
|
* sizeof(siginfo_t) is 128;
|
|
* we need to get the register values from regs, which is defined as:
|
|
* struct pt_regs {
|
|
* unsigned long u_regs[16];
|
|
* unsigned long tstate;
|
|
* unsigned long tpc;
|
|
* unsigned long tnpc;
|
|
* ....};
|
|
* pc and fp register has offset of 120 and 112;
|
|
* the pc of kill() is stored in tnpc, whose offest is 136.
|
|
*/
|
|
greg_t pc = *((unsigned long*) ((char*) ((*pfp)) + 192 + 128 + 136));
|
|
greg_t pc1 = *((unsigned long*) ((char*) ((*pfp)) + 192 + 128 + 120));
|
|
(*pfp) = *((struct frame**) ((char*) ((*pfp)) + 192 + 128 + 112));
|
|
if (pc && pc1)
|
|
{
|
|
if (bptr != NULL && extra_frame && ((char*) (*pfp) + STACK_BIAS) < (char*) bptr && ind < 2)
|
|
{
|
|
lbuf[0] = pc1;
|
|
if (ind == 0)
|
|
ind++;
|
|
}
|
|
if (bptr == NULL || ((char*) (*pfp) + STACK_BIAS) >= (char*) bptr)
|
|
{
|
|
if (ind < lsize)
|
|
lbuf[ind++] = (unsigned long) tpc;
|
|
if (ind < lsize)
|
|
lbuf[ind++] = pc;
|
|
if (ind < lsize)
|
|
lbuf[ind++] = pc1;
|
|
}
|
|
}
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: resolved sigreturn pc=0x%lx, pc1=0x%lx, fp=0x%lx\n", pc, pc1, *(pfp));
|
|
}
|
|
return ind;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* int stack_unwind( char *buf, int size, ucontext_t *context )
|
|
* This routine looks into the mcontext and
|
|
* trace stack frames to record return addresses.
|
|
*/
|
|
int
|
|
stack_unwind (char *buf, int size, void *bptr, void *eptr, ucontext_t *context, int mode)
|
|
{
|
|
/*
|
|
* trace the stack frames from user stack.
|
|
* We are assuming that the frame pointer and return address
|
|
* are null when we are at the top level.
|
|
*/
|
|
long *lbuf = (long*) buf;
|
|
int lsize = size / sizeof (long);
|
|
struct frame *fp = (struct frame *) GET_SP (context); /* frame pointer */
|
|
greg_t pc; /* program counter */
|
|
int extra_frame = 0;
|
|
if ((mode & 0xffff) == FRINFO_FROM_STACK)
|
|
extra_frame = 1;
|
|
|
|
int ind = 0;
|
|
if (bptr == NULL)
|
|
ind = process_leaf (lbuf, ind, lsize, context);
|
|
|
|
int extra_frame = 0;
|
|
if ((mode & 0xffff) == FRINFO_FROM_STACK)
|
|
extra_frame = 1;
|
|
int ind = 0;
|
|
if (bptr == NULL)
|
|
ind = process_leaf (lbuf, ind, lsize, context);
|
|
|
|
while (fp)
|
|
{
|
|
if (ind >= lsize)
|
|
break;
|
|
fp = (struct frame *) ((char *) fp + STACK_BIAS);
|
|
if (eptr && fp >= (struct frame *) eptr)
|
|
{
|
|
ind = ind >= 2 ? ind - 2 : 0;
|
|
break;
|
|
}
|
|
#if WSIZE(64) // detect signal handler
|
|
unsigned char * tpc = ((unsigned char*) (fp->fr_savpc));
|
|
struct frame * tfp = (struct frame*) ((char*) (fp->fr_savfp) + STACK_BIAS);
|
|
int old_ind = ind;
|
|
ind = process_sigreturn (lbuf, old_ind, lsize, tpc, &tfp, bptr, extra_frame);
|
|
if (ind != old_ind)
|
|
{
|
|
pc = (greg_t) tpc;
|
|
fp = tfp;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
#if WSIZE(64)
|
|
if (IN_TRAP_HANDLER (lbuf[ind - 1]))
|
|
pc = fp->fr_local[6];
|
|
else
|
|
pc = fp->fr_savpc;
|
|
#else
|
|
pc = fp->fr_savpc;
|
|
#endif
|
|
fp = fp->fr_savfp;
|
|
if (pc)
|
|
{
|
|
if (bptr != NULL && extra_frame && ((char*) fp + STACK_BIAS) < (char*) bptr && ind < 2)
|
|
{
|
|
lbuf[0] = pc;
|
|
if (ind == 0)
|
|
ind++;
|
|
}
|
|
if (bptr == NULL || ((char*) fp + STACK_BIAS) >= (char*) bptr)
|
|
lbuf[ind++] = pc;
|
|
}
|
|
}
|
|
|
|
/* 4616238: _door_return may have a frame that has non-zero
|
|
* saved stack pointer and zero pc
|
|
*/
|
|
if (pc == (greg_t) NULL)
|
|
break;
|
|
}
|
|
|
|
if (ind >= lsize)
|
|
{ /* truncated stack handling */
|
|
ind = lsize - 1;
|
|
lbuf[ind++] = SP_TRUNC_STACK_MARKER;
|
|
}
|
|
return ind * sizeof (long);
|
|
}
|
|
|
|
#elif ARCH(Intel)
|
|
|
|
/* get __NR_<syscall_name> constants */
|
|
#include <syscall.h>
|
|
|
|
/*
|
|
* From uts/intel/ia32/os/sendsig.c:
|
|
*
|
|
* An amd64 signal frame looks like this on the stack:
|
|
*
|
|
* old %rsp:
|
|
* <128 bytes of untouched stack space>
|
|
* <a siginfo_t [optional]>
|
|
* <a ucontext_t>
|
|
* <siginfo_t *>
|
|
* <signal number>
|
|
* new %rsp: <return address (deliberately invalid)>
|
|
*
|
|
* The signal number and siginfo_t pointer are only pushed onto the stack in
|
|
* order to allow stack backtraces. The actual signal handling code expects the
|
|
* arguments in registers.
|
|
*
|
|
* An i386 SVR4/ABI signal frame looks like this on the stack:
|
|
*
|
|
* old %esp:
|
|
* <a siginfo32_t [optional]>
|
|
* <a ucontext32_t>
|
|
* <pointer to that ucontext32_t>
|
|
* <pointer to that siginfo32_t>
|
|
* <signo>
|
|
* new %esp: <return address (deliberately invalid)>
|
|
*/
|
|
|
|
#if WSIZE(32)
|
|
#define OPC_REG(x) ((x)&0x7)
|
|
#define MRM_REGD(x) (((x)>>3)&0x7)
|
|
#define MRM_REGS(x) ((x)&0x7)
|
|
#define RED_ZONE 0
|
|
#elif WSIZE(64)
|
|
#define OPC_REG(x) (B|((x)&0x7))
|
|
#define MRM_REGD(x) (R|(((x)>>3)&0x7))
|
|
#define MRM_REGS(x) (B|((x)&0x7))
|
|
#define RED_ZONE 16
|
|
#endif
|
|
#define MRM_EXT(x) (((x)>>3)&0x7)
|
|
#define MRM_MOD(x) ((x)&0xc0)
|
|
|
|
#define RAX 0
|
|
#define RDX 2
|
|
#define RSP 4
|
|
#define RBP 5
|
|
|
|
struct AdvWalkContext
|
|
{
|
|
unsigned char *pc;
|
|
unsigned long *sp;
|
|
unsigned long *sp_safe;
|
|
unsigned long *fp;
|
|
unsigned long *fp_sav;
|
|
unsigned long *fp_loc;
|
|
unsigned long rax;
|
|
unsigned long rdx;
|
|
unsigned long ra_sav;
|
|
unsigned long *ra_loc;
|
|
unsigned long regs[16];
|
|
int tidx; /* targets table index */
|
|
uint32_t cval; /* cache value */
|
|
};
|
|
|
|
static unsigned long
|
|
getRegVal (struct AdvWalkContext *cur, int r, int *undefRez)
|
|
{
|
|
if (cur->regs[r] == 0)
|
|
{
|
|
if (r == RBP)
|
|
{
|
|
tprintf (DBG_LT3, "getRegVal: returns cur->regs[RBP]=0x%lx cur->pc=0x%lx\n",
|
|
(unsigned long) cur->fp, (unsigned long) cur->pc);
|
|
return (unsigned long) cur->fp;
|
|
}
|
|
*undefRez = 1;
|
|
}
|
|
tprintf (DBG_LT3, "getRegVal: cur->regs[%d]=0x%lx cur->pc=0x%lx\n",
|
|
r, (unsigned long) cur->regs[r], (unsigned long) cur->pc);
|
|
return cur->regs[r];
|
|
}
|
|
|
|
static unsigned char *
|
|
check_modrm (unsigned char *pc)
|
|
{
|
|
unsigned char modrm = *pc++;
|
|
unsigned char mod = MRM_MOD (modrm);
|
|
if (mod == 0xc0)
|
|
return pc;
|
|
unsigned char regs = modrm & 0x07;
|
|
if (regs == RSP)
|
|
{
|
|
if (mod == 0x40)
|
|
return pc + 2; // SIB + disp8
|
|
if (mod == 0x80)
|
|
return pc + 5; // SIB + disp32
|
|
return pc + 1; // SIB
|
|
}
|
|
if (mod == 0x0)
|
|
{
|
|
if (regs == RBP)
|
|
pc += 4; // disp32
|
|
}
|
|
else if (mod == 0x40)
|
|
pc += 1; /* byte */
|
|
else if (mod == 0x80)
|
|
pc += 4; /* word */
|
|
return pc;
|
|
}
|
|
|
|
static int
|
|
read_int (unsigned char *pc, int w)
|
|
{
|
|
if (w == 1)
|
|
return *((char *) pc);
|
|
if (w == 2)
|
|
return *(short*) pc;
|
|
return *(int*) pc;
|
|
}
|
|
|
|
/* Return codes */
|
|
enum
|
|
{
|
|
RA_FAILURE = 0,
|
|
RA_SUCCESS,
|
|
RA_END_OF_STACK,
|
|
RA_SIGRETURN,
|
|
RA_RT_SIGRETURN
|
|
};
|
|
|
|
/* Cache value encodings */
|
|
static const uint32_t RA_FROMFP = (uint32_t) - 1; /* get the RA from the frame pointer */
|
|
static const uint32_t RA_EOSTCK = (uint32_t) - 2; /* end-of-stack */
|
|
|
|
|
|
#define MAXCTX 16
|
|
#define MAXTRGTS 64
|
|
#define MAXJMPREG 2
|
|
#define MAXJMPREGCTX 3
|
|
|
|
#define DELETE_CURCTX() __collector_memcpy (cur, buf + (--nctx), sizeof (*cur))
|
|
|
|
/**
|
|
* Look for pc in AddrTable_RA_FROMFP and in AddrTable_RA_EOSTCK
|
|
* @param wctx
|
|
* @return
|
|
*/
|
|
static int
|
|
cache_get (struct WalkContext *wctx)
|
|
{
|
|
unsigned long addr;
|
|
if (AddrTable_RA_FROMFP != NULL)
|
|
{
|
|
uint64_t idx = wctx->pc % ValTableSize;
|
|
addr = AddrTable_RA_FROMFP[ idx ];
|
|
if (addr == wctx->pc)
|
|
{ // Found in AddrTable_RA_FROMFP
|
|
unsigned long *sp = NULL;
|
|
unsigned long fp = wctx->fp;
|
|
/* validate fp before use */
|
|
if (fp < wctx->sp || fp >= wctx->sbase - sizeof (*sp))
|
|
return RA_FAILURE;
|
|
sp = (unsigned long *) fp;
|
|
fp = *sp++;
|
|
unsigned long ra = *sp++;
|
|
unsigned long tbgn = wctx->tbgn;
|
|
unsigned long tend = wctx->tend;
|
|
if (ra < tbgn || ra >= tend)
|
|
if (!__collector_check_segment (ra, &tbgn, &tend, 0))
|
|
return RA_FAILURE;
|
|
unsigned long npc = adjust_ret_addr (ra, ra - tbgn, tend);
|
|
if (npc == 0)
|
|
return RA_FAILURE;
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d cached pc=0x%lX\n", __LINE__, npc);
|
|
wctx->pc = npc;
|
|
wctx->sp = (unsigned long) sp;
|
|
wctx->fp = fp;
|
|
wctx->tbgn = tbgn;
|
|
wctx->tend = tend;
|
|
return RA_SUCCESS;
|
|
}
|
|
}
|
|
if (NULL == AddrTable_RA_EOSTCK)
|
|
return RA_FAILURE;
|
|
uint64_t idx = wctx->pc % ValTableSize;
|
|
addr = AddrTable_RA_EOSTCK[ idx ];
|
|
if (addr != wctx->pc)
|
|
return RA_FAILURE;
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d cached RA_END_OF_STACK\n", __LINE__);
|
|
return RA_END_OF_STACK;
|
|
}
|
|
/**
|
|
* Save pc in RA_FROMFP or RA_EOSTCK cache depending on val
|
|
* @param wctx
|
|
*/
|
|
static void
|
|
cache_put (struct WalkContext *wctx, const uint32_t val)
|
|
{
|
|
if (RA_FROMFP == val)
|
|
{
|
|
// save pc in RA_FROMFP cache
|
|
if (NULL != AddrTable_RA_FROMFP)
|
|
{
|
|
uint64_t idx = wctx->pc % ValTableSize;
|
|
AddrTable_RA_FROMFP[ idx ] = wctx->pc;
|
|
if (NULL != AddrTable_RA_EOSTCK)
|
|
if (AddrTable_RA_EOSTCK[ idx ] == wctx->pc)
|
|
// invalidate pc in RA_EOSTCK cache
|
|
AddrTable_RA_EOSTCK[ idx ] = 0;
|
|
}
|
|
return;
|
|
}
|
|
if (RA_EOSTCK == val)
|
|
{
|
|
// save pc in RA_EOSTCK cache
|
|
if (NULL != AddrTable_RA_EOSTCK)
|
|
{
|
|
uint64_t idx = wctx->pc % ValTableSize;
|
|
AddrTable_RA_EOSTCK[ idx ] = wctx->pc;
|
|
if (NULL != AddrTable_RA_FROMFP)
|
|
{
|
|
if (AddrTable_RA_FROMFP[ idx ] == wctx->pc)
|
|
// invalidate pc in RA_FROMFP cache
|
|
AddrTable_RA_FROMFP[ idx ] = 0;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int
|
|
process_return_real (struct WalkContext *wctx, struct AdvWalkContext *cur, int cache_on)
|
|
{
|
|
if ((unsigned long) cur->sp >= wctx->sbase ||
|
|
(unsigned long) cur->sp < wctx->sp)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: not in stack: %p [0x%lX-0x%lX]\n",
|
|
cur->sp, wctx->sp, wctx->sbase);
|
|
return RA_FAILURE;
|
|
}
|
|
|
|
unsigned long ra;
|
|
if (cur->sp == cur->ra_loc)
|
|
{
|
|
ra = cur->ra_sav;
|
|
cur->sp++;
|
|
}
|
|
else if (cur->sp >= cur->sp_safe && (unsigned long) cur->sp < wctx->sbase)
|
|
ra = *cur->sp++;
|
|
else
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: not safe: %p >= %p\n", cur->sp, cur->sp_safe);
|
|
return RA_FAILURE;
|
|
}
|
|
if (ra == 0)
|
|
{
|
|
if (cache_on)
|
|
cache_put (wctx, RA_EOSTCK);
|
|
wctx->pc = ra;
|
|
wctx->sp = (unsigned long) cur->sp;
|
|
wctx->fp = (unsigned long) cur->fp;
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d RA_END_OF_STACK\n", __LINE__);
|
|
return RA_END_OF_STACK;
|
|
}
|
|
|
|
unsigned long tbgn = wctx->tbgn;
|
|
unsigned long tend = wctx->tend;
|
|
if (ra < tbgn || ra >= tend)
|
|
{
|
|
if (!__collector_check_segment (ra, &tbgn, &tend, 0))
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: not in segment: 0x%lX [0x%lX-0x%lX]\n",
|
|
ra, wctx->tbgn, wctx->tend);
|
|
return RA_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (cur->cval == RA_FROMFP)
|
|
{
|
|
if (wctx->fp == (unsigned long) (cur->sp - 2))
|
|
{
|
|
if (cache_on)
|
|
cache_put (wctx, RA_FROMFP);
|
|
}
|
|
else
|
|
cur->cval = 0;
|
|
}
|
|
|
|
unsigned long npc = adjust_ret_addr (ra, ra - tbgn, tend);
|
|
if (npc == 0)
|
|
{
|
|
if (cur->cval == RA_FROMFP)
|
|
{
|
|
/* We have another evidence that we can trust this RA */
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: trusted fp, pc = 0x%lX\n", wctx->pc);
|
|
wctx->pc = ra;
|
|
}
|
|
else
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: 0 after adjustment\n");
|
|
return RA_FAILURE;
|
|
}
|
|
}
|
|
else
|
|
wctx->pc = npc;
|
|
wctx->sp = (unsigned long) cur->sp;
|
|
wctx->fp = (unsigned long) cur->fp;
|
|
wctx->tbgn = tbgn;
|
|
wctx->tend = tend;
|
|
return RA_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
process_return (struct WalkContext *wctx, struct AdvWalkContext *cur)
|
|
{
|
|
return process_return_real (wctx, cur, 1);
|
|
}
|
|
|
|
static void
|
|
omp_cache_put (unsigned long *cur_sp_safe, struct WalkContext * wctx_pc_save,
|
|
struct WalkContext *wctx, uint32_t val)
|
|
{
|
|
if (omp_no_walk && (OmpCurCtxs == NULL || OmpCtxs == NULL || OmpVals == NULL || OmpRAs == NULL))
|
|
{
|
|
size_t sz = OmpValTableSize * sizeof (*OmpCurCtxs);
|
|
OmpCurCtxs = (struct WalkContext *) __collector_allocCSize (__collector_heap, sz, 1);
|
|
sz = OmpValTableSize * sizeof (*OmpCtxs);
|
|
OmpCtxs = (struct WalkContext *) __collector_allocCSize (__collector_heap, sz, 1);
|
|
sz = OmpValTableSize * sizeof (*OmpVals);
|
|
OmpVals = (uint32_t*) __collector_allocCSize (__collector_heap, sz, 1);
|
|
sz = OmpValTableSize * sizeof (*OmpRAs);
|
|
OmpRAs = (unsigned long*) __collector_allocCSize (__collector_heap, sz, 1);
|
|
}
|
|
if (OmpCurCtxs == NULL || OmpCtxs == NULL || OmpVals == NULL || OmpRAs == NULL)
|
|
return;
|
|
|
|
#define USE_18434988_OMP_CACHE_WORKAROUND
|
|
#ifndef USE_18434988_OMP_CACHE_WORKAROUND
|
|
uint64_t idx = wctx_pc_save->pc * ROOT_IDX;
|
|
OmpVals[ idx % OmpValTableSize ] = val;
|
|
idx = (idx + val) * ROOT_IDX;
|
|
__collector_memcpy (&(OmpCurCtxs[ idx % OmpValTableSize ]), wctx_pc_save, sizeof (struct WalkContext));
|
|
idx = (idx + val) * ROOT_IDX;
|
|
__collector_memcpy (&(OmpCtxs[ idx % OmpValTableSize ]), wctx, sizeof (struct WalkContext));
|
|
#endif
|
|
unsigned long *sp = NULL;
|
|
unsigned long fp = wctx_pc_save->fp;
|
|
int from_fp = 0;
|
|
if (val == RA_END_OF_STACK)
|
|
{
|
|
sp = (unsigned long *) (wctx->sp);
|
|
sp--;
|
|
TprintfT (DBG_LT1, "omp_cache_put: get sp from EOS, sp=%p\n", sp);
|
|
}
|
|
else
|
|
{
|
|
if (fp < wctx_pc_save->sp || fp >= wctx_pc_save->sbase - sizeof (*sp))
|
|
{
|
|
sp = (unsigned long *) (wctx->sp);
|
|
sp--;
|
|
TprintfT (DBG_LT1, "omp_cache_put: get sp from sp, sp=%p\n", sp);
|
|
}
|
|
else
|
|
{
|
|
TprintfT (DBG_LT1, "omp_cache_put: get sp from fp=0x%lx\n", fp);
|
|
sp = (unsigned long *) fp;
|
|
from_fp = 1;
|
|
}
|
|
}
|
|
|
|
if (sp < cur_sp_safe || ((unsigned long) sp >= wctx->sbase))
|
|
return;
|
|
|
|
unsigned long ra = *sp++;
|
|
if (from_fp)
|
|
{
|
|
unsigned long tbgn = wctx_pc_save->tbgn;
|
|
unsigned long tend = wctx_pc_save->tend;
|
|
if (ra < tbgn || ra >= tend)
|
|
{
|
|
sp = (unsigned long *) (wctx->sp);
|
|
sp--;
|
|
ra = *sp++;
|
|
}
|
|
}
|
|
#ifdef USE_18434988_OMP_CACHE_WORKAROUND
|
|
uint64_t idx1 = wctx_pc_save->pc * ROOT_IDX;
|
|
uint64_t idx2 = (idx1 + val) * ROOT_IDX;
|
|
uint64_t idx3 = (idx2 + val) * ROOT_IDX;
|
|
uint64_t idx4 = (idx3 + val) * ROOT_IDX;
|
|
OmpRAs [ idx4 % OmpValTableSize ] = 0; // lock
|
|
OmpVals[ idx1 % OmpValTableSize ] = val;
|
|
__collector_memcpy (&(OmpCurCtxs[ idx2 % OmpValTableSize ]), wctx_pc_save, sizeof (struct WalkContext));
|
|
__collector_memcpy (&(OmpCtxs [ idx3 % OmpValTableSize ]), wctx, sizeof (struct WalkContext));
|
|
OmpRAs [ idx4 % OmpValTableSize ] = ra;
|
|
#else
|
|
idx = (idx + val) * ROOT_IDX;
|
|
OmpRAs[ idx % OmpValTableSize ] = ra;
|
|
#endif
|
|
TprintfT (DBG_LT1, "omp_cache_put: pc=0x%lx\n", wctx_pc_save->pc);
|
|
}
|
|
|
|
/*
|
|
* See bug 17166877 - malloc_internal unwind failure.
|
|
* Sometimes there are several calls right after ret, like:
|
|
* leave
|
|
* ret
|
|
* call xxx
|
|
* call xxxx
|
|
* call xxxxx
|
|
* If they are also jump targets, we should better not
|
|
* create new jump context for those, since they may
|
|
* end up into some other function.
|
|
*/
|
|
static int
|
|
is_after_ret (unsigned char * npc)
|
|
{
|
|
if (*npc != 0xe8)
|
|
return 0;
|
|
unsigned char * onpc = npc;
|
|
int ncall = 1;
|
|
int maxsteps = 10;
|
|
int mincalls = 3;
|
|
int steps = 0;
|
|
while (*(npc - 5) == 0xe8 && steps < maxsteps)
|
|
{
|
|
npc -= 5;
|
|
ncall++;
|
|
steps++;
|
|
}
|
|
if (*(npc - 1) != 0xc3 || *(npc - 2) != 0xc9)
|
|
return 0;
|
|
steps = 0;
|
|
while (*(onpc + 5) == 0xe8 && steps < maxsteps)
|
|
{
|
|
onpc += 5;
|
|
ncall++;
|
|
steps++;
|
|
}
|
|
if (ncall < mincalls)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
find_i386_ret_addr (struct WalkContext *wctx, int do_walk)
|
|
{
|
|
if (wctx->sp == 0)
|
|
// Some artificial contexts may have %sp set to 0. See SETFUNCTIONCONTEXT()
|
|
return RA_FAILURE;
|
|
|
|
/* Check cached values */
|
|
int retc = cache_get (wctx);
|
|
if (retc != RA_FAILURE)
|
|
return retc;
|
|
|
|
/* An attempt to perform code analysis for call stack tracing */
|
|
unsigned char opcode;
|
|
unsigned char extop;
|
|
unsigned char extop2;
|
|
unsigned char modrm;
|
|
int imm8; /* immediate operand, byte */
|
|
int immv; /* immediate operand, word(2) or doubleword(4) */
|
|
int reg; /* register code */
|
|
|
|
/* Buffer for branch targets (analysis stoppers) */
|
|
unsigned char *targets[MAXTRGTS];
|
|
int ntrg = 0; /* number of entries in the table */
|
|
targets[ntrg++] = (unsigned char*) wctx->pc;
|
|
targets[ntrg++] = (unsigned char*) - 1;
|
|
|
|
struct AdvWalkContext buf[MAXCTX];
|
|
struct AdvWalkContext *cur = buf;
|
|
CALL_UTIL (memset)((void*) cur, 0, sizeof (*cur));
|
|
|
|
cur->pc = (unsigned char*) wctx->pc;
|
|
cur->sp = (unsigned long*) wctx->sp;
|
|
cur->sp_safe = cur->sp - RED_ZONE; /* allow for the 128-byte red zone on amd64 */
|
|
cur->fp = (unsigned long*) wctx->fp;
|
|
cur->tidx = 1;
|
|
DprintfT (SP_DUMP_UNWIND, "\nstack_unwind (x86 walk):%d %p start\n", __LINE__, cur->pc);
|
|
|
|
int nctx = 1; /* number of contexts being processed */
|
|
int cnt = 8192; /* number of instructions to analyse */
|
|
|
|
/*
|
|
* The basic idea of our x86 stack unwind is that we don't know
|
|
* if we can trust the frame-pointer register. So we walk
|
|
* instructions to find a return instruction, at which point
|
|
* we know the return address is on the top of the stack, etc.
|
|
*
|
|
* A severe challenge to walking x86 instructions is when we
|
|
* encounter "jmp *(reg)" instructions, where we are expected
|
|
* to jump to the (unknown-to-us) contents of a register.
|
|
*
|
|
* The "jmp_reg" code here attempts to keep track of the
|
|
* context for such a jump, deferring any handling of such
|
|
* a difficult case. We continue with other contexts, hoping
|
|
* that some other walk will take us to a return instruction.
|
|
*
|
|
* If no other walk helps, we return to "jmp_reg" contexts.
|
|
* While we don't know the jump target, it is possible that the
|
|
* bytes immediately following the jmp_reg instruction represent
|
|
* one possible target, as might be the case when a "switch"
|
|
* statement is compiled.
|
|
*
|
|
* Unfortunately, the bytes following a "jmp_reg" instruction might
|
|
* instead be a jump target from somewhere else -- execution might
|
|
* never "fall through" from the preceding "jmp_reg". Those bytes
|
|
* might not even be instructions at all. There are many uses of
|
|
* jmp_reg instructions beyond just compiling switch statements.
|
|
*
|
|
* So walking the bytes after a "jmp_reg" instruction can lead
|
|
* to bugs and undefined behavior, including SEGV and core dump.
|
|
*
|
|
* We currently do not really understand the "jmp_reg" code below.
|
|
*/
|
|
int jmp_reg_switch_mode = 0;
|
|
int num_jmp_reg = 0; // number of jmp *reg met when switch mode is off or when in current switch case
|
|
int total_num_jmp_reg = 0; // number of total jmp *reg met
|
|
struct AdvWalkContext * jmp_reg_ctx[MAXJMPREG]; // context of jmp *reg met when switch mode is off or when in current switch case
|
|
struct AdvWalkContext * jmp_reg_switch_ctx[MAXJMPREG]; // context of jmp *reg used in switch cases
|
|
struct AdvWalkContext * jmp_reg_switch_backup_ctx = NULL; // context of the first jmp *reg used in switch cases
|
|
|
|
int cur_jmp_reg_switch = 0; // current switch table
|
|
int num_jmp_reg_switch = 0; // number of switch table
|
|
int jmp_reg_switch_case = 0; // case number in current switch table
|
|
unsigned char * jmp_reg_switch_pc = NULL; // the start pc of current switch case
|
|
unsigned char * jmp_reg_switch_pc_old = NULL; // backup for deleteing context of jump target
|
|
unsigned char * jmp_reg_switch_base = NULL; // start pc for checking offsets
|
|
int max_jmp_reg_switch_case = 2;
|
|
#if WSIZE(32)
|
|
int max_switch_pc_offset = 512;
|
|
#else // WSIZE(64)
|
|
int max_switch_pc_offset = 1024;
|
|
#endif
|
|
int expected_num_jmp_reg = 1; // should be smaller than MAXJMPREG
|
|
int max_num_jmp_reg_seen = 4; // try to resolve return if there are so many such instructions
|
|
|
|
|
|
int save_ctx = 0; // flag to save walk context in the cache to speed up unwind
|
|
struct WalkContext wctx_pc_save;
|
|
if (do_walk == 0)
|
|
// do_walk is the flag indicating not walking through the instructions, resolving the RA from the stack fp first
|
|
__collector_memcpy (&wctx_pc_save, wctx, sizeof (struct WalkContext));
|
|
|
|
startWalk:
|
|
if (do_walk == 0)
|
|
{ // try to resolve RA from stack frame pointer
|
|
if (OmpCurCtxs == NULL || OmpCtxs == NULL || OmpVals == NULL || OmpRAs == NULL)
|
|
{
|
|
do_walk = 1;
|
|
goto startWalk;
|
|
}
|
|
// before goto checkFP, try the RA from cache (key: WalkContext -> value: caller's WalkContext))
|
|
uint64_t idx = wctx->pc * ROOT_IDX;
|
|
uint32_t val = OmpVals[idx % OmpValTableSize];
|
|
idx = (idx + val) * ROOT_IDX;
|
|
#ifdef USE_18434988_OMP_CACHE_WORKAROUND
|
|
// Check ra: if it is 0 - then cache is invalid
|
|
uint64_t idx4;
|
|
idx4 = (idx + val) * ROOT_IDX;
|
|
idx4 = (idx4 + val) * ROOT_IDX;
|
|
if (0 == OmpRAs[ idx4 % OmpValTableSize ]) // Invalid cache
|
|
goto checkFP;
|
|
#endif
|
|
struct WalkContext saved_ctx;
|
|
__collector_memcpy (&saved_ctx, &OmpCurCtxs[ idx % OmpValTableSize ], sizeof (struct WalkContext));
|
|
if (wctx->pc == saved_ctx.pc
|
|
&& wctx->sp == saved_ctx.sp
|
|
&& wctx->fp == saved_ctx.fp
|
|
&& wctx->tbgn == saved_ctx.tbgn
|
|
&& wctx->tend == saved_ctx.tend)
|
|
{ // key match, RA may be valid
|
|
idx = (idx + val) * ROOT_IDX;
|
|
unsigned long *sp = NULL;
|
|
unsigned long fp = wctx->fp;
|
|
int from_fp = 0;
|
|
if (val == RA_END_OF_STACK)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "find_i386_ret_addr:%d -- RA_END_OF_STACK: pc=0x%lx\n", __LINE__, wctx->pc);
|
|
__collector_memcpy (wctx, &OmpCtxs[ idx % OmpValTableSize ], sizeof (struct WalkContext));
|
|
return val;
|
|
}
|
|
else
|
|
{
|
|
if (fp < wctx->sp || fp >= wctx->sbase - sizeof (*sp))
|
|
{
|
|
TprintfT (DBG_LT1, "omp_cache_get -- wrong fp: pc=0x%lx\n", wctx->pc);
|
|
sp = (unsigned long *) (OmpCtxs[ idx % OmpValTableSize ].sp);
|
|
sp--;
|
|
if (sp < cur->sp_safe || (unsigned long) sp >= wctx->sbase)
|
|
{
|
|
goto checkFP;
|
|
}
|
|
unsigned long ra = *sp;
|
|
uint64_t idx2 = (idx + val) * ROOT_IDX;
|
|
if (OmpRAs[ idx2 % OmpValTableSize ] == ra)
|
|
{
|
|
__collector_memcpy (wctx, &OmpCtxs[ idx % OmpValTableSize ], sizeof (struct WalkContext));
|
|
TprintfT (DBG_LT1, "omp_cache_get -- ra match with target sp: pc=0x%lx, ra=0x%lx, val=%d\n", wctx->pc, ra, val);
|
|
return val;
|
|
}
|
|
TprintfT (DBG_LT1, "omp_cache_get -- ra mismatch: ra=0x%lx, expected ra=0x%lx, val=%d\n", ra, OmpRAs[ idx2 % OmpValTableSize ], val);
|
|
goto checkFP;
|
|
}
|
|
sp = (unsigned long *) fp;
|
|
from_fp = 1;
|
|
}
|
|
|
|
uint64_t idx2 = (idx + val) * ROOT_IDX;
|
|
unsigned long ra = *sp++;
|
|
if (from_fp)
|
|
{
|
|
unsigned long tbgn = wctx->tbgn;
|
|
unsigned long tend = wctx->tend;
|
|
if (ra < tbgn || ra >= tend)
|
|
{
|
|
sp = (unsigned long *) (OmpCtxs[ idx % OmpValTableSize ].sp);
|
|
sp--;
|
|
//if (sp < cur->sp_safe - 16 || (unsigned long)sp >= wctx->sbase - sizeof(*sp)) {
|
|
// The check above was replaced with the check below,
|
|
// because we do not know why "- 16" and "- sizeof(*sp)" was used.
|
|
if (sp < cur->sp_safe || (unsigned long) sp >= wctx->sbase)
|
|
goto checkFP;
|
|
else
|
|
ra = *sp;
|
|
}
|
|
}
|
|
if (OmpRAs[ idx2 % OmpValTableSize ] == ra)
|
|
{
|
|
TprintfT (DBG_LT1, "omp_cache_get -- ra match: pc=0x%lx\n", wctx->pc);
|
|
__collector_memcpy (wctx, &OmpCtxs[ idx % OmpValTableSize ], sizeof (struct WalkContext));
|
|
return val;
|
|
}
|
|
}
|
|
goto checkFP;
|
|
}
|
|
else
|
|
{
|
|
CALL_UTIL (memset)(jmp_reg_ctx, 0, MAXJMPREG * sizeof (struct AdvWalkContext *));
|
|
CALL_UTIL (memset)(jmp_reg_switch_ctx, 0, MAXJMPREG * sizeof (struct AdvWalkContext *));
|
|
}
|
|
while (cnt--)
|
|
{
|
|
if (nctx == 0 && (num_jmp_reg == expected_num_jmp_reg || jmp_reg_switch_mode == 1))
|
|
{ // no context available, try jmp switch mode
|
|
int i = 0;
|
|
if (num_jmp_reg == expected_num_jmp_reg)
|
|
jmp_reg_switch_mode = 0; // first jmp reg expected, restart switch mode
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: begin switch mode, num_jmp_reg = %d, jmp_reg_switch_backup_ctx=%p, jmp_reg_switch_case=%d, jmp_reg_switch_mode=%d.\n",
|
|
num_jmp_reg, jmp_reg_switch_backup_ctx, jmp_reg_switch_case, jmp_reg_switch_mode);
|
|
// the ideal asm of switch is
|
|
// jmp reg
|
|
// ...//case 1
|
|
// ret
|
|
// ...//case 2
|
|
// ret
|
|
// ...//etc
|
|
if (jmp_reg_switch_mode == 0)
|
|
{
|
|
num_jmp_reg_switch = num_jmp_reg; // backup num_jmp_reg
|
|
jmp_reg_switch_mode = 1; // begin switch mode
|
|
for (i = 0; i < num_jmp_reg_switch; i++)
|
|
{
|
|
if (jmp_reg_switch_ctx[i] == NULL)
|
|
jmp_reg_switch_ctx[i] = (struct AdvWalkContext*) alloca (sizeof (*jmp_reg_switch_ctx[i]));
|
|
if (jmp_reg_switch_ctx[i] != NULL)
|
|
{ // backup jmp_reg_ctx
|
|
__collector_memcpy (jmp_reg_switch_ctx[i], jmp_reg_ctx[i], sizeof (*jmp_reg_switch_ctx[i]));
|
|
cur_jmp_reg_switch = 0; // reset the current switch table
|
|
jmp_reg_switch_case = 0; // reset the case number in current switch table
|
|
}
|
|
}
|
|
if (jmp_reg_switch_backup_ctx == NULL)
|
|
{ // only backup when the first jmp *reg is met for restoring later, if switch mode fails to resolve RA
|
|
jmp_reg_switch_backup_ctx = (struct AdvWalkContext*) alloca (sizeof (*jmp_reg_switch_backup_ctx));
|
|
if (jmp_reg_switch_backup_ctx != NULL)
|
|
__collector_memcpy (jmp_reg_switch_backup_ctx, cur, sizeof (*cur));
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: back up context for switch mode.\n");
|
|
}
|
|
}
|
|
if (jmp_reg_switch_mode == 1)
|
|
{ // in the process of trying switch cases
|
|
if (cur_jmp_reg_switch == num_jmp_reg_switch)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: have tried all switch with max_jmp_reg_switch_case for each\n");
|
|
if (jmp_reg_switch_backup_ctx != NULL)
|
|
__collector_memcpy (cur, jmp_reg_switch_backup_ctx, sizeof (*cur));
|
|
int rc = process_return_real (wctx, cur, 0);
|
|
if (rc == RA_SUCCESS)
|
|
{
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
break; // have tried all switch with max_jmp_reg_switch_case for each, goto checkFP
|
|
}
|
|
unsigned char *npc = jmp_reg_switch_ctx[cur_jmp_reg_switch]->pc;
|
|
if (jmp_reg_switch_case == 0)
|
|
// first switch case
|
|
npc = check_modrm (npc); // pc next to "jmp reg" instruction
|
|
else if (jmp_reg_switch_pc != NULL)
|
|
npc = jmp_reg_switch_pc; // // pc next to "ret" instruction of previous case
|
|
else
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: unexpected jum switch mode situation, jmp_reg_switch_case=%d, jmp_reg_switch_pc=%p\n",
|
|
jmp_reg_switch_case, jmp_reg_switch_pc);
|
|
break; //goto checkFP
|
|
}
|
|
jmp_reg_switch_base = npc;
|
|
struct AdvWalkContext *new = buf + nctx;
|
|
nctx += 1;
|
|
__collector_memcpy (new, jmp_reg_switch_ctx[cur_jmp_reg_switch], sizeof (*new));
|
|
new->pc = npc;
|
|
cur = new; /* advance the new context first */
|
|
jmp_reg_switch_pc = NULL;
|
|
jmp_reg_switch_case++;
|
|
if (jmp_reg_switch_case == max_jmp_reg_switch_case)
|
|
{ // done many cases, change to another switch table
|
|
cur_jmp_reg_switch++;
|
|
jmp_reg_switch_case = 0;
|
|
}
|
|
}
|
|
num_jmp_reg = 0;
|
|
}
|
|
if (jmp_reg_switch_mode == 1)
|
|
{ // when processing switch cases, check pc each time
|
|
unsigned long tbgn = wctx->tbgn;
|
|
unsigned long tend = wctx->tend;
|
|
if ((unsigned long) (cur->pc) < tbgn || (unsigned long) (cur->pc) >= tend)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: pc out of range, pc=0x%lx\n", (unsigned long) (cur->pc));
|
|
break;
|
|
}
|
|
if (jmp_reg_switch_base != NULL && cur->pc > jmp_reg_switch_base + max_switch_pc_offset)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: limit the walk offset after jmp reg instruction\n");
|
|
if (jmp_reg_switch_backup_ctx != NULL)
|
|
__collector_memcpy (cur, jmp_reg_switch_backup_ctx, sizeof (*cur));
|
|
int rc = process_return_real (wctx, cur, 0);
|
|
if (rc == RA_SUCCESS)
|
|
{
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
break; // limit the walk offset after jmp reg instruction, got checkFP
|
|
}
|
|
}
|
|
|
|
if (nctx == 0)
|
|
break;
|
|
// dump_targets (__LINE__, ntrg, targets);
|
|
while (cur->pc > targets[cur->tidx])
|
|
cur->tidx += 1;
|
|
if (cur->pc == targets[cur->tidx])
|
|
{
|
|
/* Stop analysis. Delete context. */
|
|
if (jmp_reg_switch_mode == 0 || cur->pc != jmp_reg_switch_pc_old)
|
|
{
|
|
if (jmp_reg_switch_mode == 1 && nctx == 1 && jmp_reg_switch_pc == NULL)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d old target, cur->pc=%p, jmp_reg_switch_pc=%p, nctx=%d\n",
|
|
__LINE__, cur->pc, jmp_reg_switch_pc, nctx);
|
|
jmp_reg_switch_pc = cur->pc; // save cp before delete context, may be used as a start of switch case
|
|
jmp_reg_switch_pc_old = jmp_reg_switch_pc;
|
|
}
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, old target.\n", __LINE__);
|
|
DELETE_CURCTX ();
|
|
if (cur >= buf + nctx)
|
|
cur = buf;
|
|
continue;
|
|
}
|
|
if (jmp_reg_switch_mode == 1 && cur->pc == jmp_reg_switch_pc_old)
|
|
jmp_reg_switch_pc_old = NULL; // reset jmp_reg_switch_pc_old to delete the context later when cur->pc != jmp_reg_switch_pc_old
|
|
}
|
|
|
|
/* let's walk the next x86 instruction */
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d cur:%ld pc=0x%lx %02x %02x %02x %02x %02x %02x %02x sp=0x%lx\n",
|
|
__LINE__, (long) (cur - buf), (unsigned long) cur->pc,
|
|
(int) cur->pc[0], (int) cur->pc[1], (int) cur->pc[2],
|
|
(int) cur->pc[3], (int) cur->pc[4], (int) cur->pc[5],
|
|
(int) cur->pc[6], (unsigned long) cur->sp);
|
|
int v = 4; /* Operand size */
|
|
int a = 4; /* Address size */
|
|
/* int W = 0; REX.W bit */
|
|
#if WSIZE(64)
|
|
int R = 0; /* REX.R bit */
|
|
#endif
|
|
int X = 0; /* REX.X bit */
|
|
int B = 0; /* REX.B bit */
|
|
/* Check prefixes */
|
|
int done = 0;
|
|
while (!done)
|
|
{
|
|
opcode = *cur->pc++;
|
|
switch (opcode)
|
|
{
|
|
case 0x66: /* opd size override */
|
|
v = 2;
|
|
break;
|
|
case 0x67: /*addr size override */
|
|
a = 2;
|
|
break;
|
|
#if WSIZE(64)
|
|
case 0x40: /* REX */
|
|
case 0x41:
|
|
case 0x42:
|
|
case 0x43:
|
|
case 0x44:
|
|
case 0x45:
|
|
case 0x46:
|
|
case 0x47:
|
|
case 0x48:
|
|
case 0x49:
|
|
case 0x4a:
|
|
case 0x4b:
|
|
case 0x4c:
|
|
case 0x4d:
|
|
case 0x4e:
|
|
case 0x4f:
|
|
B = (opcode & 0x1) ? 8 : 0;
|
|
X = (opcode & 0x2) ? 8 : 0;
|
|
R = (opcode & 0x4) ? 8 : 0;
|
|
if (opcode & 0x8) /* 64 bit operand size */
|
|
v = 8;
|
|
opcode = *cur->pc++;
|
|
done = 1;
|
|
break;
|
|
#endif
|
|
default:
|
|
done = 1;
|
|
break;
|
|
}
|
|
}
|
|
int z = (v == 8) ? 4 : v;
|
|
switch (opcode)
|
|
{
|
|
case 0x0: /* add Eb,Gb */
|
|
case 0x01: /* add Ev,Gv */
|
|
case 0x02: /* add Gb,Eb */
|
|
case 0x03: /* add Gv,Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x04: /* add %al,Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x05: /* add %eax,Iz */
|
|
cur->pc += z;
|
|
break;
|
|
case 0x06: /* push es */
|
|
cur->sp -= 1;
|
|
break;
|
|
case 0x07: /* pop es */
|
|
cur->sp += 1;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
break;
|
|
case 0x08: /* or Eb,Gb */
|
|
case 0x09: /* or Ev,Gv */
|
|
case 0x0a: /* or Gb,Eb */
|
|
case 0x0b: /* or Gv,Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x0c: /* or %al,Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x0d: /* or %eax,Iz */
|
|
cur->pc += z;
|
|
break;
|
|
case 0x0e: /* push cs */
|
|
cur->sp -= 1;
|
|
break;
|
|
case 0x0f: /* two-byte opcodes */
|
|
extop = *cur->pc++;
|
|
switch (extop)
|
|
{ /* RTM or HLE */
|
|
case 0x01:
|
|
extop2 = *cur->pc;
|
|
switch (extop2)
|
|
{
|
|
case 0xd5: /* xend */
|
|
case 0xd6: /* xtest */
|
|
cur->pc++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case 0x03:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x0b:
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, undefined instruction. opcode=0x%02x\n",
|
|
__LINE__, (int) opcode);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
case 0x05: /* syscall */
|
|
case 0x34: /* sysenter */
|
|
if (cur->rax == __NR_exit)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode=0x%02x\n",
|
|
__LINE__, (int) opcode);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
}
|
|
else if (cur->rax == __NR_rt_sigreturn)
|
|
{
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d give up return address under jmp switch mode, opcode=0x%02x\n",
|
|
__LINE__, (int) opcode);
|
|
goto checkFP;
|
|
}
|
|
wctx->sp = (unsigned long) cur->sp;
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_RT_SIGRETURN);
|
|
return RA_RT_SIGRETURN;
|
|
}
|
|
#if WSIZE(32)
|
|
else if (cur->rax == __NR_sigreturn)
|
|
{
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: give up return address under jmp switch mode, opcode = 0x34\n");
|
|
goto checkFP;
|
|
}
|
|
wctx->sp = (unsigned long) cur->sp;
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_SIGRETURN);
|
|
return RA_SIGRETURN;
|
|
}
|
|
#endif
|
|
/* Check for Linus' trick in the vsyscall page */
|
|
while (*cur->pc == 0x90) /* nop */
|
|
cur->pc++;
|
|
if (*cur->pc == 0xeb) /* jmp imm8 */
|
|
cur->pc += 2;
|
|
break;
|
|
case 0x0d: /* nop Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x10: /* xmm Vq,Wq */
|
|
case 0x11:
|
|
case 0x12:
|
|
case 0x13:
|
|
case 0x14:
|
|
case 0x15:
|
|
case 0x16:
|
|
case 0x17:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x18: /* prefetch */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x1E: /* endbr64/endbr32 (f3 0f 1e .. ) is parsing as repz nop edx */
|
|
cur->pc += 2;
|
|
break;
|
|
case 0x1f: /* nop Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x28: /* xmm Vq,Wq */
|
|
case 0x29:
|
|
case 0x2a:
|
|
case 0x2b:
|
|
case 0x2c:
|
|
case 0x2d:
|
|
case 0x2e:
|
|
case 0x2f:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x30: /* wrmsr */
|
|
case 0x31: /* rdtsc */
|
|
case 0x32: /* rdmsr */
|
|
case 0x33: /* rdpmc */
|
|
break;
|
|
/* case 0x34: sysenter (see above) */
|
|
case 0x38: case 0x3a:
|
|
extop2 = *cur->pc++;
|
|
cur->pc = check_modrm (cur->pc);
|
|
// 21275311 Unwind failure in native stack for java application running on jdk8
|
|
// Three-byte opcodes "66 0f 3a ??" should consume an additional "immediate" byte.
|
|
if (extop == 0x3a)
|
|
cur->pc++;
|
|
break;
|
|
case 0x40: case 0x41: case 0x42: case 0x43: /* CMOVcc Gv,Ev */
|
|
case 0x44: case 0x45: case 0x46: case 0x47:
|
|
case 0x48: case 0x49: case 0x4a: case 0x4b:
|
|
case 0x4c: case 0x4d: case 0x4e: case 0x4f:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x50: case 0x51: case 0x52: case 0x53:
|
|
case 0x54: case 0x55: case 0x56: case 0x57:
|
|
case 0x58: case 0x59: case 0x5a: case 0x5b:
|
|
case 0x5c: case 0x5d: case 0x5e: case 0x5f:
|
|
case 0x60: case 0x61: case 0x62: case 0x63:
|
|
case 0x64: case 0x65: case 0x66: case 0x67:
|
|
case 0x68: case 0x69: case 0x6a: case 0x6b:
|
|
case 0x6c: case 0x6d: case 0x6e: case 0x6f:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x70: case 0x71: case 0x72: case 0x73:
|
|
cur->pc = check_modrm (cur->pc) + 1;
|
|
break;
|
|
case 0x74: case 0x75: case 0x76:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x77:
|
|
break;
|
|
case 0x7c: case 0x7d: case 0x7e: case 0x7f:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x80: case 0x81: case 0x82: case 0x83: /* Jcc Jz */
|
|
case 0x84: case 0x85: case 0x86: case 0x87:
|
|
case 0x88: case 0x89: case 0x8a: case 0x8b:
|
|
case 0x8c: case 0x8d: case 0x8e: case 0x8f:
|
|
immv = read_int (cur->pc, z);
|
|
cur->pc += z;
|
|
if (nctx < (jmp_reg_switch_mode ? MAXJMPREGCTX : MAXCTX))
|
|
{
|
|
int tidx = 0;
|
|
unsigned char *npc = cur->pc + immv;
|
|
if ((unsigned long) npc < wctx->tbgn || (unsigned long) npc >= wctx->tend)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode=0x%02x\n",
|
|
__LINE__, (int) opcode);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
}
|
|
if (is_after_ret (npc))
|
|
break;
|
|
while (npc > targets[tidx])
|
|
tidx += 1;
|
|
if (npc != targets[tidx])
|
|
{
|
|
if (ntrg < MAXTRGTS)
|
|
{
|
|
for (int i = 0; i < nctx; i++)
|
|
if (buf[i].tidx >= tidx)
|
|
buf[i].tidx++;
|
|
|
|
/* insert a new target */
|
|
for (int i = ntrg; i > tidx; i--)
|
|
targets[i] = targets[i - 1];
|
|
ntrg += 1;
|
|
targets[tidx++] = npc;
|
|
}
|
|
else
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d ntrg=max(%d)\n",
|
|
__LINE__, ntrg);
|
|
struct AdvWalkContext *new = buf + nctx;
|
|
nctx += 1;
|
|
__collector_memcpy (new, cur, sizeof (*new));
|
|
new->pc = npc;
|
|
new->tidx = tidx;
|
|
cur = new; /* advance the new context first */
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d nctx=max(%d)\n",
|
|
__LINE__, ntrg);
|
|
break;
|
|
case 0x90: case 0x91: case 0x92: case 0x93: /* setcc Eb */
|
|
case 0x94: case 0x95: case 0x96: case 0x97:
|
|
case 0x98: case 0x99: case 0x9a: case 0x9b:
|
|
case 0x9c: case 0x9d: case 0x9e: case 0x9f:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xa0: /* push fs */
|
|
cur->sp -= 1;
|
|
break;
|
|
case 0xa1: /* pop fs */
|
|
cur->sp += 1;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
break;
|
|
case 0xa2: /* cpuid */
|
|
break;
|
|
case 0xa3: /* bt Ev,Gv */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xa4: /* shld Ev,Gv,Ib */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xa5: /* shld Ev,Gv,%cl */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xa8: /* push gs */
|
|
cur->sp -= 1;
|
|
break;
|
|
case 0xa9: /* pop gs */
|
|
cur->sp += 1;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
break;
|
|
case 0xaa: /* rsm */
|
|
break;
|
|
case 0xab: /* bts Ev,Gv */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xac: /* shrd Ev,Gv,Ib */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xad: /* shrd Ev,Gv,%cl */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xae: /* group15 */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xaf: /* imul Gv,Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xb1: /* cmpxchg Ev,Gv */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xb3:
|
|
case 0xb6: /* movzx Gv,Eb */
|
|
case 0xb7: /* movzx Gv,Ew */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xba: /* group8 Ev,Ib */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xbb: /* btc Ev,Gv */
|
|
case 0xbc: /* bsf Gv,Ev */
|
|
case 0xbd: /* bsr Gv,Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xbe: /* movsx Gv,Eb */
|
|
case 0xbf: /* movsx Gv,Ew */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xc0: /* xadd Eb,Gb */
|
|
case 0xc1: /* xadd Ev,Gv */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xc2: /* cmpps V,W,Ib */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xc3: /* movnti M,G */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xc6: /* shufps V,W,Ib */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xc7: /* RDRAND */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xc8: case 0xc9: case 0xca: case 0xcb: /* bswap */
|
|
case 0xcc: case 0xcd: case 0xce: case 0xcf:
|
|
break;
|
|
case 0xd0: case 0xd1: case 0xd2: case 0xd3:
|
|
case 0xd4: case 0xd5: case 0xd6: case 0xd7:
|
|
case 0xd8: case 0xd9: case 0xda: case 0xdb:
|
|
case 0xdc: case 0xdd: case 0xde: case 0xdf:
|
|
case 0xe0: case 0xe1: case 0xe2: case 0xe3:
|
|
case 0xe4: case 0xe5: case 0xe6: case 0xe7:
|
|
case 0xe8: case 0xe9: case 0xea: case 0xeb:
|
|
case 0xec: case 0xed: case 0xee: case 0xef:
|
|
case 0xf0: case 0xf1: case 0xf2: case 0xf3:
|
|
case 0xf4: case 0xf5: case 0xf6: case 0xf7:
|
|
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
|
|
case 0xfc: case 0xfd: case 0xfe: case 0xff:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
default:
|
|
if (jmp_reg_switch_mode == 1 && extop == 0x0b)
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d invalid opcode ub2: 0x0f %x jmp_reg_switch_mode=%d\n",
|
|
__LINE__, (int) extop, jmp_reg_switch_mode);
|
|
else
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d unknown opcode: 0x0f %x jmp_reg_switch_mode=%d\n",
|
|
__LINE__, (int) extop, jmp_reg_switch_mode);
|
|
DELETE_CURCTX ();
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 0x10: /* adc Eb,Gb */
|
|
case 0x11: /* adc Ev,Gv */
|
|
case 0x12: /* adc Gb,Eb */
|
|
case 0x13: /* adc Gv,Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x14: /* adc %al,Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x15: /* adc %eax,Iz */
|
|
cur->pc += z;
|
|
break;
|
|
case 0x16: /* push ss */
|
|
cur->sp -= 1;
|
|
break;
|
|
case 0x17: /* pop ss */
|
|
cur->sp += 1;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
break;
|
|
case 0x18: /* sbb Eb,Gb */
|
|
case 0x19: /* sbb Ev,Gv */
|
|
case 0x1a: /* sbb Gb,Eb */
|
|
case 0x1b: /* sbb Gv,Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x1c: /* sbb %al,Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x1d: /* sbb %eax,Iz */
|
|
cur->pc += z;
|
|
break;
|
|
case 0x1e: /* push ds */
|
|
cur->sp -= 1;
|
|
break;
|
|
case 0x1f: /* pop ds */
|
|
cur->sp += 1;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
break;
|
|
case 0x20: /* and Eb,Gb */
|
|
case 0x21: /* and Ev,Gv */
|
|
case 0x22: /* and Gb,Eb */
|
|
case 0x23: /* and Gv,Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x24: /* and %al,Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x25: /* and %eax,Iz */
|
|
cur->pc += z;
|
|
break;
|
|
case 0x26: /* seg=es prefix */
|
|
break;
|
|
case 0x27: /* daa */
|
|
break;
|
|
case 0x28: /* sub Eb,Gb */
|
|
case 0x29: /* sub Ev,Gv */
|
|
case 0x2a: /* sub Gb,Eb */
|
|
case 0x2b: /* sub Gv,Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x2c: /* sub %al,Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x2d: /* sub %eax,Iz */
|
|
cur->pc += z;
|
|
break;
|
|
case 0x2e: /* seg=cs prefix */
|
|
break;
|
|
case 0x2f: /* das */
|
|
break;
|
|
case 0x30: /* xor Eb,Gb */
|
|
case 0x31: /* xor Ev,Gv */
|
|
case 0x32: /* xor Gb,Eb */
|
|
case 0x33: /* xor Gv,Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x34: /* xor %al,Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x35: /* xor %eax,Iz */
|
|
cur->pc += z;
|
|
break;
|
|
case 0x36: /* seg=ss prefix */
|
|
break;
|
|
case 0x37: /* aaa */
|
|
break;
|
|
case 0x38: /* cmp Eb,Gb */
|
|
case 0x39: /* cmp Ev,Gv */
|
|
case 0x3a: /* cmp Gb,Eb */
|
|
case 0x3b: /* cmp Gv,Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x3c: /* cmp %al,Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x3d: /* cmp %eax,Iz */
|
|
cur->pc += z;
|
|
break;
|
|
case 0x3e: /* seg=ds prefix */
|
|
break;
|
|
case 0x3f: /* aas */
|
|
break;
|
|
#if WSIZE(32)
|
|
case 0x40: /* inc %eax */
|
|
case 0x41: /* inc %ecx */
|
|
case 0x42: /* inc %edx */
|
|
case 0x43: /* inc %ebx */
|
|
break;
|
|
case 0x44: /* inc %esp */
|
|
/* Can't be a valid stack pointer - delete context */
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode 0x44.\n", __LINE__);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
case 0x45: /* inc %ebp */
|
|
case 0x46: /* inc %esi */
|
|
case 0x47: /* inc %edi */
|
|
case 0x48: /* dec %eax */
|
|
case 0x49: /* dec %ecx */
|
|
case 0x4a: /* dec %edx */
|
|
case 0x4b: /* dec %ebx */
|
|
break;
|
|
case 0x4c: /* dec %esp */
|
|
/* Can't be a valid stack pointer - delete context */
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode 0x4c.\n", __LINE__);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
case 0x4d: /* dec %ebp */
|
|
case 0x4e: /* dec %esi */
|
|
case 0x4f: /* dec %edi */
|
|
break;
|
|
#endif
|
|
case 0x50: /* push %eax */
|
|
case 0x51: /* push %ecx */
|
|
case 0x52: /* push %edx */
|
|
case 0x53: /* push %ebx */
|
|
case 0x54: /* push %esp */
|
|
case 0x55: /* push %ebp */
|
|
case 0x56: /* push %esi */
|
|
case 0x57: /* push %edi */
|
|
cur->sp -= 1;
|
|
reg = OPC_REG (opcode);
|
|
if (reg == RBP)
|
|
{
|
|
#if 0
|
|
/* Don't do this check yet. Affects tail calls. */
|
|
/* avoid other function's prologue */
|
|
if ((cur->pc[0] == 0x89 && cur->pc[1] == 0xe5) ||
|
|
(cur->pc[0] == 0x8b && cur->pc[1] == 0xec))
|
|
{
|
|
/* mov %esp,%ebp */
|
|
DELETE_CURCTX ();
|
|
break;
|
|
}
|
|
#endif
|
|
if (cur->fp_loc == NULL)
|
|
{
|
|
cur->fp_loc = cur->sp;
|
|
cur->fp_sav = cur->fp;
|
|
}
|
|
}
|
|
break;
|
|
case 0x58: /* pop %eax */
|
|
case 0x59: /* pop %ecx */
|
|
case 0x5a: /* pop %edx */
|
|
case 0x5b: /* pop %ebx */
|
|
case 0x5c: /* pop %esp */
|
|
case 0x5d: /* pop %ebp */
|
|
case 0x5e: /* pop %esi */
|
|
case 0x5f: /* pop %edi */
|
|
reg = OPC_REG (opcode);
|
|
cur->regs[reg] = 0;
|
|
if (isInside ((unsigned long) cur->sp, (unsigned long) cur->sp_safe, wctx->sbase))
|
|
cur->regs[reg] = *cur->sp;
|
|
DprintfT (SP_DUMP_UNWIND, "stack_unwind:%d cur->regs[%d]=0x%lx\n",
|
|
__LINE__, reg, (unsigned long) cur->regs[reg]);
|
|
if (reg == RDX)
|
|
{
|
|
if (cur->sp >= cur->sp_safe &&
|
|
(unsigned long) cur->sp < wctx->sbase)
|
|
cur->rdx = *cur->sp;
|
|
}
|
|
else if (reg == RBP)
|
|
{
|
|
if (cur->fp_loc == cur->sp)
|
|
{
|
|
cur->fp = cur->fp_sav;
|
|
cur->fp_loc = NULL;
|
|
}
|
|
else if (cur->sp >= cur->sp_safe &&
|
|
(unsigned long) cur->sp < wctx->sbase)
|
|
cur->fp = (unsigned long*) (*cur->sp);
|
|
}
|
|
else if (reg == RSP)
|
|
{
|
|
/* f.e. JVM I2CAdapter */
|
|
if (cur->sp >= cur->sp_safe && (unsigned long) cur->sp < wctx->sbase)
|
|
{
|
|
unsigned long *nsp = (unsigned long*) (*cur->sp);
|
|
if (nsp >= cur->sp && nsp <= cur->fp)
|
|
{
|
|
cur->sp = nsp;
|
|
}
|
|
else
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "stack_unwind%d give up return address, opcode=0x%02x\n",
|
|
__LINE__, opcode);
|
|
goto checkFP;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d give up return address, opcode=0x%02x\n",
|
|
__LINE__, opcode);
|
|
goto checkFP;
|
|
}
|
|
break;
|
|
}
|
|
cur->sp += 1;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
{
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
}
|
|
break;
|
|
case 0x60: /* pusha(d) */
|
|
cur->sp -= 8;
|
|
break;
|
|
case 0x61: /* popa(d) */
|
|
cur->sp += 8;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
break;
|
|
case 0x62: /* group AVX, 4-bytes EVEX prefix */
|
|
{
|
|
unsigned char *pc = cur->pc - 1; // points to the beginning of the instruction
|
|
int len = parse_x86_AVX_instruction (pc);
|
|
if (len < 4)
|
|
{
|
|
DELETE_CURCTX ();
|
|
}
|
|
else
|
|
{
|
|
pc += len;
|
|
cur->pc = pc;
|
|
}
|
|
}
|
|
break;
|
|
case 0x63: /* arpl Ew,Gw (32) movsxd Gv,Ev (64)*/
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x64: /* seg=fs prefix */
|
|
case 0x65: /* seg=gs prefix */
|
|
break;
|
|
case 0x66: /* opd size override */
|
|
case 0x67: /* addr size override */
|
|
break;
|
|
case 0x68: /* push Iz */
|
|
cur->sp = (unsigned long*) ((long) cur->sp - z);
|
|
cur->pc += z;
|
|
break;
|
|
case 0x69: /* imul Gv,Ev,Iz */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += z;
|
|
break;
|
|
case 0x6a: /* push Ib */
|
|
cur->sp = (unsigned long*) ((long) cur->sp - v);
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x6b: /* imul Gv,Ev,Ib */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x6c: case 0x6d: case 0x6e: case 0x6f:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x70: /* jo Jb */
|
|
case 0x71: /* jno Jb */
|
|
case 0x72: /* jb Jb */
|
|
case 0x73: /* jnb Jb */
|
|
case 0x74: /* jz Jb */
|
|
case 0x75: /* jnz Jb */
|
|
case 0x76: /* jna Jb */
|
|
case 0x77: /* ja Jb */
|
|
case 0x78: /* js Jb */
|
|
case 0x79: /* jns Jb */
|
|
case 0x7a: /* jp Jb */
|
|
case 0x7b: /* jnp Jb */
|
|
case 0x7c: /* jl Jb */
|
|
case 0x7d: /* jge Jb */
|
|
case 0x7e: /* jle Jb */
|
|
case 0x7f: /* jg Jb */
|
|
imm8 = *(char*) cur->pc++;
|
|
if (nctx < (jmp_reg_switch_mode ? MAXJMPREGCTX : MAXCTX))
|
|
{
|
|
int tidx = 0;
|
|
unsigned char *npc = cur->pc + imm8;
|
|
if (is_after_ret (npc))
|
|
break;
|
|
while (npc > targets[tidx])
|
|
tidx += 1;
|
|
if (npc != targets[tidx])
|
|
{
|
|
if (ntrg < MAXTRGTS)
|
|
{
|
|
for (int i = 0; i < nctx; i++)
|
|
if (buf[i].tidx >= tidx)
|
|
buf[i].tidx++;
|
|
|
|
/* insert a new target */
|
|
for (int i = ntrg; i > tidx; i--)
|
|
targets[i] = targets[i - 1];
|
|
ntrg += 1;
|
|
targets[tidx++] = npc;
|
|
}
|
|
else
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d ntrg(%d)=max\n", __LINE__, ntrg);
|
|
struct AdvWalkContext *new = buf + nctx;
|
|
nctx += 1;
|
|
__collector_memcpy (new, cur, sizeof (*new));
|
|
new->pc = npc;
|
|
new->tidx = tidx;
|
|
cur = new; /* advance the new context first */
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d nctx(%d)=max\n", __LINE__, nctx);
|
|
break;
|
|
case 0x80: /* group1 Eb,Ib */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x81: /* group1 Ev,Iz */
|
|
modrm = *cur->pc;
|
|
if (MRM_MOD (modrm) == 0xc0 && MRM_REGS (modrm) == RSP)
|
|
{
|
|
int immz = read_int (cur->pc + 1, z);
|
|
extop = MRM_EXT (modrm);
|
|
if (extop == 0) /* add imm32,%esp */
|
|
cur->sp = (unsigned long*) ((long) cur->sp + immz);
|
|
else if (extop == 4) /* and imm32,%esp */
|
|
cur->sp = (unsigned long*) ((long) cur->sp & immz);
|
|
else if (extop == 5) /* sub imm32,%esp */
|
|
cur->sp = (unsigned long*) ((long) cur->sp - immz);
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
}
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += z;
|
|
break;
|
|
case 0x82: /* group1 Eb,Ib */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x83: /* group1 Ev,Ib */
|
|
modrm = *cur->pc;
|
|
if (MRM_MOD (modrm) == 0xc0 && MRM_REGS (modrm) == RSP)
|
|
{
|
|
imm8 = (char) cur->pc[1]; /* sign extension */
|
|
extop = MRM_EXT (modrm);
|
|
if (extop == 0) /* add imm8,%esp */
|
|
cur->sp = (unsigned long*) ((long) cur->sp + imm8);
|
|
else if (extop == 4) /* and imm8,%esp */
|
|
cur->sp = (unsigned long*) ((long) cur->sp & imm8);
|
|
else if (extop == 5) /* sub imm8,%esp */
|
|
cur->sp = (unsigned long*) ((long) cur->sp - imm8);
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
}
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc += 1;
|
|
break;
|
|
case 0x84: /* test Eb,Gb */
|
|
case 0x85: /* test Ev,Gv */
|
|
case 0x86: /* xchg Eb,Gb */
|
|
case 0x87: /* xchg Ev,Gv */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x88: /* mov Eb,Gb */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x89: /* mov Ev,Gv */
|
|
modrm = *cur->pc;
|
|
if (MRM_MOD (modrm) == 0xc0)
|
|
{
|
|
if (MRM_REGS (modrm) == RBP && MRM_REGD (modrm) == RSP)
|
|
/* movl %esp,%ebp */
|
|
cur->fp = cur->sp;
|
|
else if (MRM_REGS (modrm) == RSP && MRM_REGD (modrm) == RBP)
|
|
{ /* mov %ebp,%esp */
|
|
cur->sp = cur->fp;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
if (wctx->fp == (unsigned long) cur->sp)
|
|
cur->cval = RA_FROMFP;
|
|
}
|
|
}
|
|
else if (MRM_MOD (modrm) == 0x80)
|
|
{
|
|
if (MRM_REGS (modrm) == RSP && MRM_REGD (modrm) == RBP)
|
|
{
|
|
if (cur->pc[1] == 0x24)
|
|
{ /* mov %ebp,disp32(%esp) - JVM */
|
|
immv = read_int (cur->pc + 2, 4);
|
|
cur->fp_loc = (unsigned long*) ((char*) cur->sp + immv);
|
|
cur->fp_sav = cur->fp;
|
|
}
|
|
}
|
|
}
|
|
else if (MRM_MOD (modrm) == 0x40)
|
|
{
|
|
if (MRM_REGS (modrm) == RSP && MRM_REGD (modrm) == RDX)
|
|
{
|
|
if (cur->pc[1] == 0x24 && cur->pc[2] == 0x0)
|
|
{ /* movl %edx,0(%esp) */
|
|
cur->ra_loc = cur->sp;
|
|
cur->ra_sav = cur->rdx;
|
|
}
|
|
}
|
|
else if (MRM_REGS (modrm) == RSP && MRM_REGD (modrm) == RBP)
|
|
{
|
|
if (cur->pc[1] == 0x24)
|
|
{ /* mov %ebp,disp8(%esp) - JVM */
|
|
imm8 = ((char*) (cur->pc))[2];
|
|
cur->fp_loc = (unsigned long*) ((char*) cur->sp + imm8);
|
|
cur->fp_sav = cur->fp;
|
|
}
|
|
}
|
|
}
|
|
else if (MRM_MOD (modrm) == 0x0)
|
|
{
|
|
if (MRM_REGS (modrm) == RSP && MRM_REGD (modrm) == RBP)
|
|
{
|
|
if (cur->pc[1] == 0x24)
|
|
{ /* mov %ebp,(%esp) */
|
|
cur->fp_loc = cur->sp;
|
|
cur->fp_sav = cur->fp;
|
|
}
|
|
}
|
|
else if (MRM_REGS (modrm) == RSP && MRM_REGD (modrm) == RDX)
|
|
{
|
|
if (cur->pc[1] == 0x24)
|
|
{ /* movl %edx,(%esp) */
|
|
cur->ra_loc = cur->sp;
|
|
cur->ra_sav = cur->rdx;
|
|
}
|
|
}
|
|
}
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x8a: /* mov Gb,Eb */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x8b: /* mov Gv,Ev */
|
|
modrm = *cur->pc;
|
|
if (MRM_MOD (modrm) == 0xc0)
|
|
{
|
|
if (MRM_REGS (modrm) == RSP && MRM_REGD (modrm) == RBP)
|
|
/* mov %esp,%ebp */
|
|
cur->fp = cur->sp;
|
|
else if (MRM_REGS (modrm) == RBP && MRM_REGD (modrm) == RSP)
|
|
{ /* mov %ebp,%esp */
|
|
cur->sp = cur->fp;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
if (wctx->fp == (unsigned long) cur->sp)
|
|
cur->cval = RA_FROMFP;
|
|
}
|
|
}
|
|
else if (MRM_MOD (modrm) == 0x80)
|
|
{
|
|
if (MRM_REGS (modrm) == RSP && MRM_REGD (modrm) == RBP)
|
|
{
|
|
if (cur->pc[1] == 0x24)
|
|
{ /* mov disp32(%esp),%ebp */
|
|
immv = read_int (cur->pc + 2, 4);
|
|
unsigned long *ptr = (unsigned long*) ((char*) cur->sp + immv);
|
|
if (cur->fp_loc == ptr)
|
|
{
|
|
cur->fp = cur->fp_sav;
|
|
cur->fp_loc = NULL;
|
|
}
|
|
else if (ptr >= cur->sp_safe && (unsigned long) ptr < wctx->sbase)
|
|
cur->fp = (unsigned long*) (*ptr);
|
|
}
|
|
}
|
|
}
|
|
else if (MRM_MOD (modrm) == 0x40)
|
|
{
|
|
if (MRM_REGS (modrm) == RSP && MRM_REGD (modrm) == RBP)
|
|
{
|
|
if (cur->pc[1] == 0x24)
|
|
{ /* mov disp8(%esp),%ebp - JVM */
|
|
imm8 = ((char*) (cur->pc))[2];
|
|
unsigned long *ptr = (unsigned long*) ((char*) cur->sp + imm8);
|
|
if (cur->fp_loc == ptr)
|
|
{
|
|
cur->fp = cur->fp_sav;
|
|
cur->fp_loc = NULL;
|
|
}
|
|
else if (ptr >= cur->sp_safe && (unsigned long) ptr < wctx->sbase)
|
|
cur->fp = (unsigned long*) (*ptr);
|
|
}
|
|
}
|
|
}
|
|
else if (MRM_MOD (modrm) == 0x0)
|
|
{
|
|
if (MRM_REGS (modrm) == RSP && MRM_REGD (modrm) == RBP)
|
|
{
|
|
if (cur->pc[1] == 0x24)
|
|
{ /* mov (%esp),%ebp */
|
|
if (cur->fp_loc == cur->sp)
|
|
{
|
|
cur->fp = cur->fp_sav;
|
|
cur->fp_loc = NULL;
|
|
}
|
|
else if (cur->sp >= cur->sp_safe &&
|
|
(unsigned long) cur->sp < wctx->sbase)
|
|
cur->fp = (unsigned long*) *cur->sp;
|
|
}
|
|
}
|
|
}
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x8c: /* mov Mw,Sw */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x8d: /* lea Gv,M */
|
|
modrm = *cur->pc;
|
|
if (MRM_REGD (modrm) == RSP)
|
|
{
|
|
unsigned char *pc = cur->pc;
|
|
// Mez: need to use always regs[RSP/RBP] instead cur->sp(or fp):
|
|
cur->regs[RSP] = (unsigned long) cur->sp;
|
|
cur->regs[RBP] = (unsigned long) cur->fp;
|
|
cur->pc++;
|
|
int mod = (modrm >> 6) & 3;
|
|
int r_m = modrm & 7;
|
|
long val = 0;
|
|
int undefRez = 0;
|
|
if (mod == 0x3)
|
|
val = getRegVal (cur, MRM_REGS (modrm), &undefRez);
|
|
else if (r_m == 4)
|
|
{ // SP or R12. Decode SIB-byte.
|
|
int sib = *cur->pc++;
|
|
int scale = 1 << (sib >> 6);
|
|
int index = X | ((sib >> 3) & 7);
|
|
int base = B | (sib & 7);
|
|
if (mod == 0)
|
|
{
|
|
if ((base & 7) == 5)
|
|
{ // BP or R13
|
|
if (index != 4) // SP
|
|
val += getRegVal (cur, index, &undefRez) * scale;
|
|
val += read_int (cur->pc, 4);
|
|
cur->pc += 4;
|
|
}
|
|
else
|
|
{
|
|
val += getRegVal (cur, base, &undefRez);
|
|
if (index != 4) // SP
|
|
val += getRegVal (cur, index, &undefRez) * scale;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
val += getRegVal (cur, base, &undefRez);
|
|
if (index != 4) // SP
|
|
val += getRegVal (cur, index, &undefRez) * scale;
|
|
if (mod == 1)
|
|
{
|
|
val += read_int (cur->pc, 1);
|
|
cur->pc++;
|
|
}
|
|
else
|
|
{ // mod == 2
|
|
val += read_int (cur->pc, 4);
|
|
cur->pc += 4;
|
|
}
|
|
}
|
|
}
|
|
else if (mod == 0)
|
|
{
|
|
if (r_m == 5)
|
|
{ // BP or R13
|
|
val += read_int (cur->pc, 4);
|
|
cur->pc += 4;
|
|
}
|
|
else
|
|
val += getRegVal (cur, MRM_REGS (modrm), &undefRez);
|
|
}
|
|
else
|
|
{ // mod == 1 || mod == 2
|
|
val += getRegVal (cur, MRM_REGS (modrm), &undefRez);
|
|
if (mod == 1)
|
|
{
|
|
val += read_int (cur->pc, 1);
|
|
cur->pc++;
|
|
}
|
|
else
|
|
{ // mod == 2
|
|
val += read_int (cur->pc, 4);
|
|
cur->pc += 4;
|
|
}
|
|
}
|
|
if (undefRez)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "stack_unwind%d cannot calculate RSP. cur->pc=0x%lx val=0x%lx\n",
|
|
__LINE__, (unsigned long) cur->pc, (unsigned long) val);
|
|
goto checkFP;
|
|
}
|
|
cur->regs[MRM_REGD (modrm)] = val;
|
|
DprintfT (SP_DUMP_UNWIND, "stack_unwind%d cur->pc=0x%lx val=0x%lx wctx->sp=0x%lx wctx->sbase=0x%lx\n",
|
|
__LINE__, (unsigned long) cur->pc, (unsigned long) val,
|
|
(unsigned long) wctx->sp, (unsigned long) wctx->sbase);
|
|
if (cur->pc != check_modrm (pc))
|
|
DprintfT (SP_DUMP_UNWIND, "stack_unwind%d ERROR: cur->pc=0x%lx != check_modrm(0x%lx)=0x%lx\n",
|
|
__LINE__, (unsigned long) cur->pc, (unsigned long) pc,
|
|
(unsigned long) check_modrm (pc));
|
|
if (MRM_REGD (modrm) == RSP)
|
|
{
|
|
if (!isInside ((unsigned long) val, wctx->sp, wctx->sbase))
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "stack_unwind%d cannot calculate RSP. cur->pc=0x%lx opcode=0x%02x val=0x%lx wctx->sp=0x%lx wctx->sbase=0x%lx\n",
|
|
__LINE__, (unsigned long) cur->pc, opcode, (unsigned long) val,
|
|
(unsigned long) wctx->sp, (unsigned long) wctx->sbase);
|
|
goto checkFP;
|
|
}
|
|
cur->sp = (unsigned long *) val;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
}
|
|
}
|
|
else
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x8e: /* mov Sw,Ew */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x8f: /* pop Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->sp += 1;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
break;
|
|
case 0x90: /* nop */
|
|
break;
|
|
case 0x91: /* xchg %eax,%ecx */
|
|
case 0x92: /* xchg %eax,%edx */
|
|
case 0x93: /* xchg %eax,%ebx */
|
|
case 0x94: /* xchg %eax,%esp XXXX */
|
|
case 0x95: /* xchg %eax,%ebp XXXX */
|
|
case 0x96: /* xchg %eax,%esi */
|
|
case 0x97: /* xchg %eax,%edi */
|
|
break;
|
|
case 0x98: /* cbw/cwde */
|
|
case 0x99: /* cwd/cwq */
|
|
break;
|
|
case 0x9a: /* callf Ap */
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
struct AdvWalkContext* tmpctx = (struct AdvWalkContext *) alloca (sizeof (*cur));
|
|
__collector_memcpy (tmpctx, cur, sizeof (*cur));
|
|
int rc = process_return (wctx, tmpctx);
|
|
if (rc != RA_FAILURE)
|
|
{
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
cur->pc += 2 + a;
|
|
break;
|
|
case 0x9b: /* fwait */
|
|
case 0x9c: /* pushf Fv */
|
|
case 0x9d: /* popf Fv */
|
|
case 0x9e: /* sahf */
|
|
case 0x9f: /* lahf */
|
|
break;
|
|
case 0xa0: /* mov al,Ob */
|
|
case 0xa1: /* mov eax,Ov */
|
|
case 0xa2: /* mov Ob,al */
|
|
case 0xa3: /* mov Ov,eax */
|
|
cur->pc += a;
|
|
break;
|
|
case 0xa4: /* movsb Yb,Xb */
|
|
case 0xa5: /* movsd Yv,Xv */
|
|
case 0xa6: /* cmpsb Yb,Xb */
|
|
case 0xa7: /* cmpsd Xv,Yv */
|
|
break;
|
|
case 0xa8: /* test al,Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xa9: /* test eax,Iz */
|
|
cur->pc += z;
|
|
break;
|
|
case 0xaa: /* stosb Yb,%al */
|
|
case 0xab: /* stosd Yv,%eax */
|
|
case 0xac: /* lodsb %al,Xb */
|
|
case 0xad: /* lodsd %eax,Xv */
|
|
case 0xae: /* scasb %al,Yb */
|
|
case 0xaf: /* scasd %eax,Yv */
|
|
break;
|
|
case 0xb0: /* mov %al,Ib */
|
|
case 0xb1: /* mov %cl,Ib */
|
|
case 0xb2: /* mov %dl,Ib */
|
|
case 0xb3: /* mov %bl,Ib */
|
|
case 0xb4: /* mov %ah,Ib */
|
|
case 0xb5: /* mov %ch,Ib */
|
|
case 0xb6: /* mov %dh,Ib */
|
|
case 0xb7: /* mov %bh,Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xb8: /* mov Iv,%eax */
|
|
case 0xb9: /* mov Iv,%ecx */
|
|
case 0xba: /* mov Iv,%edx */
|
|
case 0xbb: /* mov Iv,%ebx */
|
|
case 0xbc: /* mov Iv,%esp */
|
|
case 0xbd: /* mov Iv,%rbp */
|
|
case 0xbe: /* mov Iv,%esi */
|
|
case 0xbf: /* mov Iv,%edi */
|
|
reg = OPC_REG (opcode);
|
|
if (reg == RAX)
|
|
cur->rax = read_int (cur->pc, v);
|
|
cur->pc += v;
|
|
break;
|
|
case 0xc0: /* group2 Eb,Ib */
|
|
case 0xc1: /* group2 Ev,Ib */
|
|
cur->pc = check_modrm (cur->pc) + 1;
|
|
break;
|
|
case 0xc2: /* ret Iw */
|
|
/* In the dynamic linker we may see that
|
|
* the actual return address is at sp+immv,
|
|
* while sp points to the resolved address.
|
|
*/
|
|
{
|
|
immv = read_int (cur->pc, 2);
|
|
int rc = process_return (wctx, cur);
|
|
if (rc != RA_FAILURE)
|
|
{
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "stack_unwind%d give up return address under jmp switch mode, opcode = 0xc2\n", __LINE__);
|
|
goto checkFP;
|
|
}
|
|
wctx->sp += immv;
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode 0xc2.\n", __LINE__);
|
|
DELETE_CURCTX ();
|
|
}
|
|
break;
|
|
case 0xc3: /* ret */
|
|
{
|
|
int rc = process_return (wctx, cur);
|
|
if (rc != RA_FAILURE)
|
|
{
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
if (jmp_reg_switch_mode == 1)
|
|
jmp_reg_switch_pc = cur->pc;
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode 0xc3.\n", __LINE__);
|
|
DELETE_CURCTX ();
|
|
}
|
|
break;
|
|
case 0xc4: /* group AVX, 3-bytes VEX prefix */
|
|
{
|
|
unsigned char *pc = cur->pc - 1; // points to the beginning of the instruction
|
|
int len = parse_x86_AVX_instruction (pc);
|
|
if (len < 3)
|
|
DELETE_CURCTX ();
|
|
else
|
|
{
|
|
pc += len;
|
|
cur->pc = pc;
|
|
}
|
|
}
|
|
break;
|
|
case 0xc5: /* group AVX, 2-bytes VEX prefix */
|
|
{
|
|
unsigned char *pc = cur->pc - 1; // points to the beginning of the instruction
|
|
int len = parse_x86_AVX_instruction (pc);
|
|
if (len < 2)
|
|
DELETE_CURCTX ();
|
|
else
|
|
{
|
|
pc += len;
|
|
cur->pc = pc;
|
|
}
|
|
}
|
|
break;
|
|
case 0xc6:
|
|
modrm = *cur->pc;
|
|
if (modrm == 0xf8) /* xabort */
|
|
cur->pc += 2;
|
|
else /* mov Eb,Ib */
|
|
cur->pc = check_modrm (cur->pc) + 1;
|
|
break;
|
|
case 0xc7:
|
|
modrm = *cur->pc;
|
|
if (modrm == 0xf8) /* xbegin */
|
|
cur->pc += v + 1;
|
|
else
|
|
{ /* mov Ev,Iz */
|
|
extop = MRM_EXT (modrm);
|
|
if (extop != 0)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d give up return address, opcode = 0xc7\n", __LINE__);
|
|
goto checkFP;
|
|
}
|
|
if (MRM_MOD (modrm) == 0xc0 && MRM_REGS (modrm) == RAX)
|
|
cur->rax = read_int (cur->pc + 1, z);
|
|
cur->pc = check_modrm (cur->pc) + z;
|
|
}
|
|
break;
|
|
case 0xc8: /* enter Iw,Ib */
|
|
cur->pc += 3;
|
|
break;
|
|
case 0xc9: /* leave */
|
|
/* mov %ebp,%esp */
|
|
cur->sp = cur->fp;
|
|
/* pop %ebp */
|
|
if (cur->fp_loc == cur->sp)
|
|
{
|
|
cur->fp = cur->fp_sav;
|
|
cur->fp_loc = NULL;
|
|
}
|
|
else if (cur->sp >= cur->sp_safe &&
|
|
(unsigned long) cur->sp < wctx->sbase)
|
|
{
|
|
cur->fp = (unsigned long*) (*cur->sp);
|
|
if (wctx->fp == (unsigned long) cur->sp)
|
|
cur->cval = RA_FROMFP;
|
|
}
|
|
cur->sp += 1;
|
|
if (cur->sp - RED_ZONE > cur->sp_safe)
|
|
cur->sp_safe = cur->sp - RED_ZONE;
|
|
break;
|
|
case 0xca: /* retf Iw */
|
|
cur->pc += 2; /* XXXX process return */
|
|
break;
|
|
case 0xcb: /* retf */
|
|
break; /* XXXX process return */
|
|
case 0xcc: /* int 3 */
|
|
break;
|
|
case 0xcd: /* int Ib */
|
|
if (*cur->pc == 0x80)
|
|
{
|
|
if (cur->rax == __NR_exit)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode 0xcd.\n", __LINE__);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
}
|
|
else if (cur->rax == __NR_rt_sigreturn)
|
|
{
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d give up return address under jmp switch mode, opcode=0xcd\n",
|
|
__LINE__);
|
|
goto checkFP;
|
|
}
|
|
wctx->sp = (unsigned long) cur->sp;
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_RT_SIGRETURN);
|
|
return RA_RT_SIGRETURN;
|
|
}
|
|
#if WSIZE(32)
|
|
else if (cur->rax == __NR_sigreturn)
|
|
{
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d give up return address under jmp switch mode, opcode = 0xc2\n",
|
|
__LINE__);
|
|
goto checkFP;
|
|
}
|
|
wctx->sp = (unsigned long) cur->sp;
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_SIGRETURN);
|
|
return RA_SIGRETURN;
|
|
}
|
|
#endif
|
|
}
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xce: /* into */
|
|
case 0xcf: /* iret */
|
|
break;
|
|
case 0xd0: /* shift group2 Eb,1 */
|
|
case 0xd1: /* shift group2 Ev,1 */
|
|
case 0xd2: /* shift group2 Eb,%cl */
|
|
case 0xd3: /* shift group2 Ev,%cl */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xd4: /* aam Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xd5: /* aad Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xd6: /* falc? */
|
|
break;
|
|
case 0xd7:
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc++;
|
|
break;
|
|
case 0xd8: /* esc instructions */
|
|
case 0xd9:
|
|
case 0xda:
|
|
case 0xdb:
|
|
case 0xdc:
|
|
case 0xdd:
|
|
case 0xde:
|
|
case 0xdf:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xe0: /* loopne Jb */
|
|
case 0xe1: /* loope Jb */
|
|
case 0xe2: /* loop Jb */
|
|
case 0xe3: /* jcxz Jb */
|
|
imm8 = *(char*) cur->pc++;
|
|
if (nctx < (jmp_reg_switch_mode ? MAXJMPREGCTX : MAXCTX))
|
|
{
|
|
int tidx = 0;
|
|
unsigned char *npc = cur->pc + imm8;
|
|
if (is_after_ret (npc))
|
|
break;
|
|
while (npc > targets[tidx])
|
|
tidx += 1;
|
|
if (npc != targets[tidx])
|
|
{
|
|
if (ntrg < MAXTRGTS)
|
|
{
|
|
for (int i = 0; i < nctx; i++)
|
|
if (buf[i].tidx >= tidx)
|
|
buf[i].tidx++;
|
|
/* insert a new target */
|
|
for (int i = ntrg; i > tidx; i--)
|
|
targets[i] = targets[i - 1];
|
|
ntrg += 1;
|
|
targets[tidx++] = npc;
|
|
}
|
|
else
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: ntrg = max\n");
|
|
struct AdvWalkContext *new = buf + nctx;
|
|
nctx += 1;
|
|
__collector_memcpy (new, cur, sizeof (*new));
|
|
new->pc = npc;
|
|
new->tidx = tidx;
|
|
cur = new; /* advance the new context first */
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: nctx = max\n");
|
|
break;
|
|
case 0xe4: case 0xe5:
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->pc++;
|
|
break;
|
|
case 0xe6: case 0xe7:
|
|
cur->pc++;
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xec: case 0xed: case 0xee: case 0xef:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0xe8: /* call Jz (f64) */
|
|
{
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
struct AdvWalkContext* tmpctx = (struct AdvWalkContext *) alloca (sizeof (*cur));
|
|
__collector_memcpy (tmpctx, cur, sizeof (*cur));
|
|
int rc = process_return (wctx, tmpctx);
|
|
if (rc != RA_FAILURE)
|
|
{
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
int immz = read_int (cur->pc, z);
|
|
if (immz == 0)
|
|
/* special case in PIC code */
|
|
cur->sp -= 1;
|
|
cur->pc += z;
|
|
}
|
|
break;
|
|
case 0xe9: /* jump Jz */
|
|
{
|
|
int immz = read_int (cur->pc, z);
|
|
unsigned char *npc = cur->pc + z + immz;
|
|
if ((unsigned long) npc < wctx->tbgn || (unsigned long) npc >= wctx->tend)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode 0xe9.\n", __LINE__);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
}
|
|
int tidx = 0;
|
|
while (npc > targets[tidx])
|
|
tidx += 1;
|
|
if (npc != targets[tidx])
|
|
{
|
|
if (ntrg < MAXTRGTS)
|
|
{
|
|
for (int i = 0; i < nctx; i++)
|
|
if (buf[i].tidx >= tidx)
|
|
buf[i].tidx++;
|
|
/* insert a new target */
|
|
for (int i = ntrg; i > tidx; i--)
|
|
targets[i] = targets[i - 1];
|
|
ntrg += 1;
|
|
targets[tidx++] = npc;
|
|
}
|
|
else
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: ntrg = max\n");
|
|
cur->pc = npc;
|
|
cur->tidx = tidx;
|
|
continue; /* advance this context first */
|
|
}
|
|
else
|
|
{
|
|
/* Delete context */
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode 0xe9.\n", __LINE__);
|
|
DELETE_CURCTX ();
|
|
}
|
|
}
|
|
break;
|
|
case 0xeb: /* jump imm8 */
|
|
{
|
|
imm8 = *(char*) cur->pc++;
|
|
int tidx = 0;
|
|
unsigned char *npc = cur->pc + imm8;
|
|
while (npc > targets[tidx])
|
|
tidx += 1;
|
|
if (npc != targets[tidx])
|
|
{
|
|
if (ntrg < MAXTRGTS)
|
|
{
|
|
for (int i = 0; i < nctx; i++)
|
|
if (buf[i].tidx >= tidx)
|
|
buf[i].tidx++;
|
|
/* insert a new target */
|
|
for (int i = ntrg; i > tidx; i--)
|
|
targets[i] = targets[i - 1];
|
|
ntrg += 1;
|
|
targets[tidx++] = npc;
|
|
}
|
|
else
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: ntrg = max\n");
|
|
cur->pc = npc;
|
|
cur->tidx = tidx;
|
|
continue; /* advance this context first */
|
|
}
|
|
else
|
|
{
|
|
/* Delete context */
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode 0xeb.\n", __LINE__);
|
|
DELETE_CURCTX ();
|
|
}
|
|
}
|
|
break;
|
|
case 0xf0: /* lock prefix */
|
|
case 0xf2: /* repne prefix */
|
|
case 0xf3: /* repz prefix */
|
|
break;
|
|
case 0xf4: /* hlt */
|
|
extop2 = *(cur->pc - 3);
|
|
if (extop2 == 0x90)
|
|
{
|
|
// 17851712 occasional SEGV in find_i386_ret_addr in unwind.c during attach
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_END_OF_STACK);
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d returns RA_END_OF_STACK\n", __LINE__);
|
|
return RA_END_OF_STACK;
|
|
}
|
|
/* We see 'hlt' in _start. Stop analysis, revert to FP */
|
|
/* A workaround for the Linux main stack */
|
|
if (nctx > 1)
|
|
{
|
|
DELETE_CURCTX ();
|
|
break;
|
|
}
|
|
if (cur->fp == 0)
|
|
{
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: give up return address under jmp switch mode, opcode = 0xf4\n");
|
|
goto checkFP;
|
|
}
|
|
cache_put (wctx, RA_EOSTCK);
|
|
wctx->pc = 0;
|
|
wctx->sp = 0;
|
|
wctx->fp = 0;
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_END_OF_STACK);
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d returns RA_END_OF_STACK\n", __LINE__);
|
|
return RA_END_OF_STACK;
|
|
}
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d give up return address, opcode = 0xf4\n", __LINE__);
|
|
goto checkFP;
|
|
case 0xf5: /* cmc */
|
|
break;
|
|
case 0xf6: /* group3 Eb */
|
|
modrm = *cur->pc;
|
|
extop = MRM_EXT (modrm);
|
|
cur->pc = check_modrm (cur->pc);
|
|
if (extop == 0x0) /* test Ib */
|
|
cur->pc += 1;
|
|
break;
|
|
case 0xf7: /* group3 Ev */
|
|
modrm = *cur->pc;
|
|
extop = MRM_EXT (modrm);
|
|
cur->pc = check_modrm (cur->pc);
|
|
if (extop == 0x0) /* test Iz */
|
|
cur->pc += z;
|
|
break;
|
|
case 0xf8: /* clc */
|
|
case 0xf9: /* stc */
|
|
case 0xfa: /* cli */
|
|
case 0xfb: /* sti */
|
|
case 0xfc: /* cld */
|
|
case 0xfd: /* std */
|
|
break;
|
|
case 0xfe: /* group4 */
|
|
modrm = *cur->pc;
|
|
extop = MRM_EXT (modrm);
|
|
switch (extop)
|
|
{
|
|
case 0x0: /* inc Eb */
|
|
case 0x1: /* dec Eb */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x7:
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
default:
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d unknown opcode: 0xfe %x\n",
|
|
__LINE__, extop);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
}
|
|
break;
|
|
case 0xff: /* group5 */
|
|
modrm = *cur->pc;
|
|
extop = MRM_EXT (modrm);
|
|
switch (extop)
|
|
{
|
|
case 0x0: /* inc Ev */
|
|
case 0x1: /* dec Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x2: /* calln Ev */
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
struct AdvWalkContext* tmpctx = (struct AdvWalkContext *) alloca (sizeof (*cur));
|
|
__collector_memcpy (tmpctx, cur, sizeof (*cur));
|
|
int rc = process_return (wctx, tmpctx);
|
|
if (rc != RA_FAILURE)
|
|
{
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
cur->pc = check_modrm (cur->pc);
|
|
break;
|
|
case 0x3: /* callf Ep */
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
struct AdvWalkContext* tmpctx = (struct AdvWalkContext *) alloca (sizeof (*cur));
|
|
__collector_memcpy (tmpctx, cur, sizeof (*cur));
|
|
int rc = process_return (wctx, tmpctx);
|
|
if (rc != RA_FAILURE)
|
|
{
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
cur->pc = check_modrm (cur->pc); /* XXXX */
|
|
break;
|
|
case 0x4: /* jumpn Ev */
|
|
/* This instruction appears in PLT or
|
|
* in tail call optimization.
|
|
* In both cases treat it as return.
|
|
* Save jump *(reg) - switch, etc, for later use when no ctx left
|
|
*/
|
|
if (modrm == 0x25 || /* jumpn *disp32 */
|
|
MRM_MOD (modrm) == 0x40 || /* jumpn byte(reg) */
|
|
MRM_MOD (modrm) == 0x80) /* jumpn word(reg) */
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: PLT or tail call: %p\n", cur->pc - 1);
|
|
int rc = process_return (wctx, cur);
|
|
if (rc != RA_FAILURE)
|
|
{
|
|
if (jmp_reg_switch_mode == 1 && total_num_jmp_reg < max_num_jmp_reg_seen)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: give up return address under jmp switch mode, opcode = 0xff\n");
|
|
goto checkFP;
|
|
}
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
else if (modrm != 0x24 /*ignore SIB*/) /* jumpn *(reg) or jumpn reg */
|
|
{
|
|
// 22846120 stack unwind does not find caller of __memcpy_ssse3_back with B64 intel-Linux
|
|
/*
|
|
* For now, let's deal rather narrowly with this scenario. If:
|
|
* - we are in the middle of an "ff e2" instruction, and
|
|
* - the next instruction is undefined ( 0f 0b == ud2 )
|
|
* then test return. (Might eventually have to broaden the scope
|
|
* of this fix to other registers/etc.)
|
|
*/
|
|
if (cur->pc[0] == 0xe2 && cur->pc[1] == 0x0f && cur->pc[2] == 0x0b)
|
|
{
|
|
int rc = process_return_real (wctx, cur, 0);
|
|
if (rc == RA_SUCCESS)
|
|
{
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
// 22691241 shjsynprog, jsynprog core dump from find_i386_ret_addr
|
|
/*
|
|
* Here is another oddity. Java 9 seems to emit dynamically generated
|
|
* code where a code block ends with a "jmp *reg" and then padding to a
|
|
* multiple-of-16 boundary and then a bunch of 0s. In this case, let's
|
|
* not continue to walk bytes since we would be walking off the end of
|
|
* the instructions into ... something. Treating them as instructions
|
|
* can lead to unexpected results, including SEGV.
|
|
*/
|
|
/*
|
|
* While the general problem deserves a better solution, let's look
|
|
* here only for one particular case:
|
|
* 0xff 0xe7 jmp *reg
|
|
* nop to bring us to a multiple-of-16 boundary
|
|
* 0x0000000000000a00 something that does not look like an instruction
|
|
*
|
|
* A different nop might be used depending on how much padding is needed
|
|
* to reach that multiple-of-16 boundary. We've seen two:
|
|
* 0x90 one byte
|
|
* 0x0f 0x1f 0x40 0x00 four bytes
|
|
*/
|
|
// confirm the instruction is 0xff 0xe7
|
|
if (cur->pc[0] == 0xe7)
|
|
{
|
|
// check for correct-length nop and find next 16-byte boundary
|
|
int found_nop = 0;
|
|
unsigned long long *boundary = 0;
|
|
switch ((((unsigned long) (cur->pc)) & 0xf))
|
|
{
|
|
case 0xb: // look for 4-byte nop
|
|
if (*((unsigned *) (cur->pc + 1)) == 0x00401f0f)
|
|
found_nop = 1;
|
|
boundary = (unsigned long long *) (cur->pc + 5);
|
|
break;
|
|
case 0xe: // look for 1-byte nop
|
|
if (cur->pc[1] == 0x90)
|
|
found_nop = 1;
|
|
boundary = (unsigned long long *) (cur->pc + 2);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// if nop is found, check what's at the boundary
|
|
if (found_nop && *boundary == 0x000000000a00)
|
|
{
|
|
DELETE_CURCTX ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: probably PLT or tail call or switch table: %p\n",
|
|
cur->pc - 1);
|
|
if (num_jmp_reg < expected_num_jmp_reg)
|
|
{
|
|
if (jmp_reg_ctx[num_jmp_reg] == NULL)
|
|
jmp_reg_ctx[num_jmp_reg] = (struct AdvWalkContext *) alloca (sizeof (*cur));
|
|
if (jmp_reg_ctx[num_jmp_reg] != NULL)
|
|
__collector_memcpy (jmp_reg_ctx[num_jmp_reg], cur, sizeof (*cur));
|
|
}
|
|
if (num_jmp_reg < expected_num_jmp_reg ||
|
|
(num_jmp_reg >= expected_num_jmp_reg &&
|
|
jmp_reg_ctx[expected_num_jmp_reg - 1] != NULL &&
|
|
cur->pc != jmp_reg_ctx[expected_num_jmp_reg - 1]->pc))
|
|
{
|
|
num_jmp_reg++;
|
|
total_num_jmp_reg++;
|
|
}
|
|
if (jmp_reg_switch_mode == 1 && total_num_jmp_reg >= max_num_jmp_reg_seen)
|
|
{
|
|
int rc = process_return_real (wctx, cur, 0);
|
|
if (rc == RA_SUCCESS)
|
|
{
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d delete context, opcode 0xff.\n", __LINE__);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
case 0x5: /* jmpf Ep */
|
|
cur->pc = check_modrm (cur->pc); /* XXXX */
|
|
break;
|
|
case 0x6: /* push Ev */
|
|
cur->pc = check_modrm (cur->pc);
|
|
cur->sp -= 1;
|
|
break;
|
|
case 0x7:
|
|
cur->pc = check_modrm (cur->pc); /* XXXX */
|
|
if (jmp_reg_switch_mode == 1)
|
|
{
|
|
int rc = process_return_real (wctx, cur, 0);
|
|
if (rc == RA_SUCCESS)
|
|
{
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d unknown opcode: 0xff %x\n",
|
|
__LINE__, (int) extop);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d unknown opcode: 0x%x\n",
|
|
__LINE__, (int) opcode);
|
|
DELETE_CURCTX ();
|
|
break;
|
|
}
|
|
|
|
/* switch to next context */
|
|
if (++cur >= buf + nctx)
|
|
cur = buf;
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d switch context: cur=0x%lx(%ld) nctx=%d cnt=%d\n",
|
|
__LINE__, (unsigned long) cur, (long) (cur - buf), (int) nctx, (int) cnt);
|
|
}
|
|
|
|
checkFP:
|
|
Tprintf (DBG_LT3, "find_i386_ret_addr:%d checkFP: wctx=0x%lx fp=0x%lx ln=0x%lx pc=0x%lx sbase=0x%lx sp=0x%lx tbgn=0x%lx tend=0x%lx\n",
|
|
__LINE__, (unsigned long) wctx, (unsigned long) wctx->fp,
|
|
(unsigned long) wctx->ln, (unsigned long) wctx->pc, (unsigned long) wctx->sbase,
|
|
(unsigned long) wctx->sp, (unsigned long) wctx->tbgn, (unsigned long) wctx->tend);
|
|
|
|
if (jmp_reg_switch_mode == 1)
|
|
{ // not deal with switch cases not ending with ret
|
|
if (jmp_reg_switch_backup_ctx != NULL)
|
|
__collector_memcpy (cur, jmp_reg_switch_backup_ctx, sizeof (*cur));
|
|
DprintfT (SP_DUMP_UNWIND, "stack_unwind jmp reg mode on: pc = 0x%lx cnt = %d, nctx = %d\n", wctx->pc, cnt, nctx);
|
|
}
|
|
|
|
unsigned long *cur_fp = cur->fp;
|
|
unsigned long *cur_sp = cur->sp;
|
|
if (do_walk == 0)
|
|
__collector_memcpy (&wctx_pc_save, wctx, sizeof (struct WalkContext));
|
|
|
|
/* Resort to the frame pointer */
|
|
if (cur->fp_loc)
|
|
cur->fp = cur->fp_sav;
|
|
cur->sp = cur->fp;
|
|
if ((unsigned long) cur->sp >= wctx->sbase ||
|
|
(unsigned long) cur->sp < wctx->sp)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d do_walk=%d cur->sp=0x%p out of range. wctx->sbase=0x%lx wctx->sp=0x%lx wctx->pc=0x%lx\n",
|
|
__LINE__, (int) do_walk, cur->sp, (unsigned long) wctx->sbase,
|
|
(unsigned long) wctx->sp, (unsigned long) wctx->pc);
|
|
if (do_walk == 0)
|
|
{
|
|
cur->sp = cur_sp;
|
|
cur->fp = cur_fp;
|
|
do_walk = 1;
|
|
save_ctx = 1;
|
|
goto startWalk;
|
|
}
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_FAILURE);
|
|
return RA_FAILURE;
|
|
}
|
|
|
|
unsigned long fp = *cur->sp++;
|
|
if (fp <= (unsigned long) cur->sp || fp >= wctx->sbase)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d fp=0x%016llx out of range. cur->sp=%p wctx->sbase=0x%lx wctx->pc=0x%lx\n",
|
|
__LINE__, (unsigned long long) fp, cur->sp,
|
|
(unsigned long) wctx->sbase, (unsigned long) wctx->pc);
|
|
if (do_walk == 0)
|
|
{
|
|
cur->sp = cur_sp;
|
|
cur->fp = cur_fp;
|
|
do_walk = 1;
|
|
save_ctx = 1;
|
|
goto startWalk;
|
|
}
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_FAILURE);
|
|
return RA_FAILURE;
|
|
}
|
|
|
|
unsigned long ra = *cur->sp++;
|
|
if (ra == 0)
|
|
{
|
|
cache_put (wctx, RA_EOSTCK);
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c:%d returns RA_END_OF_STACK wctx->pc = 0x%lx\n", __LINE__, wctx->pc);
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_END_OF_STACK);
|
|
return RA_END_OF_STACK;
|
|
}
|
|
|
|
unsigned long tbgn = wctx->tbgn;
|
|
unsigned long tend = wctx->tend;
|
|
if (ra < tbgn || ra >= tend)
|
|
{
|
|
// We do not know yet if update_map_segments is really needed
|
|
if (!__collector_check_segment (ra, &tbgn, &tend, 0))
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: __collector_check_segment fail. wctx->pc = 0x%lx\n", wctx->pc);
|
|
if (do_walk == 0)
|
|
{
|
|
cur->sp = cur_sp;
|
|
cur->fp = cur_fp;
|
|
do_walk = 1;
|
|
save_ctx = 1;
|
|
goto startWalk;
|
|
}
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_FAILURE);
|
|
return RA_FAILURE;
|
|
}
|
|
}
|
|
|
|
unsigned long npc = adjust_ret_addr (ra, ra - tbgn, tend);
|
|
if (npc == 0)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: adjust_ret_addr fail. wctx->pc = 0x%lx\n", wctx->pc);
|
|
if (do_walk == 0)
|
|
{
|
|
cur->sp = cur_sp;
|
|
cur->fp = cur_fp;
|
|
do_walk = 1;
|
|
save_ctx = 1;
|
|
goto startWalk;
|
|
}
|
|
if (save_ctx)
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_FAILURE);
|
|
return RA_FAILURE;
|
|
}
|
|
wctx->pc = npc;
|
|
wctx->sp = (unsigned long) cur->sp;
|
|
wctx->fp = fp;
|
|
wctx->tbgn = tbgn;
|
|
wctx->tend = tend;
|
|
|
|
if (save_ctx)
|
|
{
|
|
omp_cache_put (cur->sp_safe, &wctx_pc_save, wctx, RA_SUCCESS);
|
|
DprintfT (SP_DUMP_UNWIND, "unwind.c: cache walk context. wctx_pc_save->pc = 0x%lx\n", wctx_pc_save.pc);
|
|
}
|
|
return RA_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* We have the return address, but we would like to report to the user
|
|
* the calling PC, which is the instruction immediately preceding the
|
|
* return address. Unfortunately, x86 instructions can have variable
|
|
* length. So we back up 8 bytes and try to figure out where the
|
|
* calling PC starts. (FWIW, call instructions are often 5-bytes long.)
|
|
*/
|
|
unsigned long
|
|
adjust_ret_addr (unsigned long ra, unsigned long segoff, unsigned long tend)
|
|
{
|
|
unsigned long npc = 0;
|
|
int i = segoff < 8 ? segoff : 8;
|
|
for (; i > 1; i--)
|
|
{
|
|
unsigned char *ptr = (unsigned char*) ra - i;
|
|
int z = 4;
|
|
int a = 4;
|
|
int done = 0;
|
|
int bVal;
|
|
while (!done)
|
|
{
|
|
bVal = getByteInstruction (ptr);
|
|
if (bVal < 0)
|
|
return 0;
|
|
switch (bVal)
|
|
{
|
|
case 0x26:
|
|
case 0x36:
|
|
#if WSIZE(64)
|
|
ptr += 1;
|
|
break;
|
|
#endif
|
|
case 0x64:
|
|
case 0x65:
|
|
bVal = getByteInstruction (ptr + 1);
|
|
if (bVal < 0)
|
|
return 0;
|
|
if (bVal == 0xe8)
|
|
// a workaround for bug 16193041, assuming "call Jz" has no segment override prefix
|
|
done = 1;
|
|
else
|
|
ptr += 1;
|
|
break;
|
|
case 0x66:
|
|
z = 2;
|
|
ptr += 1;
|
|
break;
|
|
case 0x67:
|
|
a = 2;
|
|
ptr += 1;
|
|
break;
|
|
default:
|
|
done = 1;
|
|
break;
|
|
}
|
|
}
|
|
#if WSIZE(64)
|
|
bVal = getByteInstruction (ptr);
|
|
if (bVal < 0)
|
|
return 0;
|
|
if (bVal >= 0x40 && bVal <= 0x4f)
|
|
{ /* XXXX not all REX codes applicable */
|
|
if (bVal & 0x8)
|
|
z = 4;
|
|
ptr += 1;
|
|
}
|
|
#endif
|
|
int opcode = getByteInstruction (ptr);
|
|
if (opcode < 0)
|
|
return 0;
|
|
ptr++;
|
|
switch (opcode)
|
|
{
|
|
case 0xe8: /* call Jz (f64) */
|
|
ptr += z;
|
|
break;
|
|
case 0x9a: /* callf Ap */
|
|
ptr += 2 + a;
|
|
break;
|
|
case 0xff: /* calln Ev , callf Ep */
|
|
{
|
|
int extop = MRM_EXT (*ptr);
|
|
if (extop == 2 || extop == 3)
|
|
ptr = check_modrm (ptr);
|
|
}
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
if ((unsigned long) ptr == ra)
|
|
{
|
|
npc = ra - i;
|
|
break;
|
|
}
|
|
}
|
|
if (npc == 0)
|
|
{
|
|
unsigned char * ptr = (unsigned char *) ra;
|
|
#if WSIZE(32)
|
|
// test __kernel_sigreturn or __kernel_rt_sigreturn
|
|
if ((ra + 7 < tend && getByteInstruction (ptr) == 0x58
|
|
&& getByteInstruction (ptr + 1) == 0xb8
|
|
&& getByteInstruction (ptr + 6) == 0xcd
|
|
&& getByteInstruction (ptr + 7) == 0x80) /* pop %eax; mov $NNNN, %eax; int */
|
|
|| (ra + 7 < tend && getByteInstruction (ptr) == 0x58
|
|
&& getByteInstruction (ptr + 1) == 0xb8
|
|
&& getByteInstruction (ptr + 6) == 0x0f
|
|
&& getByteInstruction (ptr + 7) == 0x05) /* pop %eax; mov $NNNN, %eax; syscall */
|
|
|| (ra + 6 < tend && getByteInstruction (ptr) == 0xb8
|
|
&& getByteInstruction (ptr + 5) == 0xcd
|
|
&& getByteInstruction (ptr + 6) == 0x80) /* mov $NNNN, %eax; int */
|
|
|| (ra + 6 < tend && getByteInstruction (ptr) == 0xb8
|
|
&& getByteInstruction (ptr + 5) == 0x0f
|
|
&& getByteInstruction (ptr + 6) == 0x05)) /* mov $NNNN, %eax; syscall */
|
|
#else //WSIZE(64)
|
|
// test __restore_rt
|
|
if (ra + 8 < tend && getByteInstruction (ptr) == 0x48
|
|
&& getByteInstruction (ptr + 7) == 0x0f
|
|
&& getByteInstruction (ptr + 8) == 0x05) /* mov $NNNNNNNN, %rax; syscall */
|
|
#endif
|
|
{
|
|
npc = ra;
|
|
}
|
|
}
|
|
if (npc == 0 && __collector_java_mode
|
|
&& __collector_java_asyncgetcalltrace_loaded)
|
|
{ // detect jvm interpreter code for java user threads
|
|
unsigned char * ptr = (unsigned char *) ra;
|
|
#if WSIZE(32)
|
|
// up to J170
|
|
/*
|
|
* ff 24 9d e0 64 02 f5 jmp *-0xafd9b20(,%ebx,4)
|
|
* 8b 4e 01 movl 1(%esi),%ecx
|
|
* f7 d1 notl %ecx
|
|
* 8b 5d ec movl -0x14(%ebp),%ebx
|
|
* c1 e1 02 shll $2,%ecx
|
|
* eb d8 jmp .-0x26 [ 0x92a ]
|
|
* 83 ec 08 subl $8,%esp || 8b 65 f8 movl -8(%ebp),%esp
|
|
* */
|
|
if (ra - 20 >= (ra - segoff) && ((*ptr == 0x83 && *(ptr + 1) == 0xec) || (*ptr == 0x8b && *(ptr + 1) == 0x65))
|
|
&& *(ptr - 2) == 0xeb
|
|
&& *(ptr - 5) == 0xc1 && *(ptr - 4) == 0xe1
|
|
&& *(ptr - 8) == 0x8b && *(ptr - 7) == 0x5d
|
|
&& *(ptr - 10) == 0xf7 && *(ptr - 9) == 0xd1
|
|
&& *(ptr - 13) == 0x8b && *(ptr - 12) == 0x4e
|
|
&& *(ptr - 20) == 0xff && *(ptr - 19) == 0x24 && *(ptr - 18) == 0x9d)
|
|
{
|
|
npc = ra - 20;
|
|
}
|
|
// J180 J190
|
|
// ff 24 9d ** ** ** ** jmp *-0x*******(,%ebx,4)
|
|
if (npc == 0
|
|
&& ra - 7 >= (ra - segoff)
|
|
&& *(ptr - 7) == 0xff
|
|
&& *(ptr - 6) == 0x24
|
|
&& *(ptr - 5) == 0x9d)
|
|
{
|
|
npc = ra - 7;
|
|
}
|
|
#else //WSIZE(64)
|
|
// up to J170
|
|
/*
|
|
* 41 ff 24 da jmp *(%r10,%rbx,8)
|
|
* 41 8b 4d 01 movl 1(%r13),%ecx
|
|
* f7 d1 notl %ecx
|
|
* 48 8b 5d d8 movq -0x28(%rbp),%rbx
|
|
* c1 e1 02 shll $2,%ecx
|
|
* eb cc jmp .-0x32 [ 0xd23 ]
|
|
* 48 8b 65 f0 movq -0x10(%rbp),%rsp
|
|
*/
|
|
if (ra - 19 >= (ra - segoff) && *ptr == 0x48 && ((*(ptr + 1) == 0x8b && *(ptr + 2) == 0x65) || (*(ptr + 1) == 0x83 && *(ptr + 2) == 0xec))
|
|
&& *(ptr - 2) == 0xeb
|
|
&& *(ptr - 5) == 0xc1 && *(ptr - 4) == 0xe1
|
|
&& *(ptr - 9) == 0x48 && *(ptr - 8) == 0x8b && *(ptr - 7) == 0x5d
|
|
&& *(ptr - 11) == 0xf7 && *(ptr - 10) == 0xd1
|
|
&& *(ptr - 15) == 0x41 && *(ptr - 14) == 0x8b && *(ptr - 13) == 0x4d
|
|
&& *(ptr - 19) == 0x41 && *(ptr - 18) == 0xff)
|
|
npc = ra - 19;
|
|
// J180 J190
|
|
// 41 ff 24 da jmp *(%r10,%rbx,8)
|
|
if (npc == 0
|
|
&& ra - 4 >= (ra - segoff)
|
|
&& *(ptr - 4) == 0x41
|
|
&& *(ptr - 3) == 0xff
|
|
&& *(ptr - 2) == 0x24
|
|
&& *(ptr - 1) == 0xda)
|
|
npc = ra - 4;
|
|
#endif
|
|
}
|
|
|
|
return npc;
|
|
}
|
|
|
|
/*
|
|
* Parses AVX instruction and returns its length.
|
|
* Returns 0 if parsing failed.
|
|
* https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf
|
|
*/
|
|
static int
|
|
parse_x86_AVX_instruction (unsigned char *pc)
|
|
{
|
|
/*
|
|
* VEX prefix has a two-byte form (0xc5) and a three byte form (0xc4).
|
|
* If an instruction syntax can be encoded using the two-byte form,
|
|
* it can also be encoded using the three byte form of VEX.
|
|
* The latter increases the length of the instruction by one byte.
|
|
* This may be helpful in some situations for code alignment.
|
|
*
|
|
Byte 0 Byte 1 Byte 2 Byte 3
|
|
(Bit Position) 7 0 7 6 5 4 0 7 6 3 2 10
|
|
3-byte VEX [ 11000100 ] [ R X B | m-mmmm ] [ W | vvvv | L | pp ]
|
|
7 0 7 6 3 2 10
|
|
2-byte VEX [ 11000101 ] [ R | vvvv | L | pp ]
|
|
7 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
|
|
4-byte EVEX [ 01100010 ] [ R X B R1 0 0 m m ] [ W v v v v 1 p p ] [ z L1 L B1 V1 a a a ]
|
|
|
|
R: REX.R in 1's complement (inverted) form
|
|
0: Same as REX.R=1 (64-bit mode only)
|
|
1: Same as REX.R=0 (must be 1 in 32-bit mode)
|
|
|
|
X: REX.X in 1's complement (inverted) form
|
|
0: Same as REX.X=1 (64-bit mode only)
|
|
1: Same as REX.X=0 (must be 1 in 32-bit mode)
|
|
|
|
B: REX.B in 1's complement (inverted) form
|
|
0: Same as REX.B=1 (64-bit mode only)
|
|
1: Same as REX.B=0 (Ignored in 32-bit mode).
|
|
|
|
W: opcode specific (use like REX.W, or used for opcode
|
|
extension, or ignored, depending on the opcode byte)
|
|
|
|
m-mmmm:
|
|
00000: Reserved for future use (will #UD)
|
|
00001: implied 0F leading opcode byte
|
|
00010: implied 0F 38 leading opcode bytes
|
|
00011: implied 0F 3A leading opcode bytes
|
|
00100-11111: Reserved for future use (will #UD)
|
|
|
|
vvvv: a register specifier (in 1's complement form) or 1111 if unused.
|
|
|
|
L: Vector Length
|
|
0: scalar or 128-bit vector
|
|
1: 256-bit vector
|
|
|
|
pp: opcode extension providing equivalent functionality of a SIMD prefix
|
|
00: None
|
|
01: 66
|
|
10: F3
|
|
11: F2
|
|
*
|
|
* Example: 0xc5f877L vzeroupper
|
|
* VEX prefix: 0xc5 0x77
|
|
* Opcode: 0xf8
|
|
*
|
|
*/
|
|
int len = 0;
|
|
disassemble_info dis_info;
|
|
dis_info.arch = bfd_arch_i386;
|
|
dis_info.mach = bfd_mach_x86_64;
|
|
dis_info.flavour = bfd_target_unknown_flavour;
|
|
dis_info.endian = BFD_ENDIAN_UNKNOWN;
|
|
dis_info.endian_code = dis_info.endian;
|
|
dis_info.octets_per_byte = 1;
|
|
dis_info.disassembler_needs_relocs = FALSE;
|
|
dis_info.fprintf_func = fprintf_func;
|
|
dis_info.fprintf_styled_func = fprintf_styled_func;
|
|
dis_info.stream = NULL;
|
|
dis_info.disassembler_options = NULL;
|
|
dis_info.read_memory_func = read_memory_func;
|
|
dis_info.memory_error_func = memory_error_func;
|
|
dis_info.print_address_func = print_address_func;
|
|
dis_info.symbol_at_address_func = symbol_at_address_func;
|
|
dis_info.symbol_is_valid = symbol_is_valid;
|
|
dis_info.display_endian = BFD_ENDIAN_UNKNOWN;
|
|
dis_info.symtab = NULL;
|
|
dis_info.symtab_size = 0;
|
|
dis_info.buffer_vma = 0;
|
|
dis_info.buffer = pc;
|
|
dis_info.buffer_length = 8;
|
|
|
|
disassembler_ftype disassemble = print_insn_i386;
|
|
if (disassemble == NULL)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "parse_x86_AVX_instruction ERROR: unsupported disassemble\n");
|
|
return 0;
|
|
}
|
|
len = disassemble (0, &dis_info);
|
|
DprintfT (SP_DUMP_UNWIND, "parse_x86_AVX_instruction: returned %d pc: %p\n", len, pc);
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* In the Intel world, a stack frame looks like this:
|
|
*
|
|
* %fp0->| |
|
|
* |-------------------------------|
|
|
* | Args to next subroutine |
|
|
* |-------------------------------|-\
|
|
* %sp0->| One word struct-ret address | |
|
|
* |-------------------------------| > minimum stack frame (8 bytes)
|
|
* | Previous frame pointer (%fp0)| |
|
|
* %fp1->|-------------------------------|-/
|
|
* | Local variables |
|
|
* %sp1->|-------------------------------|
|
|
*
|
|
*/
|
|
|
|
int
|
|
stack_unwind (char *buf, int size, void *bptr, void *eptr, ucontext_t *context, int mode)
|
|
{
|
|
long *lbuf = (long*) buf;
|
|
int lsize = size / sizeof (long);
|
|
int ind = 0;
|
|
int do_walk = 1;
|
|
int extra_frame = 0;
|
|
if (mode & FRINFO_NO_WALK)
|
|
do_walk = 0;
|
|
if ((mode & 0xffff) == FRINFO_FROM_STACK)
|
|
extra_frame = 1;
|
|
|
|
/*
|
|
* trace the stack frames from user stack.
|
|
* We are assuming that the frame pointer and return address
|
|
* are null when we are at the top level.
|
|
*/
|
|
struct WalkContext wctx;
|
|
wctx.pc = GET_PC (context);
|
|
wctx.sp = GET_SP (context);
|
|
wctx.fp = GET_FP (context);
|
|
wctx.ln = (unsigned long) context->uc_link;
|
|
unsigned long *sbase = (unsigned long*) __collector_tsd_get_by_key (unwind_key);
|
|
if (sbase && *sbase > wctx.sp)
|
|
wctx.sbase = *sbase;
|
|
else
|
|
{
|
|
wctx.sbase = wctx.sp + 0x100000;
|
|
if (wctx.sbase < wctx.sp) /* overflow */
|
|
wctx.sbase = (unsigned long) - 1;
|
|
}
|
|
// We do not know yet if update_map_segments is really needed
|
|
__collector_check_segment (wctx.pc, &wctx.tbgn, &wctx.tend, 0);
|
|
|
|
for (;;)
|
|
{
|
|
if (ind >= lsize || wctx.pc == 0)
|
|
break;
|
|
if (bptr != NULL && extra_frame && wctx.sp <= (unsigned long) bptr && ind < 2)
|
|
{
|
|
lbuf[0] = wctx.pc;
|
|
if (ind == 0)
|
|
{
|
|
ind++;
|
|
if (ind >= lsize)
|
|
break;
|
|
}
|
|
}
|
|
if (bptr == NULL || wctx.sp > (unsigned long) bptr)
|
|
{
|
|
lbuf[ind++] = wctx.pc;
|
|
if (ind >= lsize)
|
|
break;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
if (eptr != NULL && wctx.sp >= (unsigned long) eptr)
|
|
{
|
|
ind = ind >= 2 ? ind - 2 : 0;
|
|
goto exit;
|
|
}
|
|
int ret = find_i386_ret_addr (&wctx, do_walk);
|
|
DprintfT (SP_DUMP_UNWIND, "stack_unwind (x86 walk):%d find_i386_ret_addr returns %d\n", __LINE__, ret);
|
|
if (ret == RA_FAILURE)
|
|
{
|
|
/* lbuf[ind++] = SP_FAILED_UNWIND_MARKER; */
|
|
goto exit;
|
|
}
|
|
|
|
if (ret == RA_END_OF_STACK)
|
|
goto exit;
|
|
#if WSIZE(32)
|
|
if (ret == RA_RT_SIGRETURN)
|
|
{
|
|
struct SigFrame
|
|
{
|
|
unsigned long arg0;
|
|
unsigned long arg1;
|
|
unsigned long arg2;
|
|
} *sframe = (struct SigFrame*) wctx.sp;
|
|
ucontext_t *ncontext = (ucontext_t*) sframe->arg2;
|
|
wctx.pc = GET_PC (ncontext);
|
|
if (!__collector_check_segment (wctx.pc, &wctx.tbgn, &wctx.tend, 0))
|
|
{
|
|
/* lbuf[ind++] = SP_FAILED_UNWIND_MARKER; */
|
|
goto exit;
|
|
}
|
|
unsigned long nsp = GET_SP (ncontext);
|
|
/* Check the new stack pointer */
|
|
if (nsp <= sframe->arg2 || nsp > sframe->arg2 + sizeof (ucontext_t) + 1024)
|
|
{
|
|
/* lbuf[ind++] = SP_FAILED_UNWIND_MARKER; */
|
|
goto exit;
|
|
}
|
|
wctx.sp = nsp;
|
|
wctx.fp = GET_FP (ncontext);
|
|
break;
|
|
}
|
|
else if (ret == RA_SIGRETURN)
|
|
{
|
|
struct sigcontext *sctx = (struct sigcontext*) wctx.sp;
|
|
wctx.pc = sctx->eip;
|
|
if (!__collector_check_segment (wctx.pc, &wctx.tbgn, &wctx.tend, 0))
|
|
{
|
|
/* lbuf[ind++] = SP_FAILED_UNWIND_MARKER; */
|
|
goto exit;
|
|
}
|
|
wctx.sp = sctx->esp;
|
|
wctx.fp = sctx->ebp;
|
|
break;
|
|
}
|
|
#elif WSIZE(64)
|
|
if (ret == RA_RT_SIGRETURN)
|
|
{
|
|
ucontext_t *ncontext = (ucontext_t*) wctx.sp;
|
|
wctx.pc = GET_PC (ncontext);
|
|
if (!__collector_check_segment (wctx.pc, &wctx.tbgn, &wctx.tend, 0))
|
|
{
|
|
/* lbuf[ind++] = SP_FAILED_UNWIND_MARKER; */
|
|
goto exit;
|
|
}
|
|
unsigned long nsp = GET_SP (ncontext);
|
|
/* Check the new stack pointer */
|
|
if (nsp <= wctx.sp || nsp > wctx.sp + sizeof (ucontext_t) + 1024)
|
|
{
|
|
/* lbuf[ind++] = SP_FAILED_UNWIND_MARKER; */
|
|
goto exit;
|
|
}
|
|
wctx.sp = nsp;
|
|
wctx.fp = GET_FP (ncontext);
|
|
break;
|
|
}
|
|
#endif /* WSIZE() */
|
|
if (bptr != NULL && extra_frame && wctx.sp <= (unsigned long) bptr && ind < 2)
|
|
{
|
|
lbuf[0] = wctx.pc;
|
|
if (ind == 0)
|
|
{
|
|
ind++;
|
|
if (ind >= lsize)
|
|
break;
|
|
}
|
|
}
|
|
if (bptr == NULL || wctx.sp > (unsigned long) bptr)
|
|
{
|
|
lbuf[ind++] = wctx.pc;
|
|
if (ind >= lsize)
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
#if defined(DEBUG)
|
|
if ((SP_DUMP_UNWIND & __collector_tracelevel) != 0)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND, "stack_unwind (x86 walk):%d found %d frames\n\n", __LINE__, ind);
|
|
for (int i = 0; i < ind; i++)
|
|
DprintfT (SP_DUMP_UNWIND, " %3d: 0x%lx\n", i, (unsigned long) lbuf[i]);
|
|
}
|
|
#endif
|
|
dump_stack (__LINE__);
|
|
if (ind >= lsize)
|
|
{
|
|
ind = lsize - 1;
|
|
lbuf[ind++] = (unsigned long) SP_TRUNC_STACK_MARKER;
|
|
}
|
|
return ind * sizeof (long);
|
|
}
|
|
|
|
#elif ARCH(Aarch64)
|
|
|
|
static int
|
|
stack_unwind (char *buf, int size, void *bptr, void *eptr, ucontext_t *context, int mode)
|
|
{
|
|
if (buf && bptr && eptr && context && size + mode > 0)
|
|
getByteInstruction ((unsigned char *) eptr);
|
|
int ind = 0;
|
|
__u64 *lbuf = (void *) buf;
|
|
int lsize = size / sizeof (__u64);
|
|
__u64 pc = context->uc_mcontext.pc;
|
|
__u64 sp = context->uc_mcontext.sp;
|
|
__u64 stack_base;
|
|
unsigned long tbgn = 0;
|
|
unsigned long tend = 0;
|
|
|
|
unsigned long *sbase = (unsigned long*) __collector_tsd_get_by_key (unwind_key);
|
|
if (sbase && *sbase > sp)
|
|
stack_base = *sbase;
|
|
else
|
|
{
|
|
stack_base = sp + 0x100000;
|
|
if (stack_base < sp) // overflow
|
|
stack_base = (__u64) -1;
|
|
}
|
|
DprintfT (SP_DUMP_UNWIND,
|
|
"unwind.c:%d stack_unwind %2d pc=0x%llx sp=0x%llx stack_base=0x%llx\n",
|
|
__LINE__, ind, (unsigned long long) pc, (unsigned long long) sp,
|
|
(unsigned long long) stack_base);
|
|
|
|
while (sp && pc)
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND,
|
|
"unwind.c:%d stack_unwind %2d pc=0x%llx sp=0x%llx\n",
|
|
__LINE__, ind, (unsigned long long) pc, (unsigned long long) sp);
|
|
// Dl_info dlinfo;
|
|
// if (!dladdr ((void *) pc, &dlinfo))
|
|
// break;
|
|
// DprintfT (SP_DUMP_UNWIND, "%2d: %llx <%s+%llu> (%s)\n",
|
|
// ind, (unsigned long long) pc,
|
|
// dlinfo.dli_sname ? dlinfo.dli_sname : "(?)",
|
|
// (unsigned long long) pc - (unsigned long long) dlinfo.dli_saddr,
|
|
// dlinfo.dli_fname);
|
|
lbuf[ind++] = pc;
|
|
if (ind >= lsize || sp >= stack_base || (sp & 15) != 0)
|
|
break;
|
|
if (pc < tbgn || pc >= tend)
|
|
if (!__collector_check_segment ((unsigned long) pc, &tbgn, &tend, 0))
|
|
{
|
|
DprintfT (SP_DUMP_UNWIND,
|
|
"unwind.c:%d __collector_check_segment failed. sp=0x%lx\n",
|
|
__LINE__, (unsigned long) sp);
|
|
break;
|
|
}
|
|
pc = ((__u64 *) sp)[1];
|
|
__u64 old_sp = sp;
|
|
sp = ((__u64 *) sp)[0];
|
|
if (sp < old_sp)
|
|
break;
|
|
}
|
|
if (ind >= lsize)
|
|
{
|
|
ind = lsize - 1;
|
|
lbuf[ind++] = (__u64) SP_TRUNC_STACK_MARKER;
|
|
}
|
|
return ind * sizeof (__u64);
|
|
}
|
|
#endif /* ARCH() */
|