Kod Assembly - problem

0

Hejka,

Od dzisiaj zacząłem naukę programowania w Assembly. Przerobiłem troszkę książki do Assembly i zrobiłem z niej póki co wszystkie zadania, ale zadania znacząco się utrudniły. Docelowo mam zrobić program, który wypisuje liczby od 90 do 100 i operuje głównie na rejestrach. Dla uproszczenie chciałem stworzyć program, który zapisuje pierwsze 10 liczb na stosie i wyświetla wszystkie 10. Dopiero potem chciałem zrobić te zadanie z książki. Niestety program nie wyświetla nic na wyjściu, Analizowałem go już któryś raz. Może jest ktoś chętny kto by mi pomógł, albo chociaż nakierował?

;   Program zapisuje na stosie 10 wartości 0,1,2...10
;   Następnie wypisuje ostatnią wartość na ekran
;   Program w fazie roboczej. Domyślnie ma wypisywać wszystkie wartości z stosu na ekran
 
section .text ; sekcja na kod
global _start ; wymagane rozpoczęcie etykietą globalną
 
_start: ; początek kodu w etykiecie _start
 
    push rbp ; umieszczenie rejestru rbp na początku stosu
    mov rbp, rsp ; zapisanie do rbp prawdziwego wskaźnika stosu
 
    mov rbx, 10 ; maksymalna wartość zmiennej, a przy okazji ich ilość
    xor rsi, rsi ; zerowanie rejestru
 
    cmp rsi, rbx
    jb loop
 
    loop:
 
        cmp rsi, rbx ; porównywanie dwóch wartości i zapisywanie wyniku porównania do flag
 
        jae show ; jeżeli [rsi] >= [rbx] czyli jeżeli ilość liczb i ich wartość osiąga maksimum lub więcej przerwij wykonywanie pętli (loop) i idź do kodu zaczynającego się od etykiety show
 
        push rsi ; zapis liczbę na stosie
        inc rsi ; zwiększ liczbę o jeden
 
        jb loop ; jeżeli [rsi] <= [rbx] wykonaj pętle jeszcze raz
 
    show:
 
        mov rax, 1 ; wywołanie funkcji zapisu do pliku
        mov rdi, 1 ; deskryptor ustawiony na terminal/ekran
        pop rbp ; zdjęcie wartości z wierzchołka stos i zapisanie jej do rejestru rbp
        mov rsi, rbp ; co ma zostać wyświetlone
        mov rdx, 1 ; jaką ma długość w bajtach
        syscall ; wywołanie systemowe
 
        mov rax, 60 ; numer funkcji systemowej - sys_exit
        syscall ; wywołanie tej funkcji
0

Po pierwsze to Polacy programują (zresztą coraz mniej) w asemblerze, który raczej się pisze przez jedno s.
Po drugie - zaznaczam, nie mam pojęcia jak się robi syscal w unixach - ale nie widzi mi się wypisanie małych liczb na wyjście, jak mniemam, tekstowe. Te małe liczby są pełne zer (co ma wiadomo skutek) lub niewidzialnych znaków kontrolnych. Choćby nawet czynność była udana, nie widać nic.

0

Czy używasz jakiegoś makra o nazwie syscall? Druga rzecz -nie podałeś jakiego systemu operacyjnego używasz (gdzie to uruchamiasz), jak kompilujesz.

0

Szczerze nie wiem do końca o co chodzi z tym makrem, bo niedawno robiłem prosty program wypisujący HelloWorld na syscall i on działał. Jeżeli chodzi o system to jest Linux, a ściślej Deepin, uruchamiam to w terminalu, kompiluje w plik elf64 i używam nasma oraz ld

0

Makro - to inaczej taki #define jak w języku C. To makro "syscall" musi być gdzieś zdefiniowane - np. jako "int 0x80" albo "sysenter". Skoro się to kompiluje bez błędów to znaczy, że gdzieś ta definicja makra syscall jest. Może po prostu dołączasz gdzieś czyjś plik ze źródłami gdzie ona jest zdefiniowana.

0

https://pasteboard.co/IZ1NwdE.png

Szczerze mówiąc to był pierwszy program, który wykonałem. Napisałem tylko ten kod i wykonałem tylko te polecenia u dołu w terminalu

0

A czy ten program ze screenshotu działa jeszcze? Tzn czy wyświetla hello world?

0

No. Nawet w terminalu jest. Przed chwilą go skompilowałem i odpaliłem

0

To znaczy, że ten syscall jest gdzieś zdefiniowany poza Twoim kodem - bo nie ma błędów kompilacji, czyli zostawmy go. A próbowałeś debugować ten kod? Debugowanie kodu jest dobrą formą nauki (weryfikacji) jak działa napisany kod.

0

Właśnie nie mogłem znaleźć debuggera. Szukałem, ale był jakiś stary ald co nie chciał się kompilować

0

