/* backtrace.c -- 
 * Created: Sun Feb  9 10:06:01 2003 by faith@dict.org
 * Copyright 2003, 2004 Rickard E. Faith (faith@dict.org)
 *  
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *  
 * $Id: backtrace.c,v 1.20 2004/01/18 01:52:30 faith Exp $
 *
 * Compile with:
 
 gcc -g -O2 -Wall backtrace.c -o backtrace -std=c99 -pedantic -Wshadow \
 -Wpointer-arith -Wcast-align -Wstrict-prototypes -Wmissing-prototypes \
 -Winline -Wmissing-declarations -Wredundant-decls -Wnested-externs -Wundef \
 -Wwrite-strings -Waggregate-return -Wchar-subscripts

 */

/*
  bugs:
  symbols in shared libs need to use the offset
  
 */

#ifdef BT_PRODUCTION
#define BT_DEMO 0
#endif
 
#ifndef BT_DEMO
#define BT_DEMO    1
/* 0 = production; 1 = testing */
#endif

#ifndef BT_STACK
#define BT_STACK   1
/* 1 = print out the call stack */
/* 0 = no debugging information will be read from the file system
       (implies BT_SYMBOLS = BT_LINES = BT_ARGS = 0) */
#endif

#ifndef BT_SYMBOLS
#define BT_SYMBOLS 1
/* 1 = get symbol names from .symtab */
/* 0 = no ELF debugging information will be read from the file system
       (implies BT_LINES = BT_ARGS = 0) */
#endif

#ifndef BT_LINES
#define BT_LINES   1
/* 1 = get file/lines from .debug_line */
/* 0 = no DWARF2 debugging information will be read from the file system
       (implies BT_ARGS = 0) */
#endif

#ifndef BT_ARGS
#define BT_ARGS    1
/* 1 = get types from the DWARF2 .debug_abbrev, .debug_info, and
 *     .debug_str sections; and unwind stack using .debug_frame.  This
 *     nearly doubles the code size, has not been aggressively
 *     debugged, and is expected to be inaccurate * in some cases. */
/* 0 = only provide symbol and file/line information, which is simpler to
 *     decode */
#endif

/* Make the defines consistent. */
#if !BT_STACK
#undef BT_SYMBOLS
#define BT_SYMBOLS 0
#endif
#if !BT_SYMBOLS
#undef BT_LINES
#define BT_LINES   0
#endif
#if !BT_LINES
#undef BT_ARGS
#define BT_ARGS    0
#endif

#define BT_ARGS_DEBUG_PR 0      /* 1 = debug, 2 = dump args */
#define BT_ARGS_DEBUG_RF 0
#define BT_ARGS_DEBUG_RC 0
#define BT_ARGS_DEBUG_RR 0
#define BT_ARGS_DEBUG_FA 0
#define BT_ARGS_DEBUG_RI 0
#define BT_ARGS_DEBUG_SECTION_NAMES 0

#undef   _GNU_SOURCE
#define  _GNU_SOURCE
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifdef XAPPGROUP
#include "scrnintstr.h"
#undef  bt_printf
#define bt_printf ErrorF
#endif

#ifdef BT_PRODUCTION
#include "maaP.h"
#include "backtrace.h"
#define bt_printf maaLogBacktrace
#define bt_abort  maaFatalAbort(__func__, "Terminating after backtrace\n")
#endif

#ifndef bt_printf
#define bt_printf printf
#endif

#ifndef bt_abort
#define bt_abort  abort()
#endif

                                /* Entry points */
#ifndef _BACKTRACE_H_
extern void bt_init(const char *program_name);
extern void bt_backtrace(const char *program_name);
extern void bt_signal(int sig, const char *program_name);
#endif

/* Max number of lines used from /proc//maps */
#define BT_MAX_MAPS      64

/* Max number of ELF sections used from a file.  Note that we only use
 * .symtab, .strtab, .shstrtab, and .debug_*.  We could restrict the
 * number of .debug_* sections read. */
#define BT_MAX_SECTIONS  16

/* Size of buffer used to cache section reads. */
#define BT_BUF_SIZE      128

/* Size of the string pool used for bt_strdup(). */
#define BT_NAME_SPACE    (4 * 1024)

#if BT_LINES
/* Max number of file names cached from .debug_line sections. */
#define BT_MAX_FILES     512
/* Size of the string pool used for file name caching from .debug_line */
#define BT_FILE_SPACE    (4 * 1024)
#endif

#define BT_MAX_REG   20
#define BT_MAX_DEPTH  3

/* Magic structures and numbers from:
 *
 * EXECUTABLE AND LINKABLE FORMAT (ELF): Portable Formats Specification,
 * Version 1.1; Tool Interface Standards (TIS)
 * A text version from: http://www.muppetlabs.com/~breadbox/software/ELF.txt
 *
 * DWARF Debugging Information Format. UNIX International. Programming
 * Languages SIG. Revision: 2.0.0 (July 27, 1993).
 * Nroff/PostScript: http://reality.sgiweb.org/davea/libdwarf2002Nov22.tar.gz 
 *
 * Note that there are numerous GPL and LGPL ELF and DWARF libraries
 * that these routines could be written to use.  However, for this
 * implementation:
 *
 *    a) I read the specifications (not the code) listed above
 *
 *    b) I asked someone else to look at the gdb sources that processed
 *    __restore_rt and that person told me that glibc uses signal
 *    trampolines called __restore and __restore_rt and that these are
 *    assumed to have the following byte sequences, with instruction
 *    boundaries marked with /, and that the top of the stack may start
 *    on any of the instruction boundaries (although I've only ever seen
 *    it start at the first one):
 *
 *       0x58 / 0xb8 0x77 0x00 0x00 0x00 / 0xcd 0x80
 *              0xb8 0xad 0x00 0x00 0x00 / 0xcd 0x80
 *
 *    We could just punt and assume the last frame is the fault EIP, but
 *    checking for these sequences makes these routines robust when not
 *    called from a signal handler.
 *
 *    c) I asked someone how gdb maps the DWARF2 registers to the Linux
 *    registers.  The mapping is as follows:
 *
 *    DWARF2 magic number       Linux symbol (from sys/ucontext.h)
 *    0				REG_EAX
 *    1				REG_ECX
 *    2				REG_EDX
 *    3				REG_EBX
 *    4				REG_ESP
 *    5				REG_EBP
 *    6				REG_ESI
 *    7				REG_EDI
 *    8				REG_EIP 
 *    9				REG_EFL
 *    10			REG_CS
 *    11			REG_SS
 *    12			REG_DS
 *    13			REG_ES
 *    14			REG_FS
 *    15			REG_GS
 *
 *
 * These routines may not be robust in the face of gcc changes, but they
 * fulfill these design goals:
 *
 *    1) works with gcc3 under Linux 2.4.x
 *    2) independent of non-X11/MIT/BSD licensed code or libraries
 *    3) small
 *    4) doesn't use malloc
 *    5) has relatively small stack footprint
 */

/************************************************************************/
/***** Start of ELF symbol table support ********************************/
/************************************************************************/
typedef struct {
    int           fd;
    int           need;         /* Need lseek() */
    unsigned long offset;
    unsigned long start;
    unsigned long length;
    unsigned long pos;
    unsigned char buf[BT_BUF_SIZE];
} bt_buf_t;

typedef struct {
    int           idx;
    int           type;
    const char    *name;
    unsigned long offset;
    unsigned long size;
    unsigned long entry;
    bt_buf_t      buf;
} bt_section_t;

typedef struct {
    unsigned long lower;
    unsigned long upper;
    const char    *name;
    int           seq;          /* Sequence of entries with same name */
    unsigned long reloc;        /* Relocation offset */
    int           this;         /* 1 = this is the executing binary */

    int           open;
    int           fd;
    bt_buf_t      buf;

    int           count;
    bt_section_t  sect[BT_MAX_SECTIONS];
    bt_section_t  *sym, *str, *sh_str;
} bt_map_t;

static const char    *bt_program_name;

#if BT_SYMBOLS

typedef unsigned long  elf_addr;
typedef unsigned short elf_half;
typedef unsigned long  elf_off;
typedef signed long    elf_sword;
typedef unsigned long  elf_word;
typedef unsigned char  elf_byte;

typedef struct {
    elf_word magic;
    elf_byte class;              /* 1 = 32-bit, 2 = 64-bit */
    elf_byte data;               /* 1 = LSB,    2 = MSB */
    elf_byte ver;
    elf_byte reserved0;
    elf_word reserved1;
    elf_word reserved2;
    elf_half type;
    elf_half machine;
    elf_word version;
    elf_addr entry;
    elf_off  phOff;
    elf_off  shOff;
    elf_word flags;
    elf_half ehSize;
    elf_half phEntSize;
    elf_half phNum;
    elf_half shEntSize;
    elf_half shNum;
    elf_half shStrIdx;
} elf_header_t;

typedef struct {
    elf_word name;
    elf_word type;
    elf_word flags;
    elf_addr addr;
    elf_off  offset;
    elf_word size;
    elf_word link;
    elf_word info;
    elf_word align;
    elf_word ent_size;
} elf_section_t;

typedef struct {
    elf_word name;               /* Offset into shStrIdx table */
    elf_addr value;
    elf_word size;
    elf_byte info;
    elf_byte other;
    elf_half shIdx;
} elf_symbol_t;

typedef struct {
    elf_word type;
    elf_off  offset;
    elf_addr vaddr;
    elf_addr paddr;
    elf_word fileSize;
    elf_word memSize;
    elf_word flags;
    elf_word align;
} elf_program_t;

static int           bt_map_count;
static bt_map_t      bt_maps[BT_MAX_MAPS];

static char          bt_name_space[BT_NAME_SPACE];
static char          *bt_name_end;

static char          bt_buf[256]; /* Multi-purpose buffer */

static unsigned long bt_physical; /* Physical bytes read */
static unsigned long bt_block;    /* Physical bytes read as blocks */
static unsigned long bt_logical;  /* Logical bytes read */
#endif
/************************************************************************/
/***** End of ELF symbol table support **********************************/
/************************************************************************/

/************************************************************************/
/***** Start of DWARF2 file/line support ********************************/
/************************************************************************/
#if BT_LINES
static int           bt_file_count;
static char          *bt_files[BT_MAX_FILES];
static char          bt_file_space[BT_FILE_SPACE];
static char          *bt_file_end;
static int           bt_file_max;

