Come si esegue il debug di un formato binario?


11

Vorrei essere in grado di eseguire il debug della creazione di un generatore binario. In questo momento sto fondamentalmente stampando i dati di input sul parser binario, quindi andando in profondità nel codice e stampando la mappatura dell'input sull'output, quindi prendendo la mappatura dell'output (numeri interi) e usandola per individuare il numero intero corrispondente nel binario. Abbastanza goffo e richiede che modifico profondamente il codice sorgente per ottenere il mapping tra input e output.

Sembra che tu possa vedere il binario in diverse varianti (nel mio caso mi piacerebbe vederlo in blocchi di 8 bit come numeri decimali, perché è abbastanza vicino all'input). In realtà, alcuni numeri sono a 16 bit, circa 8, circa 32, ecc. Quindi forse ci sarebbe un modo per visualizzare il binario con ciascuno di questi diversi numeri evidenziati in memoria in qualche modo.

L'unico modo in cui ho potuto vedere che ciò è possibile è se si crea effettivamente un visualizzatore specifico per il formato / layout binario effettivo. Quindi sa dove dovrebbero essere nella sequenza i numeri a 32 bit e dove dovrebbero essere i numeri a 8 bit, ecc. Questo è un sacco di lavoro e un po 'complicato in alcune situazioni. Quindi mi chiedo se c'è un modo generale per farlo.

Mi chiedo anche quale sia attualmente il modo generale di eseguire il debug di questo tipo di cose, quindi forse posso avere qualche idea su cosa provare da quello.


75
Hai una risposta che dice "usa direttamente il hexdump, e fai questo e quello in più" - e quella risposta ha ottenuto molti voti positivi. E una seconda risposta, 5 ore dopo (!), Dicendo solo "usa un hexdump". Quindi hai accettato il secondo a favore del primo? Sul serio?
Doc Brown,

4
Mentre potresti avere una buona ragione per usare un formato binario, considera invece se puoi semplicemente usare un formato di testo esistente come JSON. La leggibilità umana conta molto e le macchine e le reti sono in genere abbastanza veloci che al giorno d'oggi non è necessario utilizzare un formato personalizzato per ridurre le dimensioni.
jpmc26,

4
@ jpmc26 c'è ancora molto da usare per i formati binari e lo sarà sempre. La leggibilità umana è in genere secondaria rispetto alle prestazioni, ai requisiti di archiviazione e alle prestazioni della rete. E ci sono ancora molte aree in cui in particolare le prestazioni della rete sono scarse e lo spazio di archiviazione limitato. Inoltre, non dimenticare che tutti i sistemi devono interfacciarsi con sistemi legacy (sia hardware che software) e devono supportare i loro formati di dati.
jwenting

4
@jwenting No, in realtà il tempo degli sviluppatori è di solito il pezzo più costoso di un'applicazione. Certo, potrebbe non essere il caso se lavori su Google o Facebook, ma la maggior parte delle app non funziona su quella scala. E quando i tuoi sviluppatori trascorrono del tempo sulle cose è la risorsa più costosa, la leggibilità umana conta molto più dei 100 millisecondi in più che il programma può analizzare.
jpmc26,

3
@ jpmc26 Non vedo nulla nella domanda che mi suggerisce che l'OP è quello che definisce il formato.
JimmyJames,

Risposte:


76

Per i controlli ad hoc, basta usare un hexdump standard e imparare a osservarlo.

Se vuoi prepararti per un'indagine adeguata, di solito scrivo un decodificatore separato in qualcosa come Python - idealmente questo sarà guidato direttamente da un documento di specifica del messaggio o IDL, ed essere il più automatizzato possibile (quindi non c'è possibilità di introdurre manualmente lo stesso bug in entrambi i decodificatori).

Infine, non dimenticare che dovresti scrivere unit test per il tuo decodificatore, usando un input predefinito corretto.


