Como você depura um formato binário?

11

Eu gostaria de poder depurar a construção de um construtor binário. No momento, estou basicamente imprimindo os dados de entrada no analisador binário e, depois, aprofundando o código e imprimindo o mapeamento da entrada na saída, pegando o mapeamento de saída (inteiros) e usando-o para localizar o número inteiro correspondente no binário. Muito desajeitado e requer que eu modifique o código fonte profundamente para obter o mapeamento entre entrada e saída.

Parece que você pode ver o binário em diferentes variantes (no meu caso, eu gostaria de vê-lo em blocos de 8 bits como números decimais, porque isso é muito próximo da entrada). Na verdade, alguns números são de 16 bits, 8, 32, etc. Então, talvez exista uma maneira de visualizar o binário com cada um desses números diferentes destacados na memória de alguma maneira.

A única maneira de ver que isso é possível é se você criar um visualizador específico para o formato / layout binário real. Portanto, ele sabe onde na sequência os números de 32 bits devem estar e onde os números de 8 bits devem estar etc. Isso é muito trabalhoso e meio complicado em algumas situações. Então, me perguntando se existe uma maneira geral de fazer isso.

Também estou me perguntando qual é a maneira geral de depurar esse tipo de coisa atualmente, então talvez eu possa ter algumas idéias sobre o que tentar com isso.

Lance Pollard
fonte
75
Você recebeu uma resposta dizendo "use o hexdump diretamente e faça isso e aquilo adicionalmente" - e essa resposta recebeu muitos votos positivos. E uma segunda resposta, 5 horas depois (!), Dizendo apenas "use um hexdump". Então você aceitou o segundo em favor do primeiro? Seriamente?
Doc Brown
4
Embora você possa ter um bom motivo para usar um formato binário, considere se você pode apenas usar um formato de texto existente como JSON. A legibilidade humana conta muito, e máquinas e redes geralmente são rápidas o suficiente para que hoje em dia seja desnecessário o uso de um formato personalizado para reduzir o tamanho.
Jpmc26
4
@ jpmc26 ainda há muito uso para formatos binários e sempre será. A legibilidade humana geralmente é secundária ao desempenho, aos requisitos de armazenamento e ao desempenho da rede. E ainda existem muitas áreas em que o desempenho da rede é especialmente ruim e o armazenamento limitado. Além disso, não esqueça que todos os sistemas precisam interagir com sistemas legados (hardware e software) e precisam suportar seus formatos de dados.
Jwenting 17/01/19
4
@jwenting Não, na verdade, o tempo do desenvolvedor geralmente é a parte mais cara de um aplicativo. Claro, isso pode não ser o caso se você estiver trabalhando no Google ou no Facebook, mas a maioria dos aplicativos não opera nessa escala. E quando seus desenvolvedores gastam tempo com material é o recurso mais caro, a legibilidade humana conta muito mais do que os 100 milissegundos extras para o programa analisá-lo.
Jpmc26 17/01/19
3
@ jpmc26 Não vejo nada na pergunta que sugira que o OP é quem define o formato.
precisa saber é o seguinte

Respostas:

76

Para verificações ad-hoc, basta usar um hexdump padrão e aprender a olhá-lo.

Se você deseja se preparar para uma investigação adequada, normalmente escrevo um decodificador separado em algo como Python - idealmente, isso será direcionado diretamente de um documento de especificação de mensagem ou IDL e será o mais automatizado possível (para que não haja chance de introduzir manualmente o mesmo bug nos dois decodificadores).

Por fim, não se esqueça de que você deve escrever testes de unidade para o seu decodificador, usando entradas enlatadas corretas e conhecidas.

Sem utilidade
fonte
2
"basta usar um hexdump padrão e aprender a olhá-lo." Sim. Na minha experiência, várias seções de qualquer coisa com até 200 bits podem ser anotadas em um quadro branco para comparação agrupada, o que às vezes ajuda com esse tipo de coisa para começar.
Mast
1
Acho que um decodificador separado vale o esforço se os dados binários desempenham um papel importante no aplicativo (ou sistema, em geral). Isso é especialmente verdadeiro se o formato dos dados for variável: os dados em layouts fixos podem ser vistos em um hexdump com um pouco de prática, mas atingem rapidamente um muro de praticabilidade. Depuramos o tráfego USB e CAN com decodificadores de pacotes comerciais, e escrevi um decodificador PROFIBus (onde variáveis ​​se espalham por bytes, completamente ilegíveis em um dump hexadecimal) e achamos os três imensamente úteis.
Peter - Restabelece Monica
10

O primeiro passo para fazer isso é que você precisa de uma maneira de encontrar ou definir uma gramática que descreva a estrutura dos dados, isto é, um esquema.

Um exemplo disso é um recurso de linguagem do COBOL, conhecido informalmente como copybook. Nos programas COBOL, você definiria a estrutura dos dados na memória. Essa estrutura foi mapeada diretamente para a maneira como os bytes foram armazenados. Isso é comum nas linguagens daquela época, em oposição às linguagens contemporâneas comuns, nas quais o layout físico da memória é uma preocupação de implementação que é abstraída do desenvolvedor.

Uma pesquisa no google por linguagem de esquema de dados binários exibe várias ferramentas. Um exemplo é o Apache DFDL . Talvez já exista uma interface do usuário para isso.