typedef unsigned long  uword;
typedef signed long    sword;
typedef unsigned short uhalf;
typedef unsigned char  ubyte;
typedef signed char    sbyte;

typedef struct {                /* Warning, this isn't packed.  Argh. */
    uword    total_length;
    uhalf    version;
    uword    prologue_length;
    ubyte    mil; /* minimum instruction length */
    ubyte    default_is_stmt;
    sbyte    line_base;
    ubyte    line_range;
    ubyte    opcode_base;
} dwarf_header_t;

typedef struct {
    uword    length;
    uhalf    version;
    uword    offset;
    ubyte    bytes;
} dwarf_info_t;

typedef struct {
    unsigned long address;
    int           file;
    int           line;
} dwarf_state_t;

typedef enum {
    DW_LNE_end_sequence = 1,
    DW_LNE_set_address  = 2,
    DW_LNE_define_file  = 3
} dwarf_lne_t;

typedef enum {
    DW_LNS_copy             = 1,
    DW_LNS_advance_pc       = 2,
    DW_LNS_advance_line     = 3,
    DW_LNS_set_file         = 4,
    DW_LNS_set_column       = 5,
    DW_LNS_negate_stmt      = 6,
    DW_LNS_set_basic_block  = 7,
    DW_LNS_const_add_pc     = 8,
    DW_LNS_fixed_advance_pc = 9
} dwarf_lns_t;
#endif
/************************************************************************/
/***** End of DWARF2 file/line support **********************************/
/************************************************************************/

/************************************************************************/
/***** Start of DWARF2 arg/frame support ********************************/
/************************************************************************/
#if BT_STACK
typedef enum {
    bt_unknown,
    bt_reg,
    bt_off
} bt_flag_t;

typedef enum {
    bt_enc_address  = 0x00010000,
    bt_enc_float    = 0x00020000,
    bt_enc_signed   = 0x00040000,
    bt_enc_unsigned = 0x00080000
} bt_enc_t;

typedef struct {
    const char    *name;
    const char    *type;

    bt_enc_t      enc;
    bt_flag_t     flag;
    int           offset;
    unsigned char reg;
} bt_arg_t;

typedef enum {
    bt_undefined = 0,
    bt_offset,
    bt_same_value,
    bt_register
} bt_type_t;

typedef struct {
    bt_type_t   type;
    signed long value;
} bt_reg_state_t;

typedef struct {
    int            invalid;
    int            depth;
    unsigned long  reg, offset;
    unsigned long  loc;
    unsigned long  size;        /* Undocumented gcc 0x2e */
    bt_reg_state_t r[BT_MAX_REG][BT_MAX_DEPTH];
} bt_reg_t;

typedef struct {
    int valid;
    int value;
} bt_regvals_t;

static const int bt_regmap[] = { REG_EAX, REG_ECX, REG_EDX, REG_EBX,
                                 REG_ESP, REG_EBP, REG_ESI, REG_EDI,
                                 REG_EIP, REG_EFL, REG_CS,  REG_SS,
                                 REG_DS,  REG_ES,  REG_FS,  REG_GS };
enum {
    DW_EAX = 0,
    DW_ECX,
    DW_EDX,
    DW_EBX,
    DW_ESP,
    DW_EBP,
    DW_ESI,
    DW_EDI,
    DW_EIP,
    DW_EFL,
    DW_CS,
    DW_SS,
    DW_DS,
    DW_ES,
    DW_FS,
    DW_GS,
    DW_MAX_REG
};
#endif

#if BT_ARGS
typedef struct {
    uword length;
    uword cie_id;
    ubyte version;
    ubyte augmentation[BT_BUF_SIZE];
    uword caf;                  /* code_alignment_factor */
    sword daf;                  /* data_alignment_factor */
    ubyte rar;                  /* return_address_register */
} dwarf_cie_t;

typedef struct {
    uword length;
    uword cie_pointer;
    uword initial_location;
    uword address_range;
} dwarf_fde_t;

typedef enum {
    DW_AT_location   = 0x02,
    DW_AT_name       = 0x03,
    DW_AT_byte_size  = 0x0b,
    DW_AT_bit_offset = 0x0c,
    DW_AT_bit_size   = 0x0d,
    DW_AT_low_pc     = 0x11,
    DW_AT_high_pc    = 0x12,
    DW_AT_encoding   = 0x3e,
    DW_AT_frame_base = 0x40,
    DW_AT_type       = 0x49
} dwarf_at_t;

typedef enum {
    DW_FORM_addr      = 0x01,
    DW_FORM_block2    = 0x03,
    DW_FORM_block4    = 0x04,
    DW_FORM_data2     = 0x05,
    DW_FORM_data4     = 0x06,
    DW_FORM_data8     = 0x07,
    DW_FORM_string    = 0x08,
    DW_FORM_block     = 0x09,
    DW_FORM_block1    = 0x0a,
    DW_FORM_data1     = 0x0b,
    DW_FORM_flag      = 0x0c,
    DW_FORM_sdata     = 0x0d,
    DW_FORM_strp      = 0x0e,
    DW_FORM_udata     = 0x0f,
    DW_FORM_ref_addr  = 0x10,
    DW_FORM_ref1      = 0x11,
    DW_FORM_ref2      = 0x12,
    DW_FORM_ref4      = 0x13,
    DW_FORM_ref8      = 0x14,
    DW_FORM_ref_udata = 0x15,
    DW_FORM_indirect  = 0x16
} dwarf_form_t;

typedef enum {
    DW_TAG_array_type             = 0x01,
    DW_TAG_class_type             = 0x02,
    DW_TAG_entry_point            = 0x03,
    DW_TAG_enumeration_type       = 0x04,
    DW_TAG_formal_parameter       = 0x05,
    DW_TAG_imported_declaration   = 0x08,
    DW_TAG_label                  = 0x0a,
    DW_TAG_lexical_block          = 0x0b,
    DW_TAG_member                 = 0x0d,
    DW_TAG_pointer_type           = 0x0f,
    DW_TAG_reference_type         = 0x10,
    DW_TAG_compile_unit           = 0x11,
    DW_TAG_string_type            = 0x12,
    DW_TAG_structure_type         = 0x13,
    DW_TAG_subroutine_type        = 0x15,
    DW_TAG_typedef                = 0x16,
    DW_TAG_union_type             = 0x17,
    DW_TAG_unspecified_parameters = 0x18,
    DW_TAG_variant                = 0x19,
    DW_TAG_common_block           = 0x1a,
    DW_TAG_common_inclusion       = 0x1b,
    DW_TAG_inheritance            = 0x1c,
    DW_TAG_inlined_subroutine     = 0x1d,
    DW_TAG_module                 = 0x1e,
    DW_TAG_ptr_to_member_type     = 0x1f,
    DW_TAG_set_type               = 0x20,
    DW_TAG_subrange_type          = 0x21,
    DW_TAG_with_stmt              = 0x22,
    DW_TAG_access_declaration     = 0x23,
    DW_TAG_base_type              = 0x24,
    DW_TAG_catch_block            = 0x25,
    DW_TAG_const_type             = 0x26,
    DW_TAG_constant               = 0x27,
    DW_TAG_enumerator             = 0x28,
    DW_TAG_file_type              = 0x29,
    DW_TAG_friend                 = 0x2a,
    DW_TAG_namelist               = 0x2b,
    DW_TAG_namelist_item          = 0x2c,
    DW_TAG_packed_type            = 0x2d,
    DW_TAG_subprogram             = 0x2e,
    DW_TAG_template_type_param    = 0x2f,
    DW_TAG_template_value_param   = 0x30,
    DW_TAG_thrown_type            = 0x31,
    DW_TAG_try_block              = 0x32,
    DW_TAG_variant_part           = 0x33,
    DW_TAG_variable               = 0x34,
    DW_TAG_volatile_type          = 0x35,
    DW_TAG_lo_user                = 0x4080,
    DW_TAG_hi_user                = 0xffff
} dwarf_tag_t;

typedef enum {
    DW_ATE_address       = 0x1,
    DW_ATE_boolean       = 0x2,
    DW_ATE_complex_float = 0x3,
    DW_ATE_float         = 0x4,
    DW_ATE_signed        = 0x5,
    DW_ATE_signed_char   = 0x6,
    DW_ATE_unsigned      = 0x7,
    DW_ATE_unsigned_char = 0x8,
    DW_ATE_lo_user       = 0x80,
    DW_ATE_hi_user       = 0xff
} dwarf_ate_t;

typedef enum {                  /* High 2 bits */
    DW_CFA_advance_loc = (0x01 << 6),
    DW_CFA_offset      = (0x02 << 6),
    DW_CFA_restore     = (0x03 << 6)
} dwarf_cfa_high_t;

typedef enum {
    DW_CFA_set_loc          = 0x01,
    DW_CFA_advance_loc1     = 0x02,
    DW_CFA_advance_loc2     = 0x03,
    DW_CFA_advance_loc4     = 0x04,
    DW_CFA_offset_extended  = 0x05,
    DW_CFA_restore_extended = 0x06,
    DW_CFA_undefined        = 0x07,
    DW_CFA_same_value       = 0x08,
    DW_CFA_register         = 0x09,
    DW_CFA_remember_state   = 0x0a,
    DW_CFA_restore_state    = 0x0b,
    DW_CFA_def_cfa          = 0x0c,
    DW_CFA_def_cfa_register = 0x0d,
    DW_CFA_def_cfa_offset   = 0x0e,
    DW_CFA_nop              = 0x00,
    DW_CFA_lo_user          = 0x1c,
    DW_CFA_hi_user          = 0x3f
} dwarf_cfa_t;


typedef enum {
    DW_OP_reg_mask = 0x50,
    DW_OP_fbreg    = 0x91
} dwarf_op_t;
#endif
/************************************************************************/
/***** Start of DWARF2 arg/frame support ********************************/
/************************************************************************/

/************************************************************************/
/***** Start of ELF stack backtrace functions ***************************/
/************************************************************************/
#if !BT_STACK
#define GETSTACK /* empty */
#else
#define H(X)                                                                \
    do {                                                                    \
        if (!count) {                                                       \
            if (!(frame[X] = __builtin_frame_address(X))                    \
                || !(addr[X] = __builtin_return_address(X))) count = X;     \
        }                                                                   \
    } while (0)