2
"basta usare un hexdump standard e imparare a osservarlo." Sì. Nella mia esperienza, più sezioni di qualsiasi cosa fino a 200 bit possono essere scritte su una lavagna per un confronto raggruppato, che a volte aiuta con questo tipo di cose per iniziare.
Albero

1
Trovo che un decodificatore separato valga la pena se i dati binari svolgono un ruolo importante nell'applicazione (o nel sistema, in generale). Ciò è particolarmente vero se il formato dei dati è variabile: i dati in layout fissi possono essere individuati in un hexdump con un po 'di pratica ma colpiscono velocemente un muro di fattibilità. Abbiamo eseguito il debug del traffico USB e CAN con decodificatori di pacchetti commerciali e ho scritto un decodificatore PROFIBus (in cui le variabili si estendono su byte, completamente illeggibili in un dump esadecimale), e le abbiamo trovate immensamente utili.
Peter - Ripristina Monica il

10

Il primo passo per farlo è che hai bisogno di un modo per trovare o definire una grammatica che descriva la struttura dei dati, cioè uno schema.

Un esempio di ciò è una funzione linguistica di COBOL che è informalmente nota come quaderno. Nei programmi COBOL definiresti la struttura dei dati in memoria. Questa struttura è stata mappata direttamente sul modo in cui i byte sono stati memorizzati. Questo è comune ai linguaggi di quell'epoca rispetto ai linguaggi contemporanei comuni in cui la disposizione fisica della memoria è una preoccupazione di implementazione che viene sottratta allo sviluppatore.

Una ricerca su Google per il linguaggio dello schema di dati binari rivela una serie di strumenti. Un esempio è Apache DFDL . Potrebbe esserci già un'interfaccia utente per questo.


2
Questa funzione non è riservata alle lingue dell'era "antica". Le strutture e i sindacati C e C ++ possono essere allineati alla memoria. C # ha StructLayoutAttribute, che ho usato per trasmettere dati binari.
Kasper van den Berg,

1
@KaspervandenBerg A meno che tu non stia dicendo che C e C ++ li hanno aggiunti di recente, ritengo che la stessa era. Il punto è che questi formati non erano semplicemente per la trasmissione dei dati, sebbene fossero usati per quello, ma mappavano direttamente su come il codice funzionava con i dati in memoria e su disco. Questo non è, in generale, il modo in cui le lingue più recenti tendono a funzionare sebbene possano avere tali caratteristiche.
JimmyJames

@KaspervandenBerg C ++ non lo fa tanto quanto pensi. È possibile utilizzare strumenti specifici dell'implementazione per allineare ed eliminare il riempimento (e, certamente, sempre più lo standard aggiunge funzionalità per questo tipo di cose) e l'ordine dei membri è deterministico (ma non necessariamente uguale a quello in memoria!).
Corse di leggerezza in orbita

6

ASN.1 , Abstract Syntax Notation One, fornisce un modo per specificare un formato binario.

  • DDT - Sviluppa utilizzando dati campione e unit test.
  • Una discarica testuale può essere utile. Se in XML è possibile comprimere / espandere le sotto-gerarchie.
  • ASN.1 non è realmente necessario ma è più semplice una specifica di file basata sulla grammatica, più dichiarativa.

6
Se la parata senza fine delle vulnerabilità della sicurezza nei parser ASN.1 è un'indicazione, adottarla fornirebbe sicuramente un buon esercizio nel debug dei formati binari.
Mark

1
@Mark molti array di piccoli byte (e che in vari alberi della gerarchia) spesso non sono gestiti a destra (in modo sicuro) in C (ad esempio, non usando eccezioni). Non sottovalutare mai la bassa livellosità, l'insicurezza intrinseca di C. ASN.1 in - per esempio - java non espone questo problema. Poiché l'analisi della grammatica ASN.1 potrebbe essere eseguita in modo sicuro, anche C potrebbe essere eseguito con una base di codice piccola e sicura. E parte delle vulnerabilità sono inerenti al formato binario stesso: si possono sfruttare costrutti "legali" della grammatica del formato, che hanno una semantica disastrosa.
Joop Eggen,

