Asembler - analiza kodu NASM (zliczanie literek 'a' w zdaniu)

0

Witam. Jakiś czas temu rozpoczęliśmy na studiach naukę asemblera i zostaliśmy rzuceni z marszu na głęboką wodę. Nie jestem zwolennikiem nauki przez analizę cudzego kodu, wolę krok po kroku sprawdzić treści w książce, ale tym razem niestety nie mam wyboru - goni mnie czas.
Poniżej przedstawię kawałek kodu pod linuxa w formacie NASM. Bardzo prosiłbym o pomoc w analizie wybranych przeze mnie fragmentów.

section. text
global _start

_start:

		mov		eax, 3
		mov		ebx, 0
		mov		ecx, tekst
		mov		edx, [dlugosc]
		int		80h
		xor		ecx, ecx ; dlaczego ecx i ebx są zerowane w miejscu, kiedy linijkę wcześniej wprowadzaliśmy do nich podstawowe wartości? czy to nie powinno spowodować błędów w kompilacji?
		xor		ebx, ebx

_add:
		inc		ebx ; nawet, jeśli nie podalibyśmy żadnej litery a, program powinien wejść do tego miejsca i zinkrementować ebx. Czy nie będzie w takim razie w przekroju całego programu o jedną literę a za dużo, czy popełniam gdzieś błąd myślowy? 

_petla:
		mov		sil, [tekst+ecx]
		inc		ecx
		cmp		sil, 97
		je		_add
		cmp		ecx, 19 ; największa czarna magia dla mnie - 19 to komenda XOFF, mająca pauzować transmisję. Jakie ma to powiązania z instrukcją porównania?
		jne		_petla

_end:
		add		ebx, 47 ; co to ma wywołać? 47 to w kodzie ASCII zwykły slash, jaki ma cel sumowanie tego z zawartością rejestru bazowego?
		mov		[tekst],ebx
		mov		eax, 4
		mov		ebx, 1
		mov		ecx, tekst
		mov		edx, 1
		int		80h

		mov		eax, 1
		int		80h

section .data

tekst		TIMES	20	db	0 ; dlaczego zmienna tekst jest deklarowana jako dwudziestokrotne zero?
dlugosc	dd		$-tekst ; prosiłbym jeszcze o wyjaśnienie zapisu $-tekst

To wszystko. Program niemal w całości jest dla mnie niezrozumiały, więc jeśli ktoś byłby w stanie, oprócz odpowiedzi na moje pytania, krok po kroku przedstawić działanie kodu, byłbym bardzo wdzięczny. Pozdrawiam.

1
  1. int to wywołanie przerwania którego "parametry" są zwykle ustalane rejestrami. Siłą rzeczy w kodzie masz ustawienie parametrów, wywołanie przerwania a potem zerowanie parametrów żeby zrobić z nimi coś innego. Nie rozumiem skad błąd kompilacji. To tak jakbyś miał:
int a = 10;
int b = 20;
funkcja(a,b);
a = 0;
b = 0;

Czemu wg ciebie taki kod miałby jakieś błędy generować? o_O
2. Nie pisałeś że kod jest poprawny ;] Faktycznie będzie o 1 literkę za dużo, ale nie do końca, patrz 4.
3. Ty chłopie W OGÓLE nie rozumiesz co się w tym kodzie dzieje. Czeski film. ecx to jest licznik którym idziemy po tablicy ze stringiem. Porównanie go z 19 oznacza że doszlismy do KOŃCA STRINGA (bo tablica ma 20 znaków, patrz 5.). To jest zwykła LICZBA a nie znak ascii. W ogóle za używanie liczb zamiast po prostu znaków ascii powinna być chłosta.
4. Dodanie 47 zapewne służy do tego żeby np. liczbę 3 zamienić na znak 3 którego to kod ascii wynosi 51, czyli właśnie 47+1+3. Ale idiota który to wymyślił powinien zawisnąć. Normalny człowiek inkrementowałby po ludzku a potem dodał ZNAK 0 żeby uzyskać odpowiednią liczbę. Poza tym kod zakłada że literek jest mniej niż 10 ;]
5. tekst jest po prostu tablicą 2 bajtów, że wyzerowanych to akurat mało istotne, a ten $ na końcu oznacza po prostu "koniec stringa"

