Как вы отлаживаете двоичный формат?

11

Я хотел бы иметь возможность отладки сборки двоичного сборщика. Прямо сейчас я в основном распечатываю входные данные в двоичном парсере, а затем углубляюсь в код и распечатываю отображение входных данных на выход, затем беру выходное отображение (целые числа) и использую его, чтобы найти соответствующее целое число в двоичном Довольно неуклюже, и требует, чтобы я глубоко изменил исходный код, чтобы получить соответствие между вводом и выводом.

Похоже, что вы можете просматривать двоичный файл в разных вариантах (в моем случае я хотел бы видеть его в 8-битных кусках как десятичные числа, потому что это довольно близко к вводу). На самом деле, некоторые числа 16-разрядные, некоторые 8, некоторые 32 и т. Д. Поэтому, возможно, был бы способ просмотреть двоичный файл, в котором каждое из этих различных чисел каким-то образом выделено в памяти.

Единственный способ увидеть это возможно, если вы действительно создадите визуализатор, соответствующий конкретному двоичному формату / макету. Таким образом, он знает, где в последовательности должны быть 32-разрядные числа, а также где должны быть 8-разрядные числа и т. Д. В некоторых ситуациях это очень трудоемкий и сложный процесс. Так интересно, есть ли общий способ сделать это.

Мне также интересно, каков общий способ отладки такого рода вещей в настоящее время, так что, возможно, я смогу получить некоторые идеи о том, что из этого попробовать.

Лэнс Поллард
источник
75
У вас есть один ответ, говорящий «используйте hexdump напрямую, и делайте то и это дополнительно» - и этот ответ получил много голосов. И второй ответ, 5 часов спустя (!), Говорящий только «используйте hexdump». Тогда вы приняли второй в пользу первого? Шутки в сторону?
Док Браун
4
Хотя у вас может быть веская причина использовать двоичный формат, подумайте, можно ли вместо этого просто использовать существующий текстовый формат, такой как JSON. Удобочитаемость для человека очень важна, и машины и сети, как правило, достаточно быстрые, поэтому использование нестандартного формата для уменьшения размера в настоящее время не требуется.
jpmc26
4
@ jpmc26 все еще много пользы для двоичных форматов, и так будет всегда. Читаемость человеком обычно является вторичной по отношению к производительности, требованиям к хранилищу и производительности сети. И все еще есть много областей, где особенно низкая производительность сети и ограниченная память. Кроме того, не забывайте, что все системы должны взаимодействовать с устаревшими системами (как аппаратными, так и программными) и поддерживать их форматы данных.
С
4
@jwenting Нет, на самом деле, время разработки, как правило, самая дорогая часть приложения. Конечно, это может быть не так, если вы работаете в Google или Facebook, но большинство приложений не работают в таком масштабе. А когда ваши разработчики тратят время на что-то, это самый дорогой ресурс, читаемость для человека намного больше, чем 100 дополнительных миллисекунд, чтобы программа могла его проанализировать.
jpmc26
3
@ jpmc26 Я не вижу в этом вопросе ничего, что подсказывало бы мне, что ОП определяет формат.
JimmyJames

Ответы:

76

Для специальных проверок, просто используйте стандартную hexdump и научитесь смотреть на нее.

Если вы хотите подготовиться к надлежащему расследованию, я обычно пишу отдельный декодер в чем-то вроде Python - в идеале это будет происходить непосредственно из документа спецификации сообщения или IDL, и будет как можно более автоматизированным (так что нет никакой возможности вручную ввести одна и та же ошибка в обоих декодерах).

Наконец, не забывайте, что вы должны писать модульные тесты для своего декодера, используя известный правильный ввод данных.

Бесполезный
источник
2
«Просто используйте стандартную hexdump и научитесь смотреть на это». Ага. По моему опыту, на доске можно записать несколько разделов из чего угодно, вплоть до 200 битов, для группового сравнения, что иногда помогает с такими вещами начать работу.
Мачта
1
Я считаю, что отдельный декодер стоит усилий, если бинарные данные играют важную роль в приложении (или системе в целом). Это особенно верно, если формат данных является переменным: данные в фиксированных макетах могут быть обнаружены в hexdump с небольшой практикой, но быстро достигают стены выполнимости. Мы отлаживали трафик USB и CAN с помощью коммерческих декодеров пакетов, и я написал декодер PROFIBus (где переменные распределяются по байтам, совершенно не читаемые в шестнадцатеричном дампе) и нашел все три из них чрезвычайно полезными.
Питер - Восстановить Монику
10

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

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

Поиск в Google для языка схемы двоичных данных включает в себя ряд инструментов. Примером является Apache DFDL . Для этого уже может быть пользовательский интерфейс.

JimmyJames
источник
2
Эта функция не предназначена только для языков «древней» эпохи. Структуры и объединения C и C ++ могут быть выровнены по памяти. C # имеет StructLayoutAttribute, который я использую для передачи двоичных данных.
Каспер ван ден Берг
1
@KaspervandenBerg Если вы не говорите, что C и C ++ добавили их недавно, я считаю, что это та же самая эпоха. Дело в том, что эти форматы были не просто для передачи данных, хотя они использовались для этого, они напрямую отображали, как код работает с данными в памяти и на диске. В общем, дело не в том, как работают новые языки, хотя они могут иметь такие функции.
JimmyJames
@KaspervandenBerg C ++ не делает это так, как вы думаете. Можно использовать инструментальные средства, специфичные для реализации, для выравнивания и устранения заполнения (и, по общему признанию, стандарт все чаще добавляет функции для такого рода вещей), и порядок членов является детерминированным (но не обязательно таким же, как в памяти!).
Гонки
6

