zq.py and sylph-run.sh hard-coded '/home/fabi/RE - Project Sylpheed/...' (the dashed dir was renamed 'RE Project Sylpheed'), so both were broken. Resolve relative to the script directory now; zq.py honours $SYLPHEED_DB. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
3.1 KiB
Python
Executable File
71 lines
3.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Sylpheed static-analysis helper over DuckDB `sylpheed.db`.
|
|
|
|
Hides the gotchas: DECIMAL bounds (DuckDB rejects 0x literals), read-only connect,
|
|
and the fact that the engine vtable / rdata is NOT in the DB (read it from guest
|
|
memory with `xenia-rs exec ... --dump-addr=0x<va>` instead).
|
|
|
|
Usage:
|
|
zq.py dis <lo_hex> <hi_hex> # disassemble [lo,hi)
|
|
zq.py fn <pc_hex> # function containing pc (address,name,end)
|
|
zq.py xref <target_hex> # xrefs whose target == addr (callers)
|
|
zq.py callers <vtable_off_dec> # call-sites of vtable slot at byte offset N
|
|
# (finds `lwz r11, N(r11)` + reports the fn)
|
|
zq.py grep <substr> # instructions whose operands LIKE %substr%
|
|
zq.py find <word_hex> # instructions whose raw word == value (e.g. a ptr)
|
|
"""
|
|
import duckdb, sys, os
|
|
|
|
# Resolve the DB next to this script so the tool survives the tree being renamed
|
|
# (the old hard-coded '/home/fabi/RE - Project Sylpheed/...' path went stale when
|
|
# the dir became 'RE Project Sylpheed'). Override with $SYLPHEED_DB.
|
|
DB = os.environ.get('SYLPHEED_DB',
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'sylpheed.db'))
|
|
c = duckdb.connect(DB, read_only=True)
|
|
H = lambda x: '0x%08x' % x
|
|
|
|
|
|
def _fn(pc):
|
|
r = c.execute('SELECT address,name,end_address FROM functions WHERE address<=? AND end_address>? '
|
|
'ORDER BY address DESC LIMIT 1', [pc, pc]).fetchall()
|
|
return f'{r[0][1]}({H(r[0][0])})' if r else '?'
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print(__doc__); return
|
|
cmd = sys.argv[1]
|
|
if cmd == 'dis':
|
|
lo, hi = int(sys.argv[2], 16), int(sys.argv[3], 16)
|
|
for a, m, o in c.execute('SELECT address,mnemonic,operands FROM instructions '
|
|
'WHERE address>=? AND address<? ORDER BY address', [lo, hi]).fetchall():
|
|
print(H(a), m, o)
|
|
elif cmd == 'fn':
|
|
print(_fn(int(sys.argv[2], 16)))
|
|
elif cmd == 'xref':
|
|
t = int(sys.argv[2], 16)
|
|
for s, k, i, sf in c.execute('SELECT source,kind,instruction,source_func FROM xrefs '
|
|
'WHERE target=? ORDER BY source', [t]).fetchall():
|
|
print(H(s), k, 'in', _fn(s), ':', i)
|
|
elif cmd == 'callers':
|
|
off = int(sys.argv[2]) # decimal byte offset, e.g. 196 for vtable[49]
|
|
pat = f'r11, {off}(r11)'
|
|
for (a,) in c.execute("SELECT address FROM instructions WHERE mnemonic='lwz' AND operands=? "
|
|
'ORDER BY address', [pat]).fetchall():
|
|
print(H(a), 'in', _fn(a))
|
|
elif cmd == 'grep':
|
|
sub = sys.argv[2]
|
|
for a, m, o in c.execute("SELECT address,mnemonic,operands FROM instructions "
|
|
"WHERE operands LIKE ? ORDER BY address", [f'%{sub}%']).fetchall():
|
|
print(H(a), m, o, ' in', _fn(a))
|
|
elif cmd == 'find':
|
|
w = int(sys.argv[2], 16)
|
|
for (a,) in c.execute('SELECT address FROM instructions WHERE raw=? ORDER BY address', [w]).fetchall():
|
|
print(H(a))
|
|
else:
|
|
print(__doc__)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|