GNU как, ставит работает, но printf нет
Это код, с которым я сейчас играю:
# file-name: test.s
# 64-bit GNU as source code.
.global main
.section .text
main:
lea message, %rdi
push %rdi
call puts
lea message, %rdi
push %rdi
call printf
push $0
call _exit
.section .data
message: .asciz "Hello, World!"
Инструкция по компиляции: gcc test.s -o test
Редакция 1:
.global main
.section .text
main:
lea message, %rdi
call puts
lea message, %rdi
call printf
mov $0, %rdi
call _exit
.section .data
message: .asciz "Hello, World!"
Окончательная редакция (Работы):
.global main
.section .text
main:
lea message, %rdi
call puts
mov $0, %rax
lea message, %rdi
call printf
# flush stdout buffer.
mov $0, %rdi
call fflush
# put newline to offset PS1 prompt when the program ends.
# - ironically, doing this makes the flush above redundant and can be removed.
# - The call to fflush is retained for display and
# to keep the block self contained.
mov $'\n', %rdi
call putchar
mov $0, %rdi
call _exit
.section .data
message: .asciz "Hello, World!"
Я изо всех сил пытаюсь понять, почему вызов put успешен, но вызов printf приводит к ошибке сегментации.
Может кто-нибудь объяснить это поведение и как printf предназначен для вызова?
Спасибо заранее
Резюме:
- printf получает строку печати из%rdi и количество дополнительных аргументов в нижнем DWORD% rax.
- Результаты printf не могут быть видны до тех пор, пока в stdout не будет введен символ новой строки или не будет вызван fflush(0).
1 ответ
puts
неявно добавляет символ новой строки, а stdout буферизуется строкой (по умолчанию на терминалах). Так что текст из printf
может просто сидеть там в буфере. Ваш звонок в_exit(2)
не очищает буферы, потому что это exit_group(2)
системный вызов, а не exit(3)
библиотечная функция. (См. Мою версию вашего кода ниже).
Ваш звонок в printf(3)
тоже не совсем верно, потому что вы не ноль %al
перед вызовом функции var-args без аргументов FP. (Хороший улов @RossRidge, я пропустил это). xor %eax,%eax
это лучший способ сделать это. %al
будет ненулевым (от puts()
возвращаемое значение), по-видимому, поэтому printf segfaults. Я проверил на своей системе, и printf, похоже, не возражает, когда стек не выровнен (что происходит, поскольку вы дважды нажимали перед вызовом, в отличие от put).
Кроме того, вам не нужно никаких push
инструкции в этом коде. Первый аргумент идет в %rdi
, Первые 6 целочисленных аргументов заносятся в регистры, 7-й и последующие идут в стек. Вы также пренебрегаете выталкиванием стека после возврата функций, который работает только потому, что ваша функция никогда не пытается вернуться после путаницы в стеке.
ABI требует выравнивания стека на 16B, и push
это один из способов сделать это, который на самом деле может быть более эффективным, чем sub $8, %rsp
на последних процессорах Intel со стековым механизмом, и это занимает меньше байтов. (См. X86-64 SysV ABI и другие ссылки в вики-теге x86).
Улучшенный код:
.text
.global main
main:
lea message, %rdi # or mov $message, %edi if you don't need the code to be position-independent: default code model has all labels in the low 2G, so you can use shorter 32bit instructions
push %rbx # align the stack for another call
mov %rdi, %rbx # save for later
call puts
xor %eax,%eax # %al = 0 = number of FP args for var-args functions
mov %rbx, %rdi # or mov %ebx, %edi will normally be safe, since the pointer is known to be pointing to static storage, which will be in the low 2G
call printf
# optionally putchar a '\n', or include it in the string you pass to printf
#xor %edi,%edi # exit with 0 status
#call exit # exit(3) does an fflush and other cleanup
pop %rbx # restore caller's rbx, and restore the stack
xor %eax,%eax # return 0
ret
.section .rodata # constants should go in .rodata
message: .asciz "Hello, World!"
lea message, %rdi
это дешево, и делать это в два раза меньше инструкций, чем два mov
инструкции по использованию %rbx
, Но так как нам нужно было отрегулировать стек на 8B, чтобы строго следовать гарантии ABI, выровненной по 16B, мы могли бы сделать это, сохранив регистр, сохраняющий вызов. mov reg,reg
очень дешевый и маленький, так что использование сохраняемого вызова reg естественно.
С помощью mov %edi, %ebx
и тому подобное сохраняет префикс REX в кодировке машинного кода. Если вы не уверены / не понимаете, почему безопасно копировать только младшие 32 бита, обнуляя верхние 32 бита, а затем использовать 64-битные регистры. Как только вы поймете, что происходит, вы узнаете, когда вы можете сохранить байты машинного кода, используя 32-битный размер операнда.