Standardowym debuggerem pod Linuxem jest GDB. Jeśli wolisz graficzną wersję (pod X-Window) to możesz użyć programu DDD (https://www.gnu.org/software/ddd/).

2

Możesz wykorzystać printf (chyba).
Wersja działająca na debianie 10, niekoniecznie będzie działało u Ciebie:

section .data
	format db "%d",0xa,0
section .text
global main
extern printf

exit:
	xor rdi,rdi
	mov rax,60		;sys_exit
	syscall

main:
	push rbp	;ramka stosu
	mov rbp,rsp
	
	mov rbx,90
	mov r15,101

loop:
	mov rsi,rbx
	mov rdi,format
	xor rax,rax
	call printf WRT ..plt
	
	inc rbx
	cmp rbx,r15
	jnz loop

	call exit

kompilacja może wyglądać jakoś tak(chyba):

nasm -f elf64 -o liczby.o liczby.s
gcc -m64 -o liczby liczby.o

================
Możesz też polecieć hardwejem i zrobić to tak jak rozpocząłeś - tylko wywołaniami write itp.
Pamięć na cyfry możesz alokować na przykład na stosie - bo czemu nie.
Obliczać ilość cyfr, danej liczby możesz czymś takim (żeby wiedzieć ile pamięci przydzielić):

;ret - digits in rax
;param - number in rbx
digits_to_print:	
	finit
	push rbx
	fldl2t			;log2(10)	
	fld1			;1
	fdiv st1		;div 1/log2(10)
	fild qword [rsp]	;load number
	fyl2x			;log2(number) / log2(10)
	frndint			;round up
	fistp qword [rsp]	;store on stack
	pop rax
	add rax,2		;correct new line + '\0'
	ret
2

Po pierwsze syscall nie jest makrem tylko instrukcją dodaną w x86_64. Na studiach dalej uczą przedpotopowego x86, może dlatego ludki nie ogarniają.
Linie 17 i 18 nie mają sensu. Co do wypisywania, problem w tym że wrzucasz liczby, które nie są białymi znakami ascii więc ich nie widać.
I masz plusa za kreatywne użycie stosu! Podoba mi się

0

@vtx dziękuje za pomoc i polecenie mi fajnego debuggera. @Riki Dziękuje za ten kod. Z pewnością mi się przyda. @elwis Dzięki za wszystko. Rozjaśniłeś mi dużo kwestii np ten syscall co do którego sam zacząłem mieć wątpliwości. Już wiem o co chodzi. No i przede wszystkim za zwrócenie uwagi na bardzo istotny problem związany z wypisywaniem liczb. To dużo wyjaśnia. W takim razie poprawię kod

0

A i dziękuje za miły komentarz z stosem

1

Do gdb istnieją ciekawe rozszerzenia. Np. https://github.com/hugsy/gef. Rozszerzenia sprawiają, że widać więcej rzeczy bez wpisywania komend także polecam. Dodatkowo możesz sobie dzięki temu łatwiej debugować heap przykładowo, ale to tak na marginesie.

1

Sprawdziłem na szybko ten kod strace i gdb. strace służy do śledzenia wywołań systemowych i sygnałów. Wyrzuciło u mnie takie coś

execve("./asm", ["./asm"], [/* 76 vars */]) = 0
write(1, 0x9, 1)                        = -1 EFAULT (Bad address)
_exit(0)                                = ?
+++ exited with 0 +++

Z tego wynika że wywoływana jest funkcja write, która przyjmuje 3 argumenty, drugi argument to adres do bufora. Jak robisz pop rbp w 35 linii to nagle zmieniasz wartość w rbp na 0x9, a to jest wrzucane do rsi jako adres, a nie wartość która ma być pobrana. Więc wskazuje na adres 0x9, dlatego się wysypuje i nic nie wypisuje. Co można potem sprawdzić właśnie w gdb. Dajesz brakpoint na show, potem komendą disassemble sprawdzasz gdzie jest pc, następnie si wykonujesz kolejną instrukcję i sprawdzasz rejestry i r. To są skróty od info registers i stepi.

Czemu nic nie wypisuje? Bo np u mnie pierwsze kilkanaście bnajtów spod rbp (czyli usnąłem ten pop rbp z linii 35) to białe znaki jak \0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0dq]l\377\177\0\0\0\0\0\0\0\0\0\0. Może zwiększ ilośc bajtów na 50 albo 100, wtedy coś wypisze.

Jak kompilowałem ten kod i go uruchomiłem:

# plik `run`, bez żadnego rozszerzenia. Potem trzeba zmienić prawa 
# na wykonywanie komendą `sudo chmod +x run`. Wtedy nie musisz 
# wklepywać każdej linii do terminala , tylko odpalasz pliku run.
nasm -felf64 -g asm.asm
ld -g -o asm asm.o
strace ./asm
gdb -q ./asm 
./asm 
0

Dzięki bardzo za pomoc

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