
" Когда сами константы становятся переменными. "
Пролог
В этом тексте я расскажу про одну полезную и широко распространенную утилиту для калибровки микроконтроллерных прошивок под названием TunerPRO.
Стандарт программирования ISO-26262 требует раздельного хранения калибровочных данных, конфигурационных данных и кода внутри прошивки. Это требование возникло не на пустом месте, а как раз для того, чтобы после выпуска релиза с программой можно было проводить гибкую товарную политику. Для этого и была разработана программа TunerPRO.
TunerPRO - это бесплатный бинарный редактор прошивок. По своей сути это функциональный аналог утилиты STM32Studio, только для любого микроконтроллера.
Эта программа позволит вам редактировать константы в готовом .bin файле. Минуя стадию повторной пересборки всего проекта прошивки (компиляции и компоновки). Можно сказать, что TunerPRO - хакерская tool-а. Ещё TunerPRO позволяет сравнивать bin файлы.
Эта утилита связывает воедино всю информацию про переменные: глобальные и константы. В данном случае константы. Это адрес ячейки памяти в bin-аре, размерность переменной, размер параметра, формат ее хранения в памяти, имя переменной, множитель, единицу измерения, максимальное и минимальное значение. В то время как map файл дает только инфу про имя ,адрес в памяти и размер.
Определения
Параметр - это переменная, которая помимо адреса в памяти, значения и размера несет в себе ещё и метаданные про физическую величину, размерность, тип данных, единицы измерения, множитель, тип доступа, формат записи и прочее, и прочее.
patch - (заплатка) - маленькое изменение в коде или в бинарном файле.
метаданные - информация о другой информации, или данные, относящиеся к дополнительной информации о содержимом или объекте. Метаданные раскрывают сведения о признаках и свойствах, характеризующих какие-либо сущности, позволяющие автоматически искать и управлять ими в больших информационных потоках.
XDF Файл - Данный файл содержит информацию о параметрах, адреса таблиц и их параметры, числовые константы, функции и др., что позволяет визуализировать калибровки прошивки в программе, чтобы иметь возможность интуитивно понятно редактировать прошивку прибора. Это родной формат для утилиты TunerPro. XDF-файл должен четко подходить к прошивке вашего прибора. Xdf это база данных про переменные в компьютерной программе. В высшем своем проявлении этот файл говорит как интерпретировать каждый байт в бинарном файле с прошивкой.
Вот пример реального XDF файла
<!-- Written 11/15/2025 14:38:18 -->
<XDFFORMAT version="1.80">
<XDFHEADER>
<flags>0x1</flags>
<fileversion>"ver 1"</fileversion>
<deftitle>nucleo_f401re_esp_01</deftitle>
<description>ARM CORTEX_M4</description>
<author>aabzel</author>
<BASEOFFSET offset="0" subtract="0" />
<DEFAULTS datasizeinbits="8" sigdigits="2" outputtype="2" signed="1" lsbfirst="0" float="0" />
<REGION type="0xFFFFFFFF" startaddress="0x0" size="0x80000" regioncolor="0x0" regionflags="0x0" name="Binary File" desc="This region describes the bin file edited by this XDF" />
</XDFHEADER>
<XDFCONSTANT uniqueid="0x6298" flags="0xC">
<title>tuner_pro_s8</title>
<CATEGORYMEM index="0" category="1" />
<EMBEDDEDDATA mmedtypeflags="0x01" mmedaddress="0x3461C" mmedelementsizebits="8" mmedmajorstridebits="0" mmedminorstridebits="0" />
<outputtype>2</outputtype>
<rangehigh>120.000000</rangehigh>
<rangelow>-120.000000</rangelow>
<datatype>3</datatype>
<unittype>2</unittype>
<DALINK index="0" />
<MATH equation="X">
<VAR id="X" />
</MATH>
</XDFCONSTANT>
<XDFCONSTANT uniqueid="0x4478">
<title>tuner_pro_float</title>
<EMBEDDEDDATA mmedtypeflags="0x10002" mmedaddress="0x34624" mmedelementsizebits="32" mmedmajorstridebits="0" mmedminorstridebits="0" />
<datatype>2</datatype>
<unittype>3</unittype>
<DALINK index="0" />
<MATH equation="X">
<VAR id="X" />
</MATH>
</XDFCONSTANT>
<XDFCONSTANT uniqueid="0x5C94" flags="0xC">
<title>tuner_pro_u8</title>
<EMBEDDEDDATA mmedaddress="0x34614" mmedelementsizebits="8" mmedmajorstridebits="0" mmedminorstridebits="0" />
<outputtype>2</outputtype>
<rangehigh>200.000000</rangehigh>
<datatype>3</datatype>
<unittype>3</unittype>
<DALINK index="0" />
<MATH equation="X">
<VAR id="X" />
</MATH>
</XDFCONSTANT>
</XDFFORMAT>
Входные данные для утилиты TunerPro
Тип файла |
Расширение |
Тип файла |
Пояснение |
binary file |
bin |
бинарный |
Файл с прошивкой |
bin definition file |
XDF |
текстовый |
Аналог map файла |
XDF файлы можно генерировать из MATLAB файлов, т.к. только там есть физический смысл переменной. XDF файлы чем-то похоже на a2l файлы из протокола XCP.
Информацию про названия глобальных константных переменных их размер и расположение в физической памяти можно выделить из *.elf файла командой read elf. Буквально одной строчкой.
readelf -a firmware.elf | grep OBJECT | grep GLOBAL | grep "DEFAULT 1"
C:\projects\debug\source\projects\nda\build>readelf -a nda.elf | grep OBJECT | grep GLOBAL | grep "DEFAULT 1"
6502: 010747f8 40 OBJECT GLOBAL DEFAULT 1 __mprec_tinytens
6687: 01072268 240 OBJECT GLOBAL DEFAULT 1 CanConfig
6725: 01072178 16 OBJECT GLOBAL DEFAULT 1 SysTickConfig
6750: 0106e6e4 8 OBJECT GLOBAL DEFAULT 1 ClockInfo
6836: 01072f68 60 OBJECT GLOBAL DEFAULT 1 CliConfig
6849: 010712c8 336 OBJECT GLOBAL DEFAULT 1 ClockReg
6931: 01072188 16 OBJECT GLOBAL DEFAULT 1 SuperCycleConfig
6976: 0106f88c 512 OBJECT GLOBAL DEFAULT 1 crc16LookUpTable
6997: 01073810 608 OBJECT GLOBAL DEFAULT 1 GpioConfig
7021: 01071c28 120 OBJECT GLOBAL DEFAULT 1 PortReg
7061: 01074730 200 OBJECT GLOBAL DEFAULT 1 __mprec_tens
7066: 0106e9b4 840 OBJECT GLOBAL DEFAULT 1 s_aFlexCan_Norma[...]
7072: 01073010 144 OBJECT GLOBAL DEFAULT 1 LedMonoConfig
7109: 01072258 8 OBJECT GLOBAL DEFAULT 1 StoreFsConfig
7255: 01071ce4 16 OBJECT GLOBAL DEFAULT 1 ClockOutConfig
7269: 01072fa4 108 OBJECT GLOBAL DEFAULT 1 LittleFsConfig
7281: 01074294 4 OBJECT GLOBAL DEFAULT 1 _global_impure_ptr
7311: 01074708 40 OBJECT GLOBAL DEFAULT 1 __mprec_bigtens
7395: 01071bd4 84 OBJECT GLOBAL DEFAULT 1 GpioReg
7400: 010731e8 84 OBJECT GLOBAL DEFAULT 1 WriterConfig
7408: 0106ef2c 540 OBJECT GLOBAL DEFAULT 1 CanReg
7543: 0107452c 32 OBJECT GLOBAL DEFAULT 1 __sf_fake_stderr
7599: 0107323c 20 OBJECT GLOBAL DEFAULT 1 CoreConfig
7603: 01070094 16 OBJECT GLOBAL DEFAULT 1 SwComponentConfig
7622: 0106ecfc 560 OBJECT GLOBAL DEFAULT 1 s_aFlexCan_DataB[...]
7652: 01072198 24 OBJECT GLOBAL DEFAULT 1 BoardConfig
7657: 01071d44 80 OBJECT GLOBAL DEFAULT 1 RamSectorConfig
7666: 01071cf4 0 OBJECT GLOBAL DEFAULT 1 DmaConfig
7672: 010721b0 168 OBJECT GLOBAL DEFAULT 1 Wires
7687: 01070108 864 OBJECT GLOBAL DEFAULT 1 test_list
7713: 01071cf4 48 OBJECT GLOBAL DEFAULT 1 FlashSectorConfig
7757: 01073170 120 OBJECT GLOBAL DEFAULT 1 UartConfig
7788: 01074628 96 OBJECT GLOBAL DEFAULT 1 __month_lengths
7796: 010730f0 120 OBJECT GLOBAL DEFAULT 1 StringReaderConfig
7850: 01073768 168 OBJECT GLOBAL DEFAULT 1 PinConfig
7903: 01071cd0 20 OBJECT GLOBAL DEFAULT 1 BootConfig
7962: 01073168 6 OBJECT GLOBAL DEFAULT 1 TimeConfig
7978: 0107454c 32 OBJECT GLOBAL DEFAULT 1 __sf_fake_stdin
7993: 01071fb0 456 OBJECT GLOBAL DEFAULT 1 ParamArray
8018: 01071d94 540 OBJECT GLOBAL DEFAULT 1 InterruptConfig
8281: 01072358 640 OBJECT GLOBAL DEFAULT 1 CanMessageBuffer[...]
8335: 01071d24 32 OBJECT GLOBAL DEFAULT 1 FlashConfig
8346: 01074614 12 OBJECT GLOBAL DEFAULT 1 _C_numeric_locale
8366: 01073250 1304 OBJECT GLOBAL DEFAULT 1 IntNumInfo
8427: 010730c4 20 OBJECT GLOBAL DEFAULT 1 PostponeFunConfig
8483: 01073a70 5 OBJECT GLOBAL DEFAULT 1 LogConfig
8583: 0107456c 32 OBJECT GLOBAL DEFAULT 1 __sf_fake_stdout
8605: 01074190 257 OBJECT GLOBAL DEFAULT 1 _ctype_
8622: 0106f4e4 208 OBJECT GLOBAL DEFAULT 1 SystemInitInstance
8635: 01071b1c 120 OBJECT GLOBAL DEFAULT 1 FlashRegisters
8693: 0106e920 12 OBJECT GLOBAL DEFAULT 1 UartRegs
Что надо из ПО?
Название утилиты |
Назначение |
ST-LINK_CLI.exe |
Для загрузки прошивки |
TunerPro.exe |
Для редактирования бинаря |
WinMerge.exe |
Для сравнения файлов: текстовых и бинарных |
Практическая часть
Посмотрим, как работает утилита TunerPro. Вот я накропал простецкий модульный тест, который просто печатает константы из Flash памяти.
#include "test_tuner_pro.h"
#include "log.h"
#include "unit_test_check.h"
#include "array_diag.h"
const char tuner_pro_text[16] = "test_data";
const uint8_t tuner_pro_u8 = 0x12;
const uint16_t tuner_pro_u16 = 0x1234;
const uint32_t tuner_pro_u32 = 0x12345678;
const int8_t tuner_pro_s8 = -8;
const int16_t tuner_pro_s16 = -16;
const int32_t tuner_pro_s32 = -32;
const float tuner_pro_float = 5.5;
const double tuner_pro_double = 7.7;
/*
tr tuner_pro+
*/
bool test_tuner_pro_types(void) {
bool res = true;
LOG_DEBUG(SYS, "%s():", __FUNCTION__);
EXPECT_EQ(1, sizeof(tuner_pro_u8));
EXPECT_EQ(1, sizeof(tuner_pro_s8));
EXPECT_EQ(2, sizeof(tuner_pro_s16));
EXPECT_EQ(2, sizeof(tuner_pro_u16));
EXPECT_EQ(4, sizeof(tuner_pro_s32));
EXPECT_EQ(4, sizeof(tuner_pro_u32));
EXPECT_EQ(4, sizeof(tuner_pro_float));
EXPECT_EQ(8, sizeof(tuner_pro_double));
EXPECT_EQ(16, sizeof(tuner_pro_text));
return res;
}
bool test_tuner_pro_vals(void) {
bool res = true;
LOG_DEBUG(SYS, "%s():", __FUNCTION__);
LOG_INFO(SYS, "U8:0x%x", tuner_pro_u8);
LOG_INFO(SYS, "U16:0x%x", tuner_pro_u16);
LOG_INFO(SYS, "U32:0x%x", tuner_pro_u32);
LOG_INFO(SYS, "S8:%d", tuner_pro_s8);
LOG_INFO(SYS, "S16:%d", tuner_pro_s16);
LOG_INFO(SYS, "S32:%d", tuner_pro_s32);
LOG_INFO(SYS, "f:%f", tuner_pro_float);
LOG_INFO(SYS, "d:%f", tuner_pro_double);
LOG_INFO(SYS, "text:[%s]", tuner_pro_text);
return res;
}
bool test_tuner_pro_mem(void) {
bool res = true;
LOG_DEBUG(SYS, "%s():", __FUNCTION__);
LOG_INFO(SYS, "U8:0x%s", ArrayToStr((uint8_t*)&tuner_pro_u8,sizeof(tuner_pro_u8)));
LOG_INFO(SYS, "U16:0x%s", ArrayToStr((uint8_t*)&tuner_pro_u16,sizeof(tuner_pro_u16)));
LOG_INFO(SYS, "U32:0x%s", ArrayToStr((uint8_t*)&tuner_pro_u32,sizeof(tuner_pro_u32)));
LOG_INFO(SYS, "S8:0x%s", ArrayToStr((uint8_t*)&tuner_pro_s8,sizeof(tuner_pro_s8)));
LOG_INFO(SYS, "S16:0x%s", ArrayToStr((uint8_t*)&tuner_pro_s16,sizeof(tuner_pro_s16)));
LOG_INFO(SYS, "S32:0x%s", ArrayToStr((uint8_t*)&tuner_pro_s32,sizeof(tuner_pro_s32)));
LOG_INFO(SYS, "tuner_pro_float:0x%s", ArrayToStr((uint8_t*)&tuner_pro_float,sizeof(tuner_pro_float)));
LOG_INFO(SYS, "tuner_pro_double:0x%s", ArrayToStr((uint8_t*)&tuner_pro_double,sizeof(tuner_pro_double)));
LOG_INFO(SYS, "tuner_pro_text:0x%s", ArrayToStr((uint8_t*)&tuner_pro_text,sizeof(tuner_pro_text)));
return res;
}
bool test_tuner_pro_address(void) {
bool res = true;
LOG_DEBUG(SYS, "%s():", __FUNCTION__);
LOG_INFO(SYS, "&tuner_pro_u8:0x%p", &tuner_pro_u8);
LOG_INFO(SYS, "&tuner_pro_u16:0x%p", &tuner_pro_u16);
LOG_INFO(SYS, "&tuner_pro_u32:0x%p", &tuner_pro_u32);
LOG_INFO(SYS, "&tuner_pro_s8:0x%p", &tuner_pro_s8);
LOG_INFO(SYS, "&tuner_pro_s16:0x%p", &tuner_pro_s16);
LOG_INFO(SYS, "&tuner_pro_s32:0x%p", &tuner_pro_s32);
LOG_INFO(SYS, "&tuner_pro_float:0x%p", &tuner_pro_float);
LOG_INFO(SYS, "&tuner_pro_double:0x%p", &tuner_pro_double);
LOG_INFO(SYS, "&tuner_pro_text:0x%p", tuner_pro_text);
return res;
}
В результате исполнения я увидел
1.348-->
1.502-->tr tuner+
cmd_unit_test_run() argc 1
5.832,+5803,103 I,[TEST] key [tuner+]
5.834,+2,104 W,[TEST] unit_test_run_key() key tuner
************* Run test tuner_pro_types .1/49
!OKTEST
************* Run test tuner_pro_mem .2/49
6.140,+306,105 I,[SYS] U8:0x12
6.142,+2,106 I,[SYS] U16:0x3412
6.144,+2,107 I,[SYS] U32:0x78563412
6.146,+2,108 I,[SYS] S8:0xF8
6.148,+2,109 I,[SYS] S16:0xF0FF
6.150,+2,110 I,[SYS] S32:0xE0FFFFFF
6.152,+2,111 I,[SYS] tuner_pro_float:0x0000B040
6.154,+2,112 I,[SYS] tuner_pro_double:0xCDCCCCCCCCCC1E40
6.157,+3,113 I,[SYS] tuner_pro_text:0x746573745F6461746100000000000000
!OKTEST
************* Run test tuner_pro_const_vals .3/49
6.462,+305,114 I,[SYS] text:[U8:0x12,S8:-8,U16:0x1234,U32:0x12345678,S16:-16,S32:-32,f:5.500000,d:7.700000,text:[test_data],]
!OKTEST
************* Run test tuner_pro_vals .4/49
6.769,+307,115 I,[SYS] text:[U8:0x12,S8:-8,U16:0x1234,U32:0x12345678,S16:-16,S32:-32,f:5.500000,d:7.700000,text:[test_data],]
!OKTEST
************* Run test tuner_pro_address .5/49
7.076,+307,116 I,[SYS] &tuner_pro_u8:0x8034614
7.079,+3,117 I,[SYS] &tuner_pro_u16:0x8034616
7.081,+2,118 I,[SYS] &tuner_pro_u32:0x8034618
7.083,+2,119 I,[SYS] &tuner_pro_s8:0x803461c
7.086,+3,120 I,[SYS] &tuner_pro_s16:0x803461e
7.088,+2,121 I,[SYS] &tuner_pro_s32:0x8034620
7.090,+2,122 I,[SYS] &tuner_pro_float:0x8034624
7.093,+3,123 I,[SYS] &tuner_pro_double:0x8034628
7.095,+2,124 I,[SYS] &tuner_pro_text:0x8034604
!OKTEST
7.399,+304,125 I,[TEST] TestDuration:1565 ms=1.565 s=0.026083333 min
7.403,+4,126 I,[TEST] All 5 tests passed!
Теперь я закрываю IDE и далее работаю только с бинарём. Вот у меня есть *.bin прошивка. В ней есть глобальные переменные. Надо изменить константу без пере сборки всего проекта прошивки и залить эту модифицированную прошивку в flash память микроконтроллер.
Название константы |
Адрес в памяти |
bin file addr |
Size |
Начальное |
tuner_pro_s8 |
0x803461c |
3461c |
1 |
-8 |
tuner_pro_s16 |
0x803461e |
3461e |
2 |
-16 |
tuner_pro_s32 |
0x8034620 |
34620 |
4 |
-32 |
tuner_pro_text |
0x8034604 |
34604 |
16 |
test_data |
tuner_pro_double |
0x8034628 |
34628 |
8 |
5.5 |
tuner_pro_float |
0x8034624 |
34624 |
4 |
5.5 |
tuner_pro_u8 |
0x8034614 |
34614 |
1 |
0x12 |
tuner_pro_u16 |
0x8034616 |
34616 |
2 |
0x1234 |
tuner_pro_u32 |
0x8034618 |
34618 |
4 |
0x12345678 |
Сам файл XDF можно создать прямо внутри утилиты TunerPRO. Первым делом надо объявить заголовок. Открываем XDF -> XDF Header Editor. Тут надо только указать размер прошивки.

