【问题标题】:My (AT&T) assembly (x86-x64) code should increment but doesn't我的 (AT&T) 程序集 (x86-x64) 代码应该增加但不会增加
【发布时间】:2018-03-05 12:33:09
【问题描述】:

我正在尝试在汇编中制作一个小程序(用于 AT&T)。我试图以整数的形式从用户那里获取输入,然后将其递增,然后输出递增的值。但是,该值不会增加。我花了最后几个小时尝试了我能想到的一切,但它仍然不起作用,所以我认为我可能不太了解装配中的概念,导致我没有发现错误。这是我的代码:

1 hiString: .asciz "Hi\n"
  2 formatstr: .asciz "%ld"
  3 
  4 .global main
  5 
  6 main:
  7     movq $0, %rax           #no vector registers printf
  8     movq $hiString, %rdi    #load hiString
  9     call printf             #printf
 10     call inout              #inout
 11     movq $0, %rdi           #loading exit value into register rdi
 12     call exit               #exit
 13 
 14 inout:
 15     pushq %rbp              #Pushing bp
 16     movq %rsp, %rbp         #Moving sp to bp
 17     subq $8, %rsp           #Space on stack for variable
 18     leaq -8(%rbp), %rsi
 19     movq $formatstr, %rdi   #1st argument scanf
 20     movq $0, %rax           #no vector for scanf registers
 21     call scanf              #scanf
 22     incq %rsi
 23     call printf

从我朋友的教程中,我了解到第 17 行到第 19 行是必要的,但是,我认为我没有使用那里的堆栈空间,所以我怀疑错误与此有关。我当然不确定。提前谢谢你。

编辑,更新代码(printf 现在仍在子程序中调用)

    1 hiString: .asciz "hi\n"
  2 formatstr: .asciz "%ld"
  3 
  4 .global main
  5 
  6 main:
  7     movq $0, %rax          
  8     movq $hiString, %di   
  9     call printf             
 10     call inout              
 11     movq $0, %rdi           
 12     call exit               
 13 
 14 inout:
 15     pushq %rbp             
 16     movq %rsp, %rbp         
 17     subq $8, %rsp         
 18     leaq -8(%rbp), %rsi
 19     movq $formatstr, %rdi   
 20     movq $0, %rax           
 21     call scanf              
 22     popq %rax
 23     incq %rax
 24     movq %rax, %rsi
 25     movq $0, %rax
 26     call printf
 27     addq $8, %rs  

它现在运行并递增,但是,当输出递增的值时,值后面会出现一些奇怪的符号。

编辑:没关系,上面只发生过一次,现在没有输出增量值,只有奇怪的迹象。

【问题讨论】:

  • 您上次的编辑再次从inout 的末尾删除了ret....

标签: c assembly x86-64 att


【解决方案1】:

这是关于如何正确调用scanf 的经典困惑的汇编级版本。

 14 inout:
 15     pushq %rbp              #Pushing bp
 16     movq %rsp, %rbp         #Moving sp to bp
 17     subq $8, %rsp           #Space on stack for variable
 18     leaq -8(%rbp), %rsi
 19     movq $formatstr, %rdi   #1st argument scanf
 20     movq $0, %rax           #no vector for scanf registers
 21     call scanf              #scanf

(编者注:更喜欢在 Linux 非 PIE 可执行文件中使用 mov $formatstr, %edi,或者更可移植的与位置无关的 lea formatstr(%rip), %rdi 将静态存储中的字符串地址放入寄存器中)。

到目前为止,您的代码是正确的(除了您没有正确对齐堆栈,但现在不要担心,scanf 可能会让您侥幸逃脱)。更新:现代版本的 glibc 确实有 a scanf that faults on a misaligned RSP,例如 Ubuntu 18.04,可能更早。

 22     incq %rsi

这就是你出错的地方。在调用之前,您将 RSI(scanf 的第二个参数寄存器)设置为指向存储位置的 指针scanf 从标准输入读取一个数字并将其写入该存储位置,而不是 RSI。

