Jak działa debugowanie odwrotne?


82

GDB ma nową wersję, która obsługuje odwrotne debugowanie (patrz http://www.gnu.org/software/gdb/news/reversible.html ). Zacząłem się zastanawiać, jak to działa.

Wydaje mi się, że aby uruchomić debugowanie wsteczne, musisz przechowywać cały stan maszyny, w tym pamięć dla każdego kroku. Spowodowałoby to niewiarygodnie wolne działanie, nie wspominając o zużyciu dużej ilości pamięci. Jak rozwiązuje się te problemy?


4
Wyobrażam sobie, że możesz sobie poradzić z przechowywaniem delt stanów, a nie całego stanu, ale nadal wydaje się, że może to być kosztowne.
spender


Zapisywanie delt może rzeczywiście działać bardzo dobrze i jest naprawdę konieczne dla wydajnego, odwracalnego rozwiązania pełnego systemu.
jakobengblom2

Odpowiedzi:


131

Jestem opiekunem gdb i jednym z autorów nowego odwrotnego debugowania. Chętnie porozmawiam o tym, jak to działa. Jak spekulowało kilka osób, musisz zapisać wystarczający stan komputera, aby móc go później przywrócić. Istnieje wiele schematów, z których jednym jest po prostu zapisywanie rejestrów lub lokalizacji pamięci, które są modyfikowane przez każdą instrukcję maszynową. Następnie, aby „cofnąć” tę instrukcję, po prostu przywróć dane w tych rejestrach lub komórkach pamięci.

Tak, jest drogi, ale nowoczesne procesory są tak szybkie, że kiedy i tak jesteś interaktywny (wykonujesz kroki lub punkty przerwania), tak naprawdę nie zauważysz tego tak bardzo.


4
Ale czy odwrotne debugowanie pozwala tylko na wycofanie nexti stepwpisanie poleceń, czy też na cofnięcie dowolnej liczby instrukcji? Na przykład, jeśli ustawię punkt przerwania w instrukcji i pozwolę mu działać do tego czasu, czy mogę wrócić do poprzedniej instrukcji, mimo że ją przeskoczyłem?
Nathan Fellman

10
> Ale czy odwrotne debugowanie pozwala tylko na wycofanie poleceń next i step, które wpisałeś, czy też pozwala cofnąć dowolną liczbę instrukcji. Możesz cofnąć dowolną liczbę instrukcji. Nie jesteś ograniczony, na przykład zatrzymujesz się tylko w punktach, w których zatrzymałeś się, gdy jechałeś do przodu. Możesz ustawić nowy punkt przerwania i przejść do niego wstecz> Na przykład, jeśli ustawię punkt przerwania w instrukcji i pozwolę mu działać do tego czasu, czy mogę wrócić do poprzedniej instrukcji, mimo że ją przeskoczyłem Tak Tak długo, jak ty włączony tryb nagrywania, zanim wbiegłeś do punktu przerwania
Michael Snyder

3
Przepraszam za niesformatowany tekst, nie wiem, o co chodzi.
Michael Snyder

10
Martwię się, że odwrotne debugowanie może cofnąć czas i sprawić, że cofniemy się do lat 60. lub 70. Nie chcę nosić dzwoneczków i znowu zapuszczać włosy.
Tin Man

3
A wywołania systemowe, które modyfikują stan w systemie operacyjnym? Czy to po prostu nie działa poprawnie? A co, gdy modyfikuje nieprzezroczysty uchwyt?
Adrian,

12

Pamiętaj, że nie możesz zapomnieć o użyciu symulatorów, maszyn wirtualnych i rejestratorów sprzętowych w celu wykonania odwrotnego wykonania.

Innym rozwiązaniem, które można wdrożyć, jest śledzenie wykonywania na sprzęcie fizycznym, tak jak robią to GreenHills i Lauterbach w ich debugerach sprzętowych. Na podstawie tego stałego śladu działania każdej instrukcji można przejść do dowolnego punktu w śladzie, usuwając po kolei efekty każdej instrukcji. Zauważ, że zakłada to, że możesz prześledzić wszystkie rzeczy, które wpływają na stan widoczny w debugerze.

Innym sposobem jest użycie metody punkt kontrolny + ponowne wykonanie, która jest używana przez VmWare Workstation 6.5 i Virtutech Simics 3.0 (i nowsze) i która wydaje się pochodzić z Visual Studio 2010. Tutaj używasz maszyny wirtualnej lub symulatora uzyskać poziom pośredni w wykonywaniu systemu. Regularnie zrzucasz cały stan na dysk lub do pamięci, a następnie polegasz na tym, że symulator jest w stanie deterministycznie ponownie wykonać dokładnie tę samą ścieżkę programu.

Upraszczając, działa to w ten sposób: powiedz, że jesteś w czasie T podczas wykonywania systemu. Aby przejść do czasu T-1, podnieś punkt kontrolny z punktu t <T, a następnie wykonaj (Tt-1) cykle, aby zakończyć jeden cykl przed miejscem, w którym byłeś. Może to działać bardzo dobrze i mieć zastosowanie nawet do obciążeń, które wykonują operacje we / wy dysku, składają się z kodu na poziomie jądra i wykonują pracę sterownika urządzenia. Kluczem jest posiadanie symulatora, który zawiera cały system docelowy ze wszystkimi jego procesorami, urządzeniami, pamięcią i IO. Aby uzyskać więcej informacji, zobacz listę dyskusyjną gdb i dyskusję po niej na liście dyskusyjnej gdb. Sam używam tego podejścia dość regularnie do debugowania skomplikowanego kodu, szczególnie w sterownikach urządzeń i wczesnych uruchomieniach systemu operacyjnego.

Innym źródłem informacji jest biała księga firmy Virtutech na temat punktów kontrolnych (którą napisałem w pełnej jawności).


Zobacz także jakob.engbloms.se/archives/1547 i dwa kolejne posty na blogu, aby uzyskać dokładniejsze omówienie technik odwrotnego debugowania.
jakobengblom2

Co powiesz na możliwość „ustawiania punktów zapisu” zamiast wdrażania odwrotnego kroku. Tak więc, debugujesz iw pewnym momencie możesz wybrać bieżący krok jako „punkt zapisu”, a później masz możliwość przeskoczenia z powrotem do tego punktu zapisu i kolejnego kroku do przodu, edytując zmienne, jeśli to konieczne. Coś w rodzaju „migawek” dla maszyn wirtualnych lub „punktów przywracania” dla systemów operacyjnych.
Rolf,

9

Podczas sesji EclipseCon zapytaliśmy również, jak oni to robią z Chronon Debugger for Java. Ten nie pozwala faktycznie cofnąć się, ale może odtworzyć nagrane wykonanie programu w taki sposób, że to on czuje się jak odwrotnej debugowania. (Główna różnica polega na tym, że nie można zmienić uruchomionego programu w debugerze Chronon, podczas gdy można to zrobić w większości innych debuggerów Java).

Jeśli dobrze to zrozumiałem, manipuluje kodem bajtowym uruchomionego programu tak, że każda zmiana stanu wewnętrznego programu jest rejestrowana. Stany zewnętrzne nie muszą być dodatkowo rejestrowane. Jeśli wpływają w jakiś sposób na twój program, to musisz mieć wewnętrzną zmienną pasującą do tego stanu zewnętrznego (a zatem ta wewnętrzna zmienna jest wystarczająca).

W czasie odtwarzania mogą następnie w zasadzie odtworzyć każdy stan uruchomionego programu na podstawie zarejestrowanych zmian stanu.

Co ciekawe, zmiany stanu są znacznie mniejsze, niż można by się spodziewać na pierwszy rzut oka. Więc jeśli masz warunkową instrukcję „if”, możesz pomyśleć, że potrzebujesz co najmniej jednego bitu, aby zarejestrować, czy program przyjął instrukcję then, czy else. W wielu przypadkach można nawet tego uniknąć, na przykład w przypadku, gdy te różne gałęzie zawierają wartość zwracaną. Wtedy wystarczy tylko zanotować wartość zwracaną (która i tak byłaby potrzebna) i przeliczyć decyzję o wykonywanej gałęzi z samej wartości zwracanej.


8

Chociaż to pytanie jest stare, większość odpowiedzi jest również i tak jak pozostaje interesującym tematem, zamieszczam odpowiedź z 2015 roku. Rozdziały 1 i 2 mojej pracy magisterskiej, Łączenie debugowania wstecznego i programowania na żywo z myśleniem wizualnym w programowaniu komputerowym , obejmują niektóre historyczne podejścia do odwrotnego debugowania (szczególnie skoncentrowane na podejściu do tworzenia migawek (lub punktów kontrolnych) i odtwarzania) oraz wyjaśnia różnicę między tym a wszechwiedzącym debugowaniem:

Komputer, po wykonaniu programu do przodu do pewnego momentu, powinien naprawdę być w stanie dostarczyć nam informacji o tym. Taka poprawa jest możliwa i znajduje się w tak zwanych wszechwiedzących debuggerach. Zwykle są klasyfikowane jako debugery wsteczne, chociaż można je dokładniej opisać jako debuggery "rejestrowania historii", ponieważ po prostu rejestrują informacje podczas wykonywania, aby wyświetlić lub zapytać później, zamiast pozwolić programiście cofnąć się w czasie w wykonywanym programie . „Wszechwiedzący” pochodzi z faktu, że cała historia stanu programu, po zarejestrowaniu, jest dostępna dla debuggera po jego wykonaniu. Nie ma wtedy potrzeby ponownego uruchamiania programu ani ręcznego instrumentowania kodu.

Oparte na oprogramowaniu wszechwiedzące debugowanie rozpoczęło się w systemie EXDAMS z 1969 r., Gdzie zostało nazwane „odtwarzaniem historii w czasie debugowania”. Debuger GNU, GDB, obsługuje wszechwiedzące debugowanie od 2009 roku dzięki funkcji „zapis procesu i odtwarzanie”. TotalView, UndoDB i Chronon wydają się być obecnie najlepszymi wszechwiedzącymi debuggerami, ale są to systemy komercyjne. Wydaje się, że TOD dla języka Java jest najlepszą alternatywą typu open source, która wykorzystuje częściowe deterministyczne odtwarzanie, a także częściowe przechwytywanie śledzenia i rozproszoną bazę danych, aby umożliwić rejestrowanie dużych ilości informacji.

Istnieją również debugery, które nie tylko umożliwiają nawigację po nagraniu, ale w rzeczywistości są w stanie cofnąć się w czasie wykonywania. Można je dokładniej opisać jako debuggery cofające się w czasie, podróżujące w czasie, dwukierunkowe lub wsteczne.

Pierwszym takim systemem był prototyp COPE z 1981 roku ...


4

mozilla rrjest solidniejszą alternatywą dla odwrotnego debugowania GDB

https://github.com/mozilla/rr

Wbudowany zapis i odtwarzanie GDB ma poważne ograniczenia, np. Brak obsługi instrukcji AVX: odwrotne debugowanie gdb kończy się niepowodzeniem z komunikatem „Rekord procesu nie obsługuje instrukcji 0xf0d pod adresem”

Zalety rr:

  • obecnie znacznie bardziej niezawodny. Przetestowałem to stosunkowo długie serie kilku złożonych programów.
  • oferuje również interfejs GDB z protokołem gdbserver, dzięki czemu jest świetnym zamiennikiem
  • mały spadek wydajności dla większości programów, sam tego nie zauważyłem bez wykonywania pomiarów
  • generowane ślady są małe na dysku, ponieważ zapisywanych jest bardzo niewiele zdarzeń niedeterministycznych, do tej pory nigdy nie musiałem martwić się o ich rozmiar

rr osiąga to, najpierw uruchamiając program w sposób, który rejestruje to, co się wydarzyło przy każdym niedeterministycznym zdarzeniu, takim jak zmiana wątku.

Następnie podczas drugiego przebiegu powtórki używa tego pliku śledzenia, który jest zaskakująco mały, do rekonstrukcji dokładnie tego, co wydarzyło się w pierwotnym niedeterministycznym przebiegu, ale w sposób deterministyczny, do przodu lub do tyłu.

rr został pierwotnie opracowany przez Mozillę, aby pomóc im odtworzyć błędy synchronizacji, które pojawiły się podczas ich nocnych testów następnego dnia. Ale aspekt odwrotnego debugowania jest również fundamentalny w przypadku błędu, który pojawia się tylko godzinami w trakcie wykonywania, ponieważ często chcesz cofnąć się, aby zbadać, jaki poprzedni stan doprowadził do późniejszej awarii.

Poniższy przykład prezentuje niektóre z jego cech, szczególnie te reverse-next, reverse-stepi reverse-continuepoleceń.

Zainstaluj na Ubuntu 18.04:

sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
sudo cpupower frequency-set -g performance
# Overcome "rr needs /proc/sys/kernel/perf_event_paranoid <= 1, but it is 3."
echo 'kernel.perf_event_paranoid=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Program testowy:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int f() {
    int i;
    i = 0;
    i = 1;
    i = 2;
    return i;
}