#define GETSTACK                                                            \
    do {                                                                    \
        memset(frame, 0, sizeof(frame));                                    \
        memset(addr, 0, sizeof(addr));                                      \
        H(0);  H(1);  H(2);  H(3);  H(4);  H(5);  H(6);  H(7);              \
        H(8);  H(9);  H(10); H(11); H(12); H(13); H(14); H(15);             \
        H(16); H(17); H(18); H(19); H(20); H(21); H(22); H(23);             \
        H(24); H(25); H(26); H(27); H(28); H(29); H(30); H(31);             \
    } while (0)

static int bt_check(unsigned long target)
{
    unsigned char t0[] = { 0x58, 0xb8, 0x77, 0x00, 0x00, 0x00, 0xcd, 0x80 };
    unsigned char t1[] = {       0xb8, 0xad, 0x00, 0x00, 0x00, 0xcd, 0x80 };
    size_t        i;
    const unsigned char *stack = (unsigned char *)target;

    if (!stack) return 1;
    for (i = 0; i < sizeof(t0); i++) if (stack[i] != t0[i]) goto try_t1;
    return 1;

  try_t1:
    for (i = 0; i < sizeof(t1); i++) if (stack[i] != t1[i]) return 0;
    return 1;
}

#endif
/************************************************************************/
/***** End of ELF stack backtrace functions *****************************/
/************************************************************************/

/************************************************************************/
/***** Start of ELF symbol table functions ******************************/
/************************************************************************/
#if !BT_SYMBOLS
static bt_map_t *bt_find_map(unsigned long addr)
{
    return NULL;
}
static const char *bt_find_symbol(bt_map_t *map, unsigned long addr,
                                  unsigned long *offset)
{
    return NULL;
}
#else
static const char *bt_strdup(const char *s)
{
    int  len;
    char *pt;

    if (!bt_name_end) bt_name_end = bt_name_space;
    
    if (!s) return "??";
    len = strlen(s);
    if ((size_t)(bt_name_end + len + 2 - bt_name_space)
        >= sizeof(bt_name_space)) return "??";
    strcpy(pt = bt_name_end, s);
    bt_name_end[len] = '\0';
    bt_name_end += len + 1;
    return pt;
}

static __inline__ void bt_seek(bt_buf_t *buf, unsigned long pos)
{
    int retval;

    buf->pos  = pos + buf->offset;
    if (buf->start
        && buf->pos >= buf->start
        && buf->pos < buf->start + sizeof(buf->buf) - 16) return;
    lseek(buf->fd, buf->pos, SEEK_SET);
    if ((retval = read(buf->fd, buf->buf, sizeof(buf->buf))) > 0) {
        buf->start   = buf->pos;
        buf->length  = retval;
        bt_block    += retval;
    }
}

static __inline__ unsigned long bt_tell(bt_buf_t *buf)
{
    return buf->pos - buf->offset;
}

static void bt_read(bt_buf_t *buf, void *datum, int size)
{
    int retval;

    if (buf->start
        && buf->pos >= buf->start
        && buf->pos + size <= buf->start + buf->length) {
        memcpy(datum, buf->buf + buf->pos - buf->start, size);
        buf->pos   += size;
        bt_logical += size;
    } else {
        if (buf->need) lseek(buf->fd, buf->pos, SEEK_SET);
        if ((retval = read(buf->fd, datum, size)) > 0) {
            buf->pos    += retval;
            bt_physical += retval;
        }
        if ((retval = read(buf->fd, buf->buf, sizeof(buf->buf))) > 0) {
            buf->start   = buf->pos;
            buf->length  = retval;
            bt_block    += retval;
        }
    }
}

static __inline__ unsigned char bt_read1(bt_buf_t *buf)
{
    unsigned char value = 0;
    bt_read(buf, &value, 1);
    return value;
}

static __inline__ unsigned short bt_read2(bt_buf_t *buf)
{
    unsigned short value = 0;
    bt_read(buf, &value, 2);
    return value;
}

static __inline__ unsigned long bt_read4(bt_buf_t *buf)
{
    unsigned long value = 0;
    bt_read(buf, &value, 4);
    return value;
}

static __inline__ unsigned long bt_read8(bt_buf_t *buf) /* FIXME */
{
    unsigned long value = 0;
    bt_read(buf, &value, 4);
    bt_read(buf, &value, 4);    /* Ignore high values for now */
    return value;
}

static __inline__ unsigned long bt_read128(bt_buf_t *buf)
{
    unsigned long result = 0;
    int           shift  = 0;
    unsigned char next;
    int           i;

    for (i = 0; i < 5; i++) {
        next    = bt_read1(buf);
        result |= (next & 0x7f) << shift;
        shift  += 7;
        if (!(next & 0x80)) break;
    }
    return result;
}

static signed long bt_read128s(bt_buf_t *buf)
{
    signed long   result = 0;
    int           shift  = 0;
    int           sign   = 0;
    unsigned char next;
    int           i;

    for (i = 0; i < 5; i++) {
        next    = bt_read1(buf);
        result |= (next & 0x7f) << shift;
        shift  += 7;
        sign = next & 0x40;
        if (!(next & 0x80)) break;
    }
    /* Sign extend */
    if (shift < 32 && sign) result |= -(1 << shift);
    return result;
}

static signed long bt_string_read128s(const char *s)
{
    signed long   result = 0;
    int           shift  = 0;
    int           sign   = 0;
    unsigned char next;
    int           i;

    for (i = 0; i < 5; i++) {
        next    = *s++;
        result |= (next & 0x7f) << shift;
        shift  += 7;
        sign = next & 0x40;
        if (!(next & 0x80)) break;
    }
    /* Sign extend */
    if (shift < 32 && sign) result |= -(1 << shift);
    return result;
}

static void bt_reads(bt_buf_t *buf, char *d, int len)
{
    int  i;
    char *pt;
    char byte;

    for (pt = d, i = 0; (byte = bt_read1(buf)); i++)
        if (i < len-1) *pt++ = byte;
    *pt = '\0';
}

static int bt_readl(bt_buf_t *buf, char *d, int len)
{
    int  i;
    char *pt;
    char byte;

    for (pt = d, i = 0; (byte = bt_read1(buf)) != '\n' && byte; i++)
        if (i < len-1) *pt++ = byte;
    *pt = '\0';
    return strlen(d);
}

static bt_section_t *bt_add_section(bt_map_t *map, elf_section_t *es, int idx)
{
    bt_section_t *sect;
    if (map->count >= BT_MAX_SECTIONS) return NULL;

    sect             = &map->sect[map->count++];
    
    sect->buf.fd     = map->fd;
    sect->buf.offset = es->offset;
    sect->buf.need   = 1;
    
    sect->idx        = idx;
    sect->type       = es->type;
    sect->offset     = es->offset;
    sect->size       = es->size;
    sect->entry      = es->ent_size;
    return sect;
}

static void bt_read_maps(void)
{
    int         fd;
    char        *pt, *start;
    const char  *a, *b, *p;
    bt_buf_t    buf;

    memset(bt_maps, 0, sizeof(bt_maps));
    if ((fd = open("/proc/self/maps", O_RDONLY)) < 0) return;
    buf.fd     = fd;
    buf.offset = 0;
    buf.start  = 0;
    buf.pos    = 0;
    buf.need   = 0;
    while (bt_readl(&buf, bt_buf, sizeof(bt_buf))) {
        int           state = 0;
        unsigned long l     = 0;
        unsigned long r     = 0;
        for (start = pt = bt_buf; pt && *pt && *pt != '\n'; pt++) {
            switch (state) {
            case 0:
                switch (*pt) {
                case '-': l = strtoul(start, NULL, 16); start = pt + 1; break;
                case ' ': r = strtoul(start, NULL, 16); state = 1;      break;
                }
                break;
            case 1:
                switch(*pt) {
                case '/': start = pt;                   state = 2;      break;
                }
                break;
            }
        }
        if (*pt == '\n') *pt = '\0';
        if (state == 2) {
            if (bt_map_count < BT_MAX_SECTIONS) {
                bt_map_t *map = &bt_maps[bt_map_count];
                map->fd       = -1;
                map->lower    = l;
                map->upper    = r;
                map->name     = bt_strdup(start);

                if (bt_map_count
                    && !strcmp(bt_maps[bt_map_count-1].name, map->name))
                    map->seq = bt_maps[bt_map_count-1].seq +1;

                for (a = p = map->name; p && *p; p++) if (*p == '/') a = p+1;
                for (b = p = bt_program_name; p && *p; p++)
                    if (*p == '/') b = p+1;
                if (!strcmp(a, b)) map->this = 1;
                ++bt_map_count;
            }
        }
    }
    close(fd);
}

static void bt_open_map(bt_map_t *map)
{
    int            i;
    int            seq;
    elf_header_t   eh;
    elf_section_t  es;
    elf_program_t  ep;
    int            str    = 0;
    static char    buf[BT_BUF_SIZE]; /* not on stack */
    union {
        elf_byte c[4];
        elf_word w;
    }           magic = { { '\x7f', 'E', 'L', 'F' } };

    if (map->open) return;
    ++map->open;
    if ((map->fd = open(map->name, O_RDONLY)) < 0) return;
    map->buf.fd     = map->fd;
    map->buf.offset = 0;
    map->buf.need   = 1;

    memset(&eh, 0, sizeof(eh));
    bt_seek(&map->buf, 0);
    bt_read(&map->buf, &eh, sizeof(eh));
    if (eh.magic != magic.w     /* Not ELF magic */
        || eh.class != 1        /* Not 32-bit */
        || eh.data != 1         /* Not little endian */
        || eh.ver != 1          /* Not ELF version 1 */
        || eh.version != 1) {   /* Not ELF version 1 */
        close(map->fd);
        map->fd = -1;
        return;
    }
    
    for (i = 0; i < eh.shNum; i++) {
        bt_seek(&map->buf, eh.shOff + i * eh.shEntSize);
        bt_read(&map->buf, &es, sizeof(es));
        switch (es.type) {
        case 2:                 /* SYMTAB */
            map->sym = bt_add_section(map, &es, i);
            str      = es.link;
            break;
        case 3:                 /* STRTAB */
            if (i && i == eh.shStrIdx)
                map->sh_str = bt_add_section(map, &es, i);
            break;
        }
    }

    if (!map->sym || !map->sh_str) {            /* stripped? */
        close(map->fd);
        map->fd = -1;
        return;
    }

    for (i = 0; i < eh.shNum; i++) {
        bt_seek(&map->buf, eh.shOff + i * eh.shEntSize);
        bt_read(&map->buf, &es, sizeof(es));

        bt_seek(&map->sh_str->buf, es.name);
        bt_reads(&map->sh_str->buf, buf, sizeof(buf));

        if (i == map->sym->idx)         map->sym->name    = bt_strdup(buf);
        else if (i == map->sh_str->idx) map->sh_str->name = bt_strdup(buf);
        else if (i == str) {
            map->str       = bt_add_section(map, &es, i);
            map->str->name = bt_strdup(buf);
        } else if (!strncmp(buf, ".debug_", 7)) {
            bt_section_t *sect = bt_add_section(map, &es, i);
            sect->name         = bt_strdup(buf);
        }
    }

#if BT_ARGS_DEBUG_SECTION_NAMES
                                /* Print section names */
    for (i = 0; i < map->count; i++)
        bt_printf("%d %2d %s\n", i, map->sect[i].idx, map->sect[i].name);
#endif

                                /* Compute the relocation offset */
    for (i = 0, seq = 0; i < eh.phNum; i++) {
        bt_seek(&map->buf, eh.phOff + i * eh.phEntSize);
        bt_read(&map->buf, &ep, sizeof(ep));
        switch (ep.type) {
        case 1:                 /*  LOAD */
            if (seq++ == map->seq)
                map->reloc = map->lower - (ep.vaddr - ep.offset);
            break;
        }
    }
}

