Cześć mam pytanko dlaczego w około 8 minucie jest używany nawias kwadratowy do rejestru ESP żeby wczytać to co jest pod adresem komórki a chwilę później jest pokazywana instrukcja add eax , dane - Nast gdzie już rejestr eax bez konsekwencji jest bez nawiasów kwadratowych?
Generalnie (za wyjątkiem instrukcji LEA która jest wyjątkiem), nawiasy kwadratowe w x86 używane są do odwołania się do pamięci, tj. co co jest w [nawiasie] jest traktowane jako adres komórki pamięci - co zresztą sam napisałeś :) To powiedziawszy, same rejestry nie znajdują się w pamięci, więc można do nich pisać też bezpośrednio (rejestr to malutki fragment pamięci w samym CPU, dużo szybszy niż RAM). Co więcej, w samym kodzie maszynowy można zaszyć stałe (tzw. immidiate - wartości natychmiastowe), które można używać jako źródło danych zapisanych bezpośrednio w instrukcji. Kilka przykładów! 1. MOV EAX, 1234 ← do rejestru EAX wrzucana jest wartość 1234 (1234 będzie zapisane bezpośrednio w kodzie maszynowym) 2. MOV DWORD [EAX], 1234 ← wartość EAX jest interpretowana jako numer komórki w pamięci (a raczej czterech kolejnych komórek - o tym mówi prefix DWORD - double word, gdzie w tym wypadku "word" to 16-bitów, więc dword to 32-bity czyli 4 bajty), i pod ten adres z rejestru EAX wrzucana jest wartość 1234 (zapisana bezpośrednio w kodzie maszynowym) 3. MOV EAX, EBX ← do rejestru EAX wrzucana jest wartość z rejestru EBX (tj. jest kopiowana) 4. MOV [EAX], EBX ← wartość EAX jest interpretowana jako numer komórki w pamięci (a konkretnie ponownie 4 komórek, bo EBX ma wielkość 32-bitów, więc "DWORD" nie trzeba pisać bo jest domyślnie), i pod ten adres z rejestru EAX wrzucane są 32-bity z rejestru EBX 5. MOV EAX, [EBX] ← do rejestru EAX wrzucane są 4 bajty odczytane z pamięci spod adresu zawartego w rejestrze EBX 6. MOV EAX, [1234] ← do rejestru EAX wrzucane są 4 bajty odczytane z pamięci spod adresu 1234 (oraz 1235, 1236 i 1237 - czyli 4rech sąsiadujących 1-bajtowych komórek zaczynając od podanego adresu) 7. MOV WORD [1234], [6789] ← w x86 można tylko jeden jawny zapis do pamięci robić w instrukcji, więc to nie przejdzie; ale jak by przeszło, to by znaczyło: skopiuj 2 bajty z pamięci z komórek 6789-6790 do komórek w pamięci o adresach 1234-1235 (dwa bajty, ponieważ WORD, gdzie ponownie, WORD to 16 bitów, czyli 2 bajty) Technicznie można zobaczyć nawet bardzo skomplikowane wyrażenia z których wyliczony będzie adres, np. [EAX+EBX*8+12345678]. To co konkretnie można w nawias [kwadratowy] wrzucić wynika z tego jak kodowane są instrukcje w kodzie maszynowym, co opisane jest np w tym bardzo starym filmiku: re.coldwind.pl/?vart=8 ;)
@@GynvaelColdwind Ok wszystkie te przykłady wydają mi się zrozumiałe rozumiem że w nawiasach kwadratowych jest wyliczany adres komórki pamięci rozumiem również pojęcie dword to że odwołuje się do 4 kolejnych komórek pamięci bo jedna komórka pamięci może tylko pomieścić 8 bitów ,rozumiem że kiedy nie używa się nawiasów kwadratowych działa się bezpośrednio na rejestrach procesora jednak tym bardziej nie rozumiem dlaczego kiedy ESP wskazuje na ostatni element na stosie to trzeba pisać add dword [ ESP ], dane - Nast , i odwoływać się do komórek pamięci RAM a nie bezpośrednio to co jest w rejestrze procesora czyli add ESP , dane - Nast skoro ESP wskazuję w tym przypadku na zawartość eax dlaczego to niedziała ?
@@damiandzugaa2972 Generalnie "ESP wskazuje na ostatni element na stosie" jest trochę mylne, bo brzmi jakby ESP było jakoś specjalnie magiczne. W praktyce bardziej prawidłowo jest powiedzieć "ESP zawiera w sobie adres komórki pamięci" (stos to nic innego jak zwykły kawałek pamięci, gdzie programiści mówili się że będą go nazywać "stosem" i wrzucać tam zmienne lokalne / etc). A skoro stos jest po prostu fragmentem pamięci, to jeśli chcesz się odwołać do czegoś na stosie, to musisz użyć [nawiasów kwadratowych]. Np. MOV [ESP], EAX (czyli "zapisz zawartość rejestru EAX - 4 bajty - pod adres z rejestru ESP"). Jak robisz ADD ESP, 1234, to to jest po prostu dodanie 1234 do liczby (adresu) który jest w ESP. ESP nie różni się niczym od rejestru EAX czy ESI - ot kolejny 32-bitowy rejestr ogólnego przeznaczenia (zazwyczaj interpretowany jako 32-bitowa liczba / 32-bitowy adres). Po prostu przyjęło się go używać do przechowywania adresu "na stosie" (w czym pomaga fakt, że PUSH, POP, CALL i RET niejawnie się akurat to niego odwołują). Co do "ESP wskazuje w tym przypadku na zawartość eax" - to jest generalnie nieprawidłowe stwierdzenie. Rejestry nie mają adresów (bo nie są w pamięci; są oddzielnymi elementami CPU), więc z definicji nie mają swojego adresu w pamięci (bo... nie są w pamięci, więęc... nie mogą mieć adresu w pamięci). A skoro rejestry nie mają adresów, to nie można "wskazać na rejestr" (na poziomie assembly wskaźnik to nic innego jak adres w pamięci, czyli "wskazywanie na" to po prostu "posiadanie adresu czegoś w pamięci").
Po co obliczać różnicę między 'nast' a 'dane', kiedy można wpisać 'dane' do jakiegoś rejestru lub po prostu dodać na stos? pastebin.com/HKc98HkQ (chyba, że to tak specjalnie w ramach nauki :D) EDIT: a jednak nie działa... tylko dlaczego? EDIT2: ahh... już rozumiem :-) dzięki debuggerowi z następnej lekcji udało mi się podejrzeć, że dane i nast to są adresy relatywne, zaś adres, który call dodaje na stos jest absolutny :) Dzięki za bardzo przystępne filmiki :)
Nie rozumiem dlaczego tak nie może być :/ ET to nie jest adres danej linijki kodu ? [bits 32] push ET call [ebx+3*4];printf add esp,4 push 0 call [ebx];exit ET:db 'Hello World',0xa,0
Dziękuje bardzo za tak obszerną odpowiedz ! :) Już prawie wszystko jasne :) Tylko skoro rozkaz call skacze pod dany adres podany jako argument to call addr addr: Nie powinno też nie działać ? Bo skoro etykiecie addr załóżmy NASM nada wartość jeden to będziemy mieli call 1 Czyli zawsze będziemy skakać pod ten sam adres w pamięci . A jednak te rozwiązanie działa i nie bardzo rozumiem dlaczego :/
Aaa, czyli po prostu instrukcja call zachowuje się inaczej jeśli jest wywołana z etykietą, a inaczej jeśli z rejestrami tak ? Ale jest to różnica tylko na poziomie assemblera, W kodzie maszynowym zawsze jest w formie call [przesunięcie], a nie call [adres] tak ?
Witam, postanowiłem po zapoznaniu się z tym odcinkiem zrobić prosty kalkulator, który dodawałby liczby. Niestety nie wiem dlaczego, ale nie chce działać. Ja postanowiłem kod zorganizować w ten sposób, że dane są na górze. Proszę o odpowiedź :) pastebin.com/VBU0Uazj
Zauważ, że taka organizacja danych jaką pokazuje w odcinku ma swoje konkretne uzasadnienie - call wrzuca adresy poszczególnych danych na stos, gdzie szukają ich funkcje wywołane, lub wyliczamy adresy tych danych w biegu. W Twoim wypadku masz jednego calla na początku (czyli odkładasz adres "=",0 na stosie), po czym masz całą serię wywołań funkcji które nigdy do żadnych danych się nie odwołują. Najlepiej obejrzyj jeszcze raz ten odcinek i spróbuj poprawić swój kod :)
***** Okej, dziękuję serdecznie za podpowiedź, chciałem tak zrobić, ale nie mam pojęcia jak np odwołać się do innych danych. Aha, nie już wiem, czyli one są po prostu wstawiane na stos ;D Że nawet o tym nie pomyślałem :D Zmienię program i napiszę Panu czy działa :)
***** Po małej zabawie z kodem, krzaczki się już nie pokazały, program natomiast znowu "wywala" Oto kod:pastebin.com/b9KQUzuW Dochodzę do wniosku, iż assembly to super język, ale łatwo w nim się pomylić :)
Skoro po zrobieniu call nast, mamy na stosie adres który potrzebujemy, to po co go zdejmować(pop eax), a potem znów wrzucać(push eax) ?
Też się zastanawiam
Cześć mam pytanko dlaczego w około 8 minucie jest używany nawias kwadratowy do rejestru ESP żeby wczytać to co jest pod adresem komórki a chwilę później jest pokazywana instrukcja add eax , dane - Nast gdzie już rejestr eax bez konsekwencji jest bez nawiasów kwadratowych?
Generalnie (za wyjątkiem instrukcji LEA która jest wyjątkiem), nawiasy kwadratowe w x86 używane są do odwołania się do pamięci, tj. co co jest w [nawiasie] jest traktowane jako adres komórki pamięci - co zresztą sam napisałeś :)
To powiedziawszy, same rejestry nie znajdują się w pamięci, więc można do nich pisać też bezpośrednio (rejestr to malutki fragment pamięci w samym CPU, dużo szybszy niż RAM).
Co więcej, w samym kodzie maszynowy można zaszyć stałe (tzw. immidiate - wartości natychmiastowe), które można używać jako źródło danych zapisanych bezpośrednio w instrukcji.
Kilka przykładów!
1. MOV EAX, 1234 ← do rejestru EAX wrzucana jest wartość 1234 (1234 będzie zapisane bezpośrednio w kodzie maszynowym)
2. MOV DWORD [EAX], 1234 ← wartość EAX jest interpretowana jako numer komórki w pamięci (a raczej czterech kolejnych komórek - o tym mówi prefix DWORD - double word, gdzie w tym wypadku "word" to 16-bitów, więc dword to 32-bity czyli 4 bajty), i pod ten adres z rejestru EAX wrzucana jest wartość 1234 (zapisana bezpośrednio w kodzie maszynowym)
3. MOV EAX, EBX ← do rejestru EAX wrzucana jest wartość z rejestru EBX (tj. jest kopiowana)
4. MOV [EAX], EBX ← wartość EAX jest interpretowana jako numer komórki w pamięci (a konkretnie ponownie 4 komórek, bo EBX ma wielkość 32-bitów, więc "DWORD" nie trzeba pisać bo jest domyślnie), i pod ten adres z rejestru EAX wrzucane są 32-bity z rejestru EBX
5. MOV EAX, [EBX] ← do rejestru EAX wrzucane są 4 bajty odczytane z pamięci spod adresu zawartego w rejestrze EBX
6. MOV EAX, [1234] ← do rejestru EAX wrzucane są 4 bajty odczytane z pamięci spod adresu 1234 (oraz 1235, 1236 i 1237 - czyli 4rech sąsiadujących 1-bajtowych komórek zaczynając od podanego adresu)
7. MOV WORD [1234], [6789] ← w x86 można tylko jeden jawny zapis do pamięci robić w instrukcji, więc to nie przejdzie; ale jak by przeszło, to by znaczyło: skopiuj 2 bajty z pamięci z komórek 6789-6790 do komórek w pamięci o adresach 1234-1235 (dwa bajty, ponieważ WORD, gdzie ponownie, WORD to 16 bitów, czyli 2 bajty)
Technicznie można zobaczyć nawet bardzo skomplikowane wyrażenia z których wyliczony będzie adres, np. [EAX+EBX*8+12345678]. To co konkretnie można w nawias [kwadratowy] wrzucić wynika z tego jak kodowane są instrukcje w kodzie maszynowym, co opisane jest np w tym bardzo starym filmiku: re.coldwind.pl/?vart=8 ;)
@@GynvaelColdwind Ok wszystkie te przykłady wydają mi się zrozumiałe rozumiem że w nawiasach kwadratowych jest wyliczany adres komórki pamięci rozumiem również pojęcie dword to że odwołuje się do 4 kolejnych komórek pamięci bo jedna komórka pamięci może tylko pomieścić 8 bitów ,rozumiem że kiedy nie używa się nawiasów kwadratowych działa się bezpośrednio na rejestrach procesora jednak tym bardziej nie rozumiem dlaczego kiedy ESP wskazuje na ostatni element na stosie to trzeba pisać add dword [ ESP ], dane - Nast , i odwoływać się do komórek pamięci RAM a nie bezpośrednio to co jest w rejestrze procesora czyli add ESP , dane - Nast skoro ESP wskazuję w tym przypadku na zawartość eax dlaczego to niedziała ?
@@damiandzugaa2972 Generalnie "ESP wskazuje na ostatni element na stosie" jest trochę mylne, bo brzmi jakby ESP było jakoś specjalnie magiczne. W praktyce bardziej prawidłowo jest powiedzieć "ESP zawiera w sobie adres komórki pamięci" (stos to nic innego jak zwykły kawałek pamięci, gdzie programiści mówili się że będą go nazywać "stosem" i wrzucać tam zmienne lokalne / etc).
A skoro stos jest po prostu fragmentem pamięci, to jeśli chcesz się odwołać do czegoś na stosie, to musisz użyć [nawiasów kwadratowych]. Np. MOV [ESP], EAX (czyli "zapisz zawartość rejestru EAX - 4 bajty - pod adres z rejestru ESP").
Jak robisz ADD ESP, 1234, to to jest po prostu dodanie 1234 do liczby (adresu) który jest w ESP.
ESP nie różni się niczym od rejestru EAX czy ESI - ot kolejny 32-bitowy rejestr ogólnego przeznaczenia (zazwyczaj interpretowany jako 32-bitowa liczba / 32-bitowy adres). Po prostu przyjęło się go używać do przechowywania adresu "na stosie" (w czym pomaga fakt, że PUSH, POP, CALL i RET niejawnie się akurat to niego odwołują).
Co do "ESP wskazuje w tym przypadku na zawartość eax" - to jest generalnie nieprawidłowe stwierdzenie. Rejestry nie mają adresów (bo nie są w pamięci; są oddzielnymi elementami CPU), więc z definicji nie mają swojego adresu w pamięci (bo... nie są w pamięci, więęc... nie mogą mieć adresu w pamięci). A skoro rejestry nie mają adresów, to nie można "wskazać na rejestr" (na poziomie assembly wskaźnik to nic innego jak adres w pamięci, czyli "wskazywanie na" to po prostu "posiadanie adresu czegoś w pamięci").
Jest jakaś możliwość odpalenia programu bez użycia asmloadera ?
Jest ale jeżeli biędziesz chciał móc wywoływać funkcje takie jak printf to będziesz musiał pobawić się z LD.
Infinite Buffer nie koniecznie, można użyć "gcc plik.o ..."
ktoś ogląda w 2020?
Na oglądałem, i powrót w 2021
Po co obliczać różnicę między 'nast' a 'dane', kiedy można wpisać 'dane' do jakiegoś rejestru lub po prostu dodać na stos? pastebin.com/HKc98HkQ (chyba, że to tak specjalnie w ramach nauki :D)
EDIT: a jednak nie działa... tylko dlaczego?
EDIT2: ahh... już rozumiem :-) dzięki debuggerowi z następnej lekcji udało mi się podejrzeć, że dane i nast to są adresy relatywne, zaś adres, który call dodaje na stos jest absolutny :) Dzięki za bardzo przystępne filmiki :)
Nie rozumiem dlaczego tak nie może być :/ ET to nie jest adres danej linijki kodu ?
[bits 32]
push ET
call [ebx+3*4];printf
add esp,4
push 0
call [ebx];exit
ET:db 'Hello World',0xa,0
Dziękuje bardzo za tak obszerną odpowiedz ! :) Już prawie wszystko jasne :)
Tylko skoro rozkaz call skacze pod dany adres podany jako argument to
call addr
addr:
Nie powinno też nie działać ? Bo skoro etykiecie addr załóżmy NASM nada wartość jeden to będziemy mieli
call 1
Czyli zawsze będziemy skakać pod ten sam adres w pamięci . A jednak te rozwiązanie działa i nie bardzo rozumiem dlaczego :/
Aaa, czyli po prostu instrukcja call zachowuje się inaczej jeśli jest wywołana z etykietą, a inaczej jeśli z rejestrami tak ? Ale jest to różnica tylko na poziomie assemblera, W kodzie maszynowym zawsze jest w formie call [przesunięcie], a nie call [adres] tak ?
Dzięki bardzo jeszcze raz :) Teraz już wszystko rozumiem. A filmik na pewno zobaczę :)
hex editor pod ręką w vimie tabf nazwa_binarki a potem :%!xxd :)
Witam, postanowiłem po zapoznaniu się z tym odcinkiem zrobić prosty kalkulator, który dodawałby liczby. Niestety nie wiem dlaczego, ale nie chce działać. Ja postanowiłem kod zorganizować w ten sposób, że dane są na górze. Proszę o odpowiedź :) pastebin.com/VBU0Uazj
Zauważ, że taka organizacja danych jaką pokazuje w odcinku ma swoje konkretne uzasadnienie - call wrzuca adresy poszczególnych danych na stos, gdzie szukają ich funkcje wywołane, lub wyliczamy adresy tych danych w biegu.
W Twoim wypadku masz jednego calla na początku (czyli odkładasz adres "=",0 na stosie), po czym masz całą serię wywołań funkcji które nigdy do żadnych danych się nie odwołują.
Najlepiej obejrzyj jeszcze raz ten odcinek i spróbuj poprawić swój kod :)
*****
Okej, dziękuję serdecznie za podpowiedź, chciałem tak zrobić, ale nie mam pojęcia jak np odwołać się do innych danych. Aha, nie już wiem, czyli one są po prostu wstawiane na stos ;D Że nawet o tym nie pomyślałem :D Zmienię program i napiszę Panu czy działa :)
Eryk Andrzejewski
Hmm, no niestety nie mogę nic zrobić, w konsoli pojawiają się jakieś krzaczyska :c
Eryk Andrzejewski Pokaż poprawiony kod.
*****
Po małej zabawie z kodem, krzaczki się już nie pokazały, program natomiast znowu "wywala"
Oto kod:pastebin.com/b9KQUzuW
Dochodzę do wniosku, iż assembly to super język, ale łatwo w nim się pomylić :)