int main(void) {
    int i;

    i = 0;
    i = 1;
    i = 2;

    /* Local call. */
    f();

    printf("i = %d\n", i);

    /* Is randomness completely removed?
     * Recently fixed: https://github.com/mozilla/rr/issues/2088 */
    i = time(NULL);
    printf("time(NULL) = %d\n", i);

    return EXIT_SUCCESS;
}

skompiluj i uruchom:

gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c
rr record ./reverse.out
rr replay

Teraz jesteś w sesji GDB i możesz poprawnie odwrócić debugowanie:

(rr) break main
Breakpoint 1 at 0x55da250e96b0: file a.c, line 16.
(rr) continue
Continuing.

Breakpoint 1, main () at a.c:16
16          i = 0;
(rr) next
17          i = 1;
(rr) print i
$1 = 0
(rr) next
18          i = 2;
(rr) print i
$2 = 1
(rr) reverse-next
17          i = 1;
(rr) print i
$3 = 0
(rr) next
18          i = 2;
(rr) print i
$4 = 1
(rr) next
21          f();
(rr) step
f () at a.c:7
7           i = 0;
(rr) reverse-step
main () at a.c:21
21          f();
(rr) next
23          printf("i = %d\n", i);
(rr) next
i = 2
27          i = time(NULL);
(rr) reverse-next
23          printf("i = %d\n", i);
(rr) next
i = 2
27          i = time(NULL);
(rr) next
28          printf("time(NULL) = %d\n", i);
(rr) print i
$5 = 1509245372
(rr) reverse-next
27          i = time(NULL);
(rr) next
28          printf("time(NULL) = %d\n", i);
(rr) print i
$6 = 1509245372
(rr) reverse-continue
Continuing.