static const char *bt_find_symbol(bt_map_t *map, unsigned long addr,
                                  unsigned long *offset)
{
    elf_symbol_t  sym;
    unsigned long pos;
    static char   buf[BT_BUF_SIZE]; /* not on stack */

    *offset = 0;
    if (!map) return NULL;
    if (map->fd < 0) return NULL;

    bt_seek(&map->sym->buf, 0);
    for (pos = 0; pos < map->sym->size; pos += map->sym->entry) {
        bt_read(&map->sym->buf, &sym, sizeof(sym));
        if (sym.name
            && sym.shIdx
            && addr - map->reloc >= sym.value
            && addr - map->reloc < sym.value + sym.size) {
            if (sym.name < map->str->size) {
                memset(buf, 0, sizeof(buf));
                bt_seek(&map->str->buf, sym.name);
                bt_reads(&map->str->buf, buf, sizeof(buf));
                *offset = addr - sym.value - map->reloc;
                return buf;
            }
        }
    }
    return NULL;
}

static bt_map_t *bt_find_map(unsigned long addr)
{
    int      i;
    bt_map_t *map;

    for (i = 0; i < bt_map_count; i++) {
        map = &bt_maps[i];
        if (addr >= map->lower && addr <= map->upper) {
            bt_open_map(map);
            return map;
        }
    }
    return NULL;
}
#endif
/************************************************************************/
/***** End of ELF symbol table functions ********************************/
/************************************************************************/

/************************************************************************/
/***** Start of DWARF2 file/line functions ******************************/
/************************************************************************/
#if !BT_LINES
#if BT_STACK
static const char *bt_find_line(void *map, unsigned long addr, int *line)
{
    if (line) *line = 0;
    return NULL;
}
#endif
#else
static bt_section_t *bt_find_section(bt_map_t *map, const char *name)
{
    int          i;

    if (!map) return NULL;
    for (i = 0; i < map->count; i++) {
        bt_section_t *s = &map->sect[i];
        if (s->name && !strcmp(s->name, name)) return s;
    }
    return NULL;
}

static const char *bt_find_line(bt_map_t *map, unsigned long addr, int *line)
{
    dwarf_header_t dh;
    dwarf_state_t  ds;
    int            i;
    int            state;
    unsigned long  pos;
    bt_section_t   *s;
    

    *line       = 0;
    if (!(s = bt_find_section(map, ".debug_line"))) return NULL;

    bt_seek(&s->buf, 0);
    for (pos =0; pos < s->size;) {
        dh.total_length    = bt_read4(&s->buf);
        dh.version         = bt_read2(&s->buf);
        dh.prologue_length = bt_read4(&s->buf);
        dh.mil             = bt_read1(&s->buf);
        dh.default_is_stmt = bt_read1(&s->buf);
        dh.line_base       = bt_read1(&s->buf);
        dh.line_range      = bt_read1(&s->buf);
        dh.opcode_base     = bt_read1(&s->buf);

        pos += dh.total_length + 4;

        for (i = 1; i < dh.opcode_base; i++) bt_read1(&s->buf);

        for (state = 0; state != 3 && bt_tell(&s->buf) < pos;) {
            ubyte x = bt_read1(&s->buf);
            switch (state) {
            case 0:
                if (!x) state = 3;  /* First byte is a null */
                else    state = 1;  /* Have no 0s */
                break;
            case 1:
                if (!x) state = 2;  /* Have one 0 */
                break;
            case 2:
                if (!x) state = 3;  /* Have two 0s */
                else    state = 1;  /* Have no 0s */
                break;
            }
        }

        if (state != 3) return NULL; /* Punt if we can't parse */
        
        bt_file_count = 0;
        bt_file_end   = bt_file_space;
        for (state = 0; state != 2 && bt_tell(&s->buf) < pos;) {
            ubyte x = bt_read1(&s->buf);
            switch (state) {
            case 0:
                bt_files[bt_file_count++] = bt_file_end;
                *bt_file_end++ = x;
                if (!x) state = 2; /* Finished */
                else    state = 1; /* Entry */
                break;
            case 1:
                *bt_file_end++ = x;
                if (!x) {
                    /* unsigned long dirIdx = */ bt_read128(&s->buf);
                    /* unsigned long t      = */ bt_read128(&s->buf);
                    /* unsigned long length = */ bt_read128(&s->buf);
                    state = 0;
                }
                break;
            }
        }
                                /* Maintain a statistic */
        if (bt_file_end - bt_file_space > bt_file_max)
            bt_file_max = bt_file_end - bt_file_space;

#define LINE(new) do { ds.line = (new); } while (0)
#define FILE(new) do { ds.file = (new); } while (0)
#define ADDRESS(new)                                                          \
            do {                                                              \
                unsigned long n = (new); /* evaluate once */                  \
                if (ds.address && addr - map->reloc >= ds.address             \
                    && addr - map->reloc < n) {                               \
                    *line = ds.line;                                          \
                    return bt_files[ds.file-1];                               \
                }                                                             \
                ds.address = n;                                               \
            } while (0)

        for (state = 0; state >= 0 && bt_tell(&s->buf) < pos;) {
            ubyte         x = bt_read1(&s->buf);

            switch (state) {
            case 0:
                ds.address = 0;
                ds.line    = 1;
                ds.file    = 1;
                state      = 1;
                                /* Fall through */
            case 1:
                switch (x) {
                case 0:         /* Extended */
                    bt_read1(&s->buf); /* length */
                    x   = bt_read1(&s->buf);
                    switch (x) {
                    case DW_LNE_end_sequence:
                        state          = 0;
                        break;
                    case DW_LNE_set_address:
                        ADDRESS(bt_read4(&s->buf));
                        break;
                    case DW_LNE_define_file:
                        state = -1; /* Punt -- gcc doesn't seem to use
                                     * this, and it's painful to
                                     * decode */
                        bt_printf("Found DW_LNE_define_file\n");
                        break;
                    }
                    break;
                case DW_LNS_copy:                                       break;
                case DW_LNS_advance_pc:   ADDRESS(ds.address
                                                  + (bt_read128(&s->buf)
                                                     * dh.mil));
                                                                        break;
                case DW_LNS_advance_line: LINE(ds.line
                                               + bt_read128s(&s->buf)); break;
                case DW_LNS_set_file:     FILE(bt_read128(&s->buf));    break;
                case DW_LNS_set_column:   bt_read128(&s->buf);          break;
                case DW_LNS_negate_stmt:                                break;
                case DW_LNS_set_basic_block:                            break;
                case DW_LNS_const_add_pc: ADDRESS(ds.address
                                                  + ((255 - dh.opcode_base)
                                                     / dh.line_range));
                                                                        break;
                case DW_LNS_fixed_advance_pc: ADDRESS(ds.address
                                                      + bt_read2(&s->buf));
                                                                        break;
                default:
                    x -= dh.opcode_base;
                    ADDRESS(ds.address + x / dh.line_range);
                    LINE(ds.line + dh.line_base + (x % dh.line_range));
                    break;
                }
                break;
            }
        }
    }
    return NULL;
}

#endif
/************************************************************************/
/***** End of DWARF2 file/line functions ********************************/
/************************************************************************/

