Jak debugować format binarny?

11

Chciałbym móc debugować budowanie binarnego konstruktora. W tej chwili w zasadzie drukuję dane wejściowe do parsera binarnego, a następnie wchodzę głęboko w kod i drukuję mapowanie danych wejściowych na dane wyjściowe, a następnie biorę mapowanie danych wyjściowych (liczby całkowite) i używam tego do zlokalizowania odpowiedniej liczby całkowitej w pliku binarnym. Całkiem niezręczny i wymaga głębokiej modyfikacji kodu źródłowego, aby uzyskać mapowanie między wejściem a wyjściem.

Wygląda na to, że można wyświetlić plik binarny w różnych wariantach (w moim przypadku chciałbym zobaczyć go w 8-bitowych częściach jako liczby dziesiętne, ponieważ jest to dość bliskie wartości wejściowej). W rzeczywistości niektóre liczby są 16-bitowe, niektóre 8, niektóre 32 itd. Może więc istniałby sposób, aby zobaczyć dane binarne z każdą z tych różnych liczb podświetloną w jakiś sposób.

Jedynym sposobem, w jaki mogłem zobaczyć, że jest to możliwe, jest zbudowanie wizualizatora specyficznego dla rzeczywistego formatu / układu binarnego. Więc wie, gdzie w sekwencji powinny znajdować się liczby 32-bitowe, a gdzie powinny być liczby 8-bitowe itp. W niektórych sytuacjach jest to bardzo pracochłonne i trudne. Zastanawiam się więc, czy jest na to ogólny sposób.

Zastanawiam się również, jaki jest obecnie ogólny sposób debugowania tego typu rzeczy, więc może uda mi się znaleźć kilka pomysłów na to, co można z tego wypróbować.

Lance Pollard
źródło
75
Masz jedną odpowiedź: „użyj zrzutu heksowego bezpośrednio i zrób to i to dodatkowo” - i ta odpowiedź uzyskała wiele pozytywnych opinii. I druga odpowiedź, 5 godzin później (!), Mówiąc tylko „użyj zrzutu heksowego”. Więc zaakceptowałeś drugi na rzecz pierwszego? Poważnie?
Doc Brown
4
Chociaż możesz mieć dobry powód, aby użyć formatu binarnego, zastanów się, czy możesz zamiast tego użyć istniejącego formatu tekstowego, takiego jak JSON. Czytelność dla ludzi ma duże znaczenie, a maszyny i sieci są zazwyczaj wystarczająco szybkie, aby w dzisiejszych czasach niepotrzebne było używanie niestandardowego formatu w celu zmniejszenia rozmiaru.
jpmc26
4
@ jpmc26 nadal jest wiele zastosowań formatów binarnych i zawsze tak będzie. Czytelność dla ludzi jest zwykle drugorzędna w stosunku do wydajności, wymagań dotyczących pamięci i wydajności sieci. I wciąż istnieje wiele obszarów, w których wydajność sieci jest słaba, a pamięć ograniczona. Ponadto nie zapomnij o wszystkich systemach, które muszą się łączyć ze starszymi systemami (zarówno sprzętowymi, jak i programowymi) i muszą obsługiwać swoje formaty danych.
jwenting
4
@jwenting Nie, w rzeczywistości czas programisty jest zwykle najdroższym elementem aplikacji. Oczywiście, może nie być tak, jeśli pracujesz w Google lub Facebook, ale większość aplikacji nie działa na taką skalę. A gdy programiści spędzający czas na rzeczach są najdroższym zasobem, czytelność liczy się o wiele więcej niż 100 milisekund dodatkowych, aby program mógł je przeanalizować.
jpmc26
3
@ jpmc26 Nie widzę nic w pytaniu, które sugeruje mi, że OP definiuje format.
JimmyJames

Odpowiedzi:

76

Do kontroli ad hoc wystarczy użyć standardowego zrzutu heksadecymalnego i nauczyć się go obserwować.

Jeśli chcesz przygotować się do właściwego dochodzenia, zwykle piszę osobny dekoder w czymś takim jak Python - najlepiej będzie on sterowany bezpośrednio z dokumentu specyfikacji wiadomości lub IDL i będzie możliwie najbardziej zautomatyzowany (więc nie ma szans na ręczne wprowadzenie ten sam błąd w obu dekoderach).

Na koniec nie zapominaj, że powinieneś pisać testy jednostkowe dla twojego dekodera, używając znanych poprawnych danych wejściowych.

