Исследование / 13.09.2026

Энергонезависимая память, тревоги и журнал

Источник: C:\dump\fw_128k.bin, ARM Thumb little-endian, адрес загрузки 0. Адреса ниже относятся к этому бинарнику. Сопоставлены инструкции из C:\dump\Untitled-1.txt и новая декомпиляция C:\Temp\gas-re-tools\decompiled\functions. Имена функций FUN_... — автоматически присвоенные адресные имена, а смысловые названия ниже — результат анализа.

Что важно для новой прошивки

Внешняя память: подтверждено инструкциями

АдресВосстановленная операцияДоказательство
0xEC50Установить номер банка 0Аргумент игнорируется; 0xEC50–0xEC54 записывают ноль в 0x20000058
0xEC5CЧтение одного байтаSTART, управляющий байт 0xA0, два адресных байта, повторный START, 0xA1, приём и STOP
0xECF0Чтение блокаСохраняет байты в буфер; приём разбит на порции по 64; адрес маскируется 0x7FFF
0xEECCЗапись одного байтаSTART, 0xA0, старший/младший адрес, байт данных; проверки ответа после передачи
0xEF74Запись блока с учётом границы страницы64 - (address & 63) для первой порции, затем по 64 байта; повторный START/STOP между страницами
0x7FA0, 0x7FE8START и STOPИзменение линии данных при высокой линии тактирования
0x8020, 0x7F30Передать и принять байтПобитовый цикл из восьми тактов и отдельный ACK/NACK
0xF110Инициализация линий памятиGPIOA, маски 0x800, 0x1000, 0x200

Это интерфейс I²C с 7-битным адресом 0x50. Программно используются 15 адресных бит, то есть 32 КиБ, и страницы по 64 байта. Такая организация совместима с 24C256; точную маркировку микросхемы по коду установить нельзя.

Из локального SDK fm33lg0xx.h (строки 1006–1008): GPIOA=0x40000C00, GPIOB=0x40000C40, GPIOC=0x40000C80. Поэтому линии памяти — PA11=SCL, PA12=SDA. Например, 0x7F4E–0x7F7C переключают PA11 и читают PA12. PA9 опускается перед записью и поднимается после; роль WP весьма вероятна, но требует проверки дорожкой до микросхемы.

Внешняя память никак не является второй половиной fw_128k.bin и не может быть получена чтением адреса Flash 0x10000.

Восстановленные области памяти

Адрес внешней памятиРазмерНазначение и доказательство
0x0000 + 0x39*i, i=0..357 на канал, всего 228Калибровочная структура в RAM 0x200014E8 + 57*i; чтение 0x11152–0x11158, запись 0x1EA2–0x1EB4
0x00E91Маркер 0x5A, разрешающий восстановление сохранённых заводских значений; проверка 0x1115C–0x1116A, запись 0x1141A–0x11420
0x0201 + 0x13*i19 на каналНастройки канала в RAM 0x20000EA3 + 19*i; чтение 0x64AC–0x64C0, запись 0x6688–0x669E
0x024D + 6*i6 на каналЕщё один каналозависимый блок, RAM 0x20000E8B + 6*i; 0x69F4–0x6A22. Поля ещё не названы
0x12001Маркер журнала 0x5A; проверка в 0x21B8–0x21C8, запись при очистке 0x1F3C–0x1F42
0x12012Число записей little-endian; 0x13B8–0x13CC
0x1203 + 14*n14 на записьЗаписи тревог, n=0..1999; 0x13CE–0x13E4

Последняя допустимая запись занимает 0x7F55–0x7F62; следующий свободный адрес 0x7F63, всё помещается в 32 КиБ.

Текущие и заводские поля

0x112E8 читает калибровки и настройки, переносит текущие поля в запасные и сохраняет общий блок, затем ставит маркер 0x5A по 0xE9. 0x11150 выполняет обратный перенос при наличии этого маркера. Внутри 57-байтной калибровочной структуры:

Текущее полеСохранённое заводское полеРазмер
+0x06+0x102
+0x16+0x202
+0x14+0x1E2
+0x31+0x354
+0x28+0x2A2
Настройки канала +0x0E (H)Калибровки +0x242
Настройки канала +0x10 (L)Калибровки +0x262

При восстановлении код также очищает первые пять элементов двух массивов 16-битных точек (+0x04 и +0x14), затем заполняет опорные элементы из сохранённых полей. Это существенно для проектирования своей операции «заводские настройки»: она меняет и таблицу калибровки, и пороги.

Тревоги

Основной фрагмент 0xC1F6–0xC6D0 читает:

РежимНорма, состояние 0Ступень L, состояние 1Ступень H, состояние 2
Каналы 0, 2, 3; канал 1 при типе !=1x < LL <= x < Hx >= H
Канал 1 при типе 1L <= x < Hx < Lx >= H

Доказательства: сравнения 0xC230–0xC232, 0xC2A8–0xC2CE, 0xC34A–0xC34C; специальная ветка 0xC376–0xC39E, 0xC466–0xC490, 0xC4D8–0xC4DA. Границы различаются: при равенстве L обычный канал уже тревожится, кислородный ещё в норме; равенство H вызывает высокую тревогу.

В специальной ветке есть дополнительная проверка первого 16-битного поля калибровки: если оно ноль, эта ветка тревог вообще пропускается (0xC39C–0xC39E). Предыдущее состояние при этом не обязательно очищается. Смысл самого поля уточняется в анализе измерений; поведение нельзя переносить без проверки.