Breakpoint 1, main () at a.c:16
16          i = 0;

Podczas debugowania złożonego oprogramowania prawdopodobnie dojdziesz do punktu awarii, a następnie wpadniesz w głęboką ramkę. W takim przypadku nie zapominaj, że reverse-nextw przypadku wyższych klatek musisz najpierw:

reverse-finish

do tej klatki, zwykłe robienie tego upnie wystarczy.

Moim zdaniem najpoważniejsze ograniczenia rr to:

UndoDB to komercyjna alternatywa dla rr: https://undo.io Oba są oparte na śledzeniu / odtwarzaniu, ale nie jestem pewien, jak się porównują pod względem funkcji i wydajności.


Czy wiesz, jak mogę to zrobić z ddd? Dzięki
spraff

@spraff Nie jestem pewien, ale prawdopodobnie. Najpierw spróbuj połączyć ddd z gdbserver. Jeśli to zadziała, powinno działać również z rr.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@spraff jednak nie używaj ddd, używaj pulpitu nawigacyjnego gdb ;-) stackoverflow.com/questions/10115540/gdb-split-view-with-code/ ... To na pewno zadziała, ponieważ jest to zwykły GDB.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

3

Nathan Fellman napisał:

Ale czy odwrotne debugowanie pozwala tylko na wycofanie poleceń next i step, które wpisałeś, czy też pozwala cofnąć dowolną liczbę instrukcji?

