import base64 # Декодирует загружаемые секции из официального XML TI OFD, сохраняя адреса LOAD и RUN. from pathlib import Path # Сохраняет изменения только в явно переданном результате текущей сборки. import struct # Упаковывает адресуемые 16-битные слова C28x младшей октетой вперёд. import subprocess # Вызывает локальный инспектор объектных файлов без доступа к JTAG. import xml.etree.ElementTree as ET # Разбирает фактические секции COFF независимо от соседней карты линкера. import zlib # CRC32 IEEE совпадает с проверкой постоянного загрузчика и учитывает незаписанные слова 0xFFFF. APP_START = 0x300000 # Сектор H начинает область приложения; аппаратный boot остаётся в секторе A. HEADER_WORDS = 16 # Заголовок версии 1 занимает 16 слов C28x, то есть 32 физических октета. APP_DATA = APP_START + HEADER_WORDS # Исполняемые секции следуют после служебного заголовка приложения. APP_STOP = 0x338000 # Исключённая верхняя граница совпадает с началом защищённого сектора A. def descriptor(entry, loaded): # Создаёт проверяемое описание Flash-приложения без сектора постоянного загрузчика. if not loaded or any(not APP_START <= address < APP_STOP for address in loaded): # Любое слово в секторе A, CSM, OTP или RAM делает образ непригодным для обновления приложения. raise ValueError('Flash-приложение пересекает защищённый загрузчик или выходит за область 0x300000–0x337FFF') # Отказ происходит до стирания физической памяти. end = max(loaded) + 1 # Верхняя граница включает все Flash LOAD-секции, в том числе копируемый в RAM код API. if not APP_DATA <= entry < end or entry not in loaded: # Точка запуска обязана указывать на действительно присутствующее слово кода после заголовка. raise ValueError('Точка входа Flash-приложения отсутствует в загружаемом коде') # Исключает пустой заголовок и устаревшую RAM-сборку. payload = struct.pack('<' + 'H' * (end - APP_DATA), *(loaded.get(address, 0xFFFF) for address in range(APP_DATA, end))) # Пробелы между секциями должны оставаться стёртыми; порядок октетов соответствует C28x. crc = zlib.crc32(payload) # Контрольная сумма защищает весь непрерывный диапазон приложения, включая промежутки. values = (0x4146, 0x3142, 1, HEADER_WORDS, entry & 65535, entry >> 16, APP_DATA & 65535, APP_DATA >> 16, end & 65535, end >> 16, crc & 65535, crc >> 16, (~entry) & 65535, ((~entry) >> 16) & 65535, 0x5AA5, 0xA55A) # Первые четыре физических октета образуют ASCII FAB1; далее следуют версия, размер, адреса, CRC, инверсия entry и маркеры завершения. return struct.pack('<16H', *values) # Передаёт ровно 32 октета без платформенного выравнивания структуры. def validate_words(entry, loaded): # Одинаково проверяет содержимое JTAG COFF и транспортной таблицы RS Flash. if any(APP_START + index not in loaded for index in range(HEADER_WORDS)): # Все слова заголовка должны присутствовать явно, а не получаться заполнением 0xFFFF. raise ValueError('Нет полного заголовка Flash-приложения FAB1; требуется новая сборка под постоянный загрузчик') # Старый образ с codestart по 0x33FFF6 не допускается. actual = struct.pack('<16H', *(loaded[APP_START + index] for index in range(HEADER_WORDS))) # Получает описание именно из передаваемых данных. if actual != descriptor(entry, loaded): # Проверяет CRC, границы, обе половины entry и маркеры одной независимой реконструкцией. raise ValueError('Заголовок или CRC32 Flash-приложения FAB1 не соответствует данным') # Повреждение нельзя скрыть корректным именем файла или внешним паспортом. def parse_sci8(data): # Разбирает только приложение ниже сектора A, не разрешая аппаратный переход ROM. if len(data) < 24 or len(data) % 2 or data[:2] != b'\xaa\x08': # SCI8 имеет ключ 0x08AA, 22-октетный заголовок и чётный терминатор. raise ValueError('Неверный заголовок или размер Flash SCI8') # Обычный бинарный дамп и COFF не являются таблицей адресных блоков TI. def address(offset): # Адрес TI состоит из старшего 16-битного слова, затем младшего; каждое слово little endian. high, low = struct.unpack_from(' len(data): # Адрес и все слова должны помещаться в текущем файле. raise ValueError('Обрезанный блок Flash SCI8') # Не читает случайные байты за границей изображения. start = address(offset) # Получает абсолютный адрес назначения очередного блока. offset += 4 # Пропускает две половины абсолютного адреса. if not APP_START <= start < start + count <= APP_STOP: # Сектор A и весь аппаратный boot исключены независимо от имени секции. raise ValueError(f'Запрещённый Flash-диапазон 0x{start:06X}, {count} слов: загрузчик защищён') # Показывает точное пересечение до начала обновления. values = struct.unpack_from('<' + 'H' * count, data, offset) # Читает только полностью проверенный блок. if any(start + index in loaded for index in range(count)): # Дублирование адреса могло бы скрыть подмену заголовка поздним блоком. raise ValueError('Перекрывающиеся блоки Flash SCI8') # Запрещает неоднозначный порядок программирования. loaded.update((start + index, value) for index, value in enumerate(values)) # Сохраняет каждое слово по его физическому LOAD-адресу. blocks.append((start, data[offset:offset + count * 2])) # Сохраняет исходные октеты без изменения порядка секций. offset += count * 2 # Переходит к размеру следующего блока. raise ValueError('Нет терминатора Flash SCI8') # Обрезанный файл не считается полным приложением. def coff_sections(document): # Извлекает адреса и точные октеты из результата TI ofd2000 -x. coff = document.find('.//ti_coff') # Рабочая прошивка F28335 использует COFF ABI. if coff is None or coff.findtext('file_header/target_id') != '0x9d' or coff.findtext('file_header/exec') != 'true': # Проверяет процессор и признак связанного исполняемого файла. raise ValueError('Требуется исполняемый COFF C2000') # Объектные модули и файлы другой архитектуры не программируются. entry = int(coff.findtext('optional_file_header/entry_point', '0'), 0) # Entry задаёт запуск приложения через общий Flash boot. sections, loaded = {}, {} # Отдельно хранит XML полей секций и адресное пространство программируемых слов. for section in coff.findall('section'): # Рассматривает все секции, включая LOAD для функций, исполняемых из RAM. sections[section.findtext('name')] = section # Служебный заголовок находится по стабильному имени .flash_app. raw, start = section.findtext('raw_data'), int(section.findtext('virtual_addr', '0'), 0) # OFD называет virtual адресом LOAD, physical — RUN. if raw is None or section.findtext('copy') == 'true': # Только явно незагружаемые COPY/отладочные секции и секции без данных исключаются из программирования. continue # Их наличие не расширяет область разрешённого программирования. payload = base64.b64decode(raw) # Получает точные октеты вместо доверия к соседней MAP. if len(payload) != int(section.findtext('raw_data_size', '0'), 0) or len(payload) % 2 or not APP_START <= start < start + len(payload) // 2 <= APP_STOP: # Любая загружаемая секция обязана целиком находиться ниже A; RAM, OTP, CSM и несогласованный размер запрещены. raise ValueError('COFF содержит запрещённую Flash-секцию: ' + str(section.findtext('name'))) # Старый самостоятельный codestart будет отклонён. for index, value in enumerate(struct.unpack('<' + 'H' * (len(payload) // 2), payload)): # Восстанавливает 16-битное представление каждого слова C28x. if start + index in loaded: # Две секции не должны программировать одно физическое слово. raise ValueError('Перекрывающиеся LOAD-секции COFF') # Сохраняет единственность заголовка и однозначную контрольную сумму. loaded[start + index] = value # Собирает ожидаемый Flash-образ для проверки CRC и конверсии SCI8. return entry, loaded, sections # Метаданные нужны как подписанию, так и независимой проверке готового OUT. def seal_coff(image, inspector): # Заполняет только заранее выделенный linker заголовок после успешной линковки. image = Path(image) # Принимает путь текущей сборки без зависимости от рабочего каталога. document = ET.fromstring(subprocess.check_output([str(inspector), '-x', str(image)])) # Доверяет размещению секций, подтверждённому официальным TI-инспектором. entry, loaded, sections = coff_sections(document) # Отсекает любой LOAD в сектор A до изменения двоичного файла. section = sections.get('.flash_app') # Секция размером 16 слов резервируется linker и не меняет адресов остальной программы. if section is None or int(section.findtext('virtual_addr', '0'), 0) != APP_START or int(section.findtext('raw_data_size', '0'), 0) != HEADER_WORDS * 2: # Нельзя добавлять заголовок поверх машинного кода. raise ValueError('Linker должен выделить .flash_app из 16 слов по адресу 0x300000') # Ошибка требует пересборки вместо небезопасного смещения программы. offset = int(section.findtext('file_offsets/raw_data_ptr', '0'), 0) # OFD указывает точное файловое смещение содержимого секции COFF. raw, signed = bytearray(image.read_bytes()), descriptor(entry, loaded) # Считает CRC уже окончательно связанных Flash LOAD-секций. if offset <= 0 or offset + len(signed) > len(raw): # Проверяет размер файла до адресной записи заголовка. raise ValueError('Неверное смещение заголовка COFF') # Повреждённые метаданные не могут изменить постороннюю часть OUT. raw[offset:offset + len(signed)] = signed # Меняет только зарезервированные 32 октета, не трогая символы, адреса и отладочную информацию. image.write_bytes(raw) # Сохраняет подписанный результат до конверсии в SCI8 и создания внешнего SHA-256. loaded.update((APP_START + index, value) for index, value in enumerate(struct.unpack('<16H', signed))) # Приводит адресную модель к записанному заголовку. validate_words(entry, loaded) # Независимо проверяет только что сформированный результат.