Вызов printf в x86 64 с помощью ассемблера GNU
Я написал программу, использующую синтаксис AT&T для использования с GNU assembler:
.data
format: .ascii "%dn"
.text
.global main
main:
mov $format, %rbx
mov (%rbx), %rdi
mov , %rsi
call printf
ret
Я использую GCC собрать и связать с:
GCC-o main main.s
я запускаю его с помощью этой команды:
./ main
когда я запускаю программу, я получаю ошибку seg. Используя gdb, он говорит printf
не нашли. Я пытался ".extern printf", который не работает. Кто-то предложил мне хранить указатель стека перед вызовом printf
и восстановить перед RET, как мне это сделать?
2 ответов
есть несколько проблем с этим кодом. The amd64 System V ABI соглашение о вызове, используемое Linux, требует нескольких вещей. Это требует, что непосредственно перед вызов чтобы стек был по крайней мере 16-байтовым (или 32-байтовым) выровненным:
конец области входного аргумента должен быть выровнен по 16 (32, Если __m256 передано по стеку) граница байта.
после C среда выполнения вызывает ваш main
функция стек смещен на 8, потому что указатель возврата был помещен в стек на вызов. Перестроить до 16-байтовой границы можно просто пуш любой регистр общего назначения в стек и поп его в конце.
соглашение о вызове также требует, чтобы АЛ содержит количество векторных регистров, используемых для функции переменного аргумента:
%al используется для укажите количество векторных аргументов, переданных функции, требующей переменного числа аргументов
printf
является функцией переменного аргумента, поэтому АЛ необходимо установить. В этом случае вы не передаете никаких параметров в векторном регистре, поэтому вы можете установить АЛ к 0.
вы также разыменовываете указатель $format, когда он уже является адресом. Так что это неправильно:
mov $format, %rbx
mov (%rbx), %rdi
это принимает адрес формата и помещает его в RBX. Затем вы берете 8 байт по этому адресу в RBX и поместите их в RDI. RDI должен быть указатель к строке символов, а не сами символы. Эти две строки можно было заменить на:
lea format(%rip), %rdi
это использует относительную адресацию RIP.
вы должны также нуль завершить ваши строки. Вместо использования .ascii
можно использовать .asciz
на платформе x86.
рабочая версия вашей программы может выглядеть так:
# global data #
.data
format: .asciz "%d\n"
.text
.global main
main:
push %rbx
lea format(%rip), %rdi
mov , %esi # Writing to ESI zero extends to RSI.
xor %eax, %eax # Zeroing EAX is efficient way to clear AL.
call printf
pop %rbx
ret
Другие Рекомендации/Предложения
вы также должны знать из 64-битного Linux ABI, что соглашение о вызовах также требует функций, которые вы пишете, чтобы уважать сохранение определенных регистров. Список регистров и следует ли их сохранять выглядит следующим образом:
любой регистр, который говорит Yes
на сохраняется между
Регистрация столбец-это те, которые вы должны убедиться, что они сохранены в вашей функции. Функция main
- это как и любой другой C С .section .rodata
, а не .data
в 64-разрядном режиме: если у вас есть операнд назначения, который является 32-разрядным регистром, процессор будет нулевым регистр по всему 64-разрядному регистру. Это может сохранить байты на кодировке инструкции.
возможно, ваш исполняемый файл компилируется как позиционно-независимый код. Может появиться сообщение об ошибке:
перемещение R_X86_64_PC32 против символа `printf@@GLIBC_2.2.5 ' не может использоваться при создании общего объекта; перекомпилировать с-fPIC
чтобы исправить это, вам придется вызвать внешнюю функцию printf
этот путь:
call printf@plt
это вызывает функцию внешней библиотеки через таблица рычага процедуры (PLT)
вы можете посмотреть на ассемблерный код, генерируемый из эквивалентной файл c.
Бег!--3--> С испытанием.c
#include <stdio.h>
int main() {
return printf("%d\n", 1);
}
это выводит код сборки:
.file "test.c"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
pushq %rbp
movq %rsp, %rbp
movl , %esi
movl $.LC0, %edi
movl , %eax
call printf
popq %rbp
ret
.size main, .-main
.ident "GCC: (GNU) 6.1.1 20160602"
.section .note.GNU-stack,"",@progbits
это дает вам пример кода сборки, вызывающего printf,который вы можете изменить.
по сравнению с вашим кодом, вы должны изменить 2 вещи:
- %rdi должен указывать на формат, вы не должны unreferenced %rbx, это можно сделать с помощью
mov $format, %rdi
- printf имеет переменное количество аргументов, тогда вы должны добавить
mov , %eax
применение этих модификаций даст что-то вроде :
.data
format: .ascii "%d\n"
.text
.global main
main:
mov $format, %rdi
mov , %rsi
mov , %eax
call printf
ret
и затем запустите его print:
1