Энергонезависимая память, тревоги и журнал
Источник: C:\dump\fw_128k.bin, ARM Thumb little-endian, адрес загрузки 0. Адреса ниже относятся к этому бинарнику. Сопоставлены инструкции из C:\dump\Untitled-1.txt и новая декомпиляция C:\Temp\gas-re-tools\decompiled\functions. Имена функций FUN_... — автоматически присвоенные адресные имена, а смысловые названия ниже — результат анализа.
Что важно для новой прошивки
- Код обращается к отдельной последовательной энергонезависимой памяти. В Flash микроконтроллера есть алгоритмы и исходные значения, но текущие нули, коэффициенты, настройки и журнал нужно читать отдельно с этой памяти.
- Для обычных каналов есть две ступени превышения: предварительная и высокая. Для канала 1 при типе газа 1 работает другая логика — недостаток либо избыток. Просто один верхний порог для всех не повторит поведение прибора.
- Журнал создаётся при входе в ступень тревоги и содержит канал, ступень, значение и дату/время. Повторяющаяся выборка в той же ступени новую запись не создаёт.
- Восстановлены отдельные текущие и заводские значения калибровки; восстановление заводских данных не равно простому обнулению коэффициентов.
- Унаследовать стоит функции; форматы хранения и некоторые пограничные решения требуют улучшения. В исследованных ветках нет самостоятельного гистерезиса, проверок CRC и атомарного подтверждения записи.
Внешняя память: подтверждено инструкциями
| Адрес | Восстановленная операция | Доказательство |
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, 0x7FE8 | START и 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..3 | 57 на канал, всего 228 | Калибровочная структура в RAM 0x200014E8 + 57*i; чтение 0x11152–0x11158, запись 0x1EA2–0x1EB4 |
0x00E9 | 1 | Маркер 0x5A, разрешающий восстановление сохранённых заводских значений; проверка 0x1115C–0x1116A, запись 0x1141A–0x11420 |
0x0201 + 0x13*i | 19 на канал | Настройки канала в RAM 0x20000EA3 + 19*i; чтение 0x64AC–0x64C0, запись 0x6688–0x669E |
0x024D + 6*i | 6 на канал | Ещё один каналозависимый блок, RAM 0x20000E8B + 6*i; 0x69F4–0x6A22. Поля ещё не названы |
0x1200 | 1 | Маркер журнала 0x5A; проверка в 0x21B8–0x21C8, запись при очистке 0x1F3C–0x1F42 |
0x1201 | 2 | Число записей little-endian; 0x13B8–0x13CC |
0x1203 + 14*n | 14 на запись | Записи тревог, n=0..1999; 0x13CE–0x13E4 |
Последняя допустимая запись занимает 0x7F55–0x7F62; следующий свободный адрес 0x7F63, всё помещается в 32 КиБ.
Текущие и заводские поля
0x112E8 читает калибровки и настройки, переносит текущие поля в запасные и сохраняет общий блок, затем ставит маркер 0x5A по 0xE9. 0x11150 выполняет обратный перенос при наличии этого маркера. Внутри 57-байтной калибровочной структуры:
| Текущее поле | Сохранённое заводское поле | Размер |
+0x06 | +0x10 | 2 |
+0x16 | +0x20 | 2 |
+0x14 | +0x1E | 2 |
+0x31 | +0x35 | 4 |
+0x28 | +0x2A | 2 |
Настройки канала +0x0E (H) | Калибровки +0x24 | 2 |
Настройки канала +0x10 (L) | Калибровки +0x26 | 2 |
При восстановлении код также очищает первые пять элементов двух массивов 16-битных точек (+0x04 и +0x14), затем заполняет опорные элементы из сохранённых полей. Это существенно для проектирования своей операции «заводские настройки»: она меняет и таблицу калибровки, и пороги.
Тревоги
Основной фрагмент 0xC1F6–0xC6D0 читает:
- текущий канал
i: byte0x200000F8; - текущее значение
x: signed 16-bit0x20002044 + 12*i; - H: unsigned 16-bit
0x20000EA3 + 19*i + 0x0E; - L: unsigned 16-bit
0x20000EA3 + 19*i + 0x10; - предыдущее состояние: byte
0x200000CA + i, значения 0/1/2.
| Режим | Норма, состояние 0 | Ступень L, состояние 1 | Ступень H, состояние 2 |
| Каналы 0, 2, 3; канал 1 при типе !=1 | x < L | L <= x < H | x >= H |
| Канал 1 при типе 1 | L <= x < H | x < L | x >= 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) | H | L |
| 0 | 0 | 0 | 50 | 20 |
| 1 | 1 | 1 | 235 | 195 |
| 2 | 2 | 0 | 20 | 10 |
| 3 | 3 | 0 | 200 | 50 |
При масштабе одного десятичного знака значения канала 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 | Подтверждено |
| 3 | 0x10=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 целой записи в рассмотренных функциях не найден. Возможное наличие отдельного журнала ошибок по одной строке меню не подтверждается: найден и связан с реальной логикой именно журнал газовых тревог.
Что улучшить в собственной реализации
- Ввести явный тип пороговой схемы: две верхние ступени или допустимый интервал. Не привязывать смысл к номеру физического канала.
- Разделить состояния газа, неисправности датчика, низкого заряда и оповещателей; задать приоритеты одновременных событий.
- Добавить настраиваемый гистерезис и проверяемую политику подавления повторов. Проверить значения ровно на L/H и дрожание около них.
- Для отключения звука задать явные правила: что продолжает работать, когда звук включается снова, что происходит при новой высокой тревоге.
- Хранить настройки и калибровки с версией, длиной, CRC и двумя копиями/номером поколения. Сохранять заводскую копию отдельно от текущей.
- Проверять статус внешней памяти, время ожидания и фактическое чтение после записи. На ошибке восстанавливать состояние линии защиты записи: исследованные ранние возвраты из
0xEECC/0xEF74обходят финальное поднятие PA9. - Журналу нужны самостоятельные head/count/sequence, определённая политика переполнения и восстановление после обрыва питания. Каждый байт записи должен быть инициализирован. Сохранять одно актуальное значение плюс явные флаги валидности.
- Записывать не только вход, но и снятие тревоги, отказ датчика, изменение калибровки/порогов и подтверждение оператором, если это входит в требования изделия.
Проверка
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. Для окончательного сопоставления с конкретным аппаратом нужны отдельный дамп внешней памяти и измерения платы.