使用 GNU 汇编程序在 x86_64 中调用 printf [英] Calling printf in x86_64 using GNU assembler

查看:34
本文介绍了使用 GNU 汇编程序在 x86_64 中调用 printf的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经使用 AT&T 语法编写了一个用于 GNU 汇编程序的程序:

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

            .data
format:   .ascii "%d
"  

            .text
            .global main  
main:
            mov    $format, %rbx
            mov    (%rbx), %rdi
            mov    $1, %rsi
            call     printf
            ret

我使用 GCC 来组装和链接:

I use GCC to assemble and link with:

gcc -o main main.s

gcc -o main main.s

我用这个命令运行它:

./main

当我运行程序时,我遇到了段错误.通过使用 gdb,它说 printf 未找到.我试过.extern printf",它不起作用.有人建议我应该在调用 printf 之前存储堆栈指针并在 RET 之前恢复,我该怎么做?

When I run the program I get a seg fault. By using gdb, it says printf not found. I have tried ".extern printf", which does not work. Someone suggested I should store the stack pointer before calling printf and restore before RET, How do I do that?

推荐答案

此代码存在许多问题.AMD64 System V ABI 调用约定需要一些东西.它要求在 CALL 之前堆栈至少对齐 16 字节(或 32 字节):

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:

输入参数区域的末尾应对齐在 16(32,如果 __m256 是在堆栈上传递)字节边界.

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

C 运行时调用您的 main 函数后,堆栈错位了 8,因为返回指针是通过 CALL 放置在堆栈上的.要重新对齐 16 字节边界,您可以简单地PUSH 任何 通用寄存器到堆栈上,然后在最后 POP 将其关闭.

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.

调用约定还要求 AL 包含用于可变参数函数的向量寄存器的数量:

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

%al 用于指示传递给需要可变参数数量的函数的向量参数数量

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

printf 是可变参数函数,所以需要设置AL.在这种情况下,您不会在向量寄存器中传递任何参数,因此您可以将 AL 设置为 0.

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.

当 $format 指针已经是地址时,您也可以取消引用它.所以这是错误的:

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

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

这获取格式的地址并将其放入RBX.然后将 RBX 中该地址的 8 个字节放入 RDI.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

这使用 RIP 相对寻址.

This uses RIP Relative Addressing.

您还应该 NUL 终止您的字符串.您可以在 x86 平台上使用 .asciz 而不是使用 .ascii.

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
"
.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


其他建议/建议

您还应该从 64 位 Linux ABI 中了解到,调用约定还需要您编写的函数来尊重某些寄存器的保存.寄存器列表以及是否应该保留如下:


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:

任何在 Preserved 中显示 Yes 的寄存器函数调用列是您必须确保在整个函数中保留的列.函数 main 就像任何其他 C 函数一样.

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

如果您有字符串/数据,您知道它是只读的,您可以将它们放在 .rodata 部分,并使用 .section .rodata 而不是 .数据

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

在 64 位模式下:如果目标操作数是 32 位寄存器,CPU 会将寄存器零扩展到整个 64 位寄存器.这可以节省指令编码上的字节.

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针对符号`printf@@GLIBC_2.2.5'不能在制作共享对象时使用;用 -fPIC 重新编译

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

要解决此问题,您必须以这种方式调用外部函数 printf:

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

call printf@plt 

这通过过程链接表(PLT)

这篇关于使用 GNU 汇编程序在 x86_64 中调用 printf的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