Wie debuggt man ein Binärformat?

11

Ich möchte in der Lage sein, das Erstellen eines binären Builders zu debuggen. Im Moment drucke ich im Grunde genommen die Eingabedaten in den binären Parser aus, gehe dann tief in den Code ein und drucke die Zuordnung der Eingabe zur Ausgabe aus, nehme dann die Ausgabezuordnung (Ganzzahlen) und verwende diese, um die entsprechende Ganzzahl zu lokalisieren in der Binärdatei. Ziemlich klobig und erfordert, dass ich den Quellcode tiefgreifend ändere, um die Zuordnung zwischen Eingabe und Ausgabe zu erreichen.

Es scheint, als könnten Sie die Binärdatei in verschiedenen Varianten anzeigen (in meinem Fall möchte ich sie in 8-Bit-Blöcken als Dezimalzahlen anzeigen, da dies ziemlich nahe an der Eingabe liegt). Tatsächlich sind einige Zahlen 16-Bit, einige 8, einige 32 usw. Vielleicht gibt es also eine Möglichkeit, die Binärdatei mit jeder dieser verschiedenen Zahlen anzuzeigen, die auf irgendeine Weise im Speicher hervorgehoben sind.

Ich könnte nur sehen, dass dies möglich ist, wenn Sie tatsächlich einen Visualizer erstellen, der für das tatsächliche Binärformat / -layout spezifisch ist. Es weiß also, wo in der Sequenz die 32-Bit-Nummern sein sollten und wo die 8-Bit-Nummern sein sollten usw. Dies ist eine Menge Arbeit und in manchen Situationen etwas schwierig. Ich frage mich also, ob es einen allgemeinen Weg gibt, dies zu tun.

Ich frage mich auch, wie das Debuggen dieser Art von Dingen derzeit allgemein funktioniert. Vielleicht kann ich mir ein paar Ideen einfallen lassen, was ich daraus machen soll.

Lance Pollard
quelle
75
Sie haben eine Antwort erhalten, die besagt: "Verwenden Sie den Hexdump direkt und tun Sie dies und das zusätzlich" - und diese Antwort hat viele positive Stimmen erhalten. Und eine zweite Antwort, 5 Stunden später (!), Sagt nur "benutze einen Hexdump". Dann haben Sie den zweiten zugunsten des ersten angenommen? Ernsthaft?
Doc Brown
4
Obwohl Sie möglicherweise einen guten Grund haben, ein Binärformat zu verwenden, sollten Sie überlegen, ob Sie stattdessen nur ein vorhandenes Textformat wie JSON verwenden können. Die menschliche Lesbarkeit ist sehr wichtig, und Maschinen und Netzwerke sind in der Regel schnell genug, sodass die Verwendung eines benutzerdefinierten Formats zur Größenreduzierung heutzutage nicht mehr erforderlich ist.
jpmc26
4
@ jpmc26 Es gibt immer noch viel Verwendung für Binärformate und wird es immer sein. Die Lesbarkeit durch den Menschen hängt normalerweise von der Leistung, den Speicheranforderungen und der Netzwerkleistung ab. Und es gibt immer noch viele Bereiche, in denen insbesondere die Netzwerkleistung schlecht und der Speicher begrenzt ist. Vergessen Sie außerdem nicht, dass alle Systeme mit Legacy-Systemen (sowohl Hardware als auch Software) verbunden sein müssen und deren Datenformate unterstützen müssen.
Jwenting
4
@jwenting Nein, eigentlich ist Entwicklerzeit normalerweise das teuerste Teil einer Anwendung. Sicher, das ist möglicherweise nicht der Fall, wenn Sie bei Google oder Facebook arbeiten, aber die meisten Apps funktionieren nicht in dieser Größenordnung. Und wenn Ihre Entwickler Zeit mit Dingen verbringen, ist die teuerste Ressource die menschliche Lesbarkeit für viel mehr als die 100 Millisekunden mehr, die das Programm zum Parsen benötigt.
jpmc26
3
@ jpmc26 Ich sehe nichts in der Frage, was mir nahe legt, dass das OP das Format definiert.
JimmyJames

Antworten:

76

Verwenden Sie für Ad-hoc-Überprüfungen einfach einen Standard-Hexdump und lernen Sie, ihn zu betrachten.

Wenn Sie sich für eine ordnungsgemäße Untersuchung ausrüsten möchten, schreibe ich normalerweise einen separaten Decoder in etwas wie Python - idealerweise wird dieser direkt aus einem Nachrichtenspezifikationsdokument oder einer IDL gesteuert und ist so automatisiert wie möglich (es besteht also keine Möglichkeit, ihn manuell einzuführen) der gleiche Fehler in beiden Decodern).

