W jaki sposób GDB wstrzymuje wykonanie


16

Jak zapewne wiesz, możemy użyć GDB i ustawić punkty przerwania w naszym kodzie, aby wstrzymać wykonywanie w celu debugowania.

Moje pytania dotyczą tego, w jaki sposób GDB wstrzymuje proces i pozwala na przeglądanie zawartości rejestrów, i rna przykład przy użyciu . Czy te rejestry nie są stale używane przez inne procesy systemu operacyjnego? jak się nie nadpisują?

Czy to tylko migawka zawartości, a nie danych na żywo?


2
Dlaczego wszystkie rejestry nie są nadpisywane, gdy system operacyjny decyduje się na chwilę zatrzymać program i uruchomić inny?
user253751

CppCon 2018: Simon Brand „How C ++ Debuggers Work” youtube.com/watch?v=0DDrseUomfU
Robert Andrzejuk

Odpowiedzi:


24

Różni się nieznacznie w zależności od architektury, ale ważne punkty obowiązują niemal wszędzie:

  • Obsługa przerwań powoduje, że stan procesora (w tym rejestry) jest zapisywany w pamięci przed uruchomieniem ISR i przywracany po wyjściu z ISR.

  • Jeśli procedura obsługi przerwań zamienia zawartość miejsca w pamięci, w którym zapisywane są te rejestry, może wykonać zmianę kontekstu . Każdy wątek ma obszar pamięci, w którym jego rejestry są zapisywane, gdy wątek nie jest uruchomiony.

  • Przełącznik kontekstu jest kontrolowany przez harmonogram wątków, który bierze pod uwagę, czy wątek czeka na operacje we / wy, synchronizację, jaki jest jego priorytet, dostarczanie sygnału itp. Często brane jest pod uwagę liczenie wstrzymań.

  • Debuger może zwiększyć liczbę zawieszeń, co gwarantuje, że wątek nie będzie działał. Następnie może sprawdzić (i zmienić) zapisaną w rejestrze kopię rejestru.


14

Oprócz świetnych informacji @BenVoigt, pozwólcie mi dodać kilka rzeczy:

Punkt przerwania jest ustawiany przez debugger poprzez zamianę wartości kodu maszynowego (instrukcji lub części instrukcji) w procesie debugowanym na określoną instrukcję pułapki w miejscu w kodzie, który odpowiada żądanej linii (źródłowej) do złamania. Ta szczególna instrukcja pułapki ma służyć jako punkt przerwania - debugger wie o tym, podobnie jak system operacyjny.

Kiedy debugowany proces / wątek trafia w instrukcję pułapki, która uruchamia proces opisany przez @Ben, który obejmuje połowę zamiany kontekstu, która zawiesza aktualnie działający wątek (obejmujący zapisanie jego stanu procesora w pamięci) w celu potencjalnego późniejszego wznowienia. Ponieważ pułapka ta jest pułapką punktu przerwania, system operacyjny utrzymuje zawieszony proces debugowania za pomocą być może mechanizmu opisanego przez @Ben oraz powiadamia i ostatecznie wznawia debugger.

Debuger używa następnie wywołań systemowych, aby uzyskać dostęp do zapisanego stanu zawieszonego procesu / wątku, który jest debugowany.

Aby wykonać (wznowić) wiersz kodu, który się zepsuł (który ma teraz konkretną instrukcję pułapki), debugger przywróci oryginalną wartość kodu maszynowego, którą zastąpił instrukcją pułapki punktu przerwania, ewentualnie ustawi inną pułapkę gdzie indziej (np. W przypadku pojedynczego kroku, lub użytkownik tworzy nowe punkty przerwania) i oznaczy proces / wątek jako uruchamialny, być może używając mechanizmu opisanego przez @Ben.

Rzeczywiste szczegóły mogą być bardziej skomplikowane, ponieważ utrzymywanie długo działającego punktu przerwania, który został trafiony, oznacza zrobienie czegoś takiego jak zamiana pułapki punktu przerwania na prawdziwy kod, aby linia mogła działać, a następnie zamiana punktu przerwania z powrotem ...

