Files
Cesarus85--ringfall-mjolnir-vr/tools/context_diff_oracle.py
T

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()