mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-18 10:00:26 +02:00
121 lines
4.3 KiB
Python
121 lines
4.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Context diff between two emission sites.
|
|
|
|
When the SAME source spelling produces different bytes at two sites (the
|
|
vehicle_stuck / code_0003a910 fold pair), the lever is in the context, not the
|
|
spelling. This tool measures the context of a window in two functions and
|
|
reports the deltas: instruction mix before/after, x87 depth at entry, address
|
|
materialisations (lea/push of stack slots) preceding the window, and distance
|
|
to the nearest call on each side. It proposes nothing; it narrows where to
|
|
look.
|
|
|
|
Usage:
|
|
python tools/context_diff_oracle.py \
|
|
--a-unit source/units/vehicles --a-function _vehicle_stuck --a-lo 0xe5 --a-hi 0xff \
|
|
--b-unit source/ai/ai_debug --b-function _code_0003a910 --b-lo 0x201 --b-hi 0x21b
|
|
"""
|
|
import argparse
|
|
import os
|
|
import sys
|
|
from collections import Counter
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import coff_compare as cc
|
|
import capstone
|
|
|
|
MD = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_32)
|
|
|
|
X87_PUSH = ('fld', 'fild', 'fld1', 'fldz')
|
|
X87_POP = ('fstp', 'fistp', 'faddp', 'fsubp', 'fsubrp', 'fmulp', 'fdivp',
|
|
'fdivrp', 'fcompp')
|
|
|
|
|
|
def load_function(unit, function, root='.'):
|
|
path = os.path.join(root, 'build', 'split', unit.replace('/', os.sep) + '.obj')
|
|
coff = cc.load(path)
|
|
symbol = cc.symbol(coff, function)
|
|
section = coff['sections'][symbol['section'] - 1]
|
|
raw = bytes(cc._section_bytes(coff, section)[:section['size']])
|
|
return list(MD.disasm(raw, 0))
|
|
|
|
|
|
def x87_depth_at(instructions, offset):
|
|
depth = 0
|
|
for ins in instructions:
|
|
if ins.address >= offset:
|
|
break
|
|
m = ins.mnemonic
|
|
if m.startswith(X87_PUSH) and not m.startswith('fstp'):
|
|
depth += 1
|
|
if m.startswith(X87_POP) or m == 'fcompp':
|
|
depth -= 1
|
|
if m == 'fcompp':
|
|
depth -= 1
|
|
if m == 'fdivr' or m == 'fdiv' or m == 'fmul' or m == 'fadd' or m == 'fsub':
|
|
pass # memory-operand forms keep depth
|
|
return depth
|
|
|
|
|
|
def describe(instructions, lo, hi, tag):
|
|
before = [i for i in instructions if lo - 0x30 <= i.address < lo]
|
|
window = [i for i in instructions if lo <= i.address < hi]
|
|
after = [i for i in instructions if hi <= i.address < hi + 0x18]
|
|
report = {'tag': tag}
|
|
report['x87_depth_at_entry'] = x87_depth_at(instructions, lo)
|
|
report['before_mix'] = Counter(i.mnemonic for i in before)
|
|
report['after_mix'] = Counter(i.mnemonic for i in after)
|
|
report['addr_escapes_before'] = [
|
|
'%04x %s %s' % (i.address, i.mnemonic, i.op_str)
|
|
for i in before if i.mnemonic in ('lea', 'push') and 'ebp -' in i.op_str]
|
|
calls_before = [i.address for i in before if i.mnemonic == 'call']
|
|
calls_after = [i.address for i in after if i.mnemonic == 'call']
|
|
report['nearest_call'] = (
|
|
(lo - calls_before[-1]) if calls_before else None,
|
|
(calls_after[0] - hi) if calls_after else None)
|
|
report['window'] = ['%04x %-7s %s' % (i.address, i.mnemonic, i.op_str)
|
|
for i in window]
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
for side in 'ab':
|
|
parser.add_argument('--%s-unit' % side, required=True)
|
|
parser.add_argument('--%s-function' % side, required=True)
|
|
parser.add_argument('--%s-lo' % side, required=True)
|
|
parser.add_argument('--%s-hi' % side, required=True)
|
|
parser.add_argument('--root', default='.')
|
|
args = parser.parse_args()
|
|
|
|
reports = []
|
|
for side in 'ab':
|
|
unit = getattr(args, '%s_unit' % side)
|
|
function = getattr(args, '%s_function' % side)
|
|
lo = int(getattr(args, '%s_lo' % side), 16)
|
|
hi = int(getattr(args, '%s_hi' % side), 16)
|
|
instructions = load_function(unit, function, args.root)
|
|
reports.append(describe(instructions, lo, hi, '%s:%s' % (unit, function)))
|
|
|
|
a, b = reports
|
|
print('=== A', a['tag'])
|
|
print('=== B', b['tag'])
|
|
print('x87 depth at entry: A=%d B=%d' % (
|
|
a['x87_depth_at_entry'], b['x87_depth_at_entry']))
|
|
print('nearest call (before, after): A=%s B=%s' % (
|
|
a['nearest_call'], b['nearest_call']))
|
|
print('address materialisations before window:')
|
|
print(' A:', a['addr_escapes_before'] or 'none')
|
|
print(' B:', b['addr_escapes_before'] or 'none')
|
|
mix_delta = Counter(a['before_mix'])
|
|
mix_delta.subtract(b['before_mix'])
|
|
print('before-mix delta (A minus B):',
|
|
{k: v for k, v in mix_delta.items() if v} or 'identical')
|
|
mix_delta = Counter(a['after_mix'])
|
|
mix_delta.subtract(b['after_mix'])
|
|
print('after-mix delta (A minus B):',
|
|
{k: v for k, v in mix_delta.items() if v} or 'identical')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|