Możesz cofnąć dowolną liczbę instrukcji. Nie jesteś ograniczony na przykład do zatrzymywania się tylko w punktach, w których zatrzymałeś się, gdy jechałeś do przodu. Możesz ustawić nowy punkt przerwania i cofnąć się do niego.

Na przykład, jeśli ustawię punkt przerwania w instrukcji i pozwolę jej działać do tego czasu, czy mogę wrócić do poprzedniej instrukcji, mimo że ją pominąłem?

Tak. O ile włączyłeś tryb nagrywania przed biegiem do punktu przerwania.


2
Kluczową częścią każdego rozwiązania odwrotnego jest włączenie go w pewnym momencie i można go cofnąć tylko do tego momentu. Nie ma magii, która może uruchomić maszynę w odwrotnym kierunku i dowiedzieć się, co się stało wcześniej, bez jakiegoś zapisu tego, co się stało.
jakobengblom2

2

Oto jak działa inny odwrotny debugger o nazwie ODB. Wyciąg:

Wszechwiedzące debugowanie to idea zbierania "znaczników czasu" w każdym "interesującym miejscu" (ustawianie wartości, wywoływanie metody, rzucanie / wychwytywanie wyjątku) w programie, a następnie pozwalanie programiście na użycie tych znaczników czasu do zbadania historia tego programu.

