Библиотека отладки printf() с использованием таблицы строк "Кольцо декодера"
Я пишу, чтобы узнать, видел ли кто-нибудь из вас или не слышал о реализации идеи, которую я собираюсь описать.
Я заинтересован в разработке библиотеки отладки в стиле printf для встроенной цели. Цель очень удаленная, а бюджет полосы пропускания между мной и целью очень узок, поэтому я хочу иметь возможность получать сообщения отладки в очень эффективном формате.
Довольно часто операторы отладки выглядят примерно так:
myDebugLibraryPrintf("Inside loop, processing item %d out of %d.\n", i, numItems);
Конечно, когда это раскрывается в текст, напечатанная строка выглядит как "Внутри цикла, обрабатывает элемент 5 из 10.\n", всего ~42 байта или около того. Более 90% данных, напечатанных этим утверждением, являются статическими, буквальными - известными во время компиляции. Конечно, только "5" и "10" не известны во время компиляции.
То, что я хотел бы сделать, это иметь возможность отправлять обратно только эти два целых числа (8 байтов вместо 42). Как только я получу эти данные, у меня будет какое-то "кольцо декодера", которое позволит мне "восстановить" полученные данные и распечатать полное отладочное сообщение здесь, в моем местоположении.
Я бы сгенерировал "кольцо декодера", автоматически (как часть процесса сборки), дав каждому выражению myDebugLibraryPrintf() уникальный идентификатор во время компиляции, и создав таблицу, которая отображает эти уникальные идентификаторы в исходные строки формата. Затем, каждый раз, когда myDebugLibraryPrintf() вызывается для цели, она передает уникальный идентификатор и любой из "%d"
, "%f"
и т. д. Значения varargs видны в строке формата, но сама строка формата НЕ передается. (Я, вероятно, просто запретить "%s"
элементы на данный момент...) Вернувшись в мое местоположение, у нас будет программа, которая ищет уникальные идентификаторы в таблице, находит соответствующую строку формата и использует ее для восстановления исходного сообщения отладки.
Я чувствую, что кто-то, возможно, уже имел эту идею раньше, и я подумал, что, возможно, кто-то в сообществе видел бы что-то подобное (или даже знал о библиотеке с открытым исходным кодом, которая делает это).
Ограничения:
Для пояснения, я здесь имею дело с C/C++, и меня не интересует реализация заменителя printf (), полностью завершенная на 100%- такие вещи, как строки не буквального формата,
%s
(строковые) спецификаторы формата или более продвинутые спецификаторы формата, такие как помещение ширины или точности в список varargs с%*.*d
не нуждается в поддержкеЯ хочу, чтобы таблица строк генерировалась автоматически как часть процесса сборки, чтобы добавление отладки включало не больше работы, чем добавление традиционной функции printf(). Если требуется больше, чем минимальное количество усилий, никто в моем проекте не будет его использовать.
Выполнение дополнительной работы как часть процесса сборки для генерации таблицы строк в значительной степени предполагается. К счастью, у меня есть контроль над всем исходным кодом, с которым я заинтересован в использовании этой библиотеки, и у меня есть большая гибкость в процессе сборки.
Спасибо!
4 ответа
Я видел только эту идею, реализованную с помощью предопределенного набора строк. Код будет выглядеть так debug_print(INSIDE_LOOP_MSG_ID, i, n)
, Когда разработчики хотят добавить новые сообщения, им придется поместить новый текст в определенный заголовочный файл и присвоить ему новый идентификатор.
Я думаю, что идея генерировать его на лету из нормально выглядящего печатного оператора - интересная задача. Я не встречал никаких существующих реализаций.
Одной из идей может быть макрос / шаблон, который превращает первый строковый аргумент в хеш-значение во время компиляции. Так что разработчик пишет debug_print("test %d",i)
, который компилируется в debug_port_send(0x1d3s, i)
, Написание сценария постобработки для извлечения строк и хэшей для использования на стороне получения должно быть простым. (Самый простой способ разрешить коллизии хеша - вывести сообщение об ошибке и заставить пользователя немного изменить формулировку).
редактировать:
Так что я попробовал это с хешем времени компиляции по ссылке выше.
#define QQuot_(x) #x
#define QQuote(x) QQuot_(x)
#define Debug_Print(s, v) (Send( CONSTHASH(QQuote(__LINE__)##s), *((long*)&(v))))
void Send(long hash, long value)
{
printf("Sending %x %x\n", hash, value); //replace with COMMS
}
int main()
{
int i = 1;
float f= 3.14f;
Debug_Print("This is a test %d", i);
i++;
Debug_Print("This is a test %d", i);
Debug_Print("This was test %f", f);
}
С немного большей сообразительностью вы можете поддержать несколько аргументов. Изучение дизассемблирования показывает, что все хэши действительно вычисляются во время компиляции. Вывод, как и ожидалось, без столкновений из одинаковых строк. ( Эта страница подтверждает, что гекс верен для 3.14):
Sending 94b7555c 1
Sending 62fce13e 2
Sending 506e9a0c 4048f5c3
Все, что вам сейчас нужно, это скрипт обработки текста, который можно запустить в коде, который извлекает строки из Debug_Print, вычисляет хэши и заполняет таблицу на стороне вашего получателя. Получатель получает хеш-значение из Send
call, ищет строку, которая идет вместе с ней, и передает ее вместе с аргументом (аргументами) в обычный вызов printf.
Единственная проблема, которую я вижу, состоит в том, что вложенные макросы в хеше времени компиляции сбивают с толку мой плагин рефакторинга и убивают мою отзывчивость IDE. Отключение надстройки устранило эту проблему.
Я видел что-то похожее на платформу ARM. Я полагаю, что это называется " Макроячейка Embedded Trace". Ряд макросов переводит такие выражения, как TRACE_POWER_SYSTEM_VOLTAGE_REGULATOR_TRIGGER(inputX);
в два регистра записывает в регистры ETM. Обратите внимание, что это ТОЛЬКО принимает 16-битные, 32-битные и 64-битные целые числа в качестве аргументов.
Мы можем использовать инструменты ARM для извлечения этих буферов с временными метками. Затем мы применяем предварительно скомпилированный фрагмент хитрости для преобразования первой (индексной) записи в регистр в выходной файл, который выглядит следующим образом:
timestamp | POWER SYSTEM | VOLTAGE REGULATOR TRIGGER | 0x2380FF23
Код был исследован для определения типа данных аргумента, поэтому нам не нужно об этом беспокоиться. Он также может быть аннотирован отметкой времени "в реальном времени" (вместо мс с момента включения питания), а также номерами файлов и строк операторов трассировки.
ARM настроен для хранения этого циклического буфера внутри (и очень быстро), чтобы его можно было использовать в производстве. Даже если у вас нет аппаратной поддержки... некоторые аспекты этого могут быть легко воспроизведены.
Обратите внимание, что при анализе трассировки крайне важно, чтобы вы использовали только файл "декодирования", который соответствует конкретной версии кода, запущенного на устройстве.
Кажется, я вспомнил много инструментов для извлечения строковых литералов с целью интернационализации. Строки GNU могут извлекать строки непосредственно из исполняемого файла. Это должно помочь с частью задачи.
У меня была та же проблема, плюс я хотел уменьшить размер изображения (из-за крошечной встроенной вспышки). Мое решение заключается в отправке имени файла и строки (которая должна быть 14-20 байт) и наличия анализатора исходного кода на стороне сервера, который будет генерировать карту фактических текстов. Таким образом, фактический код не будет содержать строк "format", а будет иметь одну строку "filename" для каждого файла. Кроме того, имена файлов могут быть легко заменены на enum (в отличие от замены каждой строки в коде), чтобы уменьшить пропускную способность COMM.
Я надеюсь, что пример псевдо-кода поможет прояснить идею:
/* target code */
#define PRINT(format,...) send(__FILE__,__LINE__,__VA_ARGS__)
...
/* host code (c++) */
void PrintComm(istream& in)
{
string fileName;
int line,nParams;
int* params;
in>>fileName>>line>>nParams;
if (nParams>0)
{
params = new int[nParams];
for (int i=0; i<nParams; ++i)
in>>params[i];
}
const char* format = FindFormat(fileName,line);
...
delete[] params;
}