forked from 0ad/0ad
janwas
359f55700a
vfs_tree: move bucket allocator to allocators.cpp allocators: add matrix self-test zip: remove redundant timing code and add CHECK_ZFILE everywhere ogl_tex: implement app hook for overriding gl upload caps tex: add CHECK_TEX everywhere TextureManager: only call LoadTextures once (instead of for each file type) debug: add hook for translating error messages; add filtering mechanism to only show interesting debug_printfs (WIP). restructured code: debug_puts is the sysdep part, while debug_printf does filtering and then calls that This was SVN commit r2990.
490 lines
10 KiB
C++
Executable File
490 lines
10 KiB
C++
Executable File
#include "precompiled.h"
|
|
|
|
#include "lib.h"
|
|
#include "timer.h"
|
|
#include "sysdep/sysdep.h"
|
|
#include "debug.h"
|
|
|
|
#include <stdarg.h>
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
|
|
#include "bfd.h"
|
|
#include <cxxabi.h>
|
|
|
|
#ifndef bfd_get_section_size
|
|
#define bfd_get_section_size bfd_get_section_size_before_reloc
|
|
#endif
|
|
|
|
#ifdef OS_LINUX
|
|
#define GNU_SOURCE
|
|
#include <dlfcn.h>
|
|
#include <execinfo.h>
|
|
#endif
|
|
|
|
#ifdef OS_LINUX
|
|
# define DEBUGGER_WAIT 3
|
|
# define DEBUGGER_CMD "gdb"
|
|
# define DEBUGGER_ARG_FORMAT "--pid=%d"
|
|
# define DEBUGGER_BREAK_AFTER_WAIT 0
|
|
#else
|
|
# error "port"
|
|
#endif
|
|
|
|
#define PROFILE_RESOLVE_SYMBOL 0
|
|
|
|
// Hard-coded - yuck :P
|
|
#if defined(TESTING)
|
|
#define EXE_NAME "ps_test"
|
|
#elif defined(NDEBUG)
|
|
#define EXE_NAME "ps"
|
|
#else
|
|
#define EXE_NAME "ps_dbg"
|
|
#endif
|
|
|
|
struct symbol_file_context
|
|
{
|
|
asymbol **syms;
|
|
bfd *abfd;
|
|
};
|
|
symbol_file_context ps_dbg_context;
|
|
bool udbg_initialized=false;
|
|
|
|
struct symbol_lookup_context
|
|
{
|
|
symbol_file_context *file_ctx;
|
|
|
|
bfd_vma address;
|
|
const char* symbol;
|
|
const char* filename;
|
|
uint line;
|
|
|
|
bool found;
|
|
};
|
|
|
|
void unix_debug_break()
|
|
{
|
|
kill(getpid(), SIGTRAP);
|
|
}
|
|
|
|
/*
|
|
Start the debugger and tell it to attach to the current process/thread
|
|
(called by display_error)
|
|
*/
|
|
void udbg_launch_debugger()
|
|
{
|
|
pid_t orgpid=getpid();
|
|
pid_t ret=fork();
|
|
if (ret == 0)
|
|
{
|
|
// Child Process: exec() gdb (Debugger), set to attach to old fork
|
|
char buf[16];
|
|
snprintf(buf, 16, DEBUGGER_ARG_FORMAT, orgpid);
|
|
|
|
int ret=execlp(DEBUGGER_CMD, DEBUGGER_CMD, buf, NULL);
|
|
// In case of success, we should never get here anyway, though...
|
|
if (ret != 0)
|
|
{
|
|
perror("Debugger launch failed");
|
|
}
|
|
}
|
|
else if (ret > 0)
|
|
{
|
|
// Parent (original) fork:
|
|
sleep(DEBUGGER_WAIT);
|
|
}
|
|
else // fork error, ret == -1
|
|
{
|
|
perror("Debugger launch: fork failed");
|
|
}
|
|
}
|
|
|
|
void* debug_get_nth_caller(uint n, void *context)
|
|
{
|
|
// bt[0] == debug_get_nth_caller
|
|
// bt[1] == caller of get_nth_caller
|
|
// bt[2] == 1:st caller (n==1)
|
|
void *bt[n+2];
|
|
int bt_size;
|
|
|
|
bt_size=backtrace(bt, n+2);
|
|
assert(bt_size >= n+2 && "Need at least n+2 frames in get_nth_caller");
|
|
return bt[n+1]; // n==1 => bt[2], and so forth
|
|
}
|
|
|
|
const wchar_t* debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip, void* context)
|
|
{
|
|
++skip; // Skip ourselves too
|
|
|
|
// bt[0..skip] == skipped
|
|
// bt[skip..N_FRAMES+skip] == print
|
|
static const uint N_FRAMES = 16;
|
|
void *bt[skip+N_FRAMES];
|
|
int bt_size;
|
|
wchar_t *bufpos = buf;
|
|
wchar_t *bufend = buf + max_chars;
|
|
|
|
bt_size=backtrace(bt, ARRAY_SIZE(bt));
|
|
// did we get enough backtraced frames?
|
|
assert((bt_size >= skip) && "Need at least <skip> frames in the backtrace");
|
|
|
|
// Assumed max length of a single print-out
|
|
static const uint MAX_OUT_CHARS=1024;
|
|
|
|
for (uint i=skip;i<bt_size && bufpos+MAX_OUT_CHARS < bufend;i++)
|
|
{
|
|
char file[DBG_FILE_LEN];
|
|
char symbol[DBG_SYMBOL_LEN];
|
|
int line;
|
|
uint len;
|
|
|
|
if (debug_resolve_symbol(bt[i], symbol, file, &line) == 0)
|
|
len = swprintf(bufpos, MAX_OUT_CHARS, L"(0x%08x) %hs:%d %hs\n", bt[i], file, line, symbol);
|
|
else
|
|
len = swprintf(bufpos, MAX_OUT_CHARS, L"(0x%08x)\n", bt[i]);
|
|
|
|
if (len < 0)
|
|
{
|
|
// MAX_OUT_CHARS exceeded, realistically this was caused by some
|
|
// mindbogglingly long symbol name... replace the end with an
|
|
// ellipsis and a newline
|
|
memcpy(&bufpos[MAX_OUT_CHARS-6], L"...\n", 5*sizeof(wchar_t));
|
|
len = MAX_OUT_CHARS;
|
|
}
|
|
|
|
bufpos += len;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
static int slurp_symtab(symbol_file_context *ctx)
|
|
{
|
|
bfd *abfd=ctx->abfd;
|
|
asymbol ***syms=&ctx->syms;
|
|
long symcount;
|
|
unsigned int size=0;
|
|
|
|
if ((bfd_get_file_flags (abfd) & HAS_SYMS) == 0)
|
|
{
|
|
printf("slurp_symtab(): Huh? Has no symbols...\n");
|
|
return -1;
|
|
}
|
|
|
|
size = bfd_get_symtab_upper_bound(abfd);
|
|
if (size < 0)
|
|
{
|
|
bfd_perror("symtab_upper_bound");
|
|
return -1;
|
|
}
|
|
*syms = (asymbol **)malloc(size);
|
|
if (!syms)
|
|
return -1;
|
|
symcount = bfd_canonicalize_symtab(abfd, *syms);
|
|
|
|
if (symcount == 0)
|
|
symcount = bfd_read_minisymbols (abfd, FALSE, (void **)syms, &size);
|
|
if (symcount == 0)
|
|
symcount = bfd_read_minisymbols (abfd, TRUE /* dynamic */, (void **)syms, &size);
|
|
|
|
if (symcount < 0)
|
|
{
|
|
bfd_perror("slurp_symtab");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_symbols(const char *file_name, symbol_file_context *ctx)
|
|
{
|
|
char **matching=NULL;
|
|
|
|
ONCE(bfd_init());
|
|
|
|
ctx->abfd = bfd_openr (file_name, NULL);
|
|
if (ctx->abfd == NULL)
|
|
{
|
|
bfd_perror("udbg.cpp: bfd_openr");
|
|
return -1;
|
|
}
|
|
|
|
if (! bfd_check_format_matches (ctx->abfd, bfd_object, &matching))
|
|
{
|
|
if (bfd_get_error () == bfd_error_file_ambiguously_recognized)
|
|
{
|
|
printf("Error reading symbols from %s: ambiguous format\n");
|
|
while (*matching)
|
|
printf("\tPotential matching format: %s\n", *matching++);
|
|
free(matching);
|
|
}
|
|
else
|
|
{
|
|
bfd_perror("bfd_check_format_matches");
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int res=slurp_symtab(ctx);
|
|
if (res == 0)
|
|
return res;
|
|
else
|
|
{
|
|
bfd_perror("udbg.cpp: slurp_symtab");
|
|
bfd_close(ctx->abfd);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void udbg_init(void)
|
|
{
|
|
if (read_symbols(EXE_NAME, &ps_dbg_context)==0)
|
|
udbg_initialized=true;
|
|
|
|
#if PROFILE_RESOLVE_SYMBOL
|
|
{
|
|
TIMER(udbg_init_benchmark)
|
|
char symbol[DBG_SYMBOL_LEN];
|
|
char file[DBG_FILE_LEN];
|
|
int line;
|
|
debug_resolve_symbol(debug_get_nth_caller(3), symbol, file, &line);
|
|
printf("%s (%s:%d)\n", symbol, file, line);
|
|
for (int i=0;i<1000000;i++)
|
|
{
|
|
debug_resolve_symbol(debug_get_nth_caller(1), symbol, file, &line);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void find_address_in_section (bfd *abfd, asection *section, void *data)
|
|
{
|
|
symbol_lookup_context *ctx=(symbol_lookup_context *)data;
|
|
asymbol **syms=ctx->file_ctx->syms;
|
|
|
|
bfd_vma pc=ctx->address;
|
|
bfd_vma vma;
|
|
bfd_size_type size;
|
|
|
|
if (ctx->found) return;
|
|
|
|
if ((bfd_get_section_flags (abfd, section) & SEC_ALLOC) == 0)
|
|
return;
|
|
|
|
vma = bfd_get_section_vma (abfd, section);
|
|
if (pc < vma)
|
|
return;
|
|
|
|
size = bfd_get_section_size (section);
|
|
if (pc >= vma + size)
|
|
return;
|
|
|
|
ctx->found = bfd_find_nearest_line (abfd, section, syms,
|
|
pc - vma, &ctx->filename, &ctx->symbol, &ctx->line);
|
|
}
|
|
|
|
// BFD functions perform allocs with real malloc - we need to free that data
|
|
#include "nommgr.h"
|
|
int demangle_buf(char *buf, const char *symbol, size_t n)
|
|
{
|
|
int status=0;
|
|
char *alloc=NULL;
|
|
if (symbol == NULL || *symbol == '\0')
|
|
{
|
|
symbol = "??";
|
|
status = -1;
|
|
}
|
|
else
|
|
{
|
|
alloc=abi::__cxa_demangle(symbol, NULL, NULL, &status);
|
|
}
|
|
// status is 0 on success and a negative value on failure
|
|
if (status == 0)
|
|
symbol=alloc;
|
|
strncpy(buf, symbol, n);
|
|
buf[n-1]=0;
|
|
if (alloc)
|
|
free(alloc);
|
|
}
|
|
|
|
int debug_resolve_symbol_dladdr(void *ptr, char* sym_name, char* file, int* line)
|
|
{
|
|
Dl_info syminfo;
|
|
|
|
int res=dladdr(ptr, &syminfo);
|
|
if (res == 0) return -1;
|
|
|
|
if (sym_name)
|
|
{
|
|
if (syminfo.dli_sname)
|
|
demangle_buf(sym_name, syminfo.dli_sname, DBG_SYMBOL_LEN);
|
|
else
|
|
{
|
|
snprintf(sym_name, DBG_SYMBOL_LEN, "0x%08x", ptr);
|
|
sym_name[DBG_SYMBOL_LEN-1]=0;
|
|
}
|
|
}
|
|
|
|
if (file)
|
|
{
|
|
strncpy(file, syminfo.dli_fname, DBG_FILE_LEN);
|
|
file[DBG_FILE_LEN-1]=0;
|
|
}
|
|
|
|
if (line)
|
|
{
|
|
*line=0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int* line)
|
|
{
|
|
ONCE(udbg_init());
|
|
|
|
// We use our default context - in the future we could do something like
|
|
// mapping library -> file context to support more detailed reporting on
|
|
// external libraries
|
|
symbol_file_context *file_ctx=&ps_dbg_context;
|
|
bfd *abfd=file_ctx->abfd;
|
|
|
|
// Reset here if we fail later on
|
|
if (sym_name)
|
|
*sym_name=0;
|
|
if (file)
|
|
*file=0;
|
|
if (line)
|
|
*line=0;
|
|
|
|
if (!udbg_initialized)
|
|
return debug_resolve_symbol_dladdr(ptr_of_interest, sym_name, file, line);
|
|
|
|
symbol_lookup_context ctx;
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
ctx.address=(bfd_vma)ptr_of_interest;
|
|
ctx.file_ctx=file_ctx;
|
|
|
|
bfd_map_over_sections (abfd, find_address_in_section, &ctx);
|
|
|
|
// This will happen for addresses in external files. What one *could* do
|
|
// here is to figure out the originating library file and load that through
|
|
// BFD... but how often will external libraries have debugging info? really?
|
|
// At least attempt to find out the symbol name through dladdr.
|
|
if (!ctx.found)
|
|
return debug_resolve_symbol_dladdr(ptr_of_interest, sym_name, file, line);
|
|
|
|
if (sym_name)
|
|
{
|
|
demangle_buf(sym_name, ctx.symbol, DBG_SYMBOL_LEN);
|
|
}
|
|
|
|
if (file)
|
|
{
|
|
if (ctx.filename != NULL)
|
|
{
|
|
const char *h;
|
|
|
|
h = strrchr (ctx.filename, '/');
|
|
if (h != NULL)
|
|
ctx.filename = h + 1;
|
|
}
|
|
|
|
strncpy(file, ctx.filename, DBG_FILE_LEN);
|
|
file[DBG_FILE_LEN]=0;
|
|
}
|
|
|
|
if (line)
|
|
{
|
|
*line = ctx.line;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#include "mmgr.h"
|
|
|
|
void debug_puts(const char* text)
|
|
{
|
|
fputs(text, stdout);
|
|
fflush(stdout);
|
|
}
|
|
|
|
void debug_putws(const wchar_t* text)
|
|
{
|
|
fputws(text, stdout);
|
|
fflush(stdout);
|
|
}
|
|
|
|
|
|
// TODO: Do these properly. (I don't know what I'm doing; I just
|
|
// know that these functions are required in order to compile...)
|
|
|
|
int debug_write_crashlog(const char* file, wchar_t* header, void* context)
|
|
{
|
|
abort();
|
|
}
|
|
|
|
int debug_is_pointer_bogus(const void* p)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void debug_heap_check()
|
|
{
|
|
}
|
|
|
|
// if <full_monty> is true or PARANOIA #defined, all possible checks are
|
|
// performed as often as possible. this is really slow (we are talking x100),
|
|
// but reports errors closer to where they occurred.
|
|
void debug_heap_enable(DebugHeapChecks what)
|
|
{
|
|
// No-op until we find out if glibc has heap debugging
|
|
}
|
|
|
|
void debug_wprintf(const wchar_t* fmt, ...)
|
|
{
|
|
wchar_t buf[512];
|
|
int ret;
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
ret = vswprintf(buf, ARRAY_SIZE(buf), fmt, args);
|
|
va_end(args);
|
|
|
|
if (ret < 0)
|
|
{
|
|
char errbuf[256];
|
|
strerror_r(errno, errbuf, ARRAY_SIZE(errbuf));
|
|
printf("debug_wprintf: %s (%ls)\n", errbuf, fmt);
|
|
fflush(stdout);
|
|
return;
|
|
}
|
|
|
|
// According to fwide(3) and assorted manpage, FILEs are in single character or in
|
|
// wide character mode. When a FILE is in single character mode, wide character writes
|
|
// will fail, and no conversion is done automatically. Thus the manual conversion.
|
|
size_t convbuflen = wcstombs(NULL, buf, 0)+1;
|
|
char* convbuf = (char*)malloc(convbuflen);
|
|
ret = wcstombs(convbuf, buf, convbuflen);
|
|
|
|
if (ret < 0 || ret >= convbuflen) {
|
|
printf("debug_wprintf: wcstombs failed\n");
|
|
free(convbuf);
|
|
fflush(stdout);
|
|
return;
|
|
}
|
|
|
|
fputs(convbuf, stdout);
|
|
free(convbuf);
|
|
fflush(stdout);
|
|
}
|
|
|
|
// disable all automatic checks until the next debug_heap_enable.
|
|
void debug_heap_disable()
|
|
{
|
|
}
|