/************************************************************************/
/***** Start of DWARF2 arg/frame functions ******************************/
/************************************************************************/
#if !BT_ARGS
#if BT_STACK
static int bt_find_args(void *map, unsigned long target,
                        const char *symbol, int maxargs, void *argv)
{
    return 0;
}
static unsigned long bt_restore_registers(unsigned long frame,
                                          bt_reg_t *reg, bt_regvals_t *regvals)
{
    return frame;
}
static int bt_read_frame(bt_map_t *map, bt_reg_t *reg, unsigned long target)
{
    return -1;
}
#endif
#else
static void bt_read_instructions(bt_buf_t *buf, dwarf_cie_t *cie,
                                 unsigned long end, bt_reg_t *reg,
                                 unsigned long target)
{
    unsigned char ins;
    unsigned long value, offset, rgstr;
    unsigned long delta = 0;
    bt_type_t     type;
    int           i;
    
    while (bt_tell(buf) <= end) {
        ins = bt_read1(buf);
#if BT_ARGS_DEBUG_RI
        bt_printf("ins = 0x%02x (%lu <= %lu)\n", ins, bt_tell(buf), end);
#endif
        if ((ins & 0xc0) == DW_CFA_advance_loc) {
            delta = ins & 0x3f;
            goto check_loc;
        }
        if ((ins & 0xc0) == DW_CFA_offset) {
            rgstr  = ins & 0x3f;
            offset = bt_read128(buf);
            goto do_offset;
        }
        if ((ins & 0xc0) == DW_CFA_restore) {
            rgstr = ins & 0x3f;
            goto do_rstr;
        }
        switch (ins) {
        case DW_CFA_set_loc:         reg->loc = bt_read128(buf);break;
        case DW_CFA_advance_loc1:    delta    = bt_read1(buf);  goto check_loc;
        case DW_CFA_advance_loc2:    delta    = bt_read2(buf);  goto check_loc;
        case DW_CFA_advance_loc4:    delta    = bt_read4(buf);  goto check_loc;
        case DW_CFA_offset_extended: rgstr    = bt_read128(buf);
                                     offset   = bt_read128(buf);goto do_offset;
        case DW_CFA_restore_extended:rgstr    = bt_read128(buf);goto do_rstr;
        case DW_CFA_undefined:       type     = bt_undefined;   goto set_type;
        case DW_CFA_same_value:      type     = bt_same_value;  goto set_type;
        case DW_CFA_register:
            rgstr = bt_read128(buf);
            value = bt_read128(buf);
            reg->r[rgstr][reg->depth].type  = bt_register;
            reg->r[rgstr][reg->depth].value = value;
            break;
        case DW_CFA_remember_state:
            if (++reg->depth >= BT_MAX_DEPTH) {
                reg->invalid = 1;
                return;
            }
            for (i = 0; i < BT_MAX_REG; i++) {
                reg->r[i][reg->depth].type  = reg->r[i][reg->depth-1].type;
                reg->r[i][reg->depth].value = reg->r[i][reg->depth-1].value;
            }
            break;
        case DW_CFA_restore_state:
            if (--reg->depth < 1) {
                reg->invalid = 1;
                return;
            }
            break;
        case DW_CFA_def_cfa:
            reg->reg    = bt_read128(buf);
            reg->offset = bt_read128(buf);
#if BT_ARGS_DEBUG_RI
            bt_printf("def_cfa: %ld/%d 0x%08lx\n",
                      reg->reg, bt_regmap[reg->reg], reg->offset);
#endif
            break;
        case DW_CFA_def_cfa_register:
            reg->reg    = bt_read128(buf);
#if BT_ARGS_DEBUG_RI
            bt_printf("def_cfa: %ld/%d 0x%08lx\n",
                      reg->reg, bt_regmap[reg->reg], reg->offset);
#endif
            break;
        case DW_CFA_def_cfa_offset:
            reg->offset = bt_read128(buf);
#if BT_ARGS_DEBUG_RI
            bt_printf("def_cfa: %ld/%d 0x%08lx\n",
                      reg->reg, bt_regmap[reg->reg], reg->offset);
#endif
            break;
        case 0x2e:
            reg->size = bt_read128(buf);
            break;
        case DW_CFA_nop:
        case DW_CFA_lo_user:
        case DW_CFA_hi_user:
        default:
#if BT_ARGS_DEBUG_RI
            bt_printf("? ins = 0x%02x\n", ins);
#endif
            break;
        }
        continue;
      check_loc:
        value   = reg->loc + delta * cie->caf;
        if (target && target >= reg->loc && target < value) return;
#if BT_ARGS_DEBUG_RI
        bt_printf("advance_loc: 0x%08lx -> 0x%08lx (delta = %lu)\n",
                  reg->loc, value, delta);
#endif
        reg->loc = value;
        continue;
      do_offset:
        reg->r[rgstr][reg->depth].type  = bt_offset;
        reg->r[rgstr][reg->depth].value = offset * cie->daf;
#if BT_ARGS_DEBUG_RI
        bt_printf("offset: r%lu/%d at type=%d cfa%+ld\n",
                  rgstr, bt_regmap[rgstr], bt_offset, offset * cie->daf);
#endif
        continue;
      do_rstr:
        reg->r[rgstr][reg->depth].type  = reg->r[rgstr][0].type;
        reg->r[rgstr][reg->depth].value = reg->r[rgstr][0].value;
        continue;
      set_type:
        rgstr                          = bt_read128(buf);
        reg->r[rgstr][reg->depth].type = type;
        continue;
    }
}

static void bt_read_cie(bt_buf_t *buf, unsigned long start,
                        dwarf_cie_t *cie, bt_reg_t *reg,
                        unsigned long initial_location)
{
    unsigned long previous = bt_tell(buf);
    int           i;
    
    bt_seek(buf, start);
    
    memset(reg, 0, sizeof(*reg));
    reg->reg          = DW_MAX_REG;
    reg->loc          = initial_location;
    cie->length       = bt_read4(buf);
    cie->cie_id       = bt_read4(buf);
    cie->version      = bt_read1(buf);
    bt_reads(buf, (char *)cie->augmentation, sizeof(cie->augmentation));
    cie->caf          = bt_read128(buf);
    cie->daf          = bt_read128s(buf);
    cie->rar          = bt_read1(buf);
    bt_read_instructions(buf, cie, start + cie->length + 4, reg, 0);
    for (i = 0; i < BT_MAX_REG; i++) {
        reg->depth         = 1;
        reg->r[i][1].type  = reg->r[i][0].type;
        reg->r[i][1].value = reg->r[i][0].value;
    }
#if BT_ARGS_DEBUG_RC
    bt_printf("RC: caf=%lu daf=%ld rar=%d\n", cie->caf, cie->daf, cie->rar);
#endif
    
    bt_seek(buf, previous);
}

static int bt_read_frame(bt_map_t *map, bt_reg_t *reg, unsigned long target)
{
    bt_section_t    *frame = bt_find_section(map, ".debug_frame");
    unsigned long   pos    = 0;
    dwarf_fde_t     fde;
    dwarf_cie_t     cie;
#if BT_ARGS_DEBUG_RF
    int             i;

    bt_printf("RF: map=%p target=0x%08lx frame=%p\n",
              (void *)map, target, (void *)frame);
#endif
    if (!frame) return -1;
    bt_seek(&frame->buf, 0);
    while (bt_tell(&frame->buf) < frame->size && pos < frame->size) {
        bt_seek(&frame->buf, pos);
        fde.length           = bt_read4(&frame->buf);
        fde.cie_pointer      = bt_read4(&frame->buf);
        pos                 += fde.length + 4;
        if (fde.cie_pointer == 0xffffffff) continue; /* CIE */
        fde.initial_location = bt_read4(&frame->buf);
        fde.address_range    = bt_read4(&frame->buf);

        if (target < fde.initial_location
            || target >= fde.initial_location + fde.address_range) continue;

#if BT_ARGS_DEBUG_RF
        bt_printf("RF: start=0x%08lx t=0x%08lx init=0x%08lx range=0x%08lx\n",
                  fde.cie_pointer, target, fde.initial_location,
                  fde.address_range);
        bt_printf("RF: bt_tell=%lu size=%lu pos=%lu\n",
                  bt_tell(&frame->buf), frame->size, pos);
#endif

        bt_read_cie(&frame->buf, fde.cie_pointer, &cie, reg,
                    fde.initial_location);
        bt_read_instructions(&frame->buf, &cie, pos, reg, target);

#if BT_ARGS_DEBUG_RF
        bt_printf("RF: 0x%08lx 0x%08lx..0x%08lx %d rar=%d\n", target,
                  fde.initial_location,
                  fde.initial_location + fde.address_range,
                  reg->depth, cie.rar);
        for (i = 0; i < BT_MAX_REG; i++) {
            switch (reg->r[i][reg->depth].type) {
            case bt_offset:
                bt_printf("RF: r[%2d] cfa%+ld\n",
                          i, reg->r[i][reg->depth].value);
                break;
            case bt_same_value:
            case bt_register:
                bt_printf("RF: r[%2d]=r[%ld]\n",
                          i, reg->r[i][reg->depth].value);
                break;
            case bt_undefined:
                break;
            }
        }
        break;
#endif
    }
    return cie.rar;
}

static const char *bt_read_form(dwarf_form_t form, bt_buf_t *info,
                                bt_buf_t *str, unsigned long b,
                                unsigned long *value)
{
    static char   buf[BT_BUF_SIZE]; /* not on stack */
    unsigned long offset = 0;
    unsigned long length = 0;
    unsigned long i;
    unsigned char byte;

    *value = 0;
    
    switch (form) {
    case DW_FORM_addr:      *value = bt_read4(info);     return NULL;
    case DW_FORM_block2:    length = bt_read2(info);     goto do_block;
    case DW_FORM_block4:    length = bt_read4(info);     goto do_block;
    case DW_FORM_data2:     *value = bt_read2(info);     return NULL;
    case DW_FORM_data4:     *value = bt_read4(info);     return NULL;
    case DW_FORM_data8:     *value = bt_read8(info);     return NULL;
    case DW_FORM_string:                                 goto do_string;
    case DW_FORM_block:     length = bt_read128(info);   goto do_block;
    case DW_FORM_block1:    length = bt_read1(info);     goto do_block; 
    case DW_FORM_data1:     *value = bt_read1(info);     return NULL;
    case DW_FORM_flag:      *value = bt_read1(info);     return NULL;
    case DW_FORM_sdata:     *value = bt_read128s(info);  return NULL;
    case DW_FORM_strp:      offset = bt_read4(info);     goto do_strp;
    case DW_FORM_udata:     *value = bt_read128(info);   return NULL;
    case DW_FORM_ref_addr:  *value = b+bt_read128(info); return NULL;
    case DW_FORM_ref1:      *value = b+bt_read1(info);   return NULL;
    case DW_FORM_ref2:      *value = b+bt_read2(info);   return NULL;
    case DW_FORM_ref4:      *value = b+bt_read4(info);   return NULL;
    case DW_FORM_ref8:      *value = b+bt_read8(info);   return NULL;
    case DW_FORM_ref_udata: *value = bt_read128(info);   return NULL;
    case DW_FORM_indirect:
        form = bt_read128(info);
        return bt_read_form(form, info, str, b, value);
    }
    return NULL;
  do_block:
    for (i = 0; i < length; i++) {
        byte = bt_read1(info);
        if (i < sizeof(buf)) buf[i] = byte;
    }
    *value = length;
    return buf;
  do_string:
    bt_reads(info, buf, sizeof(buf));
    return buf;
  do_strp:
    bt_seek(str, offset);
    bt_reads(str, buf, sizeof(buf));
    return buf;
}

static int bt_find_abbr(unsigned long code,
                        bt_buf_t *abbr, unsigned long offset)
{
    unsigned long acode;
    dwarf_at_t    a;
    
    bt_seek(abbr, offset);
    while ((acode = bt_read128(abbr))) {
        if (acode == code) return 0;
        do {
            a = bt_read128(abbr);
            bt_read128(abbr); /* dwarf_form_t */
        } while (a);
    }
    return -1;
}

