LLDB plugin for backtraces.

This commit is contained in:
Stas Boukarev 2025-10-02 13:16:07 +03:00
parent ed00211c05
commit 4aa402d6df
2 changed files with 113 additions and 42 deletions

68
contrib/lldb_bt.py Normal file
View file

@ -0,0 +1,68 @@
import lldb
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f lldb_bt.sbcl_backtrace sbt')
print("Custom backtrace command 'sbt' loaded.")
def sbcl_backtrace(debugger, command, result, internal_dict):
thread = debugger.GetSelectedTarget().GetProcess().GetSelectedThread()
debugger.GetCommandInterpreter().HandleCommand("settings set show-progress false", lldb.SBCommandReturnObject())
if thread:
for frame in thread:
result.AppendMessage(format_frame(frame, internal_dict))
def format_frame(frame, internal_dict):
frame_number = frame.GetFrameID()
pc_address = frame.GetPCAddress()
target = lldb.debugger.GetSelectedTarget()
pc_load_addr = pc_address.GetLoadAddress(target)
module_name = frame.module.GetFileSpec() if frame.module else "???"
func = frame.GetFunction()
if func:
func_name = func.GetName()
output = f"frame #{frame_number}: {pc_load_addr:#016x} {func_name} in `{module_name}`"
return output
symbol = frame.GetSymbol()
if symbol.IsValid():
func_name = symbol.GetName()
return f"frame #{frame_number}: {pc_load_addr:#016x} {func_name} in `{module_name}`"
custom_name = None
options = lldb.SBExpressionOptions()
options.SetUnwindOnError(False)
fp = frame.GetFP()
expression = f"""
char buf[1024];
FILE* stream = (FILE*)fmemopen(buf, sizeof(buf), "w");
(void)print_backtrace_frame((char*){pc_load_addr}, (void*){fp}, -1, stream);
(int)fclose(stream);
(char*)strndup(buf, sizeof(buf));
"""
result = target.EvaluateExpression(expression, options)
if result.GetError().Fail():
print(f"ERROR: Call to print_backtrace_frame failed: {result.GetError().GetCString()}")
if result.IsValid() and result.GetValueAsSigned() != 0:
string_address = result.GetValueAsUnsigned()
error = lldb.SBError()
memory_bytes = target.ReadMemory(lldb.SBAddress(string_address, target), 1024, error)
if error.Success():
null_pos = memory_bytes.find(b'\x00')
if null_pos >= 0:
raw_bytes = memory_bytes[:null_pos]
else:
raw_bytes = memory_bytes
custom_name = raw_bytes.decode('utf-8', 'replace')
free_expr = f"(void)free((void*){string_address})"
target.EvaluateExpression(free_expr, options)
if custom_name:
return f"frame #{frame_number}: {pc_load_addr:#016x} {custom_name}"
else:
return f"frame #{frame_number}: {pc_load_addr:#016x} in `{module_name}`"

View file

@ -347,6 +347,51 @@ lisp_backtrace(int nframes)
print_lisp_backtrace(nframes, stdout);
}
void print_backtrace_frame(char *pc, void *fp, int i, FILE *f) {
if (i != -1) {
#ifdef BACKTRACE_SHOW_FRAME_SIZE
// This display is a little confusing. It's the size of the frame that this
// frame will return to.
fprintf(f, "%4d: fp=%p [%5x] pc=%p ", i, fp, (int)(*(char**)fp-(char*)fp), pc);
#else
fprintf(f, "%4d: fp=%p pc=%p ", i, fp, pc);
#endif
}
struct code *code = (void*)component_ptr_from_pc(pc);
if (code) {
lispobj name = debug_function_name_from_pc(code, pc);
if (name)
print_entry_name(barrier_load(&name), f);
else
fprintf(f, "{code_serialno=%x}", code_serialno(code));
} else if (gc_managed_heap_space_p((uword_t)pc)) {
#ifdef LISP_FEATURE_X86
// can't actually have a PC inside a random object, it's got to be a frame
// that didn't set up the pointer chain, quite possibly a signal frame such as:
// 7: fp=0xd78c8460 pc=0xf7fb51b0 Foreign function __kernel_rt_sigreturn
// 8: fp=0xd78c8478 pc=0xd9c43159 (bad PC)
// 9: fp=0xd78c84ec pc=0xd849a17e (FLET SB-C::DO-1-USE :IN SB-C::TENSION-IF-IF-1)
// where, if you print the PC actually from the context, line 8 would be 0xd823ea78.
fprintf(f, "(bad PC)");
#else
// It could be a generic-function with self-contained tramponline code,
// or the executable JMP instruction in an fdefn.
fprintf(f, "(unknown lisp object)");
#endif
} else {
#ifdef LISP_FEATURE_OS_PROVIDES_DLADDR
Dl_info info;
if (dladdr(pc, &info)) {
fprintf(f, "Foreign function %s", info.dli_sname);
} else
#endif
fprintf(f, "Foreign function");
}
putc('\n', f);
}
#if !(defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64))
/* KLUDGE: Sigh ... I know what the call frame looks like and it had
@ -660,48 +705,6 @@ describe_thread_state(void)
printf("Pending handler = %p\n", data->pending_handler);
}
static void print_backtrace_frame(char *pc, void *fp, int i, FILE *f) {
#ifdef BACKTRACE_SHOW_FRAME_SIZE
// This display is a little confusing. It's the size of the frame that this
// frame will return to.
fprintf(f, "%4d: fp=%p [%5x] pc=%p ", i, fp, (int)(*(char**)fp-(char*)fp), pc);
#else
fprintf(f, "%4d: fp=%p pc=%p ", i, fp, pc);
#endif
struct code *code = (void*)component_ptr_from_pc(pc);
if (code) {
lispobj name = debug_function_name_from_pc(code, pc);
if (name)
print_entry_name(barrier_load(&name), f);
else
fprintf(f, "{code_serialno=%x}", code_serialno(code));
} else if (gc_managed_heap_space_p((uword_t)pc)) {
#ifdef LISP_FEATURE_X86
// can't actually have a PC inside a random object, it's got to be a frame
// that didn't set up the pointer chain, quite possibly a signal frame such as:
// 7: fp=0xd78c8460 pc=0xf7fb51b0 Foreign function __kernel_rt_sigreturn
// 8: fp=0xd78c8478 pc=0xd9c43159 (bad PC)
// 9: fp=0xd78c84ec pc=0xd849a17e (FLET SB-C::DO-1-USE :IN SB-C::TENSION-IF-IF-1)
// where, if you print the PC actually from the context, line 8 would be 0xd823ea78.
fprintf(f, "(bad PC)");
#else
// It could be a generic-function with self-contained tramponline code,
// or the executable JMP instruction in an fdefn.
fprintf(f, "(unknown lisp object)");
#endif
} else {
#ifdef LISP_FEATURE_OS_PROVIDES_DLADDR
Dl_info info;
if (dladdr(pc, &info)) {
fprintf(f, "Foreign function %s", info.dli_sname);
} else
#endif
fprintf(f, "Foreign function");
}
putc('\n', f);
}
/* This function has been split from lisp_backtrace() to enable Lisp
* backtraces from gdb with call backtrace_from_fp(...). Useful for
* example when debugging threading deadlocks. (SBCL internals call