#!/usr/bin/env bash # Read the GUEST state of a live (hung) xenia_canary — who is the guest spinning # on, and what is it waiting for. # # In JIT code the x64 backend keeps: # rsi = PPCContext* rdi = guest membase # (x64_emitter.cc: GetContextReg()=rsi, GetMembaseReg()=rdi) # PPCContext offsets (computed against this build's header): # r[0..31] @ +40 (8B each) ctr @ +296 lr @ +304 # thread_state @ +2704 virtual_membase @ +2712 # # Needs ptrace: sudo sysctl -w kernel.yama.ptrace_scope=0 (restore with =1) # Read-only: gdb detaches, the process keeps running. # # Usage: ./live-guest-state.sh ["Thread Name"] (default: Main XThread) set -u WANT="${1:-Main XThread}" PID=$(pgrep -x xenia_canary | head -1) [ -n "$PID" ] || { echo "no xenia_canary running"; exit 1; } if [ "$(cat /proc/sys/kernel/yama/ptrace_scope 2>/dev/null)" != "0" ]; then echo "ABORT: ptrace is locked (yama ptrace_scope != 0). Run once:" echo " sudo sysctl -w kernel.yama.ptrace_scope=0" exit 3 fi RAW=$(mktemp /tmp/guest_state_XXXX.txt) gdb -p "$PID" -batch \ -ex "set pagination off" -ex "set confirm off" \ -ex "thread find $WANT" \ -ex "thread apply all -ascending printf \"@@TH %d %s\\n\", \$_thread, \$_gthread" \ 2>/dev/null | grep -E "Thread .* has name|@@TH" > "$RAW" # gdb "thread find" prints e.g.: Thread 34 has target name 'Main XThread (F...' GTH=$(grep -m1 "has .*name" "$RAW" | sed -E 's/.*Thread ([0-9]+) has.*/\1/') [ -n "$GTH" ] || { echo "could not locate a thread named '$WANT'"; cat "$RAW"; exit 4; } echo "gdb thread #$GTH == '$WANT' (pid $PID)" gdb -p "$PID" -batch \ -ex "set pagination off" -ex "set confirm off" \ -ex "thread $GTH" \ -ex "echo \n=== host frame ===\n" \ -ex "printf \"host rip = %#lx\\n\", \$rip" \ -ex "bt 8" \ -ex "echo \n=== guest registers (PPCContext @ rsi) ===\n" \ -ex "set \$ctx = (unsigned long)\$rsi" \ -ex "printf \"ctx = %#lx\\n\", \$ctx" \ -ex "printf \"lr = %#lx\\n\", *(unsigned long*)(\$ctx+304)" \ -ex "printf \"ctr = %#lx\\n\", *(unsigned long*)(\$ctx+296)" \ -ex "printf \"r1(sp)= %#lx\\n\", *(unsigned long*)(\$ctx+40+8*1)" \ -ex "printf \"r3 = %#lx\\n\", *(unsigned long*)(\$ctx+40+8*3)" \ -ex "printf \"r4 = %#lx\\n\", *(unsigned long*)(\$ctx+40+8*4)" \ -ex "printf \"r5 = %#lx\\n\", *(unsigned long*)(\$ctx+40+8*5)" \ -ex "printf \"r6 = %#lx\\n\", *(unsigned long*)(\$ctx+40+8*6)" \ -ex "printf \"r7 = %#lx\\n\", *(unsigned long*)(\$ctx+40+8*7)" \ -ex "printf \"r8 = %#lx\\n\", *(unsigned long*)(\$ctx+40+8*8)" \ -ex "printf \"r9 = %#lx\\n\", *(unsigned long*)(\$ctx+40+8*9)" \ -ex "printf \"r10 = %#lx\\n\", *(unsigned long*)(\$ctx+40+8*10)" \ -ex "printf \"r11 = %#lx\\n\", *(unsigned long*)(\$ctx+40+8*11)" \ -ex "printf \"r12 = %#lx\\n\", *(unsigned long*)(\$ctx+40+8*12)" \ -ex "printf \"membase = %#lx\\n\", *(unsigned long*)(\$ctx+2712)" \ -ex "echo \n=== guest stack bytes @ r1 (BE; look for 82xxxxxx = code) ===\n" \ -ex "set \$mb = *(unsigned long*)(\$ctx+2712)" \ -ex "set \$sp = *(unsigned long*)(\$ctx+40+8*1)" \ -ex "x/128xb \$mb + \$sp" \ -ex "detach" 2>&1 | grep -vE "^\[|Reading symbols|no debugging symbols|Detaching" rm -f "$RAW" echo echo "Guest code addresses look like 0x82xxxxxx — feed lr / stack hits to zq.py fn ."