Vergessen Sie nicht, dass Sie Unit-Tests für Ihren Decoder mit bekanntermaßen korrekten Eingaben schreiben sollten.

Nutzlos
quelle
2
"Verwenden Sie einfach einen Standard-Hexdump und lernen Sie, ihn zu betrachten." Jep. Nach meiner Erfahrung können mehrere Abschnitte mit bis zu 200 Bit zum gruppierten Vergleich auf ein Whiteboard geschrieben werden, was manchmal bei solchen Schritten hilfreich ist.
Mast
1
Ich finde, ein separater Decoder ist die Mühe wert, wenn die Binärdaten eine wichtige Rolle in der Anwendung (oder im System im Allgemeinen) spielen. Dies gilt insbesondere dann, wenn das Datenformat variabel ist: Daten in festen Layouts können mit ein wenig Übung in einem Hexdump erkannt werden, treffen jedoch schnell auf eine Praktikabilitätswand. Wir haben USB- und CAN-Verkehr mit kommerziellen Paketdecodern getestet, und ich habe einen PROFIBus-Decoder geschrieben (bei dem Variablen über Bytes verteilt sind und in einem Hex-Dump völlig unlesbar sind), und fand alle drei immens hilfreich.
Peter - Reinstate Monica
10

Der erste Schritt dazu besteht darin, dass Sie eine Möglichkeit benötigen, eine Grammatik zu finden oder zu definieren, die die Struktur der Daten beschreibt, dh ein Schema.

Ein Beispiel hierfür ist eine Sprachfunktion von COBOL, die informell als Copybook bezeichnet wird. In COBOL-Programmen würden Sie die Struktur der Daten im Speicher definieren. Diese Struktur wurde direkt der Art und Weise zugeordnet, in der die Bytes gespeichert wurden. Dies ist in Sprachen dieser Zeit üblich, im Gegensatz zu gängigen zeitgenössischen Sprachen, in denen das physische Layout des Speichers ein Implementierungsproblem darstellt, das vom Entwickler weg abstrahiert wird.

Bei einer Google-Suche nach der Sprache des Binärdatenschemas werden eine Reihe von Tools angezeigt. Ein Beispiel ist Apache DFDL . Möglicherweise gibt es auch dafür bereits eine Benutzeroberfläche.

JimmyJames
quelle
2
Diese Funktion ist nicht den Sprachen der "alten" Ära vorbehalten. C- und C ++ - Strukturen und -Unionen können speicherausgerichtet sein. C # hat StructLayoutAttribute, mit dem ich Binärdaten übertragen kann.
Kasper van den Berg
1
@KaspervandenBerg Wenn Sie nicht sagen, dass C und C ++ diese kürzlich hinzugefügt haben, halte ich das für dieselbe Ära. Der Punkt ist, dass diese Formate nicht nur für die Datenübertragung gedacht waren, obwohl sie dafür verwendet wurden, sondern direkt der Funktionsweise des Codes mit Daten im Speicher und auf der Festplatte zugeordnet wurden. So funktionieren neuere Sprachen im Allgemeinen nicht, obwohl sie möglicherweise über solche Funktionen verfügen.
JimmyJames
@KaspervandenBerg C ++ macht das nicht so oft, wie Sie denken. Es ist möglich, implementierungsspezifische Tools zu verwenden, um das Auffüllen auszurichten und zu eliminieren (und zugegebenermaßen fügt der Standard zunehmend Funktionen für diese Art von Dingen hinzu), und die Reihenfolge der Elemente ist deterministisch (aber nicht unbedingt dieselbe wie im Speicher!).
Leichtigkeitsrennen im Orbit
6

ASN.1 , Abstract Syntax Notation One, bietet eine Möglichkeit, ein Binärformat anzugeben.

  • DDT - Entwicklung anhand von Beispieldaten und Unit-Tests.
  • Ein Text-Dump kann hilfreich sein. In XML können Sie Subhierarchien reduzieren / erweitern.
  • ASN.1 wird nicht wirklich benötigt, aber eine grammatikalische, deklarativere Dateispezifikation ist einfacher.
Joop Eggen
quelle
6
Wenn die unendliche Parade von Sicherheitslücken in ASN.1-Parsern ein Hinweis ist, würde ihre Übernahme sicherlich eine gute Übung beim Debuggen von Binärformaten darstellen.
Mark
1
@Mark viele kleine Byte-Arrays (und das in unterschiedlichen Hierarchiebäumen) werden in C oft nicht richtig (sicher) behandelt (zum Beispiel ohne Ausnahmen). Unterschätzen Sie niemals die geringe Inhärenz von C. ASN.1 in - zum Beispiel - Java, die dieses Problem nicht aufdeckt. Da eine grammatikalisch gesteuerte Analyse nach ASN.1 sicher durchgeführt werden kann, kann sogar C mit einer kleinen und sicheren Codebasis durchgeführt werden. Ein Teil der Schwachstellen liegt im Binärformat selbst: Man kann "legale" Konstrukte der Grammatik des Formats ausnutzen, die eine katastrophale Semantik haben.
Joop Eggen
3

