blob: 261cb8e1b3633b16b5cf57aed7f08c65ff204cb3 [file] [edit]
# IPInt debugger scripts for looking at the state of the program
# Run with:
# lldb -s debug_ipint.lldb -- jsc (args)
import lldb
import bisect
import struct
from pathlib import Path
ARCH = 'arm64'
if ARCH == 'arm64':
PC_REG = 'x26'
MC_REG = 'x25'
PL_REG = 'x6'
elif ARCH == 'x64':
PC_REG = 'r13'
MC_REG = 'r12'
PL_REG = 'r10'
# Read all instruction opcodes from InPlaceInterpreter64.asm
IPINT_INSTRUCTIONS = [
'unreachable', 'nop', 'block', 'loop', 'if', 'else', 'try', 'catch', 'throw',
'rethrow', 'throw_ref', 'end', 'br', 'br_if', 'br_table', 'return', 'call', 'call_indirect',
'return_call', 'return_call_indirect', 'call_ref', 'return_call_ref', 'delegate',
'catch_all', 'drop', 'select', 'select_t', 'try_table', 'local_get', 'local_set', 'local_tee',
'global_get', 'global_set', 'table_get', 'table_set', 'i32_load_mem', 'i64_load_mem',
'f32_load_mem', 'f64_load_mem', 'i32_load8s_mem', 'i32_load8u_mem', 'i32_load16s_mem',
'i32_load16u_mem', 'i64_load8s_mem', 'i64_load8u_mem', 'i64_load16s_mem',
'i64_load16u_mem', 'i64_load32s_mem', 'i64_load32u_mem', 'i32_store_mem',
'i64_store_mem', 'f32_store_mem', 'f64_store_mem', 'i32_store8_mem', 'i32_store16_mem',
'i64_store8_mem', 'i64_store16_mem', 'i64_store32_mem', 'memory_size', 'memory_grow',
'i32_const', 'i64_const', 'f32_const', 'f64_const', 'i32_eqz', 'i32_eq',
'i32_ne', 'i32_lt_s', 'i32_lt_u', 'i32_gt_s', 'i32_gt_u', 'i32_le_s',
'i32_le_u', 'i32_ge_s', 'i32_ge_u', 'i64_eqz', 'i64_eq', 'i64_ne', 'i64_lt_s',
'i64_lt_u', 'i64_gt_s', 'i64_gt_u', 'i64_le_s', 'i64_le_u', 'i64_ge_s',
'i64_ge_u', 'f32_eq', 'f32_ne', 'f32_lt', 'f32_gt', 'f32_le', 'f32_ge',
'f64_eq', 'f64_ne', 'f64_lt', 'f64_gt', 'f64_le', 'f64_ge', 'i32_clz',
'i32_ctz', 'i32_popcnt', 'i32_add', 'i32_sub', 'i32_mul', 'i32_div_s',
'i32_div_u', 'i32_rem_s', 'i32_rem_u', 'i32_and', 'i32_or', 'i32_xor',
'i32_shl', 'i32_shr_s', 'i32_shr_u', 'i32_rotl', 'i32_rotr', 'i64_clz',
'i64_ctz', 'i64_popcnt', 'i64_add', 'i64_sub', 'i64_mul', 'i64_div_s',
'i64_div_u', 'i64_rem_s', 'i64_rem_u', 'i64_and', 'i64_or', 'i64_xor',
'i64_shl', 'i64_shr_s', 'i64_shr_u', 'i64_rotl', 'i64_rotr', 'f32_abs',
'f32_neg', 'f32_ceil', 'f32_floor', 'f32_trunc', 'f32_nearest', 'f32_sqrt',
'f32_add', 'f32_sub', 'f32_mul', 'f32_div', 'f32_min', 'f32_max', 'f32_copysign',
'f64_abs', 'f64_neg', 'f64_ceil', 'f64_floor', 'f64_trunc', 'f64_nearest',
'f64_sqrt', 'f64_add', 'f64_sub', 'f64_mul', 'f64_div', 'f64_min', 'f64_max',
'f64_copysign', 'i32_wrap_i64', 'i32_trunc_f32_s', 'i32_trunc_f32_u',
'i32_trunc_f64_s', 'i32_trunc_f64_u', 'i64_extend_i32_s', 'i64_extend_i32_u',
'i64_trunc_f32_s', 'i64_trunc_f32_u', 'i64_trunc_f64_s', 'i64_trunc_f64_u',
'f32_convert_i32_s', 'f32_convert_i32_u', 'f32_convert_i64_s', 'f32_convert_i64_u',
'f32_demote_f64', 'f64_convert_i32_s', 'f64_convert_i32_u', 'f64_convert_i64_s',
'f64_convert_i64_u', 'f64_promote_f32', 'i32_reinterpret_f32', 'i64_reinterpret_f64',
'f32_reinterpret_i32', 'f64_reinterpret_i64', 'i32_extend8_s', 'i32_extend16_s',
'i64_extend8_s', 'i64_extend16_s', 'i64_extend32_s', 'ref_null_t', 'ref_is_null',
'ref_func', 'ref_eq', 'ref_as_non_null', 'br_on_null', 'br_on_non_null', 'fb_block',
'fc_block', 'simd', 'atomic', 'struct_new', 'struct_new_default', 'struct_get',
'struct_get_s', 'struct_get_u', 'struct_set', 'array_new', 'array_new_default',
'array_new_fixed', 'array_new_data', 'array_new_elem', 'array_get', 'array_get_s',
'array_get_u', 'array_set', 'array_len', 'array_fill', 'array_copy',
'array_init_data', 'array_init_elem', 'ref_test', 'ref_test_nullable', 'ref_cast',
'ref_cast_nullable', 'br_on_cast', 'br_on_cast_fail', 'any_convert_extern',
'extern_convert_any', 'ref_i31', 'i31_get_s', 'i31_get_u', 'i32_trunc_sat_f32_s',
'i32_trunc_sat_f32_u', 'i32_trunc_sat_f64_s', 'i32_trunc_sat_f64_u', 'i64_trunc_sat_f32_s',
'i64_trunc_sat_f32_u', 'i64_trunc_sat_f64_s', 'i64_trunc_sat_f64_u', 'memory_init',
'data_drop', 'memory_copy', 'memory_fill', 'table_init', 'elem_drop', 'table_copy',
'table_grow', 'table_size', 'table_fill', 'simd_v128_const', 'simd_i32x4_extract_lane',
'memory_atomic_notify', 'memory_atomic_wait32', 'memory_atomic_wait64', 'atomic_fence',
'i32_atomic_load', 'i64_atomic_load', 'i32_atomic_load8_u', 'i32_atomic_load16_u',
'i64_atomic_load8_u', 'i64_atomic_load16_u', 'i64_atomic_load32_u', 'i32_atomic_store',
'i64_atomic_store', 'i32_atomic_store8_u', 'i32_atomic_store16_u', 'i64_atomic_store8_u',
'i64_atomic_store16_u', 'i64_atomic_store32_u', 'i32_atomic_rmw_add', 'i64_atomic_rmw_add',
'i32_atomic_rmw8_add_u', 'i32_atomic_rmw16_add_u', 'i64_atomic_rmw8_add_u',
'i64_atomic_rmw16_add_u', 'i64_atomic_rmw32_add_u', 'i32_atomic_rmw_sub', 'i64_atomic_rmw_sub',
'i32_atomic_rmw8_sub_u', 'i32_atomic_rmw16_sub_u', 'i64_atomic_rmw8_sub_u',
'i64_atomic_rmw16_sub_u', 'i64_atomic_rmw32_sub_u', 'i32_atomic_rmw_and',
'i64_atomic_rmw_and', 'i32_atomic_rmw8_and_u', 'i32_atomic_rmw16_and_u',
'i64_atomic_rmw8_and_u', 'i64_atomic_rmw16_and_u', 'i64_atomic_rmw32_and_u',
'i32_atomic_rmw_or', 'i64_atomic_rmw_or', 'i32_atomic_rmw8_or_u', 'i32_atomic_rmw16_or_u',
'i64_atomic_rmw8_or_u', 'i64_atomic_rmw16_or_u', 'i64_atomic_rmw32_or_u', 'i32_atomic_rmw_xor',
'i64_atomic_rmw_xor', 'i32_atomic_rmw8_xor_u', 'i32_atomic_rmw16_xor_u', 'i64_atomic_rmw8_xor_u',
'i64_atomic_rmw16_xor_u', 'i64_atomic_rmw32_xor_u', 'i32_atomic_rmw_xchg', 'i64_atomic_rmw_xchg',
'i32_atomic_rmw8_xchg_u', 'i32_atomic_rmw16_xchg_u', 'i64_atomic_rmw8_xchg_u',
'i64_atomic_rmw16_xchg_u', 'i64_atomic_rmw32_xchg_u', 'i32_atomic_rmw_cmpxchg',
'i64_atomic_rmw_cmpxchg', 'i32_atomic_rmw8_cmpxchg_u', 'i32_atomic_rmw16_cmpxchg_u',
'i64_atomic_rmw8_cmpxchg_u', 'i64_atomic_rmw16_cmpxchg_u', 'i64_atomic_rmw32_cmpxchg_u']
stop_commands = lldb.SBStringList()
stop_commands.AppendString("ipint_state")
go_commands = lldb.SBStringList()
go_commands.AppendString("ipint_state")
go_commands.AppendString("c")
breakpoints = []
breakpoints_enabled = []
breakpoint_locs = []
def print_value(mem, result):
print(' '.join(f'{x:02x}' for x in mem), file=result)
# interpret as a bunch of stuff
SP = ' ' * 10
print(f'{SP}\033[92mi32\033[0m{SP}{SP}\033[93mf32\033[0m{SP}{SP}\033[94mi64\033[0m{SP}{SP}\033[95mf64\033[0m', file=result)
i32 = struct.unpack('ixxxxxxxxxxxx', mem)[0]
f32 = struct.unpack('fxxxxxxxxxxxx', mem)[0]
i64 = struct.unpack('qxxxxxxxx', mem)[0]
f64 = struct.unpack('dxxxxxxxx', mem)[0]
print(f'{i32:^23}{f32:^23.5f}{i64:^23}{f64:^23.5f}', file=result)
def print_stack(proc, frame, result, num_entries=None):
gprs = {}
for reg in frame.regs[0]:
gprs[reg.name] = int(reg.value[2:], 16)
pl = gprs[PL_REG]
ptr = frame.sp
i = 0
if ptr == pl:
print('no stack entries', file=result)
else:
print('(top of stack)', file=result)
while ptr != pl and (num_entries is None or i < num_entries):
print(f'\n---- stack[{i:2}] ----', file=result)
error = lldb.SBError()
mem = proc.ReadMemory(ptr, 16, error)
if error.Success():
mem = bytearray(mem)
print_value(mem, result)
else:
print(f'can\'t read stack memory at address 0x{ptr:016x} :(', file=result)
break
ptr += 16
i += 1
def ipint_state(debugger, command, exe_ctx, result, internal_dict):
print("Current interpreter state:", file=result)
print("--------------------------", file=result)
proc = debugger.GetTargetAtIndex(0).process
frame = proc.selected_thread.frame[0]
regs = frame.regs
# Figure out where we are if breakpoints is populated
if len(breakpoints) != 0:
# current PC
pc = frame.pc
i = bisect.bisect(breakpoint_locs, pc) - 1
if i < 0:
print(f'\033[1m\033[91mnot in IPInt bounds!!!\033[0m')
else:
print(f'\033[1mCurrently executing: \033[93m{IPINT_INSTRUCTIONS[i]}\033[0m', file=result)
gprs = {}
for reg in regs[0]:
gprs[reg.name] = int(reg.value[2:], 16)
# PC = x26
pc = gprs[PC_REG]
mc = gprs[MC_REG]
print(f'PC = 0x{pc:x}', file=result)
# preview 8 bytes of PC
if True:
error = lldb.SBError()
mem = proc.ReadMemory(pc, 8, error)
if error.Success():
print(' '.join(f'{x:02x}' for x in mem), file=result)
else:
print('???', file=result)
print('', file=result)
if mc != 0:
print(f'MC = 0x{mc:x}', file=result)
# preview 8 bytes of MC
if True:
error = lldb.SBError()
mem = proc.ReadMemory(mc, 8, error)
if error.Success():
print(' '.join(f'{x:02x}' for x in mem), file=result)
else:
print('???', file=result)
print('', file=result)
else:
print('\033[1m\033[94mno metadata generated\033[0m\n', file=result)
print_stack(proc, frame, result, 2)
def ipint_stack(debugger, command, exec_ctx, result, internal_dict):
proc = debugger.GetTargetAtIndex(0).process
frame = proc.selected_thread.frame[0]
try:
print_stack(proc, frame, result, int(command))
except ValueError:
print('usage: ipint_stack <num_stack_entries>', file=result)
def ipint_local(debugger, command, exec_ctx, result, internal_dict):
try:
local_index = int(command)
except ValueError:
print('usage: ipint_local <local_idx>', file=result)
return
proc = debugger.GetTargetAtIndex(0).process
frame = proc.selected_thread.frame[0]
gprs = {}
for reg in frame.regs[0]:
gprs[reg.name] = int(reg.value[2:], 16)
pl = gprs[PL_REG]
LOCAL_SIZE = 16
error = lldb.SBError()
ptr = pl + LOCAL_SIZE * local_index
mem = proc.ReadMemory(ptr, 16, error)
if error.Success():
mem = bytearray(mem)
print_value(mem, result)
else:
print(f'can\'t read stack memory at address 0x{ptr:016x} (bad local index?)', file=result)
def ipint_continue_until(debugger, command, exec_ctx, result, internal_dict):
try:
op_index = IPINT_INSTRUCTIONS.index(command)
for i in range(len(IPINT_INSTRUCTIONS)):
breakpoints[i].enabled = (i == op_index)
proc = debugger.GetTargetAtIndex(0).process
proc.Continue()
for i in range(len(IPINT_INSTRUCTIONS)):
breakpoints[i].enabled = True
except ValueError:
print(f'can\'t find operation {command}', file=result)
def ipint_break_at(debugger, command, exec_ctx, result, internal_dict):
try:
op_index = IPINT_INSTRUCTIONS.index(command)
breakpoints[op_index].enabled = True
except ValueError:
print(f'can\'t find operation {command}', file=result)
def ipint_disable_all_breakpoints(debugger, command, exec_ctx, result, internal_dict):
for brk in breakpoints:
brk.enabled = False
def ipint_reenable_all_breakpoints(debugger, command, exec_ctx, result, internal_dict):
for brk in breakpoints:
brk.enabled = True
def ipint_continue_on_all_breakpoints(debugger, command, exec_ctx, result, internal_dict):
for brk in breakpoints:
brk.enabled = True
brk.SetAutoContinue(True)
def set_breakpoints(debugger, command, exe_ctx, result, internal_dict):
print("Initializing internal breakpoints...", file=result)
target = debugger.GetTargetAtIndex(0)
for instr in IPINT_INSTRUCTIONS:
brk = target.BreakpointCreateByName(f'ipint_{instr}')
brk.SetCommandLineCommands(stop_commands)
breakpoints.append(brk)
breakpoint_locs.append(brk.locations[0].GetLoadAddress())
print("done!", file=result)
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f debug_ipint.ipint_state ipint_state')
debugger.HandleCommand('command script add -f debug_ipint.ipint_stack ipint_stack')
debugger.HandleCommand('command script add -f debug_ipint.ipint_local ipint_local')
debugger.HandleCommand('command script add -f debug_ipint.ipint_continue_until ipint_continue_until')
debugger.HandleCommand('command script add -f debug_ipint.ipint_break_at ipint_break_at')
debugger.HandleCommand('command script add -f debug_ipint.ipint_disable_all_breakpoints ipint_disable_all_breakpoints')
debugger.HandleCommand('command script add -f debug_ipint.ipint_reenable_all_breakpoints ipint_reenable_all_breakpoints')
debugger.HandleCommand('command script add -f debug_ipint.ipint_continue_on_all_breakpoints ipint_autocontinue')
debugger.HandleCommand('command script add -f debug_ipint.set_breakpoints set_breakpoints')
print("IPInt debugger ready")