static const char *bt_find_type(bt_buf_t *info, bt_buf_t *abbr,
                                bt_buf_t *str, unsigned long base,
                                unsigned long aoffset, unsigned long offset,
                                bt_enc_t *e)
{
    unsigned long icode;
    dwarf_tag_t   tag;
    dwarf_at_t    a;
    dwarf_form_t  f;
    const char    *string = NULL;
    unsigned long value;
    unsigned long new     = 0;
    dwarf_ate_t   enc     = 0;
    const char    *name   = NULL;
    unsigned long apos    = bt_tell(abbr);
    unsigned long ipos    = bt_tell(info);
    
    bt_seek(info, offset);
    icode = bt_read128(info);
    if (!icode || bt_find_abbr(icode, abbr, aoffset)) {
        bt_seek(abbr, apos);
        bt_seek(info, ipos);
        return NULL;
    }

    tag      = bt_read128(abbr);
    bt_read1(abbr);             /* unsigned long children */

    while ((a = bt_read128(abbr))) {
        f = bt_read128(abbr);
        string = bt_read_form(f, info, str, base, &value);
        switch (a) {
        case DW_AT_name:       name = bt_strdup(string);                break;
        case DW_AT_type:       new = value;                             break;
        case DW_AT_encoding:   enc = value;                             break;
        case DW_AT_byte_size:  *e &= ~0x00f; *e |= (value & 0x0f);      break;
        case DW_AT_bit_size:   *e &= ~0x0f0; *e |= (value & 0x0f) << 4; break;
        case DW_AT_bit_offset: *e &= ~0xf00; *e |= (value & 0x0f) << 8; break;
        default:                                                        break;
        }
    }

    if (new) string = bt_find_type(info, abbr, str, base, aoffset, new, e);
    strncpy(bt_buf, string ? string : "", sizeof(bt_buf));

    bt_seek(abbr, apos);
    bt_seek(info, ipos);

    switch (tag) {
    case DW_TAG_const_type:       strcat(bt_buf, " const");    break;
    case DW_TAG_volatile_type:    strcat(bt_buf, " volatile"); break;
    case DW_TAG_class_type:       strcat(bt_buf, " class");    break;
    case DW_TAG_array_type:
        if (!name && !string) strncpy(bt_buf, "void", sizeof(bt_buf));
        strcat(bt_buf, "[]");
        break;
    case DW_TAG_pointer_type:
        if (!name && !string) strncpy(bt_buf, "void", sizeof(bt_buf));
        strcat(bt_buf, "*");
        break;
    case DW_TAG_reference_type:
        if (!name && !string) strncpy(bt_buf, "void", sizeof(bt_buf));
        strcat(bt_buf, "&");
        break;
    case DW_TAG_subroutine_type:
        if (!name && !string) strncpy(bt_buf, "void", sizeof(bt_buf));
        strcat(bt_buf, "()");
        break;
    case DW_TAG_enumeration_type:
        strcat(bt_buf, "enum");
        if (name) strcat(bt_buf, name);
        break;
    case DW_TAG_structure_type:
        strcat(bt_buf, "struct ");
        if (name) strcat(bt_buf, name);
        break;
    case DW_TAG_union_type:
        strcat(bt_buf, "union ");
        if (name) strcat(bt_buf, name);
        break;
    default:
        switch (enc) {
        case DW_ATE_address:       *e |= bt_enc_address;        return name;
        case DW_ATE_float:         *e |= bt_enc_float;          return name;
        case DW_ATE_signed:
        case DW_ATE_signed_char:   *e |= bt_enc_signed;         return name;
        case DW_ATE_unsigned:
        case DW_ATE_unsigned_char: *e |= bt_enc_unsigned;       return name;
        default:                                                return name;
        }
    }
    return bt_strdup(bt_buf);
}

static int bt_find_args(bt_map_t *map, unsigned long target,
                        const char *symbol, int maxargs, bt_arg_t *argv)
{
    bt_section_t    *info = bt_find_section(map, ".debug_info");
    bt_section_t    *abbr = bt_find_section(map, ".debug_abbrev");
    bt_section_t    *str  = bt_find_section(map, ".debug_str");
    dwarf_info_t    ih;
    unsigned long   pos;
    unsigned long   icode;
#if BT_ARGS_DEBUG_FA
    unsigned long   children;
#endif
    dwarf_tag_t     tag;
    dwarf_at_t      a;
    dwarf_form_t    f;
    const char      *string;
    const char      *name     = NULL;
    unsigned long   value, type;
    int             argc      = 0;
    int             state     = 0;
    int             symmatch  = 0;
    int             lowmatch  = 0;
    int             highmatch = 0;
    long            offset;
    unsigned long   rgstr;
    bt_flag_t       flag;

    if (!info || !abbr || !str) return argc;

    argv[0].name = NULL;
    argv[0].type = NULL;

    for (pos = 0; pos < info->size; pos = pos + ih.length + 4) {
        bt_seek(&info->buf, pos);
        ih.length  = bt_read4(&info->buf);
        ih.version = bt_read2(&info->buf);
        ih.offset  = bt_read4(&info->buf);
        ih.bytes   = bt_read1(&info->buf);

#if BT_ARGS_DEBUG_FA
        bt_printf("Compilation Unit @ 0x%lx 0x%08lx %d 0x%08lx %d\n", pos,
                  ih.length, ih.version, ih.offset, ih.bytes);
#endif

        if (ih.version != 2) continue;

        while (bt_tell(&info->buf) < pos + ih.length) {
            while (!(icode = bt_read128(&info->buf))
                   && bt_tell(&info->buf) < pos + ih.length);
            if (bt_find_abbr(icode, &abbr->buf, ih.offset)) break;

            tag      = bt_read128(&abbr->buf);
#if BT_ARGS_DEBUG_FA
            children = bt_read1(&abbr->buf);
            bt_printf("Code %lu, tag = 0x%02x children = %s\n",
                      icode, tag, children ? "yes" : "no");
#else
            bt_read1(&abbr->buf);
#endif
            if (!state) symmatch = lowmatch = highmatch = 0;
            type   = 0;
            flag   = bt_unknown;
            rgstr  = 0;
            offset = 0;
            while ((a = bt_read128(&abbr->buf))) {
                f = bt_read128(&abbr->buf);
#if BT_ARGS_DEBUG_FA
                bt_printf("  Attribute = 0x%02x; Form = 0x%02x\n", a, f);
#endif
                string = bt_read_form(f, &info->buf, &str->buf, pos, &value);
                switch (a) {
                case DW_AT_frame_base:
                case DW_AT_location:
                    if ((unsigned char)string[0] == DW_OP_fbreg) {
                        flag   = bt_off;
                        offset = bt_string_read128s(string + 1);
                    } else if (((unsigned char)string[0]
                                & DW_OP_reg_mask) == DW_OP_reg_mask) {
                        flag   = bt_reg;
                        rgstr  = string[0] & 0x0f;
                    }
                    break;
                case DW_AT_name:
                    if (!state && !strcmp(string, symbol)) symmatch=1;
                    if (state) name = bt_strdup(string);
                    break;
                case DW_AT_low_pc:
                    if (!state && target >= value)         lowmatch=1;
                    break;
                case DW_AT_high_pc:
                    if (!state && target <= value)         highmatch=1;
                    break;
                case DW_AT_type:
                    type = value;
                    break;
                case DW_AT_encoding:
                case DW_AT_byte_size:
                case DW_AT_bit_size:
                case DW_AT_bit_offset:
                    break;
                }
            }

            switch (state) {
            case 0:
                if (tag == DW_TAG_subprogram
                    && symmatch && lowmatch && highmatch) {
                    state           = 1;
                    argv[0].name    = NULL;
                    argv[0].enc     = 0;
                    argv[0].type    = bt_find_type(&info->buf, &abbr->buf,
                                                   &str->buf, pos, ih.offset,
                                                   type, &argv[0].enc);
                    if (!argv[0].type || !*argv[0].type) argv[0].type = "void";
                                /* Should be the frame base */
                    argv[0].flag    = flag;
                    argv[0].reg     = rgstr;
                    argv[0].offset  = offset;
                    if (!argc) argc = 1;
                }
                break;
            case 1:
                if (tag != DW_TAG_formal_parameter) return argc;
                if (argc >= maxargs - 1)            return argc;
                if (!argc) argc   = 1;
                argv[argc].name   = name;
                argv[argc].enc    = 0;
                 argv[argc].type   = bt_find_type(&info->buf, &abbr->buf,
                                                 &str->buf, pos, ih.offset,
                                                 type, &argv[argc].enc);
                argv[argc].flag   = flag;
                argv[argc].reg    = rgstr;
                argv[argc].offset = offset;
                ++argc;
                break;
            }
            if ((f = bt_read128(&abbr->buf)))
                bt_printf("  Zero attrib found, but form = 0x%02x\n", f);
        }
    }
    return argc;
}

static void bt_restore_registers(unsigned long fp, bt_reg_t *reg,
                                 bt_regvals_t *regvals)
{
    int           i;
    unsigned long cfa = fp + reg->offset;
    int           esp_valid = regvals[DW_ESP].valid;
    int           ebp_valid = regvals[DW_EBP].valid;

    if (regvals[reg->reg].valid && regvals[reg->reg].value > 0x100)
        cfa = regvals[reg->reg].value + reg->offset;
    if (cfa < 0x100) return;

#if BT_ARGS_DEBUG_RR
    bt_printf("RR: using cfa = 0x%08lx (fp=0x%08lx offset=%lu)\n",
              cfa, fp, reg->offset);
#endif

                                /* Fill in other registers */
    for (i = 0; i < BT_MAX_REG; i++) {
        switch (reg->r[i][reg->depth].type) {
        case bt_undefined:
            regvals[i].valid = 0;
            break;
        case bt_offset:
            regvals[i].valid = 1;
#if BT_ARGS_DEBUG_RR
            bt_printf("RR: $r%d <= cfa[%+ld]\n",
                      i, reg->r[i][reg->depth].value);
#endif
            regvals[i].value = *(unsigned long *)(cfa
                                                  + reg->r[i][reg->depth]
                                                  .value);
            break;
        case bt_register:
            regvals[i].valid = 1;
            regvals[i].value = regvals[reg->r[i][reg->depth].value].value;
            break;
        case bt_same_value:
            break;
        }
    }
    if (esp_valid) regvals[DW_ESP].valid = esp_valid;
    if (ebp_valid) regvals[DW_EBP].valid = ebp_valid;
}
#endif
/************************************************************************/
/***** End of DWARF2 arg/frame functions ********************************/
/************************************************************************/

/************************************************************************/
/***** Start of stack backtrace functions *******************************/
/************************************************************************/
#if !BT_STACK
static void bt_print(int count, void *frame[], void *addr[], greg_t *ctx)
{
}
#else

static unsigned long bt_readable_flag;
static unsigned long *bt_readable_addr;
static jmp_buf       bt_readable_env;