Andere Antworten haben das Anzeigen eines Hex-Dumps oder das Ausschreiben von Objektstrukturen in JSON beschrieben. Ich denke, beide zu kombinieren ist sehr hilfreich.

Die Verwendung eines Tools, mit dem JSON über dem Hex-Dump gerendert werden kann, ist sehr nützlich. Ich habe ein Open-Source-Tool geschrieben, das .NET-Binärdateien mit dem Namen dotNetBytes analysiert hat. Hier ist eine Ansicht einer Beispiel-DLL .

dotNetBytes Beispiel

Carl Walsh
quelle
1

Ich bin mir nicht sicher, ob ich das vollständig verstehe, aber es hört sich so an, als hätten Sie einen Parser für dieses Binärformat und steuern den Code dafür. Diese Antwort basiert also auf dieser Annahme.

Ein Parser füllt in irgendeiner Weise Strukturen, Klassen oder die Datenstruktur Ihrer Sprache aus. Wenn Sie a ToStringfür alles implementieren, was analysiert wird, erhalten Sie eine sehr einfach zu verwendende und leicht zu wartende Methode, um diese Binärdaten in einem für Menschen lesbaren Format anzuzeigen.

Sie hätten im Wesentlichen:

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

Und das war's, vom Standpunkt der Verwendung. Dies erfordert natürlich, dass Sie die ToStringFunktion für Ihre ObjectKlasse / Struktur / was auch immer implementieren / überschreiben , und Sie müssten dies auch für alle verschachtelten Klassen / Strukturen / was auch immer tun.

Sie können zusätzlich eine bedingte Anweisung verwenden, um zu verhindern, dass die ToStringFunktion im Release-Code aufgerufen wird, damit Sie keine Zeit mit etwas verschwenden, das außerhalb des Debug-Modus nicht protokolliert wird.

Sie ToStringkönnten so aussehen:

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);

Bei Ihrer ursprünglichen Frage klingt es so, als hätten Sie etwas versucht, dies zu tun, und Sie halten diese Methode für lästig, aber Sie haben auch irgendwann das Parsen eines Binärformats implementiert und Variablen zum Speichern dieser Daten erstellt. Sie müssen also nur die vorhandenen Variablen auf der entsprechenden Abstraktionsebene drucken (die Klasse / Struktur, in der sich die Variable befindet).

Dies sollten Sie nur einmal tun müssen, und Sie können dies tun, während Sie den Parser erstellen. Und es ändert sich nur, wenn sich das Binärformat ändert (was ohnehin schon zu einer Änderung Ihres Parsers führt).

In ähnlicher Weise: Einige Sprachen verfügen über robuste Funktionen zum Umwandeln von Klassen in XML oder JSON. C # ist besonders gut darin. Sie müssen Ihr Binärformat nicht aufgeben, sondern führen nur XML oder JSON in einer Debug-Protokollierungsanweisung aus und lassen Ihren Release-Code in Ruhe.

Ich persönlich würde empfehlen, den Hex-Dump-Weg nicht zu wählen, da er fehleranfällig ist (haben Sie mit dem rechten Byte begonnen, sind Sie sicher, dass Sie beim Lesen von links nach rechts die richtige Endianness "sehen" usw.) .

Beispiel: Sagen Sie Ihre ToStringsSpuckvariablen a,b,c,d,e,f,g,h. Sie führen Ihr Programm aus und bemerken einen Fehler mit g, aber das Problem begann wirklich mit c(aber Sie debuggen, also haben Sie das noch nicht herausgefunden). Wenn Sie die Eingabewerte kennen (und sollten), werden Sie sofort sehen, dass chier Probleme beginnen.

Im Vergleich zu einem Hex-Dump, der Ihnen nur sagt 338E 8455 0000 FF76 0000 E444 ....; Wenn Ihre Felder unterschiedlich groß sind, wo cbeginnt und was der Wert ist - ein Hex-Editor wird es Ihnen sagen, aber mein Punkt ist, dass dies fehleranfällig und zeitaufwändig ist. Darüber hinaus können Sie einen Test nicht einfach / schnell über einen Hex-Viewer automatisieren. Wenn Sie nach dem Parsen der Daten eine Zeichenfolge ausdrucken, erfahren Sie genau, was Ihr Programm "denkt", und dies ist ein Schritt auf dem Weg zum automatisierten Testen.

Shaz
quelle