Dlaczego GDB przeskakuje nieprzewidywalnie między wierszami i wypisuje zmienne jako „<zoptymalizowana wartość wyjściowa>”?


84

Czy ktoś może wyjaśnić to zachowanie gdb?

900         memset(&new_ckpt_info,'\0',sizeof(CKPT_INFO));
(gdb)
**903         prev_offset   = cp_node->offset;**
(gdb)
**905         m_CPND_CKPTINFO_READ(ckpt_info,(char *)cb->shm_addr.ckpt_addr+sizeof(CKPT_** HDR),i_offset);
(gdb)
**903         prev_offset   = cp_node->offset;**
(gdb)
**905         m_CPND_CKPTINFO_READ(ckpt_info,(char *)cb->shm_addr.ckpt_addr+sizeof(CKPT_ HDR),i_offset);**
(gdb)
**908         bitmap_offset  = client_hdl/32;**
(gdb)
**910         bitmap_value = cpnd_client_bitmap_set(client_hdl%32);**
(gdb)
**908         bitmap_offset  = client_hdl/32;**
(gdb)
**910         bitmap_value = cpnd_client_bitmap_set(client_hdl%32);**
(gdb)
**908         bitmap_offset  = client_hdl/32;**
(gdb)
**910         bitmap_value = cpnd_client_bitmap_set(client_hdl%32);**
(gdb)
913         found = cpnd_find_exact_ckptinfo(cb , &ckpt_info , bitmap_offset , &offset , &prev_offset);
(gdb)
916         if(!found)
(gdb) p found
$1 = <value optimized out>
(gdb) set found=0
Left operand of assignment is not an lvalue.

Dlaczego po wykonaniu linii 903 ponownie wykonuje to samo dla 905 908 910?

Kolejne rzeczy jest foundto boolzmienna typu a, więc dlaczego to pokazuje value optimized out? Nie jestem w stanie również ustawić wartości found.

Wydaje się, że jest to optymalizacja kompilatora (w tym przypadku -O2); jak mogę nadal ustawić wartość found?


8
podczas debugowania ogólnie dobrym pomysłem jest kompilacja z -O0, ponieważ optymalizacja prowadzi do tego rodzaju problemów.
LiraNuna

Odpowiedzi:


115

Aby debugować zoptymalizowany kod, naucz się języka asemblera / maszynowego.

Użyj trybu GDB TUI. Moja kopia GDB włącza to, kiedy wpisuję minus i Enter. Następnie wpisz Cx 2 (to znaczy przytrzymaj Control i naciśnij X, zwolnij oba, a następnie naciśnij 2). Spowoduje to wyświetlenie podzielonego źródła i demontażu. Następnie użyj stepii, nextiaby przesuwać jedną instrukcję maszynową na raz. Użyj Cx o, aby przełączać się między oknami TUI.

Pobierz plik PDF o języku maszynowym procesora i konwencjach wywoływania funkcji. Szybko nauczysz się rozpoznawać, co się dzieje z argumentami funkcji i zwracanymi wartościami.

Możesz wyświetlić wartość rejestru za pomocą polecenia GDB, takiego jak p $eax


Nadal mam problem z „zoptymalizowanym wyjściem”, a wartość zmiennej nie jest wyświetlana w innych oknach, ale nadal jest to świetna informacja, dzięki!
Tom Brito,

16
@TomBrito: Zoptymalizowany na zewnątrz oznacza, że ​​zmienna nie jest w pamięci. Prawdopodobnie znajduje się tylko w rejestrze procesora, co oznacza, że ​​musisz przeczytać demontaż i wydrukować wartości rejestru, aby go znaleźć.
Zan Lynx,

@Zan Lynx: Nie jestem pewien, czy zgadzam się z twoją analizą. Symbole DWARF mają wystarczającą ilość informacji, aby wyodrębnić wartości z rejestrów. Być może chodziło o to, że kompilator ustalił, że zmienna może zostać bezpiecznie odrzucona do czasu, gdy wykonanie osiągnie bieżący wiersz. W takim przypadku magazyn, w którym żyje zmienna, został prawdopodobnie ponownie użyty do czegoś innego. Myślę, że masz rację, że to normalnie miałoby miejsce tylko wtedy, gdyby zmienna została zarejestrowana.
Ian Ni-Lewis

@ IanNi-Lewis: Nie wiem, jakiej wersji DWARF używasz, ale z mojego doświadczenia wynika, że ​​GDB nie może wydrukować zmiennej, która została zapisana w rejestrze.
Zan Lynx