JimmyJames
fonte
2
Esse recurso não está reservado para idiomas da era "antiga". Estruturas e uniões C e C ++ podem ser alinhadas à memória. C # tem StructLayoutAttribute, que eu uso para transmitir dados binários.
Kasper van den Berg
1
@ KaspervandenBerg A menos que você esteja dizendo que C e C ++ os adicionaram recentemente, considero a mesma época. O ponto é que esses formatos não eram apenas para transmissão de dados, embora fossem usados ​​para isso, eles mapearam diretamente como o código trabalhava com dados na memória e no disco. Em geral, não é assim que as linguagens mais recentes tendem a funcionar, embora possam ter esses recursos.
precisa saber é o seguinte
O @KaspervandenBerg C ++ não faz isso tanto quanto você pensa. É possível usar ferramentas específicas da implementação para alinhar e eliminar o preenchimento (e, reconhecidamente, cada vez mais o padrão está adicionando recursos para esse tipo de coisa) e a ordem dos membros é determinística (mas não necessariamente a mesma da memória!).
Lightness Races in Orbit
6

ASN.1 , Resumo Sintaxe Notação Um, fornece uma maneira de especificar um formato binário.

  • DDT - Desenvolva usando dados de amostra e testes de unidade.
  • Um despejo de texto pode ser útil. Se em XML você pode recolher / expandir sub-hierarquias.
  • O ASN.1 não é realmente necessário, mas uma especificação de arquivo mais declarativa e baseada em gramática é mais fácil.
Joop Eggen
fonte
6
Se o desfile interminável de vulnerabilidades de segurança nos analisadores ASN.1 for qualquer indicação, adotá-lo certamente forneceria um bom exercício na depuração de formatos binários.
Mark
1
@Mark muitas matrizes de bytes pequenos (e que em árvores de hierarquia variadas) geralmente não são tratadas corretamente (com segurança) em C (por exemplo, não usando exceções). Nunca subestime a insegurança inerente e de baixo nível de C. ASN.1 em - por exemplo - java não expõe esse problema. Como uma análise direcionada por gramática ASN.1 poderia ser feita com segurança, mesmo C poderia ser feito com uma base de código pequena e segura. E parte das vulnerabilidades é inerente ao próprio formato binário: pode-se explorar construções "legais" da gramática do formato, que possuem semântica desastrosa.
Joop Eggen
3

Outras respostas descreveram a exibição de um hex dump ou a gravação de estruturas de objetos no JSON. Eu acho que combinar essas duas coisas é muito útil.

Usar uma ferramenta que pode renderizar o JSON no topo do dump hexadecimal é realmente útil; Eu escrevi uma ferramenta de código-fonte aberto que analisava binários do .NET chamados dotNetBytes . Aqui está uma exibição de um exemplo de DLL .

Exemplo de dotNetBytes

Carl Walsh
fonte
1

Não sei se entendi completamente, mas parece que você tem um analisador para esse formato binário e controla o código. Portanto, essa resposta se baseia nessa suposição.

Um analisador de alguma forma estará preenchendo estruturas, classes ou qualquer estrutura de dados que seu idioma tenha. Se você implementar um ToStringpara tudo o que for analisado, terá um método muito fácil de usar e de manutenção fácil de exibir esses dados binários em um formato legível por humanos.

Você teria essencialmente:

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

E é isso, do ponto de vista de usá-lo. Obviamente, isso exige que você implemente / substitua a ToStringfunção da sua Objectclasse / struct / qualquer que seja, e você também precisará fazê-lo para quaisquer classes / estruturas / estruturas aninhadas.

Além disso, você pode usar uma instrução condicional para impedir que a ToStringfunção seja chamada no código de liberação, para não perder tempo com algo que não será registrado fora do modo de depuração.

Você ToStringpode ficar assim:

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

Sua pergunta original faz parecer que você tentou fazer isso de alguma maneira e acha que esse método é oneroso, mas em algum momento também implementou a análise de um formato binário e criou variáveis ​​para armazenar esses dados. Então, tudo o que você precisa fazer é imprimir essas variáveis ​​existentes no nível apropriado de abstração (a classe / estrutura em que a variável se encontra).

Isso é algo que você deve fazer apenas uma vez e pode fazê-lo enquanto cria o analisador. E isso só será alterado quando o formato binário for alterado (o que já solicitará uma alteração no seu analisador de qualquer maneira).

Na mesma linha: algumas linguagens possuem recursos robustos para transformar classes em XML ou JSON. C # é particularmente bom nisso. Você não precisa renunciar ao seu formato binário, basta fazer o XML ou JSON em uma instrução de log de depuração e deixar seu código de versão em paz.

Eu, pessoalmente, recomendo não seguir a rota do dump hexadecimal, porque é propenso a erros (você começou no byte certo, tem certeza de que, quando está lendo da esquerda para a direita, está "vendo" a endianness correta etc.) .

Exemplo: diga suas ToStringsvariáveis ​​cuspir a,b,c,d,e,f,g,h. Você executa o seu programa e percebe um erro g, mas o problema realmente começou c(mas você está depurando, portanto ainda não percebeu isso). Se você conhece os valores de entrada (e deveria), verá instantaneamente que cé onde os problemas começam.

Comparado a um dump hexadecimal que apenas informa 338E 8455 0000 FF76 0000 E444 ....; se seus campos têm tamanhos variados, por onde ccomeça e qual é o valor - um editor hexadecimal dirá a você, mas o que quero dizer é que isso é propenso a erros e consome tempo. Não apenas isso, mas você não pode automatizar fácil / rapidamente um teste através de um visualizador hexadecimal. A impressão de uma sequência de caracteres após a análise dos dados informará exatamente o que seu programa está "pensando" e será um passo no caminho do teste automatizado.

Shaz
fonte