Assembler pomoc w zrozumieniu instrukcji

0

Mam taki kod:

  mov eax , 1
  mov ebx , 2
  push eax
  push ebx
  call a
  add esp , 8
  jmp koniec
a:
  push ebp //DO TEGO MIESJCA ROZUMIEM
  mov ebp , esp
  mov eax , [ ebp+8]
  add eax , [ ebp+12]
  pop ebp
  ret
koniec:

Nie rozumiem jaką wartość ma EBP w instrukcji PUSH. Mógłby ktoś wyjaśnić czym jest i do czego służy EBP? Naczytałem się już o tym, ale dalej nic nie rozumiem :(

Drugie pytanie to, dlaczego na koniec w EAX znajduje się wartość 120:

mov eax , 5
push eax
dec eax
call silnia (liczy silnie dla argumentu ze stosu i zwraca wynik przez rejestr EBX)
add esp , 4
mov eax , ebx

Mi wychodzi 24:

Umieść 5 w EAX
Dodaj EAX na stos
Zmniejsz EAX o 1 czyli na stosie mamy 4.
Liczymy silnie dla 4 = 24
Zmniejszamy wskaźnik stosu
Umieszczamy EBX (czyli wartość 24) w EAX.

Gdzie robię błąd?

0

Jezeli sie nie myle to wg. ABI EBP ma callee save policy
Program wykorzystuje ten rejestr wiec najpierw robi backup wrzucajac zawartosc na stos a na koncu przywraca poprzedni stan

Ide jeszcze sie upewnic co do tego save policy

1

Screenshot_20190901-185258.png

0

A drugie pytanie (edytowales post)

'call silnia (liczy silnie dla argumentu ze stosu i zwraca wynik przez rejestr EBX)'

Chyba jasne? :D

0
stivens napisał(a):

A drugie pytanie (edytowales post)

'call silnia (liczy silnie dla argumentu ze stosu i zwraca wynik przez rejestr EBX)'

Chyba jasne? :D

Czyli gdy robię dec EAX to jedynie w EAX mam 5 - 1 = 4, a na stosie dalej leży wartość 5?

1

No tak

1

Ale tam jest coś takiego jak: mov eax, [ ebp+8] i add eax, [ ebp+12], czyli muszę wiedzieć coś o EBP

Dobrze wiec tak:

push eax
push ebx
To jest 8 bajtow zaalokowanych na stosie

call a
To wrzuca 4 bajty adresu powrotu na stos (i wola procedure)

push ebp
backup ebp, kolejne 4 bajty na stosie

mov ebp , esp
Stack pointer zastepuje zawartosc ebp

mov eax , [ ebp+8]
add eax , [ ebp+12]
Wrzucamy jeden z argumentow do eax (offset 8 bajtow bo adres powrotu i backup ebp) i dodajemy drugi (bazowy offset + 4 bajty = 12)

Czyli 1+2? / ew 2+1, whatever

2

Rejestr ebp służy przede wszystkim do "zarządzania" mechanizmem funkcji i ich zmiennych lokalnych. Generalnie funkcja jest swego rodzaju abstrakcją, a ktoś dobrze wymyślił jak przedstawić pojęcie podprogramu wykorzystując do tego pamięć procesu, a konkretnie część tej pamięci jaką jest stos.

Napisałem na swoim blogu krótki artykuł na ten temat z tym, że dla architektury x64 -> https://shizz3r.blogspot.com/2018/08/reverse-engineering-puzzles-1-stack.html W każdym razie @anckor musisz poczytać sobie o calling conventions, czyli o tym jak funkcje i ich wywołanie jest reprezentowane na niskim poziomie abstrakcji. Dodam, że konwencje wywołań są różne dla różnych architektur.

Przy czym ten fragment mov ebp , esp jest jak najbardziej używany przez kompilatory. Oznacza, że możemy adresować zmienne lokalne funkcji opierając się na rejestrze ebp - rejestr ten staje się naszym base pointerem sama nazwa tego rejestru to przecież extended base pointer. Oczywiście można również powiedzieć kompilatorowi, że chcemy odwoływać się do zmiennych używając rejestru esp bezpośrednio, ale jest to dość niebezpieczne i rzadko praktykowane. Odnośnik do poczytania o tym -> https://stackoverflow.com/questions/14666665/trying-to-understand-gcc-option-fomit-frame-pointer

Podsumowując: W zdecydowanej większości przypadków ebp używamy do tego, żeby dostawać się do zmiennych lokalnych funkcji oraz jej argumentów. (w x86, w x64 tylko gdy tych argumentów jest na tyle dużo, że nie mieszczą się w odpowiednich rejestrach)

0

Zakladajac ze kompilator nie jest zbugowany (nie robi esp+8 jako modyfikacja rejestru kiedy chce odwolac sie do pamieci) to w jaki sposob odwolanie [ebp+8] jest bezpieczniejsze niz [esp+8]?

Ja przynajmniej na linuxie 64bitowym i gcc widywalem raczej rsp

1

Źle się wyraziłem. Unikanie ebp jako frame pointer jest niebezpieczne podczas samodzielnego programowania w Asmie, ponieważ nieraz ciężko jest śledzić stan stosu i możemy niechcący modyfikować nie te bajty co chcieliśmy. Kompilator niekoniecznie ma z tym problem, zależy jaki poziom optymalizacji mu podamy

1

Noo ten zestaw ktory podalem wyzej bez zadnej optymalizacji rsp uzywa. Musialbym jeszcze sprawdzic zeby nie sklamac na pewno ale jesli pamiec mnie nie myli no to rsp widuje od kompilatora.

To z tym ze czlowiek to pisal to moze sie nie pomylilem

1

Wystarczy zerknąć na przykładowy kod w godbolcie -> https://godbolt.org/ Widać tam, że kompilator pod arch x64 wrzuca normalnie mov rbp, rsp. Tak samo testowałem gcc lokalnie z defaultowymi optymalizacjami i dla takiego kodu:

#include <stdio.h>

int count(int a, int b) {
    return a + b;
}

int main() {
    int result = count(2, 2);
    printf("%d\n", result);
    return 0;
}

Kompilator wypluł dokładnie to:

push   rbp
mov    rbp,rsp
sub    rsp,0x10
mov    esi,0x2
mov    edi,0x2
call   0x64a <count>
mov    DWORD PTR [rbp-0x4],eax
mov    eax,DWORD PTR [rbp-0x4]
mov    esi,eax
lea    rdi,[rip+0xa0]        # 0x724
mov    eax,0x0
call   0x520 <printf@plt>
mov    eax,0x0
leave  
ret

Komenda kompilująca:

gcc test.c -o test

Czyli w większości przypadków po ebp/rbp jest jednak adresacja. Za to jeśli skompilujemy z flagą -fomit-frame-pointer tak jak sugerowałem wcześniej to kompilator rzeczywiście używa bezpośrednio esp/rsp do adresacji:

sub    rsp,0x18
mov    esi,0x2
mov    edi,0x2
call   0x64a <count>
mov    DWORD PTR [rsp+0xc],eax
mov    eax,DWORD PTR [rsp+0xc]
mov    esi,eax
lea    rdi,[rip+0xa3]        # 0x724
mov    eax,0x0
call   0x520 <printf@plt>
mov    eax,0x0
add    rsp,0x18
ret    

Komenda:

gcc test.c -o test -fomit-frame-pointer

2

W takim razie mea culpa
nie wiem skad mi sie to wzielo ;) (moze nauczylem sie podswiadomie mapowac rbp na rsp :D w glowie i za mocno weszlo)

1 użytkowników online, w tym zalogowanych: 0, gości: 1