Nieprzydatny
źródło
2
„po prostu użyj standardowego zrzutu heksadecymalnego i naucz się go wpatrywać”. Tak. Z mojego doświadczenia wynika, że ​​na tablicy można zapisać wiele sekcji dowolnych do 200 bitów w celu porównania zgrupowanego, co czasem pomaga w rozpoczęciu tego rodzaju rzeczy.
Maszt
1
Uważam, że osobny dekoder jest wart wysiłku, jeśli dane binarne odgrywają ważną rolę w aplikacji (lub ogólnie systemie). Jest to szczególnie prawdziwe, jeśli format danych jest zmienny: dane w ustalonych układach można z niewielką praktyką wykryć w zrzutie heksadecymalnym, ale szybko uderzają w ścianę praktyczności. Debugowaliśmy ruch USB i CAN przy użyciu komercyjnych dekoderów pakietów, a ja napisałem dekoder PROFIBus (w którym zmienne rozproszone są w bajtach, całkowicie nieczytelne na zrzutie szesnastkowym) i stwierdziłem, że wszystkie trzy są niezwykle pomocne.
Peter - Przywróć Monikę
10

Pierwszym krokiem do tego jest potrzeba znalezienia lub zdefiniowania gramatyki opisującej strukturę danych, tj. Schematu.

Przykładem tego jest funkcja językowa języka COBOL, która jest nieformalnie znana jako zeszyt. W programach COBOL definiowałbyś strukturę danych w pamięci. Ta struktura jest odwzorowana bezpośrednio na sposób przechowywania bajtów. Jest to typowe dla języków tamtej epoki, w przeciwieństwie do popularnych współczesnych języków, w których fizyczny układ pamięci stanowi problem implementacyjny, który jest oderwany od programisty.

Wyszukiwarka Google dla języka schematu danych binarnych ujawnia szereg narzędzi. Przykładem jest Apache DFDL . Może również istnieć interfejs użytkownika do tego.

JimmyJames
źródło
2
Ta funkcja nie jest zarezerwowana dla języków „starożytnych”. Struktury i związki C i C ++ można dopasować do pamięci. C # ma StructLayoutAttribute, którego używam do przesyłania danych binarnych.
Kasper van den Berg
1
@KaspervandenBerg O ile nie mówisz, że C i C ++ dodali je ostatnio, uważam, że ta sama era. Chodzi o to, że formaty te nie były po prostu przeznaczone do transmisji danych, chociaż zostały do ​​tego użyte, odwzorowały bezpośrednio na sposób działania kodu z danymi w pamięci i na dysku. Zasadniczo nie tak działają nowsze języki, chociaż mogą mieć takie funkcje.
JimmyJames
@KaspervandenBerg C ++ nie robi tego tak, jak myślisz. Możliwe jest użycie narzędzi specyficznych dla implementacji, aby wyrównać i wyeliminować wypełnianie (i, co prawda, coraz częściej standard dodaje funkcje dla tego rodzaju rzeczy), a kolejność elementów jest deterministyczna (ale niekoniecznie taka sama jak w pamięci!).
Wyścigi lekkości na orbicie
6

ASN.1 , Abstract Syntax Notation One, umożliwia określenie formatu binarnego.

  • DDT - Opracuj przy użyciu przykładowych danych i testów jednostkowych.
  • Pomocny może być zrzut tekstowy. Jeśli w XML możesz zwinąć / rozwinąć podhierarchie.
  • ASN.1 nie jest tak naprawdę potrzebny, ale łatwiejsza jest bardziej deklaratywna specyfikacja pliku oparta na gramatyce.
Joop Eggen
źródło
6
Jeśli niekończąca się parada luk w zabezpieczeniach w parserach ASN.1 jest jakąkolwiek wskazówką, przyjęcie jej z pewnością zapewni dobre ćwiczenie w debugowaniu formatów binarnych.
Mark
1
@ Zaznacz wiele małych tablic bajtowych (i to w różnych drzewach hierarchii) często nie są obsługiwane poprawnie (bezpiecznie) w C (na przykład nie używając wyjątków). Nigdy nie lekceważ niskiego poziomu, nieodłącznej niepewności C. ASN.1 w - na przykład - java nie ujawnia tego problemu. Ponieważ parsowanie w oparciu o gramatykę ASN.1 można wykonać bezpiecznie, nawet C można wykonać przy użyciu małej i bezpiecznej bazy kodu. Część luk jest nieodłącznie związana z samym formatem binarnym: można wykorzystać „legalne” konstrukcje gramatyki formatu, które mają katastrofalną semantykę.
Joop Eggen
3

