Add trace_ioserviceopen.py script to trace IOServiceOpen calls in LLDB

This commit is contained in:
Karol Mazurek
2025-06-09 01:38:54 +02:00
parent 95752eefc7
commit b0439e7220

View File

@@ -0,0 +1,109 @@
"""
trace_ioserviceopen.py
Trace IOServiceOpen calls in a target process using LLDB Python scripting to get Service Names and User Client types.
How to use:
1. In LLDB, import this script:
(lldb) command script import trace_ioserviceopen.py
2. Start tracing with:
(lldb) setup_ioserviceopen --executable_path EXE_PATH -- [args...]
or
(lldb) setup_ioserviceopen --pid PID
What it does:
- Sets a breakpoint on IOServiceOpen.
- On every call, prints:
- PID
- Executable path
- IOService name
- Type
- Does not stop or break execution; output is continuous and non-intrusive.
"""
import lldb
import shlex
def ioserviceopen_trace_hook(frame, bp_loc, dict):
# Get target and process objects from the current frame
target = frame.GetThread().GetProcess().GetTarget()
process = frame.GetThread().GetProcess()
pid = process.GetProcessID()
# Get the main module (executable) path
module = target.GetModuleAtIndex(0)
exe_path = module.GetFileSpec().fullpath or "unknown"
# x0 holds the IOService pointer, x2 holds the type argument
service = frame.FindRegister("x0").GetValueAsUnsigned()
type_val = frame.FindRegister("x2").GetValueAsUnsigned()
# Try to resolve the IOService name using IORegistryEntryGetName.
# This only works if the symbol is available and the process is not restricted.
name_str = f"0x{service:x}"
try:
# Evaluate an expression in the target to call IORegistryEntryGetName.
# This is safe if the symbol is present and the process allows it.
expr = (
'char name[128] = {0}; '
'extern int IORegistryEntryGetName(void*, char*); '
f'IORegistryEntryGetName((void*){service}, name); '
'name'
)
val = frame.EvaluateExpression(expr)
if val.IsValid() and val.GetSummary():
s = val.GetSummary().strip('"')
if s and not s.startswith('0x'):
name_str = s
except Exception:
# If anything fails, just show the pointer value
pass
print(f"\nPID: {pid}\nEXE PATH: {exe_path}\nSERVICE: {name_str}\nTYPE: {type_val}\n")
# Returning False tells LLDB to auto-continue without stopping at the breakpoint
return False
def setup_ioserviceopen(debugger, command, result, internal_dict):
args = shlex.split(command)
executable_path = None
pid = None
program_args = []
i = 0
# Parse command-line arguments for executable path, pid, and any program arguments
while i < len(args):
if args[i] == "--executable_path" and i + 1 < len(args):
executable_path = args[i + 1]
i += 2
elif args[i] == "--pid" and i + 1 < len(args):
pid = args[i + 1]
i += 2
elif args[i] == "--" and i + 1 < len(args):
program_args = args[i + 1:]
break
else:
i += 1
if executable_path and pid:
print("Error: Specify either --executable_path or --pid", file=result)
return
if not executable_path and not pid:
print("Error: Specify --executable_path or --pid", file=result)
return
# Set up the target and breakpoint, and ensure auto-continue is enabled
if executable_path:
debugger.HandleCommand(f'target create "{executable_path}"')
bp = debugger.GetSelectedTarget().BreakpointCreateByName("IOServiceOpen")
bp.SetScriptCallbackFunction("trace_ioserviceopen.ioserviceopen_trace_hook")
bp.SetAutoContinue(True)
debugger.HandleCommand(f'process launch -- {" ".join(shlex.quote(arg) for arg in program_args)}')
elif pid:
debugger.HandleCommand(f'process attach --pid {pid}')
bp = debugger.GetSelectedTarget().BreakpointCreateByName("IOServiceOpen")
bp.SetScriptCallbackFunction("trace_ioserviceopen.ioserviceopen_trace_hook")
bp.SetAutoContinue(True)
debugger.HandleCommand('continue')
def __lldb_init_module(debugger, internal_dict):
# Register the setup command with LLDB
debugger.HandleCommand('command script add -f trace_ioserviceopen.setup_ioserviceopen setup_ioserviceopen')