Files
templates/python/set_devices/disassembly.py

108 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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'])