Затем вручную добавляем интересующие нас переменные. Одну за другой. XDF -> Create New XDF Parameter

Можно добавлять всяческие метаданные для переменной. Граничные значения, физическая величина, тип данных, единицы измерения, размерность, категорию и т. д. Всё то, про что не знал Си-компилятор. Эти метаданные кристаллизируется потом в XDF файле.

Утилита увидела в bin файле значение для переменной

Теперь мы можем преспокойно изменить циферку в переменной.

Чтобы занести изменения в bin файл нажимаем File-> Save Bin. Проверить наличие изменение можно в утилите WinMerge. Вот я внес patch в прошивку и это явно видно в bin файле.

Разницу между бинарями можно вычислить даже не выходя из TunerPRO инструментом Difference Tool

Осталось только просто прошить про патченый бинарь во flash память микроконтроллера STM32F401RE . В случае с STM32 микроконтроллером это делает утилита ST-LINK_CLI.exe
echo off
cls
set project_name=nucleo_f401re_esp_01_m
set project_dir=%cd%
set artefact_bin=%project_name%.bin
set FlashTool=ST-LINK_CLI.exe
set Device=
set options=-c %Device%
set options= %options% SWD freq=4000
set options= %options% HOTPLUG LPM
set options= %options% -P %artefact_bin% 0x08000000
set options= %options% -V "after_programming"
set options= %options% -Log -TVolt
call %FlashTool% %options%
rem Reset System
call %FlashTool% -Rst
Лог загрузки прошивки в память

