Czy ktoś może mi pomóc w interpretacji tego prostego demontażu z WinDbg?
Otrzymałem następujący prosty kod C++:
#include <stdio.h>
int main(void)
{
::printf("\nHello,debugger!\n");
}
A Z WinDbg mam następujący kod do demontażu:
SimpleDemo!main:
01111380 55 push ebp
01111381 8bec mov ebp,esp
01111383 81ecc0000000 sub esp,0C0h
01111389 53 push ebx
0111138a 56 push esi
0111138b 57 push edi
0111138c 8dbd40ffffff lea edi,[ebp-0C0h]
01111392 b930000000 mov ecx,30h
01111397 b8cccccccc mov eax,0CCCCCCCCh
0111139c f3ab rep stos dword ptr es:[edi]
0111139e 8bf4 mov esi,esp
011113a0 683c571101 push offset SimpleDemo!`string' (0111573c)
011113a5 ff15b0821101 call dword ptr [SimpleDemo!_imp__printf (011182b0)]
011113ab 83c404 add esp,4
011113ae 3bf4 cmp esi,esp
011113b0 e877fdffff call SimpleDemo!ILT+295(__RTC_CheckEsp) (0111112c)
011113b5 33c0 xor eax,eax
011113b7 5f pop edi
011113b8 5e pop esi
011113b9 5b pop ebx
011113ba 81c4c0000000 add esp,0C0h
011113c0 3bec cmp ebp,esp
011113c2 e865fdffff call SimpleDemo!ILT+295(__RTC_CheckEsp) (0111112c)
011113c7 8be5 mov esp,ebp
011113c9 5d pop ebp
011113ca c3 ret
Mam pewne trudności, aby w pełni to zrozumieć. Co to jest SimpleDemo!ILT co tu robisz?
Jaki jest sens instrukcji porównującej ebp i esp w 011113c0?
Ponieważ nie mam żadnych zmiennych lokalnych w funkcji main (), dlaczego wciąż jest sub esp, 0C0h na loacation of 01111383?
Wielkie dzięki.Update 1
Choć nadal Nie wiem co ILT znaczy, ale_ _ RTC _ CheckESP służy do sprawdzania w trybie runtime. Kod ten można wyeliminować, umieszczając następującą pragmę przed funkcją main () .
#pragma runtime_checks( "su", off )
Odniesienie:
Http://msdn.microsoft.com/en-us/library/8wtf2dfz.aspx
Http://msdn.microsoft.com/en-us/library/6kasb93x.aspx
Update 2
The sub esp, 0C0h instrukcja przydziela kolejne 0c0h bajtów dodatkowego miejsca na stosie. Następnie EAX jest wypełniony 0xCCCCCCCC, to jest 4 bajty, ponieważ ECX=30h, 4*30h=0C0h, więc instrukcja rep stos dword ptr es: [edi] wypełnij dokładnie dodatkowe spacje za pomocą 0xCC. Ale co to jest to dodatkowe miejsce na stosie dla? Czy to jakiś bezpieczny pas? zauważyłem również, że jeśli wyłączę runtime check jak pokazuje Update 1, nadal jest taka dodatkowa przestrzeń na stosie, choć znacznie mniejsza. A ta przestrzeń nie jest wypełniona 0xCC.
Kod złożenia bez sprawdzania runtime jest jak poniżej:
SimpleDemo!main:
00231250 55 push ebp
00231251 8bec mov ebp,esp
00231253 83ec40 sub esp,40h <-- Still extra space allocated from stack, but smaller
00231256 53 push ebx
00231257 56 push esi
00231258 57 push edi
00231259 683c472300 push offset SimpleDemo!`string' (0023473c)
0023125e ff1538722300 call dword ptr [SimpleDemo!_imp__printf (00237238)]
00231264 83c404 add esp,4
00231267 33c0 xor eax,eax
00231269 5f pop edi
0023126a 5e pop esi
0023126b 5b pop ebx
0023126c 8be5 mov esp,ebp
0023126e 5d pop ebp
0023126f c3 ret
5 answers
Dodałem adnotację do asemblera, mam nadzieję, że to ci trochę pomoże. Linie zaczynające się od " d "są liniami kodu debugowania, linie zaczynające się od" r " są liniami kodu sprawdzania czasu pracy. Dodałem też, jak myślę, debugowanie bez sprawdzania wersji runtime i wersji release.
; The ebp register is used to access local variables that are stored on the stack,
; this is known as a stack frame. Before we start doing anything, we need to save
; the stack frame of the calling function so it can be restored when we finish.
push ebp
; These two instructions create our stack frame, in this case, 192 bytes
; This space, although not used in this case, is useful for edit-and-continue. If you
; break the program and add code which requires a local variable, the space is
; available for it. This is much simpler than trying to relocate stack variables,
; especially if you have pointers to stack variables.
mov ebp,esp
d sub esp,0C0h
; C/C++ functions shouldn't alter these three registers in this build configuration,
; so save them. These are stored below our stack frame (the stack moves down in memory)
r push ebx
r push esi
r push edi
; This puts the address of the stack frame bottom (lowest address) into edi...
d lea edi,[ebp-0C0h]
; ...and then fill the stack frame with the uninitialised data value (ecx = number of
; dwords, eax = value to store)
d mov ecx,30h
d mov eax,0CCCCCCCCh
d rep stos dword ptr es:[edi]
; Stack checking code: the stack pointer is stored in esi
r mov esi,esp
; This is the first parameter to printf. Parameters are pushed onto the stack
; in reverse order (i.e. last parameter pushed first) before calling the function.
push offset SimpleDemo!`string'
; This is the call to printf. Note the call is indirect, the target address is
; specified in the memory address SimpleDemo!_imp__printf, which is filled in when
; the executable is loaded into RAM.
call dword ptr [SimpleDemo!_imp__printf]
; In C/C++, the caller is responsible for removing the parameters. This is because
; the caller is the only code that knows how many parameters were put on the stack
; (thanks to the '...' parameter type)
add esp,4
; More stack checking code - this sets the zero flag if the stack pointer is pointing
; where we expect it to be pointing.
r cmp esi,esp
; ILT - Import Lookup Table? This is a statically linked function which throws an
; exception/error if the zero flag is cleared (i.e. the stack pointer is pointing
; somewhere unexpected)
r call SimpleDemo!ILT+295(__RTC_CheckEsp))
; The return value is stored in eax by convention
xor eax,eax
; Restore the values we shouldn't have altered
r pop edi
r pop esi
r pop ebx
; Destroy the stack frame
r add esp,0C0h
; More stack checking code - this sets the zero flag if the stack pointer is pointing
; where we expect it to be pointing.
r cmp ebp,esp
; see above
r call SimpleDemo!ILT+295(__RTC_CheckEsp)
; This is the usual way to destroy the stack frame, but here it's not really necessary
; since ebp==esp
mov esp,ebp
; Restore the caller's stack frame
pop ebp
; And exit
ret
; Debug only, no runtime checks
push ebp
mov ebp,esp
d sub esp,0C0h
d lea edi,[ebp-0C0h]
d mov ecx,30h
d mov eax,0CCCCCCCCh
d rep stos dword ptr es:[edi]
push offset SimpleDemo!`string'
call dword ptr [SimpleDemo!_imp__printf]
add esp,4
xor eax,eax
mov esp,ebp
pop ebp
ret
; Release mode (I'm assuming the optimiser is clever enough to drop the stack frame when there's no local variables)
push offset SimpleDemo!`string'
call dword ptr [SimpleDemo!_imp__printf]
add esp,4
xor eax,eax
ret
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-27 09:44:37
Numer jeden główna () Twojego kodu jest nieprawidłowo utworzona. Nie zwraca int, który obiecałeś, że wróci. Korygując tę wadę otrzymujemy:
#include int main(int argc, char *argv[]) { ::printf("\nHello,debugger!\n"); return 0; }
Dodatkowo, to bardzo dziwne widzieć #include <stdio.h>
w programie C++. I believe you want #include <cstdio>
We wszystkich przypadkach na stosie należy umieścić spację dla argumentów i dla zwracanych wartości. zwracana wartość main () wymaga przestrzeni stosu. main () s context, który ma być zapisany podczas wywołania printf () wymaga miejsca na stosie. printf () ' s argumenty wymagają przestrzeni stosu. wartość zwracana przez printf () wymaga przestrzeni stosu. To właśnie robi ramka stosu bajtów 0c0h.
Pierwszą rzeczą, która się dzieje, jest to, że przychodzący wskaźnik bas jest kopiowany na górę stosu. Następnie nowy wskaźnik stosu jest kopiowany do wskaźnika bazowego. Będziemy sprawdzać później, aby upewnić się, że stos kończy się z powrotem tam, skąd się rozpoczął (ponieważ masz włączone sprawdzanie runtime). Następnie budujemy ramkę stosu (o długości 0c0h bajtów) do przechowywania naszego kontekstu i argumenty printf () podczas wywołania printf (). Przeskakujemy do printf (). Po powrocie przeskakujemy wartość zwracaną, której nie zaznaczyłeś w kodzie (jedyna rzecz pozostała na ramce) i upewniamy się, że stos po wywołaniu jest w tym samym miejscu, w którym był przed wywołaniem. Zdejmujemy kontekst ze stosu. Następnie sprawdzamy, czy końcowy wskaźnik stosu odpowiada wartości zapisanej z przodu. Następnie wyrzucamy poprzednią wartość wskaźnika bazowego z samej góry stosu i / align = "left" /
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-26 14:41:23
Jest to kod, który jest wstawiany przez kompilator podczas budowania za pomocą runtime checking (/RTC) . Wyłącz te opcje i powinno być jaśniej. / GZ może to również powodować w zależności od wersji VS.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-26 14:13:46
40 bajtów jest najgorszym przydziałem stosu dla dowolnej wywołanej lub wywołanej później funkcji. Jest to wyjaśnione w chwalebnych szczegółach tutaj.
Do czego jest zarezerwowane miejsce na górze stosu? Najpierw tworzony jest spacja dla dowolnych zmiennych lokalnych. W tym przypadku funkcja FunctionWith6Params () ma dwa. Jednak te dwie zmienne lokalne liczą tylko 0x10 bajtów. O co chodzi z resztą przestrzeni stworzonej na szczycie stosu?
Na platformie x64, gdy kod przygotowuje stos do wywołania innej funkcji, nie używa instrukcji push, aby umieścić parametry na stosie, jak to zwykle ma miejsce w kodzie x86. Zamiast tego wskaźnik stosu zazwyczaj pozostaje stały dla określonej funkcji. Kompilator patrzy na wszystkie funkcje, które kod wywołuje w bieżącej funkcji, znajduje tę Z maksymalną liczbą parametrów, a następnie tworzy wystarczająco dużo miejsca na stosie, aby pomieścić te parametry. W tym przykładzie, Function with6params () wywołuje printf () przekazując jej 8 parametrów. Ponieważ jest to wywołana funkcja z maksymalną liczbą parametrów, kompilator tworzy 8 slotów na stosie. Cztery górne sloty na stosie będą wtedy przestrzenią domową używaną przez dowolne wywołania funkcji FunctionWith6Params ().
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-27 03:02:59
Dla przypomnienia, podejrzewam, że ILT oznacza "Incremental Linking Thunk".
Sposób, w jaki linkowanie przyrostowe (oraz edycja i kontynuacja) działa jest następujący: linker dodaje warstwę indrection dla każdego wywołania poprzez thunksy, które są zgrupowane na początku pliku wykonywalnego, a po nich dodaje ogromną zarezerwowaną przestrzeń. W ten sposób, podczas ponownego łączenia zaktualizowanego pliku wykonywalnego, może on po prostu umieścić nowy / zmieniony kod w obszarze zastrzeżonym i łatać tylko dotknięte kawałki, bez zmiany reszty kod.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-05 17:46:05