Czy te rejestry nie są stale używane przez inne procesy systemu operacyjnego? jak się nie nadpisują?

Jak opisuje @Ben, użycie już istniejącej funkcji zawieszenia / wznowienia wątku (przełączanie kontekstu / zamiana wielozadaniowości ), która pozwala procesorom współdzielić wiele procesów / wątków z wykorzystaniem podziału czasu.

Czy to tylko migawka zawartości, a nie danych na żywo?

To jest jedno i drugie. Ponieważ wątek uderzyć przerwania jest zawieszony, to obraz danych żywych (rejestry CPU, itp ..), w momencie przerwania, a miarodajne Master wartości rejestrów CPU przywrócić do procesora powinien Be gwint wznowionym . Jeśli użyjesz interfejsu użytkownika debugera do odczytu i / lub zmiany rejestrów procesora (procesu debugowanego), odczyta on i / lub zmieni migawkę / master za pomocą wywołań systemowych.


1
Cóż, większość architektur procesorów obsługuje pułapki debugowania, które na przykład uruchamiają się, gdy IP (wskaźnik instrukcji) jest równy adresowi przechowywanemu w rejestrze punktu przerwania, co oszczędza konieczności przepisywania kodu. (Dopasowując rejestry inne niż IP, można uzyskać punkty przerwania danych, a pułapki po każdej instrukcji można uzyskać pojedynczym krokiem). To, co opisałeś, jest również możliwe, o ile kod nie znajduje się w pamięci tylko do odczytu.
Ben Voigt

Re „Jeśli zmienisz rejestry procesora ...” w ostatnim akapicie, myślę, że masz na myśli „Jeśli zmienisz zapisaną kopię rejestrów CUP ...” Następnie, gdy system operacyjny wznowi proces, zmienione dane zostaną zapisane z powrotem do faktycznych rejestrów.
jamesqf

@jamesqf, tak, dzięki!
Erik Eidt

@BenVoigt, zgodził się. chociaż podczas gdy debuggery potrafią obsłużyć nieograniczoną liczbę punktów przerwania, sprzęt może obsłużyć zero lub kilka, więc debugger musi wykonać pewne czynności żonglowania.
Erik Eidt

@jamesqf: Opisywanie tego jako kopii jest nieco mylące. Jest to oficjalne miejsce przechowywania stanu wątku, gdy wątek nie jest uruchomiony.
Ben Voigt

5

Ściśle mówiąc, przynajmniej w najbardziej typowych przypadkach sam gdb nie wstrzymuje wykonania. Zamiast tego gdb pyta system operacyjny, a system operacyjny wstrzymuje wykonanie.

To może początkowo wydawać się różnicą bez różnicy - ale szczerze mówiąc, naprawdę jest różnica. Różnica polega na tym, że ta zdolność jest już wbudowana w typowy system operacyjny, ponieważ i tak musi być w stanie wstrzymać i ponownie uruchomić wykonywanie wątku - gdy wątek nie jest zaplanowany do uruchomienia (np. Potrzebuje zasobów, które nie jest obecnie dostępny) system operacyjny musi go zatrzymać, dopóki nie będzie można go zaplanować do uruchomienia.

Aby to zrobić, system operacyjny zwykle ma wydzielony blok pamięci dla każdego wątku, aby zapisać bieżący stan maszyny. Kiedy trzeba zatrzymać wątek, bieżący stan maszyny jest zapisywany w tym obszarze. Kiedy trzeba wznowić wątek, stan maszyny jest przywracany z tego obszaru.

Kiedy debugger musi wstrzymać wątek, system operacyjny wstrzymuje ten wątek dokładnie tak samo, jak robiłby to z innych powodów. Następnie, aby odczytać stan wstrzymanego wątku, debugger sprawdza stan zapisanego wątku. Jeśli zmodyfikujesz stan, debugger zapisuje do stanu zapisanego, a następnie zaczyna obowiązywać po wznowieniu wątku.

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.