将 scanf 与 x86-64 GAS 程序集一起使用

Posted

技术标签:

【中文标题】将 scanf 与 x86-64 GAS 程序集一起使用【英文标题】:Using scanf with x86-64 GAS assembly 【发布时间】:2014-11-23 22:41:51 【问题描述】:

我在尝试调用系统函数 scanf 以在我的 x86 汇编程序中工作时遇到了很多问题。目前我已经从标准中读取它,但是它只会读取没有段错误的字符(我不知道为什么,指定的字符串是 %d)。我在 x86 在线看到的 scanf 示例使用 quarky 或使用 NASM 语法编写,因此我尝试将它们改编为我的程序。

f:
    .string "%d"

_main:
    movq    $0,    %rax    #Clean rax
    movq    $f,    %rdi    #Load string format
    movq    %rcx,  %rsi    #Set storage to rcx (Not sure if this is valid)
    call    scanf
    ret

在输入字符或字符串后,使用 printf 检查 rcx 和 rax 分别返回 1 和 0(只有这样程序不会出现段错误)。

非常感谢任何有关如何在 x86 气体组装中正确使用 scanf 的见解!

【问题讨论】:

scanf 是一个标准库函数,而不是“系统函数”。对于第二个参数,它需要一个存储结果的内存地址。您可以将寄存器值移动到 RSI 并期望它将数据存储在前一个寄存器中 - 这没有任何意义。使用 LEA。 【参考方案1】:

正如您所担心的,movq %rcx, %rsi 不正确。您需要将指针传递给内存。寄存器不是内存地址空间的一部分,因此您不能拥有指向它们的指针。您需要在全局或本地分配存储。顺便说一句,您不应该将您的数据(尤其是可写的)放入默认的.text 部分,因为这是用于代码并且通常是只读的。此外,调用约定通常要求 16 字节堆栈指针对齐,因此您也应该注意这一点。

.globl main

main:
    push %rbp           # keep stack aligned
    mov  $0, %eax       # clear AL (zero FP args in XMM registers)
    leaq f(%rip), %rdi  # load format string
    leaq x(%rip), %rsi  # set storage to address of x
    call scanf
    pop %rbp
    ret

.data

f:  .string "%d"         # could be in .rodata instead
x:  .long 0

(如果您的环境需要符号前导下划线,则使用_main,可能使用_scanf。)


将符号/标签的地址放入寄存器实际上有 3 种选择。 RIP-relative LEA 是 x86-64 上的标准方式。 How to load address of function or label into register in GNU Assembler

如果您的变量位于地址空间的较低 4GiB 中,则作为优化,例如在 Linux 非 PIE(位置-依赖)可执行文件中,您可以使用 32 位绝对立即数:

    mov  $f, %edi       # load format string
    mov  $x, %esi       # set storage to address of x

movq $f, %rdi 将使用 32 位符号扩展立即数(而不是通过编写 EDI 将零扩展隐含到 RDI),但具有与 RIP 相关 LEA 相同的代码大小。

您还可以使用助记符movabsq 加载完整的 64 位绝对地址。但不要这样做,因为 10 字节指令对代码大小不利,并且仍然需要运行时修复,因为它不是位置无关的。

    movabsq $f, %rdi # load format string
    movabsq $x, %rsi # set storage to address of x

根据要求:使用局部变量进行输出可能如下所示:

    subq  $8, %rsp       # allocate 8 bytes from stack
    xor   %eax, %eax     # clear AL (and RAX)
    leaq  f(%rip), %rdi  # load format string
    movq  %rsp, %rsi     # set storage to local variable
    call  scanf
    addq  $8, %rsp       # restore stack
    ret

【讨论】:

非常感谢!为了将来参考,我将如何用本地内存空间替换全局 x? @user3210373: lea 4(%rsp), %rsi 例如。或者mov %rsp, %rsi,如果您的本地恰好在(%rsp)。 My (AT&T) assembly (x86-x64) code should increment but doesn't 显示了一个示例。 呃,mov $f, %edi 用于低地址(Linux 非 PIE 可执行文件),否则 lea f(%rip), %rdimovqmovabs 不是好的建议。

以上是关于将 scanf 与 x86-64 GAS 程序集一起使用的主要内容,如果未能解决你的问题,请参考以下文章

x86-64 GAS Intel 语法中的 RIP 相对变量引用(如“[RIP + _a]”)如何工作?

AT&T 语法中的 3 或 4 参数 x86 程序集[重复]

将 C 代码转换为 x86-64 位程序集?

将 C 代码转换为 x86-64 程序集

x86-64 在线汇编,带有诸如 https://www.mycompiler.io/new/asm-x86_64 之类的 IDE

x86-64 内核在设置 IDT 时崩溃