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