0

W porządku, dziękuję za pomoc. Jak w takim razie rozpoznać, czy działamy na bitach, czy na znakach ASCII? W jednym z programów porównywał wszystko ze 107, przy czym linker interpretował to jako po prostu "k".

cmp sil,107

Jeśli byłaby możliwość, bardzo prosiłbym o przerobienie kodu na najbardziej prawidłowy sposób, żebym miał dalsze podstawy do przerobienia pozostałych wymaganych programów.

0

Jak w takim razie rozpoznać, czy działamy na bitach, czy na znakach ASCII

Jeśli autor kodu był idiotą to niestety się nie da. Trzeba po prostu rozumieć co się w kodzie dzieje, wtedy dość oczywiste staje się jak traktować pewne wartości.

Jeśli byłaby możliwość, bardzo prosiłbym o przerobienie kodu na najbardziej prawidłowy sposób

:D :D :D

0

Czy w takim razie uzyskałbym pomoc odnośnie dobrego poradnika do assemblera? Korzystam z wikibooksa, ale tam nauczyłem się jedynie przeznaczenia instrukcji "na sztywno", i to póki co cała moja wiedza. Czytałem jeszcze książkę Drozdowskiego, ale zupełnie nie przypadła mi do gustu.

0

Ale właściwie czego oczekujesz? Bo rozumiesz że programowanie w asemblerze jest proste jak budowa cepa? To nie ma żadnych wysokopoziomowych mechanizmów, polimorfizmów, genericów, wzorców projektowych ;]
Musisz po prostu znać kilka podstawowych instrukcji (mov, xor, cpm, skoki, etykiety, int, call, ret) i mieć pod ręką zestaw przerwań z parametrami i w sumie tyle. Nic więcej tam nie ma do nauki. Reszta to jest po prostu głowa na karku i umiejętne dzielenie kodu na sensowne kawałki.

0

Może i proste, ale najpierw się tego trzeba nauczyć, a tego za proste nie uważam. Stąd kluczem jest dobry poradnik, którego nie mam. Co z tego, że znam kilka instrukcji, akurat większość z tych, które wymieniłeś, skoro nie pojmuję "logiki" rejestrowej.

0

Ale czego dokładnie nie pojmujesz? Rejestry to takie "zmienne" wielokrotnego użytku i tyle. Z czym konkretnie masz problem?

1

Dla procesora nie ma różnicy między np. liczbą 48 a znakiem 0. Różnica jest tylko w kodzie źródłowym. Dlatego powinno się trzymać zasady, że piszemy 48 jeśli chodzi o liczbę czterdzieści osiem, a '0' jeśli chodzi o znak - cyfrę zero.
Jeżeli programista tego nie przestrzegał, to zostaje tylko zastanawianie się co poeta chciał powiedzieć.

0

Korzystamy cały czas z rejestrów EAX, EBX,ECX oraz EDX. Wiem, że pierwszy to akumulator, drugi baza, itd., ale przy jakich konkretnych instrukcjach powinniśmy wybierać który konkretny? Autor tego programu korzystał jeszcze z indeksu źródłowego SIL - dlaczego akurat ten?
No i nie rozumiem podziału na długość i tekst - jedno ma odpowiadać za adres, drugie za wpisane do niego bajty. Z tymże już 20 komórek używamy na samą etykietę, co totalnie mi utrudnia zrozumienie całości. Póki co to tyle, jak zrozumiem to, to przejdę dalej ze swoimi pytaniami :).

1

Więc przede wszystkim: czy faktycznie nie da się określić, czy odwołujemy się do konkretnych bitów, czy znaków ASCII?

Z analizy kodu źródłowego można wywnioskować, w jakim kontekście użyto danej wartości - czy określa ilość czegoś, czy jest numerem telefonu, czy wskaźnikiem do pamięci, czy kodem ASCII. Dla komputera to tylko liczby.