Jestem pewien, że masz rację. Moje doświadczenie z DWARFem pochodzi z pisania własnego parsera, a nie z używania gdb, więc tak naprawdę nie wiem, do czego zdolny jest gdb.
Ian Ni-Lewis

75

Przekompiluj bez optymalizacji (-O0 na gcc).


17
Nawet -O0 może wygenerować zoptymalizowany kod wyjściowy (próbując teraz z tym walczyć), chociaż nie jestem pewien dlaczego.
Chris Gregg

@ChrisGregg Mam ten sam problem! Czy dowiedziałeś się, na czym polega problem?
Paolo M

1
@paolom wygląda na to, że może to być problem z brzękiem, więc niestety kompilowałem z g ++ w celu debugowania.
Chris Gregg

Często nie jest to rozwiązanie - zwłaszcza jeśli masz zrzut rdzenia z produkcji i / lub nie możesz odtworzyć problemu w środowisku programistycznym.
smbear

39

Declare znaleźć jako „lotny”. Powinno to powiedzieć kompilatorowi, aby NIE optymalizował go.

volatile int found = 0;

1
Nawet kiedy zadeklaruję niektóre zmienne jako „volatile” w debugerze gdb, pokazuje je jako zoptymalizowane zmienne! Czy jest już w tym coś?
M.Rez

11

Kompilator zacznie robić bardzo sprytne rzeczy z włączoną optymalizacją. Debugger pokaże kod przeskakujący dużo do przodu i do tyłu dzięki zoptymalizowanemu sposobowi przechowywania zmiennych w rejestrach. Jest to prawdopodobnie powód, dla którego nie możesz ustawić swojej zmiennej (lub w niektórych przypadkach zobaczyć jej wartości), ponieważ została sprytnie rozdzielona między rejestry dla szybkości, zamiast mieć bezpośrednią lokalizację w pamięci, do której debugger może uzyskać dostęp.

Skompilować bez optymalizacji?


6

Zwykle wartości logiczne, które są używane w gałęziach natychmiast po ich obliczeniu w ten sposób, nigdy nie są w rzeczywistości przechowywane w zmiennych. Zamiast tego kompilator po prostu rozgałęzia się bezpośrednio od kodów warunków, które zostały ustawione z poprzedniego porównania. Na przykład,

int a = SomeFunction();
bool result = --a >= 0; // use subtraction as example computation
if ( result ) 
{
   foo(); 
}
else
{
   bar();
}
return;

Zwykle kompiluje się do czegoś takiego:

call .SomeFunction  ; calls to SomeFunction(), which stores its return value in eax
sub eax, 1 ; subtract 1 from eax and store in eax, set S (sign) flag if result is negative
jl ELSEBLOCK ; GOTO label "ELSEBLOCK" if S flag is set
call .foo ; this is the "if" black, call foo()
j FINISH ; GOTO FINISH; skip over the "else" block
ELSEBLOCK: ; label this location to the assembler
call .bar
FINISH: ; both paths end up here
ret ; return

Zwróć uwagę, że „bool” nigdy nie jest nigdzie przechowywany.


4

Prawie nie możesz ustawić wartości znalezionych. Debugowanie zoptymalizowanych programów rzadko jest warte zachodu, kompilator może zmienić kolejność kodu w taki sposób, że w żaden sposób nie będzie on odpowiadał kodowi źródłowemu (poza wytworzeniem tego samego wyniku), myląc w ten sposób debugery bez końca.


4

Podczas debugowania zoptymalizowanych programów (co może być konieczne, jeśli błąd nie pojawia się w kompilacjach debugowania), często trzeba zrozumieć wygenerowany kompilator asemblera.

W Twoim konkretnym przypadku wartość zwracana cpnd_find_exact_ckptinfobędzie przechowywana w rejestrze, który jest używany na Twojej platformie do zwracania wartości. Tak ix86by było %eax. On x86_64: %raxitd. Może być konieczne wyszukanie w Google „konwencji wywoływania procedur [twój procesor]”, jeśli nie jest to żadna z powyższych.

Możesz sprawdzić ten rejestr GDBi ustawić go. Np. Na ix86:

(gdb) p $eax
(gdb) set $eax = 0 

0

Używam QtCreator z gdb.

Dodawanie

QMAKE_CXXFLAGS += -O0
QMAKE_CXXFLAGS -= -O1
QMAKE_CXXFLAGS -= -O2
QMAKE_CXXFLAGS -= -O3

U mnie działa dobrze

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.