Skip to content

Instantly share code, notes, and snippets.

@nkaretnikov
Created September 27, 2019 15:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nkaretnikov/75c8a0b814246b322d562e7333eb4ba9 to your computer and use it in GitHub Desktop.
Save nkaretnikov/75c8a0b814246b322d562e7333eb4ba9 to your computer and use it in GitHub Desktop.
lldb trace

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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment