¿Cómo se depura un formato binario?

11

Me gustaría poder depurar la construcción de un generador binario. En este momento, básicamente estoy imprimiendo los datos de entrada en el analizador binario, y luego profundizando en el código e imprimiendo la asignación de la entrada a la salida, luego tomando la asignación de salida (enteros) y utilizándola para ubicar el entero correspondiente en el binario Bastante torpe, y requiere que modifique el código fuente profundamente para llegar al mapeo entre entrada y salida.

Parece que podría ver el binario en diferentes variantes (en mi caso, me gustaría verlo en fragmentos de 8 bits como números decimales, porque está bastante cerca de la entrada). En realidad, algunos números son de 16 bits, algunos de 8, algunos de 32, etc. Por lo tanto, tal vez habría una manera de ver el binario con cada uno de estos números diferentes resaltados en la memoria de alguna manera.

La única forma en que podría ver que es posible es si realmente construyes un visualizador específico para el formato / diseño binario real. Por lo tanto, sabe en qué lugar de la secuencia deberían estar los números de 32 bits, y dónde deberían estar los números de 8 bits, etc. Esto es mucho trabajo y algo complicado en algunas situaciones. Entonces me pregunto si hay una forma general de hacerlo.

También me pregunto cuál es la forma general de depurar este tipo de cosas actualmente, por lo que tal vez pueda obtener algunas ideas sobre qué probar con eso.

Lance Pollard
fuente
75
Recibiste una respuesta que decía "usa el hexdump directamente, y haz esto y aquello adicionalmente", y esa respuesta recibió muchos votos positivos. Y una segunda respuesta, 5 horas después (!), Diciendo solo "use un hexdump". ¿Entonces aceptó el segundo a favor del primero? ¿Seriamente?
Doc Brown
44
Si bien puede tener una buena razón para usar un formato binario, considere si puede usar un formato de texto existente como JSON. La legibilidad humana cuenta mucho, y las máquinas y las redes suelen ser lo suficientemente rápidas como para que el uso de un formato personalizado para reducir el tamaño sea innecesario hoy en día.
jpmc26
44
@ jpmc26 todavía hay mucho uso para formatos binarios y siempre lo será. La legibilidad humana suele ser secundaria al rendimiento, los requisitos de almacenamiento y el rendimiento de la red. Y todavía hay muchas áreas donde especialmente el rendimiento de la red es deficiente y el almacenamiento es limitado. Además, no olvide que todos los sistemas tienen que interactuar con sistemas heredados (tanto hardware como software) y tener que admitir sus formatos de datos.
Jwenting
44
@jwenting No, en realidad, el tiempo de desarrollador suele ser la pieza más cara de una aplicación. Claro, ese puede no ser el caso si está trabajando en Google o Facebook, pero la mayoría de las aplicaciones no funcionan a esa escala. Y cuando los desarrolladores que pasan tiempo en cosas son el recurso más costoso, la legibilidad humana representa mucho más que los 100 milisegundos adicionales para que el programa los analice.
jpmc26
3
@ jpmc26 No veo nada en la pregunta que me sugiera que el OP es el que define el formato.
JimmyJames

Respuestas:

76

Para verificaciones ad-hoc, solo use un hexdump estándar y aprenda a mirarlo.

Si desea prepararse para una investigación adecuada, generalmente escribo un decodificador separado en algo como Python; idealmente, esto será impulsado directamente desde un documento de especificación de mensaje o IDL, y será lo más automatizado posible (por lo que no hay posibilidad de introducirlo manualmente el mismo error en ambos decodificadores).

Por último, no olvide que debe escribir pruebas unitarias para su decodificador, utilizando la entrada enlatada correcta conocida.

Inútil
fuente
2
"solo usa un hexdump estándar y aprende a mirarlo". Sip. En mi experiencia, se pueden escribir múltiples secciones de cualquier cosa de hasta 200 bits en una pizarra para una comparación agrupada, lo que a veces ayuda con este tipo de cosas para comenzar.
Mástil el
1
Creo que un decodificador separado bien vale la pena si los datos binarios juegan un papel importante en la aplicación (o sistema, en general). Esto es especialmente cierto si el formato de datos es variable: los datos en diseños fijos se pueden detectar en un hexdump con un poco de práctica, pero golpea rápidamente un muro de practicabilidad. Depuramos el tráfico USB y CAN con decodificadores de paquetes comerciales, y he escrito un decodificador PROFIBus (donde las variables se extienden a través de bytes, completamente ilegibles en un volcado hexadecimal), y los encontré inmensamente útiles.
Peter - Restablece a Mónica el
10

El primer paso para hacer esto es que necesita una forma de encontrar o definir una gramática que describa la estructura de los datos, es decir, un esquema.

Un ejemplo de esto es una característica del lenguaje de COBOL que se conoce informalmente como copybook. En los programas COBOL, definiría la estructura de los datos en la memoria. Esta estructura se asigna directamente a la forma en que se almacenaron los bytes. Esto es común a los lenguajes de esa época en comparación con los lenguajes contemporáneos comunes donde el diseño físico de la memoria es una preocupación de implementación que se abstrae del desarrollador.

Una búsqueda en Google para el lenguaje de esquema de datos binarios muestra varias herramientas. Un ejemplo es Apache DFDL . Es posible que ya haya una interfaz de usuario para esto también.

