Calling printf in x86_64 using GNU assembler

后端 未结 2 2115
攒了一身酷
攒了一身酷 2020-11-27 06:29

I\'ve written a program using AT&T syntax for use with GNU assembler:

            .data
format:   .ascii \"%d\\n\"  

            .text
            .glob         


        
相关标签:
2条回答
  • 2020-11-27 06:46

    There are a number of issues with this code. The AMD64 System V ABI calling convention used by Linux requires a few things. It requires that just before a CALL that the stack be at least 16-byte (or 32-byte) aligned:

    The end of the input argument area shall be aligned on a 16 (32, if __m256 is passed on stack) byte boundary.

    After the C runtime calls your main function the stack is misaligned by 8 because the return pointer was placed on the stack by CALL. To realign to 16-byte boundary you can simply PUSH any general purpose register onto the stack and POP it off at the end.

    The calling convention also requires that AL contain the number of vector registers used for a variable argument function:

    %al is used to indicate the number of vector arguments passed to a function requiring a variable number of arguments

    printf is a variable argument function, so AL needs to be set. In this case you don't pass any parameters in a vector register so you can set AL to 0.

    You also dereference the $format pointer when it is already an address. So this is wrong:

    mov  $format, %rbx
    mov  (%rbx), %rdi 
    

    This takes the address of format and places it in RBX. Then you take the 8 bytes at that address in RBX and place them in RDI. RDI needs to be a pointer to a string of characters, not the characters themselves. The two lines could be replaced with:

    lea  format(%rip), %rdi
    

    This uses RIP Relative Addressing.

    You should also NUL terminate your strings. Rather than use .ascii you can use .asciz on the x86 platform.

    A working version of your program could look like:

    # global data  #
        .data
    format: .asciz "%d\n"
    .text
        .global main
    main:
      push %rbx
      lea  format(%rip), %rdi
      mov  $1, %esi           # Writing to ESI zero extends to RSI.
      xor %eax, %eax          # Zeroing EAX is efficient way to clear AL.
      call printf
      pop %rbx
      ret
    

    Other Recommendations/Suggestions

    You should also be aware from the 64-bit Linux ABI, that the calling convention also requires functions you write to honor the preservation of certain registers. The list of registers and whether they should preserved is as follows:

    Any register that says Yes in the Preserved across Register column are ones you must ensure are preserved across your function. Function main is like any other C function.


    If you have strings/data that you know will be read only you can place them in the .rodata section with .section .rodata rather than .data


    In 64-bit mode: if you have a destination operand that is a 32-bit register, the CPU will zero extend the register across the entire 64-bit register. This can save bytes on the instruction encoding.


    It is possible your executable is being compiled as position independent code. You may receive an error similar to:

    relocation R_X86_64_PC32 against symbol `printf@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC

    To fix this you'll have to call the external function printf this way:

    call printf@plt 
    

    This calls the external library function via the Procedure Linkage Table (PLT)

    0 讨论(0)
  • 2020-11-27 06:55

    You can look at assembly code generated from an equivalent c file.
    Running gcc -o - -S -fno-asynchronous-unwind-tables test.c with test.c

    #include <stdio.h>
    int main() {
       return printf("%d\n", 1);
    }
    

    This output the assembly code:

            .file   "test.c"
            .section        .rodata
    .LC0:
            .string "%d\n"
            .text
            .globl  main
            .type   main, @function
    main:
            pushq   %rbp
            movq    %rsp, %rbp
            movl    $1, %esi
            movl    $.LC0, %edi
            movl    $0, %eax
            call    printf
            popq    %rbp
            ret
            .size   main, .-main
            .ident  "GCC: (GNU) 6.1.1 20160602"
            .section        .note.GNU-stack,"",@progbits
    

    This give you a sample of an assembly code calling printf that you can then modify.


    Comparing with your code, you should modify 2 things:

    • %rdi should point to the format, you should not unreferenced %rbx, this could be done with mov $format, %rdi
    • printf has a variable number of arguments, then you should add mov $0, %eax

    Applying these modifications will give something like :

        .data
    format: .ascii "%d\n"  
    .text
        .global main  
    main:
      mov  $format, %rdi
      mov  $1, %rsi
      mov  $0, %eax
      call printf
      ret
    

    And then running it print :

    1

    0 讨论(0)
提交回复
热议问题