Zobaczmy teraz, jak będzie wyglądał stos po wywołaniu funkcji Śledź. Pamiętajmy, że w architekturze x86 wierzchołek stosu ma najmniejszy adres:
12ff54 |
[this] ebp-Oc |
12ff58 |
[k] ebp-8 |
12ff5c |
[j] ebp-4 |
12ff60 |
[stare ebp] ebp |
12ff64 |
[adres powrotu] ebp+4 |
12ff68 |
[i] ebp+8 |
12ff6c |
[i1] ebp+Oc |
Co to wszystko znaczy? Słusznie domagamy się odpowiedzi. Umiejętność czytania stosu wywołań i rozumienia go jest nieoceniona podczas debugowania.
Rejestr EBP reprezentuje ramkę stosu — punkt odniesienia przy obliczaniu zmiennych lokalnych i parametrów funkcji. Pamięć powyżej EBP (tzn. wszystkie adresy mniejsze niż adres wskazywany przez EBP) aż do następnej ramki stosu zawiera zmienne lokalne. W tym wypadku są dwie zmienne lokalne — j oraz k. Ostatnia wartość to wskaźnik this. Wszystko poniżej EBP (tzn. wszystkie adresy większe niż adres wskazywany przez EBP) reprezentuje parametry dla funkcji, z wyjątkiem pierwszych czterech bajtów (poprzednia ramka stosu lub ramka stosu wywoływanej funkcji, oznaczone w przykładzie przez stare ebp) i czterech kolejnych bajtów (adres zwrotny). Parametry są wkładane na stos w porządku od prawej do lewej według kolejności pojawiania się ich w definicji funkcji. W tym przykładzie widzimy parametry i oraz il z funkcji Śledź. Najpierw na stos jest wkładany pierwszy argument po lewej stronie, więc pojawia się on w stosie najniżej.
Zauważmy, że przedstawiony tutaj ślad stosu jest w konwencji thiscall — domyślnej dla funkcji składowych języka C++, które nie używają list parametrów (za pomocą konwencji ...). W konwencji thiscall zakłada się, że argumenty usuwa funkcja wywoływana. W konwencji ... jednakże zakłada się, że to funkcja wywołująca oczyści stos. Przy użyciu konwencji ... zachodzi również różnica w sposobie wkładania na stos wskaźnika this. W konwencji thiscall, wskaźnik this jest wkładany przez funkcję wywoływaną — zostaje przekazany przez funkcję wywołującą w rejestrze ECX. Obserwując podany przykład, widzimy, że funkcja Śledź kończy się na instrukcji ret 8, aby zająć się dwoma przekazanymi parametrami.
W konwencji cDecl wskaźnik this jest wkładany na stos przez funkcję wywołującą. W zamieszczonym kodzie widać, że funkcja ŚledźListaPar na końcu wykonuje instrukcję ret. Zauważmy jednak, że kod wywołujący funkcję ŚledźListaPar dodaje OC do rejestru ESP, aby zawrzeć dwa parametry przekazywane przez kod wywołujący oraz wskaźnik this. (Dlaczego OC? Po cztery bajty dla każdego parametru plus cztery bajty dla wskaźnika this.)
Analizowanie pamięć' w ramce stosu może być czasem mylące ze względu na sposób przechowywania pamięci. Przypuśćmy na przykład, że znajdujemy się w punkcie zatrzymania. Obserwujemy rejestr EBP w celu znalezienia ramki stosu. Powyżej ramki stosu znajdują się zmienne lokalne. Jeśli na przykład w rejestrze EBP jest adres 0x512ff60, to pierwsza zmienna lokalna rozpoczyna się pod adresem 12ff5c i kończy