xref: /xnu-12377.1.9/tests/unit/tools/fibers_lldb.py (revision f6217f891ac0bb64f3d375211650a4c1ff8ca1ea)
1#!/usr/bin/env python3
2'''
3How to Use:
4
5Load in LLDB:
6(lldb) command script import ./tests/unit/tools/fibers_lldb.py
7
8Run the commands:
9(lldb) fibers_all         # Lists all existing fibers
10(lldb) fibers_ready       # Lists fibers in the run queue
11(lldb) fibers_current     # Gets information about the current fiber
12(lldb) fibers_regs [id]   # Get the registers saved in the fiber end (default current fiber)
13'''
14
15import lldb
16import sys
17
18def fiber_state_to_string(state):
19    """Converts a fiber state integer to a human-readable string."""
20    states = []
21    if state & 0x1:
22        states.append("RUN")
23    if state & 0x2:
24        states.append("STOP")
25    if state & 0x4:
26        states.append("WAIT")
27    if state & 0x8:
28        states.append("JOIN")
29    if state & 0x10:
30        states.append("DEAD")
31    return "|".join(states) if states else "UNKNOWN"
32
33def strip_pointer(target, addr):
34    """Strips the PAC signature from a pointer."""
35    val = target.CreateValueFromAddress("__tmp_strip_pac", lldb.SBAddress(addr, target), target.FindFirstType("unsigned long long"))
36    val.SetPreferDynamicValue(lldb.eNoDynamicValues)
37    val = val.AddressOf()
38    return val.GetValueAsAddress()
39
40def strip_fp_lr_sp(process, target, fp, lr, sp):
41    """Strip manged registers in the jmp buf from the munge token and PAC."""
42    # get the munge token (see __longjmp impl)
43    frame = process.selected_thread.GetFrameAtIndex(0)
44
45    # ref. os/tsd.h
46    # define __TSD_PTR_MUNGE 7
47    munge_token = frame.EvaluateExpression('({void** r; __asm__("mrs %0, TPIDRRO_EL0" : "=r"(r)); r[7];})')
48    if munge_token.GetError().Fail():
49        return None
50    munge_token = munge_token.GetValueAsAddress()
51
52    fp  = strip_pointer(target, fp ^ munge_token)
53    lr  = strip_pointer(target, lr ^ munge_token)
54    sp  = strip_pointer(target, sp ^ munge_token)
55    return (fp, lr, sp)
56
57def get_fiber_info(debugger, fiber_value):
58    """Retrieves information about a fiber from its SBValue address."""
59    if not fiber_value or not fiber_value.IsValid():
60        return None
61
62    fiber_address = fiber_value.GetValueAsAddress()
63
64    fiber_id_value = fiber_value.GetChildMemberWithName('id')
65    fiber_id_state = fiber_value.GetChildMemberWithName('state')
66    stack_bottom_value = fiber_value.GetChildMemberWithName('stack_bottom')
67    env_value = fiber_value.GetChildMemberWithName('env')
68    if not fiber_id_value.IsValid() or not fiber_id_state.IsValid() or not stack_bottom_value.IsValid() or not env_value.IsValid():
69        print(f"Error reading fiber memory")
70        return None
71
72    fiber_id = fiber_id_value.GetValueAsUnsigned()
73    fiber_state = fiber_id_state.GetValueAsUnsigned()
74    stack_bottom = stack_bottom_value.GetValueAsAddress()
75    env_address = env_value.AddressOf().GetValueAsAddress()
76
77    return {
78        "id": fiber_id,
79        "address": fiber_address,
80        "state": fiber_state,
81        "state_str": fiber_state_to_string(fiber_state),
82        "stack_bottom": stack_bottom,
83        "env_address": env_address
84    }
85
86def print_stack_trace_from_jmp_buf(debugger, fiber_info, result, arch):
87    """Prints a stack trace by manually walking the stack."""
88    target = debugger.GetSelectedTarget()
89    process = target.GetProcess()
90    env_address = fiber_info["env_address"]
91    error = lldb.SBError()
92    addr_size = target.GetAddressByteSize()
93
94    if arch == "x86_64":
95        result.AppendMessage(f"  Error: Register printing is not supported on x86_64.")
96        return
97
98    elif arch == "arm64":
99        FP_OFFSET = 80
100        LR_OFFSET  = 88
101        SP_OFFSET = 96
102
103        fp  = process.ReadPointerFromMemory(env_address + FP_OFFSET, error)
104        lr  = process.ReadPointerFromMemory(env_address + LR_OFFSET, error)
105        sp  = process.ReadPointerFromMemory(env_address + SP_OFFSET, error)
106
107        if error.Fail():
108            result.AppendMessage(f"  Error: Could not read registers from jmp_buf: {error}")
109            return
110
111        strip_res = strip_fp_lr_sp(process, target, fp, lr, sp)
112        if strip_res is None:
113            result.AppendMessage(f"  Error: Could not strip FP LR or SP")
114            return
115        fp, lr, sp = strip_res
116
117        result.AppendMessage(f"  Stack trace for fiber {fiber_info['id']} (manual backtrace):")
118
119        for i in range(10):  # Limit to 10 frames for simplicity
120            symbol_context = target.ResolveSymbolContextForAddress(lldb.SBAddress(lr, target), lldb.eSymbolContextEverything)
121            symbol = symbol_context.GetSymbol()
122            if symbol:
123                symbol_name = symbol.GetName()
124            else:
125                symbol_name = "unknown"
126            result.AppendMessage(f"    #{i}: 0x{lr:x} {symbol_name}")
127
128            next_fp = process.ReadPointerFromMemory(fp, error)
129            if error.Fail():
130                result.AppendMessage(f"  Error: Could not read next FP from memory: {error}")
131                break
132
133            next_lr = process.ReadPointerFromMemory(fp + 8, error) # read next LR from the stack using current SP
134            if error.Fail():
135                result.AppendMessage(f"  Error: Could not read next LR from memory: {error}")
136                break
137
138            if next_lr == 0:
139                result.AppendMessage("    End of stack or error reading memory.")
140                break
141
142            next_lr = strip_pointer(target, next_lr)
143            lr = next_lr
144            fp = next_fp
145
146    else:
147        result.AppendMessage(f"  Error: Unsupported architecture: {arch}")
148        return
149
150
151def list_fibers_all(debugger, command, result, internal_dict, arch):
152    """Lists all existing fibers."""
153    list_fibers_from_queue(debugger, command, result, internal_dict, "fibers_existing_queue", "All Existing Fibers", arch)
154
155def list_fibers_ready(debugger, command, result, internal_dict, arch):
156    """Lists fibers in the run queue (now called 'ready')."""
157    list_fibers_from_queue(debugger, command, result, internal_dict, "fibers_run_queue", "Ready Fibers (Run Queue)", arch)
158
159def list_fibers_from_queue(debugger, command, result, internal_dict, queue_name, title, arch):
160    """Lists fibers from a specified queue."""
161
162    target = debugger.GetSelectedTarget()
163
164    queue_var = target.FindFirstGlobalVariable(queue_name)
165    if not queue_var.IsValid():
166        result.SetError(f"Could not find '{queue_name}' global variable.")
167        return
168
169    result.AppendMessage(f"{title}:")
170    result.AppendMessage("-------")
171
172    queue_top_value = queue_var.GetChildMemberWithName('top')
173    if not queue_top_value.IsValid():
174        result.SetError(f"Could not find '{queue_name}.top' field.")
175        return
176
177    fiber_value = queue_top_value
178    while fiber_value and fiber_value.IsValid():
179        fiber = get_fiber_info(debugger, fiber_value)
180        if fiber:
181            result.AppendMessage(f"  ID: {fiber['id']}, Address: 0x{fiber['address']:x}, State: {fiber['state_str']}, Stack Bottom: 0x{fiber['stack_bottom']:x}")
182            try:
183                print_stack_trace_from_jmp_buf(debugger, fiber, result, arch)  # Optional: Add stack traces
184            except Exception as err:
185                result.AppendMessage(f"Error: failed to get a stacktrace: {err}")
186                break
187        else:
188            result.AppendMessage(f"Warning: Could not read fiber at address 0x{fiber_value.GetValueAsUnsigned():x}")
189            break
190
191        if queue_name == "fibers_existing_queue":
192            next_fiber_value = fiber_value.GetChildMemberWithName('next_existing')
193        else:
194            next_fiber_value = fiber_value.GetChildMemberWithName('next')
195
196        if not next_fiber_value.IsValid():
197            break
198
199        fiber_value = next_fiber_value
200
201def get_current_fiber_info(debugger, command, result, internal_dict, arch):
202    """Gets and prints information about the current fiber."""
203    target = debugger.GetSelectedTarget()
204
205    fibers_current_var = target.FindFirstGlobalVariable("fibers_current")
206    if not fibers_current_var.IsValid():
207        result.SetError("Could not find 'fibers_current' global variable.")
208        return
209
210    current_fiber = get_fiber_info(debugger, fibers_current_var)
211
212    if not current_fiber:
213        result.AppendMessage("No current fiber.")
214        return
215
216    result.AppendMessage("Current Fiber Information:")
217    result.AppendMessage("--------------------------")
218    result.AppendMessage(f"  ID: {current_fiber['id']}")
219    result.AppendMessage(f"  Address: 0x{current_fiber['address']:x}")
220    result.AppendMessage(f"  State: {current_fiber['state_str']}")
221    result.AppendMessage(f"  Stack Bottom: 0x{current_fiber['stack_bottom']:x}")
222    try:
223        print_stack_trace_from_jmp_buf(debugger, current_fiber, result, arch)  # Optional: Add stack traces
224    except Exception as err:
225        print(f"Error: failed to get a stacktrace: {err}")
226
227def print_fiber_registers(debugger, command, result, internal_dict, arch, fiber_id=None):
228    """Prints the registers of a specified fiber."""
229    target = debugger.GetSelectedTarget()
230    process = target.GetProcess()
231
232    if fiber_id is None:
233        fibers_current_var = target.FindFirstGlobalVariable("fibers_current")
234        if not fibers_current_var.IsValid():
235            result.SetError("Could not find 'fibers_current' global variable.")
236            return
237
238        current_fiber = get_fiber_info(debugger, fibers_current_var)
239        if not current_fiber:
240            result.AppendMessage("No current fiber.")
241            return
242
243    else:
244        # find the specified fiber in the existing queue
245        fiber_address = None
246        existing_queue_var = target.FindFirstGlobalVariable("fibers_existing_queue")
247        if not existing_queue_var.IsValid():
248            result.SetError("Could not find 'fibers_existing_queue' global variable.")
249            return
250
251        queue_top_value = existing_queue_var.GetChildMemberWithName('top')
252        if not queue_top_value.IsValid():
253            result.SetError(f"Could not find '{existing_queue_var.GetName()}.top' field.")
254            return
255
256        fiber_value = queue_top_value
257        while fiber_value and fiber_value.IsValid():
258            temp_fiber = get_fiber_info(debugger, fiber_value)
259            if temp_fiber and temp_fiber['id'] == int(fiber_id):
260                current_fiber = temp_fiber
261                break
262
263            next_fiber_value = fiber_value.GetChildMemberWithName('next_existing')
264            if not next_fiber_value.IsValid():
265                break
266
267            fiber_value = next_fiber_value
268
269        if not current_fiber:
270            result.AppendMessage(f"Fiber with ID {fiber_id} not found.")
271            return
272
273    env_address = current_fiber["env_address"]
274    error = lldb.SBError()
275    addr_size = target.GetAddressByteSize()
276
277    if arch == "x86_64":
278        result.AppendMessage(f"  Error: Register printing is not supported on x86_64.")
279        return
280
281    elif arch == "arm64":
282        # reference: libplatform src/setjmp/arm64/setjmp.s __longjmp
283        X19_OFFSET = 0
284        X20_OFFSET = 8
285        X21_OFFSET = 16
286        X22_OFFSET = 24
287        X23_OFFSET = 32
288        X24_OFFSET = 40
289        X25_OFFSET = 48
290        X26_OFFSET = 56
291        X27_OFFSET = 64
292        X28_OFFSET = 72
293
294        FP_OFFSET = 80
295        LR_OFFSET  = 88
296        SP_OFFSET = 96
297
298        x19 = process.ReadPointerFromMemory(env_address + X19_OFFSET, error)
299        x20 = process.ReadPointerFromMemory(env_address + X20_OFFSET, error)
300        x21 = process.ReadPointerFromMemory(env_address + X21_OFFSET, error)
301        x22 = process.ReadPointerFromMemory(env_address + X22_OFFSET, error)
302        x23 = process.ReadPointerFromMemory(env_address + X23_OFFSET, error)
303        x24 = process.ReadPointerFromMemory(env_address + X24_OFFSET, error)
304        x25 = process.ReadPointerFromMemory(env_address + X25_OFFSET, error)
305        x26 = process.ReadPointerFromMemory(env_address + X26_OFFSET, error)
306        x27 = process.ReadPointerFromMemory(env_address + X27_OFFSET, error)
307        x28 = process.ReadPointerFromMemory(env_address + X28_OFFSET, error)
308
309        fp  = process.ReadPointerFromMemory(env_address + FP_OFFSET, error)
310        lr  = process.ReadPointerFromMemory(env_address + LR_OFFSET, error)
311        sp  = process.ReadPointerFromMemory(env_address + SP_OFFSET, error)
312
313        if error.Fail():
314            result.AppendMessage(f"  Error: Could not read registers from jmp_buf: {error}")
315            return
316
317        strip_res = strip_fp_lr_sp(process, target, fp, lr, sp)
318        if strip_res is None:
319            result.AppendMessage(f"  Error: Could not strip FP LR or SP")
320            return
321        fp, lr, sp = strip_res
322
323        result.AppendMessage(f"Fiber {current_fiber['id']} Registers (arm64):")
324        result.AppendMessage("-----------------------------")
325        result.AppendMessage(f"  X19: 0x{x19:x}")
326        result.AppendMessage(f"  X20: 0x{x20:x}")
327        result.AppendMessage(f"  X21: 0x{x21:x}")
328        result.AppendMessage(f"  X22: 0x{x22:x}")
329        result.AppendMessage(f"  X23: 0x{x23:x}")
330        result.AppendMessage(f"  X24: 0x{x24:x}")
331        result.AppendMessage(f"  X25: 0x{x25:x}")
332        result.AppendMessage(f"  X26: 0x{x26:x}")
333        result.AppendMessage(f"  X27: 0x{x27:x}")
334        result.AppendMessage(f"  X28: 0x{x28:x}")
335        result.AppendMessage(f"  LR:  0x{lr:x}")
336        result.AppendMessage(f"  FP:  0x{fp:x}")
337        result.AppendMessage(f"  SP:  0x{sp:x}")
338    else:
339        result.AppendMessage(f"  Error: Unsupported architecture: {arch}")
340        return
341
342arch = None
343
344def list_fibers_all_cmd(debugger, command, result, internal_dict):
345    list_fibers_all(debugger, command, result, internal_dict, arch)
346
347def list_fibers_ready_cmd(debugger, command, result, internal_dict):
348    list_fibers_ready(debugger, command, result, internal_dict, arch)
349
350def get_current_fiber_info_cmd(debugger, command, result, internal_dict):
351    get_current_fiber_info(debugger, command, result, internal_dict, arch)
352
353def print_fiber_registers_cmd(debugger, command, result, internal_dict):
354    """Prints the registers of a specified fiber."""
355    args = command.split()
356    fiber_id = None
357    if len(args) > 0:
358        try:
359            fiber_id = int(args[0])
360        except ValueError:
361            result.SetError("Invalid fiber ID. Please provide an integer.")
362            return
363
364    print_fiber_registers(debugger, command, result, internal_dict, arch, fiber_id)
365
366def __lldb_init_module(debugger, internal_dict):
367    global arch
368    """LLDB calls this function to load the script."""
369
370    target = debugger.GetSelectedTarget()
371    platform = target.GetPlatform()
372    if platform:
373        platform_name = platform.GetTriple()
374        if "x86_64" in platform_name:
375            arch = "x86_64"
376        elif "arm64" in platform_name or "aarch64" in platform_name:
377            arch = "arm64"
378        else:
379            print(f"Warning: Unsupported architecture: {platform_name}. Stack traces may not work.")
380            arch = "unknown"
381    else:
382        print("Warning: Could not get platform information. Stack traces may not work.")
383        arch = "unknown"
384
385    debugger.HandleCommand('command script add -f fibers_lldb.list_fibers_all_cmd fibers_all')
386    debugger.HandleCommand('command script add -f fibers_lldb.list_fibers_ready_cmd fibers_ready')
387    debugger.HandleCommand('command script add -f fibers_lldb.get_current_fiber_info_cmd fibers_current')
388    debugger.HandleCommand('command script add -f fibers_lldb.print_fiber_registers_cmd fibers_regs')
389    print("The 'fibers_all', 'fibers_ready', 'fibers_current', and 'fibers_regs' commands have been added.")
390    print(f"Detected architecture: {arch}")
391