ASN.1 , Абстрактная синтаксическая нотация 1, предоставляет способ указания двоичного формата.

  • ДДТ - Разработка с использованием образцов данных и модульных тестов.
  • Текстовый дамп может быть полезным. Если в XML вы можете свернуть / развернуть иерархии.
  • ASN.1 на самом деле не нужен, но на основе грамматики более декларативная спецификация файла проще.
Joop Eggen
источник
6
Если бесконечный парад уязвимостей безопасности в синтаксических анализаторах ASN.1 является какой-либо индикацией, его принятие, безусловно, обеспечит хорошие результаты при отладке двоичных форматов.
Mark
1
@ Отметьте множество небольших байтовых массивов (и таких в разных иерархических деревьях), которые часто не обрабатываются правильно (надежно) в C (например, без использования исключений). Никогда не стоит недооценивать низкую нативность, присущую C. ASN.1, например, в java, не раскрывает эту проблему. Поскольку грамматический синтаксический анализ в ASN.1 может выполняться безопасно, даже C может выполняться с небольшой и безопасной базой кода. И часть уязвимостей присуща самому двоичному формату: можно использовать «легальные» конструкции грамматики формата, которые имеют ужасную семантику.
Joop Eggen
3

Другие ответы описывают просмотр шестнадцатеричного дампа или написание структур объектов в JSON. Я думаю, что объединение обоих из них очень полезно.

Использование инструмента, который может визуализировать JSON поверх шестнадцатеричного дампа, действительно полезно; Я написал инструмент с открытым исходным кодом, который анализировал двоичные файлы .NET под названием dotNetBytes , вот пример библиотеки DLL .

Пример dotNetBytes

Карл Уолш
источник
1

Я не уверен, что полностью понимаю, но, похоже, у вас есть парсер для этого двоичного формата, и вы управляете кодом для него. Таким образом, этот ответ построен на этом предположении.

Анализатор каким-то образом будет заполнять структуры, классы или любую структуру данных, имеющуюся в вашем языке. Если вы реализуете ToStringдля всего, что анализируется, вы получите очень простой в использовании и легко поддерживаемый метод отображения этих двоичных данных в удобочитаемом формате.

Вы бы по существу имели:

byte[] arrayOfBytes; // initialized somehow
Object obj = Parser.parse(arrayOfBytes);
Logger.log(obj.ToString());

И это все, с точки зрения его использования. Конечно, это требует от вас реализации / переопределения ToStringфункции для вашего Objectкласса / структуры / чего бы то ни было, и вам также придется делать это для любых вложенных классов / структур / whatevers.

Кроме того, вы можете использовать условный оператор для предотвращения вызова ToStringфункции в коде выпуска, чтобы не тратить время на то, что не будет зарегистрировано вне режима отладки.

Вы ToStringможете выглядеть так:

return String.Format("%d,%d,%d,%d", int32var, int16var, int8var, int32var2);

// OR

return String.Format("%s:%d,%s:%d,%s:%d,%s:%d", varName1, int32var, varName2, int16var, varName3, int8var, varName4, int32var2);

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

Это то, что вы должны сделать только один раз, и вы можете сделать это при сборке парсера. И он будет изменяться только при изменении двоичного формата (который в любом случае уже будет вызывать изменение вашего парсера).

Аналогичным образом: некоторые языки имеют надежные функции для преобразования классов в XML или JSON. C # особенно хорош в этом. Вам не нужно отказываться от своего двоичного формата, вы просто делаете XML или JSON в операторе ведения журнала отладки и оставляете свой код выпуска в покое.

Я лично рекомендую не идти по маршруту шестнадцатеричного дампа, потому что он подвержен ошибкам (вы начали с правого байта, уверены ли вы, когда читаете слева направо, что «видите» правильный порядок байтов и т. Д.) ,

Пример. Скажите, что вы ToStringsвыплевываете переменные a,b,c,d,e,f,g,h. Вы запускаете свою программу и замечаете ошибку g, но проблема действительно началась с c(но вы отлаживаете, так что вы еще не поняли). Если вы знаете входные значения (и должны это сделать), вы сразу же увидите, что cименно здесь начинаются проблемы.

По сравнению с шестнадцатеричным дампом, который просто говорит вам 338E 8455 0000 FF76 0000 E444 ....; если ваши поля различаются по размеру, где cначинается и каково значение - шестнадцатеричный редактор скажет вам, но я хочу сказать, что это подвержено ошибкам и требует много времени. Не только это, но вы не можете легко / быстро автоматизировать тест с помощью шестнадцатеричной программы просмотра. Распечатка строки после анализа данных точно скажет вам, о чем думает ваша программа, и станет одним из шагов на пути автоматического тестирования.

Shaz
источник