Bezpośrednie zarządzanie stosem

1

Cześć Wam

Chciałem z ciekawości zaimplementować sobie wydajnego quick sorta w asmie. Stwierdziłem, że będę zarządzał stosem bezpośrednio podczas przekazywania argumentów do funkcji zamiast używać instrukcji push i rejestrów służących do zarządzania argumentami właśnie typu rdi, rsi, rdx, rcx. No i pierwsze co to trzeba wydrukować jakiś tekst na standardowe wyjście. Okazało się, że bezpośrednie zarządzanie stosem przy architekturze x86-64 jest dość specyficzne być może i nie działa poprawnie.

Zobaczcie:

global main
extern printf

section .text
	main:
		push rbp										
		mov rbp, rsp									
		sub rsp, 16
		mov dword [rbp-16], type_size						
		call printf
		leave
		ret

section .rodata
type_size db "Type the size of an array: ", 0

Coś takiego generuje segmentation fault. Test z kodem na 32 bitową architekturę:

global main
extern printf

section .text
	main:
		push ebp									
		mov ebp, esp									
		sub esp, 16
		mov dword [ebp-16], type_size						
		call printf
		leave
		ret

section .rodata
type_size db "Type the size of an array: ", 0

Generuje poprawny wynik. Oto kompilacja i linkowanie, które przeprowadziłem dla architektury 64 bitowej:

nasm -f elf64 q_sort.asm
gcc q_sort.o -Wall -Wextra -o q_sort

A to dla architektury 32 bitowej:

nasm -f elf32 q_sort.asm
gcc q_sort.o -Wall -Wextra -m32 -o q_sort

Assembler to NASM. Czy ktoś ma pomysł skąd takie wyniki i dlaczego? O_o

1

Poszperałem trochę głębiej i okazało się, że chodzi tutaj o tzw. rejestry wektorowe. Rejestry te są obecne właśnie w architekturze 64 bitowej np. podczas wywoływań funkcji printf ze standardowej biblioteki C. W architekturze 32 bitowej kompilatory nie zwracają uwagi uwagi na te rejestry dlatego wypisywanie na ekran działa bez zarzutów. Informacja o tym czy dana funkcja ma zwrócić uwagę na rejestry wektorowe podczas jej wywołania jest przechowywana w rejestrze rax/eax. Zatem taki kod:

global main
extern printf
 
section .text
    main:
        push rbp                                        
        mov rbp, rsp                                    
        sub rsp, 16
        mov dword [rbp-16], type_size
        mov eax, 0 ; funkcja ma nie korzystac z rejestrow wektorowych                       
        call printf
        leave
        ret
 
section .rodata
type_size db "Type the size of an array: ", 0

... już działa poprawnie. Oczywiście w rejestrze eax przechowywana jest informacja nie tyle o "obecności" rejestrów wektorowych podczas wywołania, ale raczej chodzi tutaj o ilość tych rejestrów.

0

@Shizzer, jak to skompilowałeś, ponieważ u mnie ten kod nie zadziałał (naruszenie ochrony pamięci (zrzut pamięci), czy coś takiego pisało)? Natomiast taki kod już tak:

global main
extern printf

section .text
main:
push rbp
mov rbp, rsp
mov dword [rbp - 4], edi

mov esi, [rbp - 4]
mov rdi, type_size
xor eax, eax
call printf

leave
ret

section .rodata
type_size: db "Type the size of an array: ", 0

Kompilacja: nasm -f elf64 push_push.asm && gcc push_push.o -Wall -Wextra -o push_push && ./push_push

Powyższy kod skopiowałem i zmodyfikowałem stąd: https://www.linuxquestions.org/questions/programming-9/x86_64-assembly-help-524748/

Inne link'i:

http://wazniak.mimuw.edu.pl/index.php?title=Architektura_Komputer%C3%B3w/Wyk%C5%82ad_3:_Synteza_modelu_programowego
http://cs.brown.edu/courses/cs033/docs/guides/x64_cheatsheet.pdf
https://bart.disi.unige.it/zxgio/phd-course-2017/x86intro_slides.pdf
https://chyla.org/blog/assembler/Asembler_cz2_Stos_i_wywolanie_funkcji/

0

Wydaje mi się, że musiałem coś źle wkleić w każdym razie już piszę naprostowanie. Na architekturze x86 są inne konwencje wywołania funkcji niż na x86-64. Mianowicie odkryto, że bezpośrednie działanie na stosie jest rzeczą, która powoduje na przykład opóźnienia na procesorach 64 bitowych. Jeśli kogoś by to interesowało to tutaj jest bardzo ciekawy artykuł na ten temat -> https://stackoverflow.com/questions/2535989/what-are-the-calling-conventions-for-unix-linux-system-calls-on-i386-and-x86-6/2538212#2538212.

Z tego wynika, że można używać na przykład instrukcji push do przekazywania argumentów do funkcji lub właśnie stosu bezpośrednio, ale wykorzystywanie stosu bezpośrednio w architekturze 64 bitowej generuje takie właśnie dziwne błędy. Zgodnie z konwencją wywołań metod na x86-64 lepiej używać do tego rejestrów rdi, rsi, rdx itd. Także po prostu najlepiej napisać taki kod:

global main
extern puts

section .text
	main:
		push rbp
		mov rbp, rsp
		mov rdi, type_size
		xor eax, eax
		call puts
		pop rbp
		ret

section .rodata
type_size db "Type size: ", 0

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