summaryrefslogtreecommitdiff
path: root/arch/arm64/disassembler/disasm_test.py
diff options
context:
space:
mode:
authorRusty Wagner <rusty.wagner@gmail.com>2024-03-05 19:50:13 -0500
committerRusty Wagner <rusty.wagner@gmail.com>2024-03-05 20:34:34 -0500
commite093c21ed880ac3eb72119be15093ee04f8ce299 (patch)
tree9f720ebdc0ae415734b1199ed341668c69710a94 /arch/arm64/disassembler/disasm_test.py
parent0609276712622908254065546102381466033141 (diff)
Move architecture modules into the API repo
Diffstat (limited to 'arch/arm64/disassembler/disasm_test.py')
-rwxr-xr-xarch/arm64/disassembler/disasm_test.py240
1 files changed, 240 insertions, 0 deletions
diff --git a/arch/arm64/disassembler/disasm_test.py b/arch/arm64/disassembler/disasm_test.py
new file mode 100755
index 00000000..cd110c03
--- /dev/null
+++ b/arch/arm64/disassembler/disasm_test.py
@@ -0,0 +1,240 @@
+#!/usr/bin/env python3
+
+# run disassembly tests in test_cases.txt
+# ensure you built gofer.so (see Makefile-local)
+
+import os, sys, struct, ctypes, re
+
+RED = '\x1B[31m'
+GREEN = '\x1B[32m'
+NORMAL = '\x1B[0m'
+
+#------------------------------------------------------------------------------
+# disassemble
+#------------------------------------------------------------------------------
+
+cbuf = None
+pfunc_disasm = None
+
+def normalize(instxt):
+ #print('normalizing: %s' % instxt)
+ instxt = instxt.strip()
+
+ # collapse runs of whitespace to one space character
+ instxt = re.sub('\s+', ' ', instxt)
+
+ # remove comments
+ if ' //' in instxt:
+ instxt = instxt[0:instxt.find(' //')]
+
+ # change that range notation
+ # st4w {z14.s-z17.s}, p2, [x11, x19, lsl #2]
+ # ->
+ # st4w {z14.s, z15.s, z16.s, z17.s}, p2, [x11, x19, lsl #2]
+ m = re.search(r'{z(\d+)\.(.)-z(\d+)\.(.)}', instxt)
+ if m:
+ (lhs, suffix_a, rhs, suffix_b) = m.group(1,2,3,4)
+ assert suffix_a == suffix_b
+ (lhs, rhs) = (int(lhs), int(rhs))
+ if rhs-lhs+1 == 4:
+ replacement = '{z%d.%s, z%d.%s, z%d.%s, z%d.%s}' % \
+ (lhs, suffix_a, (lhs+1)%32, suffix_a, (lhs+2)%32, suffix_a, (lhs+3)%32, suffix_a)
+ elif rhs-lhs+1 == 3:
+ replacement = '{z%d.%s, z%d.%s, z%d.%s}' % \
+ (lhs, suffix_a, (lhs+1)%32, suffix_a, (lhs+2)%32, suffix_a)
+ instxt = instxt.replace(m.group(0), replacement)
+
+ # remove spaces from { list }
+ # eg: { v5.b, v6.b, v7.b, v8.b } -> {v5.b, v6.b, v7.b, v8.b}
+ instxt = re.sub(r'{ (.*?) }', r'{\1}', instxt)
+
+ # remove leading hex zeros
+ # 0x00000000071eb000 -> 0x71eb000
+ instxt = re.sub(r'0x00+', r'0x', instxt)
+
+ # decimal immediates to hex
+ # add x29, x15, x25, lsl #6 -> add x29, x15, x25, lsl #0x6
+ for dec_imm in re.findall(r'#\d+[,\]]', instxt):
+ hex_imm = '#0x%x' % int(dec_imm[1:-1]) + dec_imm[-1]
+ instxt = instxt.replace(dec_imm, hex_imm, 1)
+ for dec_imm in re.findall(r'#\d+$', instxt):
+ if not instxt.endswith(dec_imm): continue
+ hex_imm = '#0x%x' % int(dec_imm[1:])
+ instxt = instxt[0:-len(dec_imm)] + hex_imm
+
+ # #-3.375000000000000000e+00 -> #-3.375
+ for x in re.findall(r'#[-\+\.\de]{8,}', instxt):
+ instxt = instxt.replace(x, '#'+str(float(x[1:])))
+
+ # 0x0.000000 -> 0x0.0
+ instxt = instxt.replace('0.000000', '0.0')
+ instxt = instxt.replace('0.000', '0.0')
+
+ # lowercase everything
+ instxt = instxt.lower()
+
+ # done
+ return instxt
+
+def disassemble(addr, insnum):
+ global cbuf, pfunc_disasm
+ insword = struct.pack('<I', insnum)
+ cbuf.value = b'(uninitialized)'
+ pfunc_disasm(addr, insword, 4, ctypes.byref(cbuf), False)
+ return normalize(cbuf.value.decode('utf-8').strip())
+
+#------------------------------------------------------------------------------
+# slightly smarter than strcmp() disassembly comparison
+#------------------------------------------------------------------------------
+
+def compare_disassembly_token(a, b):
+ if a==b: return 0
+
+ trash = '#{}[]!,'
+ while a[0] in trash:
+ if a[0]!=b[0]: return -1
+ (a,b) = (a[1:], b[1:])
+ while a[-1] in trash:
+ if a[-1]!=b[-1]: return -1;
+ (a,b) = (a[:-1], b[:-1])
+
+ # x4.d is equivalent to x4
+ if a.startswith('x') and a.startswith(b) and a[len(b):] == '.d': return 0
+ if b.startswith('x') and b.startswith(a) and b[len(a):] == '.d': return 0
+ if a=='sp' and b=='sp.d': return 0
+ if a=='sp.d' and b=='sp': return 0
+
+ # cs is equivalent to hs (carry set vs. higher or same)
+ if (a,b)==('cs','hs') or (a,b)==('hs','cs'): return 0
+ # cc is equivalent to lo (carry clear vs. lower)
+ if (a,b)==('cc','lo') or (a,b)==('lo','cc'): return 0
+
+ #print('after trash removal:')
+ #print('a: ', a)
+ #print('b: ', b)
+ if a[0] in trash or b[0] in trash: return -1;
+ if a[-1] in trash or b[-1] in trash: return -1;
+ try:
+ # 0xff == 255
+ if '0x' in a: a_val = int(a,16)
+ elif '.' in a: a_val = float(a)
+ else: a_val = int(a)
+ if '0x' in b: b_val = int(b,16)
+ elif '.' in b: b_val = float(b)
+ else: b_val = int(b)
+
+ #print('a=%s, a_val=%d' % (a, a_val))
+ #print('b=%s, b_val=%d' % (b, b_val))
+ if a_val == b_val: return 0
+ # 0xbc == -68
+ if a.startswith('0x') and len(a)==4 and b.startswith('-'):
+ if struct.unpack('<b', struct.pack('<B', a_val))[0] == b_val: return 0
+ if b.startswith('0x') and len(b)==4 and a.startswith('-'):
+ if struct.unpack('<b', struct.pack('<B', b_val))[0] == a_val: return 0
+ # 0xfffffffe == -2
+ if a.startswith('0x') and len(a)==10 and b.startswith('-'):
+ if struct.unpack('<i', struct.pack('<I', a_val))[0] == b_val: return 0
+ if b.startswith('0x') and len(b)==10 and a.startswith('-'):
+ if struct.unpack('<i', struct.pack('<I', b_val))[0] == a_val: return 0
+ # 0xffffffffffffffff == -2
+ if a.startswith('0x') and len(a)==18 and b.startswith('-'):
+ if struct.unpack('<q', struct.pack('<Q', a_val))[0] == b_val: return 0
+ if b.startswith('0x') and len(b)==18 and a.startswith('-'):
+ if struct.unpack('<q', struct.pack('<Q', b_val))[0] == a_val: return 0
+
+ return -1
+
+ except ValueError:
+ return -1
+
+ return 0
+
+def compare_disassembly(a, b):
+ if (a and not b) or (b and not a): return -1
+ a = a.split()
+ b = b.split()
+ if len(a) != len(b): return -1
+ if a[0] != b[0]: return -1
+ for (ta,tb) in zip(a,b):
+ if compare_disassembly_token(ta,tb):
+ return -1
+ return 0
+
+def excusable_difference(actual, expected):
+ if actual=='dgh' and expected.startswith('hint'): return True
+ if actual=='cfinv' and expected.startswith('msr'): return True
+ if actual.startswith('mov') and expected.startswith('dupm'): return True # spec is screwed up here
+ if actual == 'sb' and expected.startswith('msr'): return True
+ if actual == 'xaflag' and expected.startswith('msr'): return True
+ if actual.startswith('at ') and expected.startswith('sys'): return True
+ if actual.startswith('dc ') and expected.startswith('sys'): return True
+ if actual.startswith('cfp ') and expected.startswith('sys'): return True
+ if actual.startswith('cmpp ') and expected.startswith('subps '): return True
+ if actual.startswith('tlbi') and expected.startswith('sys '): return True
+ if actual.startswith('msr ssbs') and expected.startswith('msr s0_'): return True
+ if actual.startswith('msr pan') and expected.startswith('msr s0_'): return True
+ if actual.startswith('axflag'): return True
+
+ return False
+
+#------------------------------------------------------------------------------
+# main
+#------------------------------------------------------------------------------
+
+ADDRESS_TEST = 0x8000000000000004
+
+def main():
+ global cbuf, pfunc_disasm
+ cbuf = ctypes.create_string_buffer(1024)
+ dll = ctypes.CDLL(os.path.join(os.path.dirname(__file__), 'gofer.so'))
+ pfunc_disasm = dll.disassemble
+ pfunc_disasm.restype = ctypes.c_int
+ pfunc_disasm.argtypes = [ctypes.c_uint64, ctypes.c_char_p, ctypes.c_uint, ctypes.c_void_p]
+
+ if sys.argv[1:] and re.match(r'^[0-9A-Fa-f]+$', sys.argv[1]):
+ insnum = int(sys.argv[1], 16)
+ print(disassemble(ADDRESS_TEST, insnum))
+ else:
+ regen = sys.argv[1:] and sys.argv[1] in ['gen', 'regen', 'generate']
+
+ with open('test_cases.txt') as fp:
+ lines = fp.readlines()
+
+ for i, line in enumerate(lines):
+ if line.startswith('// '):
+ print(line, end='')
+ continue
+ assert line[8] == ' '
+ insnum = int(line[0:8], 16)
+ actual = disassemble(ADDRESS_TEST, insnum)
+ expected = line[9:].rstrip()
+
+ if not regen:
+ print('%08X: -%s- vs -%s-' % (insnum, actual, expected))
+
+ if compare_disassembly(actual, expected):
+ if excusable_difference(actual, expected):
+ if regen:
+ print('%08X %s' % (insnum, expected))
+ continue
+
+ if regen:
+ if actual:
+ print('%08X %s' % (insnum, actual))
+ else:
+ print('%08X %s' % (insnum, expected))
+ else:
+ print('0x%08X' % insnum)
+ print(' actual:', RED, actual, NORMAL)
+ print('expected:', GREEN, expected, NORMAL)
+ print('line %d/%d (%.2f%%)' % (i, len(lines), i/len(lines)*100))
+ sys.exit(-1)
+ else:
+ if regen:
+ print(line.rstrip())
+
+ if not regen:
+ print('PASS')
+
+if __name__ == '__main__':
+ main()