Даже в 2025 году, когда вокруг нейросети, автогенерация кода и IDE с предиктивным интеллектом, работа с редкими микроконтроллерами всё ещё может обернуться настоящим хардкором. Особенно, если речь идёт о «слепой» отладке без отладчика, когда в арсенале только прошивка, HEX-файл и пара байтов на выводе. В этой статье — личный опыт, много хардкора, дизассемблирование вручную и поиск глюка в 2 КБ бинаря.

Когда говорят «отладка», в 2025 году чаще всего имеют в виду жмяк на F5 в Visual Studio Code или лог с CI/CD. Но в embedded-мире, особенно если ты копаешься в системах с 8-битным контроллером 2006 года выпуска, это слово может означать кое-что пострашнее. Например — «прошивка вылетает на 4-й секунде, данных в UART нет, отладочного интерфейса нет, документации почти нет, а заказчик просит сделать "как раньше работало"». И вот тут начинается старый добрый reverse engineering.
Почему вообще это может понадобиться
Пример из жизни: заказ на перепрошивку блока управления освещением в промышленной установке. Контроллер — неизвестный китайский клон STM8, без отладочного вывода, с залоченной флеш-памятью. Из всей информации: одна прошивка в HEX-формате и данные с логического анализатора.
Никакого SDK, никакой IDE. Только бинарник и задача: понять, что делает устройство, и воспроизвести поведение на современном MCU. Тут и появляется дизассемблер. Не IDA и не Ghidra — они слишком тяжёлые, часто не поддерживают редкие или кастомные архитектуры. В ход идут простые тулзы и тонны ручного труда.
Что мы вообще можем получить из HEX-файла
Intel HEX — это просто текстовое представление бинарных данных. Конвертируем его в бинарник:
$ objcopy -I ihex firmware.hex -O binary firmware.bin
Затем — загоняем в дизассемблер. Например, для MSP430 можно использовать msp430-objdump
:
$ msp430-objdump -D -m msp430 firmware.bin > firmware.asm
Но, конечно, всё не так просто. Бинарник может быть собран с учётом смещений, и без map-файла вы не узнаете, где начало main(), где стек и где таблица векторов. Придётся искать сигнатуры вручную. Пример — вот так может выглядеть обработчик reset в MSP430:
RESET:
mov.w #0x0280, SP ; Инициализация стека
call #main
jmp $ ; Бесконечный цикл
Если видим инициализацию SP и переход на main — можно считать, что стартовый адрес найден.
Поиск логики без дебаггера
Теперь начинается самое интересное: вручную отслеживаем, где в коде встречаются условные переходы, циклы, обращения к портам ввода-вывода. Если контроллер управляет, например, реле через порт P1.0, можно попробовать найти конструкции вроде:
bis.b #0x01, &0x0021 ; Установить бит в порту P1OUT
Находишь такие строчки — начинаешь строить карту работы прошивки. Можно вручную раскидать лейблы:
SET_RELAY:
bis.b #0x01, &0x0021
ret
Анализ цикла с обработкой прерываний таймера
TIMER_ISR:
push r5
inc.w &cnt ; Увеличить счётчик
cmp.w #0x0A, &cnt
jne SKIP_RESET
clr.w &cnt ; Обнулить, если больше 10
call #TOGGLE_RELAY
SKIP_RESET:
pop r5
reti
В этот момент понимаешь: реле переключается каждые 10 тиков таймера. Значит, частота мигания напрямую зависит от конфигурации таймера — ищем её дальше.
Восстановление логики main() с имитацией инициализации
main:
mov.w #0x0280, SP ; Настройка стека
call #init_ports ; Инициализация портов
call #init_timer ; Настройка таймера
loop:
call #read_inputs ; Опрос входов
call #process_logic ; Обработка логики
jmp loop
init_ports:
bis.b #0x01, &0x0022 ; Установить выход P1.0
ret
init_timer:
mov.w #0x0311, &TACTL ; Настройка таймера: ACLK, UP
mov.w #0x0FFF, &TACCR0 ; Значение сравнения
bis.w #0x0010, &TACCTL0 ; Разрешить прерывания
ret
Да, не очень приятно вручную распутывать такое, но оно работает. Когда видишь, как в логическом анализаторе сигнал меняется ровно так, как ты предполагал, — появляется та самая профессиональная радость, которую не заменит ни одна нейросеть.
Почему просто заменить MCU не получится
Проблема в том, что поведение микроконтроллера часто зависит от тонких нюансов: pull-up’ы, поведение пинов при ресете, тайминги. Всё это надо восстановить, иначе даже переписанная логика не заработает. Именно поэтому ручной дизассемблинг и трассировка — единственный способ понять, что на самом деле происходит.
Заключение
Зачем в 2025 году программисту дизассемблер? Затем, зачем и в 1985: чтобы понять, как работает чёрный ящик. Потому что иногда IDE нет, схемотехники нет, исходников нет, но есть задача и дедлайн. И в такие моменты весь цифровой лоск современности отлетает — и остаётся только ты, байткод и твой мозг.
Если вы никогда не пробовали дизассемблировать вручную прошивку под редкий MCU — очень советую. Это как собрать карбюратор в лесу из скрепок и зубочисток. Бесполезный навык, пока не окажешься в такой ситуации. А потом — вдруг единственно спасительный.
P.S. Не забывайте: изучайте чужие прошивки только если вы имеете на это право. Эта статья — про анализ собственных или разрешённых бинарников. Нарушать NDA — себе дороже.