static void bt_notreadable(int sig)
{
    bt_readable_flag = 0;
    longjmp(bt_readable_env, 1);
}

static int bt_readable(unsigned long addr)
{
    struct sigaction a, old;
    unsigned long    value;

    if (setjmp(bt_readable_env)) return 0;
    
    a.sa_handler = bt_notreadable;
    sigemptyset(&a.sa_mask);
    sigaddset(&a.sa_mask, SIGSEGV);
    sigaction(SIGSEGV, &a, &old);

    bt_readable_addr = (unsigned long *)addr;
    bt_readable_flag = 1;
    value            = *bt_readable_addr;
    sigaction(SIGSEGV, &old, 0);
    if (bt_readable_flag) return 1;

    /* This code shouldn't ever get executed */
    bt_readable_flag = value & 0xff; /* Guard dereference from optimization */
    return 0;
}

static void _bt_print(int idx, unsigned long target, unsigned long fp,
                      bt_map_t *map, bt_reg_t *reg, bt_regvals_t *regvals,
                      const char *quality)
{
    int             j;
    unsigned long   offset;
    const char      *symbol;
    const char      *in;
    const char      *filename;
    int             line;
    int             argc;
    bt_arg_t        argv[10];
    int             indent = 15;

    if (!map) {
        bt_printf("#%2d 0x%08lx\n", idx, target);
        return;
    }
    if (map->this) in = NULL;
    else           in = " in ";
    
    if (!(symbol = bt_find_symbol(map, target, &offset))) {
        argc   = 0;
        symbol = "??";
    } else {
        argc = bt_find_args(map, target, symbol,
                            sizeof(argv)/sizeof(argv[0]), argv);

#if BT_ARGS_DEBUG_PR > 1
        for (j = 0; j < argc; j++) {
            switch (argv[j].flag) {
            case bt_off:
                bt_printf("argv[%d] = %+d %s\n",
                          j, argv[j].offset, argv[j].name);
                break;
            case bt_reg:
                bt_printf("argv[%d] = $%d %s\n", j, argv[j].reg, argv[j].name);
                break;
            default:
                break;
            }
        }
        for (j = 0; j < 10; j++) {
            bt_printf("$%d = 0x%08lx %s\n", j, (unsigned long)regvals[j].value,
                      regvals[j].valid ? "ok" : "");
        }
        if (fp > 0x100) for (j = -8; j <= 40; j+= 4) {
            bt_printf("fp%+d = 0x%08lx\n", j, *(unsigned long *)(fp + j));
        }
#endif
    }

#ifdef XAPPGROUP
    bt_printf("#%2d 0x%08lx ", idx, target);
    if (argc && argv[0].type) bt_printf("%s ", argv[0].type);
    bt_printf("%s", symbol);
    if (argc > 1) {
        bt_printf("(");
#else
    indent = bt_printf("#%2d 0x%08lx ", idx, target);
    if (argc && argv[0].type) indent += bt_printf("%s ", argv[0].type);
    indent += bt_printf("%s", symbol);
    if (argc > 1) {
        indent += bt_printf("(");
#endif
        for (j = 1; j < argc; j++) {
            union {
                void               *addr;
                unsigned long long ull;
                signed long long   sll;
                unsigned long      ul;
                signed long        sl;
                unsigned short     us;
                signed short       ss;
                unsigned char      ub;
                signed char        sb;
                float              f;
                double             d;
            } value;
            int valid    = 0;
            int origsize = argv[j].enc & 0x00f;
            value.ull = 0;

            if (j > 1) bt_printf(",\n%*.*s", indent, indent, "");
            bt_printf("%s %s", argv[j].type, argv[j].name);
            
            if (strchr(argv[j].type, '*')) {
                argv[j].enc &= ~0x00f;
                argv[j].enc |= sizeof(void *);
            }
            
            switch (argv[j].flag) {
            case bt_off:
                if (fp) {
                    unsigned long addr = fp + argv[j].offset;
                    if (bt_readable(addr)) {
                        valid = 1;
                        switch (argv[j].enc & 0x00f) {
                        case 1:  value.ub  = *(unsigned char *)addr;     break;
                        case 2:  value.us  = *(unsigned short *)addr;    break;
                        case 8:  value.ull = *(unsigned long long *)addr;break;
                        default: value.ul  = *(unsigned long *)addr;     break;
                        }
                    }
                }
                break;
            case bt_reg:
                if (regvals[argv[j].reg].valid) {
                    int r = argv[j].reg;
                    valid = 1;
                    bt_printf("=$%d", argv[j].reg);
                    switch (argv[j].enc & 0x00f) {
                    case 1:  value.ub  = regvals[r].value & 0x00ff;   break;
                    case 2:  value.us  = regvals[r].value & 0xffff;   break;
                    case 8:  valid     = 0;                           break; 
                    default: value.ul  = regvals[r].value;            break;
                    }
                }
                break;
            case bt_unknown:
                break;
            }
            if (!valid) continue;
            if (strchr(argv[j].type, '*')) {
                const char *sym = bt_find_symbol(map, value.ul, &offset);
                bt_printf("=0x%08lx%s%s%s", value.ul,
                          sym ? " <" : "",
                          sym ? sym : "",
                          sym ? ">" : "");
                if (origsize == 1
                    && strchr(argv[j].type,'*') == strrchr(argv[j].type,'*')) {
                    const char *pt = (const char *)value.ul;
                    int        i;
                    if (bt_readable(value.ul)) {
                        bt_printf(" \"");
                        for (i = 0; i < 20 && pt && *pt; i++, ++pt) {
                            if (*pt >= 32 && *pt < 127) bt_printf("%c", *pt);
                            else                        bt_printf(".");
                        }
                        if (pt && *pt) bt_printf("...");
                        bt_printf("\"");
                    } else {
                        bt_printf(" ");
                    }
                        
                }
            } else if (argv[j].enc & bt_enc_float) {
                if ((argv[j].enc & 0x00f) == 8) bt_printf("=%f", value.d);
                else                            bt_printf("=%f", value.f);
            } else if (argv[j].enc & bt_enc_signed) {
                switch (argv[j].enc & 0x00f) {
                case 1:  bt_printf("=%d", value.sb);    break;
                case 2:  bt_printf("=%d", value.ss);    break;
                case 8:  bt_printf("=%lld", value.sll); break;
                default: bt_printf("=%ld", value.sl);   break;
                }
            } else {
                switch (argv[j].enc & 0x00f) {
                case 1:  bt_printf("=%u", value.ub);    break;
                case 2:  bt_printf("=%u", value.us);    break;
                case 8:  bt_printf("=%llu", value.ull); break;
                default: bt_printf("=%lu", value.ul);   break;
                }
            }
            if ((argv[j].enc & 0x00f) == 1
                && value.ul >= 32 && value.ul < 127) {
                bt_printf(" '%c'", (char)value.ub);
            }
        }
        bt_printf(")");
    } else if (offset) bt_printf("+0x%lx", offset);
    if (!(filename = bt_find_line(map, target, &line)))
        bt_printf("%s%s [%s]\n", in ? in : "", in ? map->name : "", quality);
    else
        bt_printf(" [%s] at %s:%d\n", quality, filename, line);
}

static void bt_print(int count, void *frame[], void *addr[], greg_t *ctx)
{
    int             i;
    bt_map_t        *map;
    static bt_reg_t reg;        /* not on stack; zero'd in bt_read_cie */
    bt_regvals_t    regvals[BT_MAX_REG];
    int             rar  = -1;
    unsigned long   ra   = 0;
    unsigned long   fp   = 0;
    int             idx  = 0;
    char            q[3] = {0, 0, 0}; /* quality indicator */
    long            fpoffset;

    bt_printf("Printing stack backtrace for %s (please wait)\n",
              bt_program_name);
    bt_printf("(t = trampoline, i = ip, r = ra; f = fp, ? = frame)\n");

                                /* set up first frame of registers */
    memset(®vals, 0, sizeof(regvals));
    
    if (ctx) {
        int mapped = sizeof(bt_regmap)/sizeof(bt_regmap[0]);
        if (mapped > BT_MAX_REG) mapped = BT_MAX_REG;
        for (i = 0; i < mapped; i++) {
            regvals[i].valid = 1;
            regvals[i].value = ctx[bt_regmap[i]];
        }
    }

    for (i = 0; i < count; i++) {
                                /* At this point, we have an RA and
                                 * registers that are current. */
        if (!ra) {
            unsigned long eip = (regvals[DW_EIP].valid
                                 ? regvals[DW_EIP].value
                                 : 0);
            ra = eip;
            q[0] = bt_check(ra) ? 't' : 'i';
        } else {
            q[0] = 'r';
        }
        if (!ra) return;        /* Cannot continue if no RA */

        map      = bt_find_map(ra);
        rar      = bt_read_frame(map, ®, ra);
        fpoffset = 0;

        if (reg.reg < DW_MAX_REG && regvals[reg.reg].value
            && regvals[reg.reg].valid) {
            fp   = regvals[reg.reg].value;
            if (reg.reg == DW_ESP) {
                fpoffset = reg.size;
                bt_printf("sz=%lu offset=%lu fpoffset=%ld\n",
                          reg.size, reg.offset, fpoffset);
            }
            q[1] = 'f';
        } else {
            fp   = (unsigned long)frame[i+1];
            q[1] = '?';
        }

        if (i && !fp) return;   /* Probably the end of the stack */

#if BT_ARGS_DEBUG_PR
        bt_printf("\nPR: map=0x%08lx ra=0x%08lx sz=%ld fp=0x%08lx+%ld"
                  " frame[%d]=%p frame[%d]=%p r%lu=0x%08lx %d +%lu rar=%d\n",
                  (unsigned long)map, ra, reg.size, fp, fpoffset,
                  i+1, frame[i+1], i, frame[i],
                  reg.reg,
                  reg.reg < DW_MAX_REG
                  ? (unsigned long)regvals[reg.reg].value
                  : 0,
                  reg.reg < DW_MAX_REG ? regvals[reg.reg].valid : 0,
                  reg.offset, rar);
#endif
        _bt_print(idx++, ra, fp + fpoffset, map, ®, regvals, q);

                                /* Unwind to next frame */
        bt_restore_registers(fp, ®, regvals);
        if (rar > -1 && rar <= DW_MAX_REG && regvals[rar].valid)
            ra = regvals[rar].value;
        else
            ra = 0;
    }
}
#endif
/************************************************************************/
/***** End of stack backtrace functions *********************************/
/************************************************************************/


/************************************************************************/
/***** Start of generic functions ***************************************/
/************************************************************************/
static const char *bt_signal_name(int sig)
{
     switch (sig) {
     case SIGHUP:    return "SIGHUP";
     case SIGINT:    return "SIGINT";
     case SIGQUIT:   return "SIGQUIT";
     case SIGILL:    return "SIGILL";
     case SIGTRAP:   return "SIGTRAP";
     case SIGABRT:   return "SIGABRT";
     case SIGBUS:    return "SIGBUS";
     case SIGFPE:    return "SIGFPE";
     case SIGKILL:   return "SIGKILL";
     case SIGUSR1:   return "SIGUSR1";
     case SIGSEGV:   return "SIGSEGV";
     case SIGUSR2:   return "SIGUSR2";
     case SIGPIPE:   return "SIGPIPE";
     case SIGALRM:   return "SIGALRM";
     case SIGTERM:   return "SIGTERM";
     case SIGSTKFLT: return "SIGSTKFLT";
     case SIGCHLD:   return "SIGCHLD";
     case SIGCONT:   return "SIGCONT";
     case SIGSTOP:   return "SIGSTOP";
     case SIGTSTP:   return "SIGTSTP";
     case SIGTTIN:   return "SIGTTIN";
     case SIGTTOU:   return "SIGTTOU";
     case SIGURG:    return "SIGURG";
     case SIGXCPU:   return "SIGXCPU";
     case SIGXFSZ:   return "SIGXFSZ";
     case SIGVTALRM: return "SIGVTALRM";
     case SIGPROF:   return "SIGPROF";
     case SIGWINCH:  return "SIGWINCH";
     case SIGIO:     return "SIGIO";
     case SIGPWR:    return "SIGPWR";
     case SIGSYS:    return "SIGSYS";
     }
     return NULL;
}

static const char *bt_signal_description(siginfo_t *info)
{
    static char buf[64];
    if (!info) return "";
    
    switch (info->si_code) {
    case SI_KERNEL:     return "kernel";
    case SI_QUEUE:      return "sigqueue";
    case SI_TIMER:      return "timer expired";
    case SI_MESGQ:      return "real time mesq state changed";
    case SI_ASYNCIO:    return "AIO completed";
    case SI_SIGIO:      return "queued SIGIO";
    }
    
    switch (info->si_signo) {
    case SIGILL:
        switch (info->si_code) {
        case ILL_ILLOPC:    return "illegal opcode";
        case ILL_ILLOPN:    return "illegal operand";
        case ILL_ILLADR:    return "illegal addressing mode";
        case ILL_ILLTRP:    return "illegal trap";
        case ILL_PRVOPC:    return "privileged opcode";
        case ILL_PRVREG:    return "privileged register";
        case ILL_COPROC:    return "coprocessor error";
        case ILL_BADSTK:    return "internal stack error";
        }
        break;
    case SIGFPE:
        switch (info->si_code) {
        case FPE_INTDIV:    return "integer divide by zero";
        case FPE_INTOVF:    return "integer overflow";
        case FPE_FLTDIV:    return "floating point divide by zero";
        case FPE_FLTOVF:    return "floating point overflow";
        case FPE_FLTUND:    return "floating point underflow";
        case FPE_FLTRES:    return "floating point inexact result";
        case FPE_FLTINV:    return "floating point invalid operation";
        case FPE_FLTSUB:    return "subscript out of range";
        }
        break;
    case SIGSEGV:
        switch (info->si_code) {
        case SEGV_MAPERR:   return "address not mapped to object";
        case SEGV_ACCERR:   return "invalid perm. for mapped object";
        }
        break;
    case SIGBUS:
        switch (info->si_code) {
        case BUS_ADRALN:    return "invalid address alignment";
        case BUS_ADRERR:    return "non-existent physical address";
        case BUS_OBJERR:    return "object specific hardware error";
        }
        break;
    case SIGTRAP:
        switch (info->si_code) {
        case TRAP_BRKPT:    return "process breakpoint";
        case TRAP_TRACE:    return "process trace trap";
        }
        break;
    case SIGCHLD:
        switch (info->si_code) {
        case CLD_EXITED:    return "child has exited";
        case CLD_KILLED:    return "child was killed";
        case CLD_DUMPED:    return "child terminated abnormally";
        case CLD_TRAPPED:   return "traced child has trapped";
        case CLD_STOPPED:   return "child has stopped";
        case CLD_CONTINUED: return "stopped child has continued";
        }
        break;
    case SIGPOLL:
        switch (info->si_code) {
        case POLL_IN:       return "data input available";
        case POLL_OUT:      return "output buffers available";
        case POLL_MSG:      return "input message available";
        case POLL_ERR:      return "I/O error";
        case POLL_PRI:      return "high priority input available";
        case POLL_HUP:      return "device disconnected";
        }
        break;
    }
    if (info->si_code) {
        sprintf(buf, "SI%d", info->si_code);
        return buf;
    }
    return NULL;
}

void bt_init(const char *program_name)
{
    static int initialized = 0;

    if (initialized++) return;
#if !BT_SYMBOLS
    bt_program_name = program_name;
#else
    if (program_name) bt_program_name = bt_strdup(program_name);
    bt_read_maps();
#endif
}

void bt_backtrace(const char *program_name)
{
#if BT_STACK
    int         count = 0;
    void        *addr[32];
    void        *frame[32];
    ucontext_t  uc = { 0, };

    GETSTACK;

    bt_init(program_name);
    if (!getcontext(&uc)) {
        bt_print(count, frame, addr, uc.uc_mcontext.gregs);
    } else {
        bt_printf("getcontext() failed\n");
    }
#endif
}

static void bt_handler(int sig, siginfo_t *info, void *context)
{
    int             count       = 0;
    void            *addr[32];
    void            *frame[32];
    int             fault_valid = 0;
    ucontext_t      *uc         = context;
    static int      reentrant   = 0;
    const char      *name, *desc, *msg;
    unsigned long   fault       = 0;

    if (reentrant++) {
        bt_printf("%s called twice\n", __func__);
        bt_abort;
    }

    GETSTACK;
    bt_init(NULL);

    switch (sig) {
    case SIGILL:
    case SIGFPE:
    case SIGSEGV:
    case SIGBUS:
        if (info) {
            fault_valid = 1;
            fault       = (unsigned long)info->si_addr;
        }
        break;
    }

    name = bt_signal_name(sig);
    desc = bt_signal_description(info);
    msg  = info ? strerror(info->si_errno) : "";

    if (info && info->si_errno) {
        bt_printf("Signal %d%s%s%s%s%s, errno = %d%s%s%s\n", sig,
                  name ? " (" : "", name ? name : "", name ? ")" : "",
                  desc ? ": " : "", desc ? desc : "",
                  info->si_errno,
                  msg ? " (" : "", msg ? msg : "", msg ? ")" : "");
    } else {
        bt_printf("Signal %d%s%s%s%s%s\n", sig,
                  name ? " (" : "", name ? name : "", name ? ")" : "",
                  desc ? ": " : "", desc ? desc : "");
    }

    if (fault_valid) {
        unsigned long offset;
        bt_map_t      *map = bt_find_map(fault);
        const char    *symbol = bt_find_symbol(map, fault, &offset);
        if (symbol)
            bt_printf("Fault: 0x%08lx (%s+0x%lx)\n", fault, symbol, offset);
        else
            bt_printf("Fault: 0x%08lx\n", fault);
    }

    bt_print(count, frame, addr, uc ? uc->uc_mcontext.gregs : 0);

#if BT_LINES
    bt_printf("Terminating (%d/%d file; %d/%d name; %luk %luk+%luk=%luk)\n",
              bt_file_max, BT_FILE_SPACE,
              (int)(bt_name_end - bt_name_space), BT_NAME_SPACE,
              bt_block/1024, bt_physical/1024, bt_logical/1024,
              (bt_physical+bt_logical)/1024);
#elif BT_SYMBOLS
    bt_printf("Terminating (%d/%d name; %lu %lu+%lu=%lu)\n",
              (int)(bt_name_end - bt_name_space), BT_NAME_SPACE,
              bt_block/1024, bt_physical/1024, bt_logical/1024,
              (bt_physical+bt_logical)/1024);
#else
    bt_printf("Terminating\n");
#endif
    bt_abort;
}

void bt_signal(int sig, const char *program_name)
{
    int              def[] = { SIGHUP, SIGQUIT, SIGILL,
                               SIGBUS, SIGFPE, SIGSEGV, SIGTERM };
    size_t           i;
    struct sigaction a;

    if (!sig) {
        for (i = 0; i < sizeof(def)/sizeof(def[0]); i++)
            bt_signal(def[i], program_name);
        return;
    }

    bt_init(program_name);
    memset(&a, 0, sizeof(a));
    a.sa_sigaction = bt_handler;
    a.sa_flags     = SA_SIGINFO;
    sigemptyset(&a.sa_mask);
    sigaddset(&a.sa_mask, sig);
    sigaction(sig, &a, 0);
}
/************************************************************************/
/***** End of generic functions *****************************************/
/************************************************************************/

/************************************************************************/
/***** Start of demo functions ******************************************/
/************************************************************************/
#if BT_DEMO
#define BT_TEST_ARGS 1

struct x {
    int y;
};

typedef int (*xyzzy)(void);

static int ffff(void)
{
    return 0;
}

static int *myf(unsigned int f, float d, char c
#if BT_TEST_ARGS
                ,struct x ys, xyzzy ff, const char *foo
#endif
    )
{
    int x = 0;
#if BT_TEST_ARGS
    bt_printf("foo = %p, d = %f\n", foo, d);
#endif
    int *y;
    *y = 1/x;
    return y;
}

int main(int argc, char **argv)
{
    struct x ys;

    ys.y = 42;
    
    bt_init(argv[0]);
    bt_printf("argv = 0x%08lx, ffff = 0x%08lx = %lu\n",
              (unsigned long)argv, (unsigned long)ffff,
              (unsigned long)ffff);
    bt_backtrace(argv[0]);
    bt_signal(0, argv[0]);
#if 1
    myf(7, 4.0, -8
#if BT_TEST_ARGS
        , ys, ffff, 0 /* "string" */
#endif
        );
#endif
    strcpy(0, 0);
    return 0;
}

#endif
/************************************************************************/
/***** End of demo functions ********************************************/
/************************************************************************/