Inne odpowiedzi opisywały wyświetlanie zrzutu heksadecymalnego lub zapisywanie struktur obiektów np. W JSON. Myślę, że połączenie obu tych elementów jest bardzo pomocne.

Bardzo przydatne jest użycie narzędzia, które może renderować JSON na szczycie zrzutu heksadecymalnego; Napisałem narzędzie typu open source, które analizowało pliki binarne .NET o nazwie dotNetBytes , oto widok przykładowej biblioteki DLL .

Przykład dotNetBytes

Carl Walsh
źródło
1

Nie jestem pewien, czy w pełni to rozumiem, ale wygląda na to, że masz parser dla tego formatu binarnego i kontrolujesz dla niego kod. Ta odpowiedź jest oparta na tym założeniu.

Analizator składni będzie w jakiś sposób wypełniał struktury, klasy lub dowolną strukturę danych, którą posiada Twój język. Jeśli zaimplementujesz a ToStringdla wszystkiego, co zostanie przeanalizowane, otrzymasz bardzo łatwą w użyciu i łatwą w utrzymaniu metodę wyświetlania tych danych binarnych w formacie czytelnym dla człowieka.

Zasadniczo miałbyś:

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

I to wszystko, z punktu widzenia korzystania z niego. Oczywiście wymaga to zaimplementowania / przesłonięcia ToStringfunkcji dla swojej Objectklasy / struct / cokolwiek, i musiałbyś to zrobić również dla wszystkich zagnieżdżonych klas / struktur / whatevers.

Możesz dodatkowo użyć instrukcji warunkowej, aby zapobiec ToStringwywołaniu funkcji w kodzie wersji, abyś nie marnował czasu na coś, co nie zostanie zarejestrowane poza trybem debugowania.

Twój ToStringmoże wyglądać tak:

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

Twoje pierwotne pytanie brzmi, jakbyś próbował to zrobić, i że uważasz, że ta metoda jest uciążliwa, ale w pewnym momencie zaimplementowałeś także parsowanie formatu binarnego i stworzyłeś zmienne do przechowywania tych danych. Wszystko, co musisz zrobić, to wydrukować istniejące zmienne na odpowiednim poziomie abstrakcji (klasa / struktura, w której znajduje się zmienna).

Jest to coś, co powinieneś zrobić tylko raz, i możesz to zrobić podczas budowania parsera. Zmieni się on tylko wtedy, gdy zmieni się format binarny (co i tak już spowoduje zmianę parsera).

Podobnie: niektóre języki mają solidne funkcje przekształcania klas w XML lub JSON. C # jest w tym szczególnie dobry. Nie musisz rezygnować z formatu binarnego, po prostu wykonuj XML lub JSON w instrukcji rejestrowania debugowania i zostaw swój kod wersji w spokoju.

Osobiście polecam nie przechodzić trasy zrzutu szesnastkowego, ponieważ jest ona podatna na błędy (czy zacząłeś od prawego bajtu, czy jesteś pewien, że kiedy czytasz od lewej do prawej, że „widzisz” prawidłową endianizm itp.) .

Przykład: Powiedz swoje ToStringszmienne wypluwające a,b,c,d,e,f,g,h. Uruchamiasz program i zauważasz błąd g, ale problem naprawdę zaczął się od c(ale debugujesz, więc jeszcze tego nie odkryłeś). Jeśli znasz wartości wejściowe (i powinieneś), od razu zobaczysz, że ctutaj zaczynają się problemy.

W porównaniu do zrzutu heksadecymalnego, który po prostu mówi 338E 8455 0000 FF76 0000 E444 ....; jeśli twoje pola mają różną wielkość, gdzie czaczyna się i jaka jest wartość - edytor szesnastkowy powie ci, ale chodzi mi o to, że jest to podatne na błędy i czasochłonne. Ponadto nie można łatwo / szybko zautomatyzować testu za pomocą przeglądarki szesnastkowej. Wydrukowanie ciągu po analizie danych powie dokładnie, co twój program „myśli” i będzie jednym krokiem na drodze zautomatyzowanego testowania.

Shaz
źródło