Как передаются параметры при вызове Printf из 64-битного asm?

Я снова учусь языку ассемблера, и единственная проблема, с которой я столкнулся до сих пор, заключалась в вызовах C. Книга, которая у меня есть, ориентирована на 32-битную, а я работаю на 64-битной. Очевидно, существует большая разница в соглашениях о вызовах и http://www.x86-64.org/documentation. сайт не работает. Итак, после некоторого копания/тестирования, компиляции фиктивных программ на C и трех дней на это я решил опубликовать свои выводы, если это поможет кому-то еще.

Нужно ли RAX указывать число с плавающей запятой? Является ли заполнение стека «теневым пространством» 16 или 32 битами? Подходит ли этот макрос для выравнивания стека для небольших программ? Я знаю, что вы можете NOP-дополнять код с помощью align, я не был уверен насчет фрейма стека.

; pf.asm compiled with 'nasm -o pf.o -f elf64 -g -F stabs'
; linked with 'gcc -o pf pf.o'
; 64-bit Bodhi (ubuntu) linux

%include "amd64_abi.mac"
[SECTION .data]
First_string:   db "First string.",10,"%s", "%d is an integer. So is %d",10
                db "Floats XMM0:%5.7f  XMM1:%.6le  XMM2:%lg",10,0  
Second_String:  db "This is the second string... %s's are not interpreted here.",10
                db " Neither are %d's nor %f's. 'Cause it is a passed value.", 10, 0
; Just a regular string for insert.
[SECTION .bss]
[SECTION .text]
EXTERN printf
GLOBAL main
main:
_preserve_64AMD_ABI_regs ; Saves RBP, RBX, R12-R15
mov rdi, First_string   ; Start of string to be formatted. Null terminated
mov rsi, Second_String  ; String addy of first %s in main string. Not interpretted
mov rcx, 0456           ; Second Integer (Register is specific for ordered arguments.)
mov rdx, 0123           ; First integer (Order of assignment does not matter.)
                        ; Order of Integer/Pointer Registers:
                        ; $1:RDI   $2:RSI   $3:RDX   $4:RCX   $5:R8   $6:R9

mov rax,0AABBCCh         ; Test value to be stored in xmm0
cvtsi2sd xmm0, rax      ; Convert quad to scalar double
mov rax,003333h         ; Test value to be stored in xmm1
cvtsi2sd xmm1, rax      ; Convert quad to scalar double
cvtsi2sd xmm2, rax      ; Convert quad to scalar double
divsd xmm2, xmm0        ; Divide scalar double

sub rsp, 16             ; Allocates 16 byte shadow memory
_prealign_stack_to16    ; Move to the lower end 16byte boundry (Seg-Fault otherwise)
;    mov rax, 3             ; Count of xmm registers used for floats. ?!needed?!
Before_Call:
call printf             ; Send the formatted string to C-printf
_return_aligned_stack   ; Returns RSP to the previous alignment
add rsp, 16             ; reallocate shadow memory

_restore_64AMD_ABI_regs_RET
; Ends pf.asm

; amd64_abi.mac
; Aligns stack (RSP) to 16 byte boundry, padding needed amount in rbx
%macro _preserve_64AMD_ABI_regs 0
push rbp
mov rbp, rsp
push rbx
push r12
push r13
push r14
push r15
%endmacro

%macro _restore_64AMD_ABI_regs_RET 0
pop r15
pop r14
pop r13
pop r12
pop rbx
mov rsp, rbp
pop rbp
ret
%endmacro

%macro _prealign_stack_to16 0
mov rbx, 0Fh            ; Bit mask for low 4-bits 10000b = 16 :: 01111b = 15b
and rbx, rsp            ; get bits 0-3 into rbx
sub rsp, rbx            ; remove them from rsp, rounding down to multiple of 16 (10h)
%endmacro

; De-aligns stack (RSP)from 16 byte boundry using saved rbx offset
%macro _return_aligned_stack 0
add rsp, rbx
%endmacro

ВЫВОД: первая строка. Это вторая строка... %s здесь не интерпретируются. Ни %d, ни %f. Потому что это переданное значение. 123 — целое число. То же самое и с 456 поплавками XMM0:11189196.0000000 XMM1:1.310700e+04 XMM2:0.0011714

Ресурсы: System V ABI v0.96: http://www.uclibc.org/docs/psABI-x86_64.pdf (он недоступен на x86-64.org. Сайт недоступен) Язык ассемблера шаг за шагом. Джефф Дантеманн Глава 12 64-битный набор инструкций Intel. http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html


person DouglasCodes    schedule 07.05.2013    source источник
comment
Очевидный подход состоит в том, чтобы сначала написать код на C, а компилятор C сгенерировать сборку. Это никогда не будет ошибкой.   -  person Hans Passant    schedule 07.05.2013


Ответы (1)


Да, RAX (фактически AL) должен содержать количество используемых регистров XMM.

Ваш код выравнивания стека слишком сложен, обычно вы просто делаете AND rsp, -16. Кроме того, выравнивание стека обычно выполняется только один раз (обычно в начале main), а затем оно поддерживается путем постоянной корректировки rsp соответствующим образом.

SYSV ABI не использует теневое пространство (это соглашение Microsoft), вместо этого он использует «красную зону», но это не влияет на последовательность вызовов.

Обновление о выравнивании стека:

В функциях, которые уже выровнены RSP (как правило, все, кроме main), вы просто убедитесь, что все вызванные функции, в свою очередь, получают RSP, которые изменены на кратное 16.

Если вы используете стандартный указатель кадра, ваши функции начинаются с PUSH RBP, поэтому вам нужно только убедиться, что вы выделяете пространство, кратное 16 (если необходимо), например:

push rbp
mov rbp, rsp
sub rsp, n*16
...
mov rsp, rbp
pop rbp
ret

В противном случае вам придется компенсировать 8 байтов RIP, помещенных в стек (как вы правильно указали в своем комментарии):

sub rsp, n*16+8
...
add rsp, n*16+8
ret

Оба вышеперечисленных применимы только в том случае, если вы вызываете другие функции, то есть в листовых функциях вы можете делать все, что хотите. Кроме того, красная зона, о которой я упоминал ранее, полезна в листовых функциях, потому что вы можете использовать 128 байтов под указателем стека без явного распределения, то есть вам вообще не нужно настраивать RSP:

; in leaf functions you can use memory under the stack pointer
; (128 byte red zone)
mov [rsp-8], rax
person Jester    schedule 07.05.2013
comment
Здорово, это очень полезно. Поэтому каждый раз, когда я нажимаю после первоначального выравнивания, делайте это как; суб рсп, 10 ч ; mov [rsp], $value... затем после вызова просто добавить его обратно? - person DouglasCodes; 07.05.2013
comment
Все вызовы будут выровнены до 16 перед вызовом, верно? Затем вызов помещает RIP в стек. Оставив его нужно скорректировать на 8... так что я могу И rsp, -16 в начале и add rsp, 8 перед RET. Правильный? - person DouglasCodes; 07.05.2013