У меня в прошивке есть UART-CLI. Поэтому я могу еще раз прогнать тот самый модульный тест и зарегистрировать изменения.
Как можно заметить, память в самом деле изменилась с 0x12 на 0x34. Другой вопрос почему printf оптимизировал и вывел старую прохардкоженную константу? Видимо это оптимизации компилятора. Но главное, что Flash память в самом деле изменилась в нужной ячейке!

Этот текст показывает как легко уязвимы .bin файлы. Все кому не лень могут их взять и подшаманить. В этом плане *.hex формат выглядит предпочтительнее так как в .hex для каждой строчки есть контрольная сумма.
TunerPRO можно иcпользовать как продвинутый hex editor.

Как это теперь применять на практике?
А вот как.
--Допустим вы пишите прошивку для какого-то прибора. И вам клиенты говорят, что нужна прошивка с измененными параметрами. В этом случае вы можете дать им ту же самую прошивку только прислать .xdf файл в котором или вы или они сами могут внести желаемое изменение.
--После старого проекта у вас не осталось сорцов. В наличии только устройство программатор и бинарь. При этом возникла потребность слегка модифицировать функционал. В одном месте. Тут вам снова поможет TunerPRO.
--Вам надо откалибровать какой-то агрегат. При этом пересборка прошивки с другими калибровочными константами занимает много времени. Ситуация осложняется тем что в прошивке нет ни UART-CLI ни NVRAM. В этом случае вы можете по крайней мере перебирать значения прямо в бинаре утилитой TunerPRO и пере накатывать новый артефакт. До тех пор пока не подберете нужную калибровку.
Достоинства Tuner PRO
++Эта утилита инвариантна к производителю микроконтроллера. Можно модифицировать прошивки для любого МК
++Это бесплатная утилита
++Утилита может интерпретировать базовые типы данных: float, uint8_t, int16_t и т. п.
Итог
Удалось получить представление о том, что такое утилита TunerPRO и как ей пользоваться.
Эта утилита позволяет вам переложить задачи калибровки с разработчика на пользователя прошивки. Вы просто даете .bin .xdf и пользователь уже сам настраивает прошивку так как требуется.
Словарь
Акроним |
Расшифровка |
XDF |
extensible calibration definition format |
MCU |
microcontroller unit |
IDE |
integrated development environment |
Gdb |
GNU Debugger |
elf |
Executable and Linkable Format |
Ссылки
Название |
URL |
Введение в TunerPRO RT |
|
TunerPro Quick guide |
https://oldskulltuning.com/wp-content/uploads/2023/03/Quick-guide-rev1.4.pdf |
UART-CLI |
|
STM Studio run-time variables monitoring and visualization tool for STM32 microcontrollers |
https://www.st.com/en/development-tools/stm-studio-stm32.html |
Вопросы
--Вот я собрал *.elf файл с прошивкой. Захотелось изменить одну константу перед загрузкой прошивки в MCU. Как мне теперь получить XDF Файл для утилиты TunerPRO? Надо создавать его вручную. Однако этот файл может генерировать MatLab, так как Simulink придает переменным физический смысл.
--XDF файл для утилиты TunerPRO это бинарный файл или текстовый файл? Текстовый
--Существует ли спецификация формата *.xdf файлов, чтобы корректным образом заполнить метаданные: заголовок, контрольную сумму и сами переменные?
--Как извлечь абсолютные адреса членов структур из *.elf файла? Gdb их же как-то достает?
--Может ли TunerPRO работать в консольном режиме?
--Как при программировании на Си отключить оптимизацию при printf печати константных переменных из flash памяти в GCC?
randomsimplenumber
Главное что даже в своей собственной программе, с исходниками и адресами в elf файле, нельзя быть уверенным, что патч сработает именно так как хочется. Вот вы что то поменяли. Где то оно вроде как сработало, но не везде.
aabzel Автор
Моя гипотеза в том, что компилятор gcc при печати констант printf-ом переводил данные в сегмент text.