3

Altre risposte hanno descritto la visualizzazione di un dump esadecimale o la scrittura di strutture di oggetti in JSON. Penso che combinare entrambi sia molto utile.

L'uso di uno strumento in grado di eseguire il rendering di JSON in cima al dump esadecimale è davvero utile; Ho scritto uno strumento open source che analizzava i binari .NET chiamati dotNetBytes , ecco una vista di una DLL di esempio .

esempio dotNetBytes


1

Non sono sicuro di aver compreso appieno, ma sembra che tu abbia un parser per questo formato binario e tu controlli il codice per esso. Quindi questa risposta si basa su tale presupposto.

Un parser riempirà in qualche modo strutture, classi o qualunque struttura di dati abbia la tua lingua. Se si implementa a ToStringper tutto ciò che viene analizzato, si finisce con un metodo molto facile da usare e di facile manutenzione per visualizzare quei dati binari in un formato leggibile dall'uomo.

Avresti essenzialmente:

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

E questo è tutto, dal punto di vista del suo utilizzo. Ovviamente ciò richiede l'implementazione / sostituzione della ToStringfunzione per la propria Objectclasse / struttura / qualunque cosa, e dovreste anche farlo per qualsiasi classe / struttura / sistema annidata.

È inoltre possibile utilizzare un'istruzione condizionale per impedire che la ToStringfunzione venga chiamata nel codice di rilascio in modo da non perdere tempo con qualcosa che non verrà registrato al di fuori della modalità di debug.

Il tuo ToStringsguardo potrebbe in questo modo:

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

La tua domanda originale sembra che tu abbia in qualche modo tentato di farlo e che pensi che questo metodo sia gravoso, ma a un certo punto hai anche implementato l'analisi di un formato binario e creato variabili per archiviare quei dati. Quindi tutto ciò che devi fare è stampare quelle variabili esistenti al livello appropriato di astrazione (la classe / struttura in cui si trova la variabile).

Questo è qualcosa che dovresti fare solo una volta e puoi farlo mentre costruisci il parser. E cambierà solo quando cambia il formato binario (che comunque richiederà già una modifica al tuo parser).

Allo stesso modo: alcune lingue hanno solide funzionalità per trasformare le classi in XML o JSON. C # è particolarmente bravo in questo. Non è necessario rinunciare al formato binario, è sufficiente eseguire XML o JSON in un'istruzione di registrazione di debug e lasciare da solo il codice di rilascio.

Personalmente consiglierei di non seguire la strada del dump esadecimale, perché è soggetto a errori (hai iniziato con il byte giusto, sei sicuro che quando leggi da sinistra a destra stai "vedendo" l'endianness corretta, ecc.) .

Esempio: dì le tue ToStringsvariabili di sputo a,b,c,d,e,f,g,h. Esegui il tuo programma e noti un bug g, ma il problema è iniziato davvero c(ma stai eseguendo il debug, quindi non l'hai ancora capito). Se conosci i valori di input (e dovresti) vedrai immediatamente che cè qui che iniziano i problemi.

Rispetto a una discarica esadecimale che ti dice solo 338E 8455 0000 FF76 0000 E444 ....; se i tuoi campi hanno dimensioni variabili, da dove cinizia e qual è il valore, un editor esadecimale ti dirà ma il mio punto è che questo è soggetto a errori e richiede tempo. Non solo, ma non puoi automatizzare facilmente / rapidamente un test tramite un visualizzatore esadecimale. Stampare una stringa dopo aver analizzato i dati ti dirà esattamente cosa sta "pensando" il tuo programma e sarà un passo lungo il percorso dei test automatizzati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.