ODB ... wstawia kod do klas programu podczas ich ładowania, a po uruchomieniu programu zdarzenia są rejestrowane.

Domyślam się, że gdb działa w ten sam sposób.


Czy więc wymagałoby to dyrektyw w kodzie, aby powiedzieć kompilatorowi i debugerowi, gdzie znajdują się te interesujące punkty?
Nathan Fellman

Nie. Na stronie www.LambdaCS.com/debugger/debugger.html znajduje się demonstracja Java Web Start, która pokazuje, jak to działa. Wygląda jak normalny program. To w każdym razie ODB, nie wiem o gdb. Ale jest bardzo fajnie :)
demoncodemonkey

Zauważ, że rozwiązanie gdb NIE zmienia w żaden sposób programu docelowego. Jeśli musisz zaprogramować program, aby go debugować, masz duże prawdopodobieństwo, że problem zniknie z powodu różnicy w czasie i innych zakłóceń. Wszystkie komercyjne narzędzia revexec są oparte na jakiejś formie zewnętrznego rekordu, który nie zmienia kodu samego programu.
jakobengblom2

@ jakobengblom2: Myślę, że kładziesz zbyt duży nacisk na różnicę między zmianą celu poprzez zapisywanie w jego pamięci, emulowanie wykonywania lub po prostu dodawanie sprzętowych punktów przerwania. Wszyscy zmieniają czas. W rzeczywistości oprzyrządowanie docelowe prawdopodobnie zmienia się w najmniejszym czasie.
Ben Voigt,

2

Odwrotne debugowanie oznacza, że ​​możesz uruchomić program wstecz, co jest bardzo przydatne do wyśledzenia przyczyny problemu.

Nie musisz przechowywać pełnego stanu maszyny dla każdego kroku, tylko zmiany. Prawdopodobnie nadal jest dość drogi.


Rozumiem, ale nadal musisz przerywać wykonywanie przy każdej zmianie, aby zapisać zmiany.
Nathan Fellman

Tak, zgadza się, ale maszyny są teraz dość szybkie i po ludzku nie wierzę, że spowolnienie jest nie do opanowania. Jest porównywalny do valgrind, może nie tak powolny jak valgrind.
Michael Snyder
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.