"""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'])