Start debugserver:
tty0 # debugserver localhost:8000 main
Start tracing:
tty1 $ lldb
tty1 (lldb) command script import trace.py
| # Public domain. | |
| # (lldb) command script import trace.py | |
| from __future__ import print_function | |
| import lldb | |
| import sys | |
| TARGET = None | |
| END_ADDR = None | |
| INVALID_ADDR = 0xffffffffffffffff | |
| SHOULD_STOP = True | |
| FILE_LOG = None | |
| def log(s): | |
| global FILE_LOG | |
| sys.stdout.write(s) | |
| sys.stdout.flush() | |
| FILE_LOG.write(s) | |
| FILE_LOG.flush() | |
| def run(cmd): | |
| res = lldb.SBCommandReturnObject() | |
| lldb.debugger.GetCommandInterpreter().HandleCommand(cmd, res) | |
| return res.GetOutput() | |
| def function_address(fname): | |
| global TARGET | |
| # XXX: Multiple functions are not supported. | |
| funcs = list(TARGET.FindFunctions(fname)) | |
| for i in range(len(funcs)): | |
| # XXX: Is this a good way to filter out inlined functions? | |
| if funcs[i].GetSymbol().GetType() != 2: | |
| del funcs[i] | |
| assert len(funcs) == 1 | |
| return funcs[0].GetFunction().GetStartAddress() | |
| def full_name(obj): | |
| return '{}.{}'.format(__name__, obj.__name__) | |
| def log_trace(): | |
| global TARGET | |
| process = TARGET.GetProcess() | |
| thread = process.GetSelectedThread() | |
| frame = thread.GetSelectedFrame() | |
| pc = frame.GetPCAddress() | |
| pc_load_addr = pc.GetLoadAddress(TARGET) | |
| func_name = pc.GetFunction().name | |
| line_num = pc.GetLineEntry().line | |
| insns = TARGET.ReadInstructions(pc, 1, 'intel') | |
| insn = insns.GetInstructionAtIndex(0) | |
| mnemonic = insn.mnemonic | |
| operands = insn.operands | |
| if func_name: | |
| file_str = '{}; {}:{}'.format(' ' * 2, func_name, line_num) | |
| else: | |
| file_str = '' | |
| log('0x{:016x}: {} {}{}\n' | |
| .format(pc_load_addr, mnemonic, operands, file_str)) | |
| def trace(frame, _bp_loc, _dict): | |
| # Don't forget to trace the current instruction. | |
| log_trace() | |
| # Continue tracing. | |
| # XXX: This seems to be the only way to start stepping from the callback. | |
| run('thread step-scripted -C {}'.format(full_name(Trace))) | |
| # XXX: Doesn't seem to matter with 'thread step-scripted'. | |
| # return False # continue | |
| return True # stop | |
| class Trace: | |
| def __init__(self, thread_plan, _dict): | |
| global END_ADDR | |
| global INVALID_ADDR | |
| assert END_ADDR is not None | |
| assert END_ADDR != INVALID_ADDR | |
| self.thread_plan = thread_plan | |
| self.end_addr = END_ADDR | |
| def explains_stop(self, event): | |
| stop_reason = self.thread_plan.GetThread().GetStopReason() | |
| if stop_reason == lldb.eStopReasonTrace: | |
| return True | |
| else: | |
| return False | |
| def should_stop(self, event): | |
| global SHOULD_STOP | |
| global TARGET | |
| log_trace() | |
| process = TARGET.GetProcess() | |
| thread = process.GetSelectedThread() | |
| frame = thread.GetSelectedFrame() | |
| pc = frame.GetPCAddress() | |
| pc_load_addr = pc.GetLoadAddress(TARGET) | |
| if pc_load_addr == self.end_addr: | |
| self.thread_plan.SetPlanComplete(True) | |
| return SHOULD_STOP | |
| else: | |
| return False | |
| def should_step(self): | |
| return True | |
| def add_trace(start_addr, end_addr, should_stop): | |
| global TARGET | |
| global SHOULD_STOP | |
| global END_ADDR | |
| END_ADDR = end_addr | |
| SHOULD_STOP = should_stop | |
| breakpoint = TARGET.BreakpointCreateByAddress(start_addr) | |
| breakpoint.SetScriptCallbackFunction(full_name(trace)) | |
| def main(): | |
| global TARGET | |
| global FILE_LOG | |
| FILE_LOG = open('/tmp/lldb.log', 'w') | |
| # debugserver localhost:8000 main | |
| run('gdb-remote 8000') | |
| debugger = lldb.debugger | |
| debugger.SetAsync(True) | |
| TARGET = debugger.GetSelectedTarget() | |
| TARGET.DeleteAllBreakpoints() | |
| TARGET.DeleteAllWatchpoints() | |
| main_addr = function_address('main') | |
| main_addr = main_addr.GetLoadAddress(TARGET) | |
| assert main_addr != INVALID_ADDR | |
| start_addr = main_addr + 8 | |
| end_addr = main_addr + 41 # ret | |
| add_trace(start_addr, end_addr, should_stop=True) | |
| # add_trace(start_addr, end_addr, should_stop=False) | |
| run('cont') | |
| def __lldb_init_module(_debugger, _dict): | |
| main() |