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 предназначен для вызова?

Спасибо заранее


Резюме:

  1. printf получает строку печати из%rdi и количество дополнительных аргументов в нижнем DWORD% rax.
  2. Результаты 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-битный размер операнда.

Другие вопросы по тегам