根据 cmets 中的讨论,您的意图是将 scanf 读取的值加一并立即将其打印出来。正如其他几个人指出的那样,在scanf 返回之后,您不能假设您加载到 RSI、RDI 或 RAX 中的值是完整的。 (x86-64 psABI 指定在函数调用中保留哪些寄存器:在整数寄存器中,仅保留 RBX、RBP 和 R12 到 R15。如果您打算进行大量汇编编程,您应该阅读本文档封面在 x86-64 上。(注意:Windows 使用不同的 ABI,其调用约定记录在 MSDN 上,请参阅x86 tag wiki 中的链接。)

因此,您必须从头开始将 args 设置为 printf,因为 scanf 破坏了这些寄存器:

       movq -8(%rbp), %rsi   # load variable as arg 2 of printf
       incq %rsi             # and add one
       movq $formatstr, %rdi # first argument to printf
       xorl %rax, %rax       # no vector args to printf
       call printf

在这里密切注意scanfprintf 之间的区别:您可以对两者使用相同的格式字符串,但是当您调用scanf 时,您传递的是存储位置的地址 (leaq -8(%rbp), %rsi),而当您调用 printf 时,您传递 要打印的值 (movq -8(%rbp), %rsi; incq %rsi)。

(事实上你应该在调用printf时使用稍微不同的格式字符串,因为你需要在数字后打印一个换行符,所以"%ld\n"会更好。)

您当前的代码几乎以不同的方式做到这一点。我这样做是因为在函数中间弄乱堆栈指针 (popq %rax) 是不好的做法。 (还记得我说过的关于未正确对齐堆栈的内容吗?如果您在入口处设置一个完整的“调用框架”,然后将堆栈指针放在一边直到退出,那么保持堆栈对齐会容易得多。从技术上讲,您只需要 让堆栈指针在每个调用指令的点对齐。)

你也没有正确结束函数:

 27     addq $8, %rs  

我认为您没有复制和粘贴整个程序 - 这看起来像是在行的中间被切断了。无论如何,如果您首先要使用帧指针(x86-64 上不需要帧指针),您应该再次使用它来退出:

        movq %rbp, %rsp
        popq %rbp
        ret

顺便说一下,“AT&T”汇编语法用于许多不同的 CPU 架构。在谈到汇编语言时,我们总是需要先了解 CPU 架构首先;语法变体(如果有)是次要的。您应该将问题命名为“我的汇编程序(x86-64,AT&T 语法)...”


作为最后一条建议,我建议你编译这个 C 程序

#include <stdio.h>

static void inout(void)
{
    long x;
    scanf("%ld", &x);
    printf("%ld\n", x+1);
}

int main(void)
{
    printf("hi\n");
    inout();
    return 0;
}

使用您选择的 C 编译器,使用等效于 -S -O2 -fno-inline 的选项(即:生成文本汇编语言,经过优化,但不进行任何内联),然后逐行读取汇编输出。每当 C 编译器所做的事情与您所做的不同时,这可能意味着它知道您不知道的事情,您应该了解这些事情。

或者更简单地说,look at it on the Godbolt compiler explorer

【讨论】:

  • 感谢您的回答和整体反馈,但是,我想我仍然需要更多的指点。我实际上尝试了您提供的代码,但是,我仍然没有得到正确的输出。 (当我在我的 inout 子例程中继续调用 printf 时,我仍然得到非递增的输入,当我在那里删除 printf 并在我的主例程中调用它时(在将 rax 的内容移动到 rsi 之后),我根本没有得到任何输出。所以,我还是不明白我的想法。
  • OP 应该进一步注意,函数可以(并且通常会)销毁它们在返回时收到的参数。 rsi 极不可能包含您在从 scanf 返回时最初放置在那里的值。
  • @RomeeB 请向我们展示您更新后的代码。不要从问题中删除原始代码,只需将更新后的代码粘贴在下面。
  • @RomeeB 我没有看到你给printf 的电话。它在哪里?
  • @RomeeB 同样,你犯了我已经指出的错误:scanf 被允许破坏所有参数寄存器的内容。您必须重新设置所有参数。相反,您的代码以某种方式期望 scanf 保留您设置的参数,但事实并非如此。在汇编中编程时,您应该真正查看平台的调用约定。
【解决方案2】:

re: 更新代码:

它现在运行并递增,但是,当输出递增的值时,值后面会出现一些奇怪的符号。

Arg 传递寄存器被调用破坏。您调用printf 而不将格式字符串放入%rdi,您必须假设在scanf 返回后保存垃圾。

使用调试器单步执行您的代码。使用ni 跳过gdb 中的calls。 (有关 GDB 提示,请参阅 标签 wiki 的底部)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-23
    • 1970-01-01
    • 1970-01-01
    • 2013-06-30
    • 1970-01-01
    相关资源
    最近更新 更多