Druga sprawa: korzystamy cały czas na rejestrach EAX, EBX,ECX oraz EDX. Wiem, że pierwszy to akumulator, drugi baza, itd.,
Są to rejestry ogólnego przeznaczenia i w większości zastosowań są zupełnie zamienne.

Autor tego programu korzystał jeszcze z indeksu źródłowego SIL - dlaczego akurat ten?
Bo miał takie widzimisię, gdy eax, ebx, ecx i edx już przeznaczył na co innego.
Zresztą rejestr sil jako dolna 8-bitowa połówka rejestru si jest dostępny tylko w trybie 64-bitowym, i to jest jedyny element w kodzie świadczący o tym że jest to tak naprawdę kod x86-64 a nie x86. Cała reszta kodu operuje na rejestrach 32-bitowych.

1

Korzystamy cały czas z rejestrów EAX, EBX,ECX oraz EDX. Wiem, że pierwszy to akumulator, drugi baza, itd., ale przy jakich konkretnych instrukcjach powinniśmy wybierać który konkretny?

Pytanie jest dość głupie, bo instrukcje ograniczają ci wybór ;] Po prostu niektóre instrukcje automatycznie wymuszają uzycie danego rejestru. Poza tymi wymuszeniami nie musisz się ty przejmować i możesz stosować rejestry zamiennie, jak ci wygodnie.

Autor tego programu korzystał jeszcze z indeksu źródłowego SIL - dlaczego akurat ten?

Bo mu brakło innych? Jakbyś kiedyś spróbował NAPISAĆ jakiś kod a nie tylko "analizować" (w cudzysłowie, bo marna ta twoja analiza) to byś szybko zrozumiał w czym rzecz. Rejestry bardzo szybko się "kończą" ;] Gdyby ten kod był trochę większy to trzeba by pewnie wrzucać rejestry na stos / zdejmować ze stosu. Spróbuj sobie napisać analogiczny kod w C ale stosując tylko 1 albo 2 zmienne typu int. Może wtedy zrozumiesz gdzie jest problem ;)

No i nie rozumiem podziału na długość i tekst - jedno ma odpowiadać za adres, drugie za wpisane do niego bajty.

A ja nie rozumiem o czym ty piszesz. Masz w tym kodzie zwykłą pętlę "for" w której jeden rejestr robi ci za odczytany znak a inny rejestr za licznik pętli. Twój ecx to jest licznik pętli po prostu a sil to dany znak. Coś w stylu:

for (int i=0;i<19;i++){
    char znak = tablica[i];
}

Gdzie odpowiednikiem i jest ecx a odpowiednikiem zmiennej znak jest sil.
Czy ty masz jakiekolwiek pojęcie o programowaniu że asembler to jest pierwszy język jaki wam pokazali? o_O

Z tymże już 20 komórek używamy na samą etykietę, co totalnie mi utrudnia zrozumienie całości.

Tego to już zupełnie nie rozumiem. Jaką etykietę? WTF? Masz tablicę na 20 elementów i tyle. Etykieta tej tablicy wskazuje tylko na to gdzie się ona zaczyna. Podobnie jak w C gdzie "nazwa tablicy może być zrzutowana do wskaźnika na 1 element". Tu jest identycznie. tekst to adres pierwszego elementu tej tablicy (bloku pamięci), [tekst] to wartość z tego adresu. To tak jakbyś miał tam:

char* tekst = malloc(20*sizeof(char));

A operator [] robił dereferencje tak jak * w C. Więc odpowiednikiem asemblerowego [tekst] jest *tekst z C. Analogicznie [tekst+5] to *(tekst+5).

0

Sory ale musze to powiedzieć nie każdy jak Shalom wyssał asemblera z mlekiem matki. To że ktoś próbuje zrozumieć ten dość specyficzny język programowania tak różny jak obecny nie zobowiązuje cię do śmieszkowania z poziomu iq delikwenta tylko na podesłaniu jakiś materiałów czy łopatologicznym wytłumaczeniu problemu. Jeśli nie możesz tego zrobić to nie pisz bo burak z ciebie wychodzi

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