Files
templates/python/set_devices/disassembly.py
T

108 lines
5.5 KiB
Python

"""TI C28x tool adapter. Decode instructions using TI's own assembler tools."""
from pathlib import Path
import subprocess
import tempfile
import os
import re
def align_disassembly(*texts):
"""Align TI listings by word offset even when instruction sizes differ."""
listings = []
for text in texts:
if text.startswith('Ошибка:'):
return texts
listing = {}
previous = None
for line in text.splitlines():
match = re.match(r'^([0-9A-Fa-f]{8})\s+', line)
if match:
previous = int(match.group(1), 16)
listing[previous] = line
elif previous is not None and line.strip():
listing[previous] += ' ' + line.strip()
if not listing:
return texts
listings.append(listing)
addresses = sorted({address for listing in listings for address in listing})
return tuple('\n'.join(listing.get(address, f'{address:08X} —') for address in addresses)
for listing in listings)
def find_ghidra(root):
configured = os.environ.get('GHIDRA_INSTALL_DIR', '')
if configured and (Path(configured) / 'support/analyzeHeadless.bat').is_file():
return configured
return next((str(path) for path in sorted(Path(root).glob('ghidra_*'), reverse=True)
if (path / 'support/analyzeHeadless.bat').is_file()), '')
def decompile_c28x(data: bytes, tools: str, base: int = 0) -> str:
"""Run the configured Ghidra C28x processor and decompiler locally."""
root = Path(tools)
headless = root / 'support/analyzeHeadless.bat'
if not tools or not headless.is_file():
raise ValueError('Укажите папку Ghidra с установленным процессором TMS320C28x.')
if not data or len(data) % 2 or len(data) > 262144:
raise ValueError('Нужен непустой чётный диапазон до 256 КиБ.')
if not 0 <= base <= 0x3fffff:
raise ValueError('Адрес C28x должен быть в диапазоне 0…0x3FFFFF (в словах).')
scripts = Path(__file__).parent / 'disassembly_scripts'
env = dict(os.environ)
jdks = sorted(path for path in root.parent.glob('jdk*')
if path.is_dir() and (path / 'bin/java.exe').is_file())
if jdks:
env['JAVA_HOME'] = str(jdks[-1])
with tempfile.TemporaryDirectory(prefix='setgui-decompile-') as folder:
work = Path(folder)
binary, output = work / 'range.bin', work / 'result.c'
binary.write_bytes(data)
arguments = [str(headless), str(work), 'analysis', '-import', str(binary),
'-processor', 'TMS320C28x:LE:32:default', '-loader', 'BinaryLoader',
'-loader-baseAddr', f'{base:x}', '-noanalysis', '-scriptPath', str(scripts),
'-postScript', 'SetguiExportC.java', str(output), '-deleteProject']
result = subprocess.run(arguments, env=env, capture_output=True, text=True,
errors='replace', timeout=180, stdin=subprocess.DEVNULL,
creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
if result.returncode or not output.is_file():
raise RuntimeError((result.stderr + '\n' + result.stdout)[-6000:])
text = output.read_text(encoding='utf-8')
if not text.rstrip().endswith('/* SETGUI_DECOMPILE_SUCCESS */'):
raise RuntimeError('Декомпиляция не завершена. ' + (result.stderr + '\n' + result.stdout)[-6000:])
return text.removesuffix('/* SETGUI_DECOMPILE_SUCCESS */\n')
def find_c28x_tools():
candidates = sorted(Path('C:/ti').glob('ti-cgt-c2000_*/bin'), reverse=True)
candidates += sorted(Path('C:/ti').glob('ccs*/ccs/tools/compiler/ti-cgt-c2000_*/bin'), reverse=True)
return next((str(path) for path in candidates
if (path / 'cl2000.exe').is_file() and (path / 'dis2000.exe').is_file()), '')
def disassemble_c28x(data: bytes, tools: str) -> str:
"""Wrap raw little-endian words in a temporary text section for dis2000."""
if not data or len(data) % 2:
raise ValueError('Для C28x нужен непустой диапазон с чётным числом байтов.')
if len(data) > 262144:
raise ValueError('Выберите диапазон не более 256 КиБ.')
directory = Path(tools)
for executable in ('cl2000.exe', 'dis2000.exe'):
if not (directory / executable).is_file():
raise ValueError(f'Не найден {executable} в {directory}')
with tempfile.TemporaryDirectory(prefix='setgui-disasm-') as folder:
root = Path(folder)
source = root / 'dump.asm'
source.write_text(' .text\n' + '\n'.join(
f' .word 0x{int.from_bytes(data[i:i + 2], "little"):04X}'
for i in range(0, len(data), 2)) + '\n', encoding='ascii')
def run(arguments):
result = subprocess.run(arguments, cwd=root, capture_output=True,
text=True, errors='replace', timeout=30,
creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
if result.returncode:
raise RuntimeError(result.stderr or result.stdout or 'Ошибка TI tools')
return result.stdout
run([str(directory / 'cl2000.exe'), '--silicon_version=28', '--compile_only',
'--output_file=dump.obj', str(source)])
return run([str(directory / 'dis2000.exe'), '--data_as_text', 'dump.obj'])