Видимых отдельных порогов отпускания, смещений гистерезиса, задержек входа и накопления числа превышений в этом фрагменте нет. Фильтрация до него может сглаживать вход, но не является подтверждением отдельного гистерезиса.

При входе в H записывается состояние 2 и событие 0x10; при входе в L — состояние 1 и событие 0x01. При неизменной ступени записи не повторяются. Переход H→L создаёт событие L, если показание осталось в диапазоне L; возвращение в норму в этом фрагменте не записывается. После обработки индекс канала увеличивается по кругу 0..3.

Значения из инициализированной RAM

Из evidence/startup_ram.bin, полученной основным исследованием распаковки стартовых данных:

КаналКод газаДесятичных знаков (config+3)HL
0005020
111235195
2202010
33020050

При масштабе одного десятичного знака значения канала 1 — 23.5 и 19.5. Эти числа — встроенные исходные значения. Они не доказывают, какие пороги сейчас находятся в конкретном приборе: загрузка из внешней памяти их заменяет.

Выходные сигналы и приоритет

0x1534 обрабатывает все четыре состояния. Наличие любого состояния 2 выбирается раньше состояния 1. Есть разные последовательности тактов: высокие тревоги используют параметры 3/4/4 и повтор через 14 вызовов; низкие — 3/4/6 и повтор через 12 вызовов (0x15A4–0x15E6, 0x1702, 0x175A). Переводить эти вызовы в миллисекунды можно только после проверки вызывающего планировщика.

Функция управляет PC4, PA6, PB5 и PC1. Точное соответствие «звук / вибрация / свет» требует трассировки платы. Byte 0x200000ED подавляет включение PC4 в нескольких ветках (0x1600–0x160E, 0x167E–0x168C, 0x16BE–0x16CC), что соответствует функции отключения одного вида оповещения. Bytes 0x200000ED и 0x200000EE сбрасываются, когда все четыре состояния нормальны (0xC694–0xC6BA).

Формат и жизненный цикл журнала

0x1360(channel, type) формирует 14 байт:

СмещениеПолеУверенность
0Индекс каналаПодтверждено аргументом и чтением в просмотрщике
1Неинициализированный/резервный байтВ просмотренном писателе запись отсутствует
2Маркер 0x5AПодтверждено
30x10=H, 0x01=LПодтверждено ветками тревог и просмотрщиком
4–5Последнее значение H, little-endianПри H обновляется перед вызовом писателя
6–7Последнее значение L, little-endianПри L обновляется перед вызовом писателя
8–10Три компонента датыИсточник RAM 0x20000E8A, 0x20000E88, 0x20000E89
11–13Три компонента времениИсточник RAM 0x20000E85..0x20000E87

При событии H валидным актуальным значением является поле 4–5; при L — 6–7. Второе поле копируется из общей структуры и может содержать старое значение, в том числе от другого канала. Просмотрщик 0x2188 выбирает нужное поле по байту типа (0x2250–0x22FA).

Сначала пишется запись, затем число записей. Результаты этих операций в 0x1360 не проверяются. При достижении 2000 код записывает счётчик 0 и переходит в состояние интерфейса 150 (0x13F2–0x1426). Поэтому это не доказанный полноценный кольцевой журнал с сохранённым числом заполненных записей. При следующей записи адрес опять начнётся с 0x1203; просмотрщик при числе 0 считает журнал пустым (0x21CA–0x21D0). Это важный кандидат на улучшение.

Очистка журнала (0x1F18, 0x1FA8) ставит маркер 0x5A и обнуляет число по 0x1201; все старые записи физически не стирает.

Текущий просмотр проверяет маркеры и допустимость отдельных полей; CRC целой записи в рассмотренных функциях не найден. Возможное наличие отдельного журнала ошибок по одной строке меню не подтверждается: найден и связан с реальной логикой именно журнал газовых тревог.

Что улучшить в собственной реализации

Проверка

verify_alarm.py исполняет исходный фрагмент 0xC1F6–0xC6D0 в ARM-эмуляторе с контролируемой RAM; только вызов внешнего писателя 0x1360 заменён сбором аргументов. Все 108 проверок прошли на Linux, Python 3.13, Unicorn 2.1.4: 90 комбинаций границ/предыдущих состояний, сохранение отключения при тревоге другого канала, 9 вариантов нулевого поля калибровки и 8 проверок исходного писателя журнала 0x1360–0x1434.

В восьми проверках журнала исполняется настоящий писатель, а на границах внешних подсистем подставляются EEPROM, обновление RTC и вывод интерфейса. Для счётчиков 0, 1998, 1999 и 2000 проверены успешная и неуспешная запись памяти. Подтверждены адреса записей, обнуление счётчика после 2000-й, продолжение обновления счётчика даже при возвращённой ошибке записи и попадание неинициализированного байта стека в поле +1. Эти результаты сохранены в emulation_results.json.

Windows-сборки Unicorn 2.1.4/2.0.1 на данном компьютере завершались с ошибкой во время выделения памяти/старта эмуляции; эти попытки не учитываются как проверки. Успешный независимый запуск выполнен в WSL.

Программное моделирование не проверяет физические выходы, неисправности датчиков и реальный EEPROM. Для окончательного сопоставления с конкретным аппаратом нужны отдельный дамп внешней памяти и измерения платы.