JimmyJames
fuente
2
Esta característica no está reservada a los idiomas de la era 'antigua'. Las estructuras y uniones C y C ++ pueden estar alineadas en memoria. C # tiene StructLayoutAttribute, que utilizo para transmitir datos binarios.
Kasper van den Berg
1
@KaspervandenBerg A menos que esté diciendo que C y C ++ agregaron estos recientemente, considero que es la misma época. El punto es que estos formatos no eran simplemente para la transmisión de datos, aunque se usaban para eso, se asignaban directamente a cómo funcionaba el código con los datos en la memoria y en el disco. En general, no es así como los lenguajes más nuevos tienden a funcionar, aunque pueden tener tales características.
JimmyJames
@KaspervandenBerg C ++ no hace eso tanto como crees que hace. Es posible utilizar herramientas específicas de implementación para alinear y eliminar el relleno (y, sin duda, cada vez más, el estándar agrega características para este tipo de cosas) y el orden de los miembros es determinista (¡pero no necesariamente lo mismo que en la memoria!).
ligereza corre en órbita el
6

ASN.1 , notación de sintaxis abstracta uno, proporciona una forma de especificar un formato binario.

  • DDT: desarrollar utilizando datos de muestra y pruebas unitarias.
  • Un volcado de texto puede ser útil. Si en XML puede contraer / expandir las jerarquías.
  • ASN.1 no es realmente necesario, pero una gramática basada en una especificación de archivo más declarativa es más fácil.
Joop Eggen
fuente
66
Si el desfile interminable de vulnerabilidades de seguridad en los analizadores ASN.1 es una indicación, su adopción sin duda proporcionaría un buen ejercicio para depurar formatos binarios.
Mark
1
@Mark muchas matrices de bytes pequeños (y que en diferentes árboles de jerarquía) a menudo no se manejan correctamente (de forma segura) en C (por ejemplo, no se utilizan excepciones). Nunca subestimes el nivel bajo, la inseguridad inherente de C. ASN.1 en, por ejemplo, Java no expone este problema. Como un análisis dirigido de gramática ASN.1 podría hacerse de forma segura, incluso C podría hacerse con una base de código pequeña y segura. Y parte de las vulnerabilidades son inherentes al formato binario en sí: uno puede explotar construcciones "legales" de la gramática del formato, que tienen una semántica desastrosa.
Joop Eggen el
3

Otras respuestas han descrito ver un volcado hexadecimal o escribir estructuras de objetos en, por ejemplo, JSON. Creo que combinar ambos es muy útil.

Usar una herramienta que pueda representar el JSON encima del volcado hexadecimal es realmente útil; Escribí una herramienta de código abierto que analizaba los binarios .NET llamados dotNetBytes , aquí hay una vista de un archivo DLL de ejemplo .

Ejemplo de dotNetBytes

Carl Walsh
fuente
1

No estoy seguro de entender completamente, pero parece que tienes un analizador para este formato binario y controlas el código. Entonces esta respuesta se basa en esa suposición.

Un analizador de alguna manera llenará estructuras, clases o cualquier estructura de datos que tenga su lenguaje. Si implementa un ToStringpara todo lo que se analiza, entonces termina con un método muy fácil de usar y fácil de mantener para mostrar esos datos binarios en un formato legible para humanos.

Esencialmente tendrías:

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

Y eso es todo, desde el punto de vista de usarlo. Por supuesto, esto requiere que implemente / anule la ToStringfunción para su Objectclase / estructura / lo que sea, y también tendría que hacerlo para cualquier clase / estructura / estructura anidada.

También puede usar una declaración condicional para evitar que se invoque la ToStringfunción en el código de lanzamiento para que no pierda el tiempo en algo que no se registrará fuera del modo de depuración.

Tu ToStringpodría verse así:

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

Su pregunta original hace que parezca que ha intentado hacer esto, y que cree que este método es oneroso, pero que en algún momento también implementó el análisis de un formato binario y creó variables para almacenar esos datos. Entonces, todo lo que tiene que hacer es imprimir esas variables existentes en el nivel apropiado de abstracción (la clase / estructura en la que se encuentra la variable).

Esto es algo que solo debe hacer una vez, y puede hacerlo mientras construye el analizador. Y solo cambiará cuando cambie el formato binario (que de todos modos ya provocará un cambio en su analizador).

En una línea similar: algunos lenguajes tienen características robustas para convertir clases en XML o JSON. C # es particularmente bueno en esto. No tiene que renunciar a su formato binario, solo hace el XML o JSON en una declaración de registro de depuración y deja solo su código de lanzamiento.

Yo personalmente recomendaría no ir a la ruta de volcado hexadecimal, ya que es propenso a errores (si comenzaste en el byte derecho, ¿estás seguro cuando estás leyendo de izquierda a derecha que estás "viendo" la endianness correcta, etc.) .

Ejemplo: Di tus ToStringsvariables de escupir a,b,c,d,e,f,g,h. Ejecutas tu programa y notas un error g, pero el problema realmente comenzó c(pero estás depurando, por lo que aún no lo has descubierto). Si conoce los valores de entrada (y debería hacerlo), verá instantáneamente que callí comienzan los problemas.

En comparación con un volcado hexadecimal que simplemente te dice 338E 8455 0000 FF76 0000 E444 ....; si sus campos varían de tamaño, dónde ccomienza y cuál es el valor, un editor hexadecimal le dirá, pero mi punto es que esto es propenso a errores y consume mucho tiempo. No solo eso, sino que no puede automatizar fácil / rápidamente una prueba a través de un visor hexadecimal. Imprimir una cadena después de analizar los datos le dirá exactamente qué está 'pensando' su programa, y ​​será un paso en el camino de las pruebas automatizadas.

Shaz
fuente