我正在尝试在汇编中制作一个小程序(用于 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
从我 friend 的教程中,我了解到第 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
它现在运行并递增,但是,当输出递增的值时,该值后面会显示一些奇怪的符号。
编辑:没关系,上面只发生过一次,现在没有输出增量值,只有奇怪的迹象。
最佳答案
这是关于如何调用 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
从 stdin 读取一个数字并将其写入该存储位置,而不是 RSI。从评论中的讨论来看,您的意图是在
scanf
读取的值上加一。并立即打印出来。正如其他几个人指出的那样,在 scanf
之后返回,您不能假设您加载到 RSI、RDI 或 RAX 中的值是完整的。 (x86-64 psABI 指定在函数调用时要保留哪些寄存器:在整数寄存器中,仅保留 RBX、RBP 和 R12 到 R15。如果您打算进行大量汇编编程,则应全面阅读本文档在 x86-64 上。(注意:Windows 使用不同的 ABI,其调用约定记录在 MSDN 上,请参阅 x86 tag wiki 中的链接。))所以你必须将参数设置为
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
密切关注
scanf
的区别和 printf
此处:您可以对两者使用相同的格式字符串,但是当您调用 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
关于c - 我的 (AT&T) 程序集 (x86-x64) 代码应该增加但不增加,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46389957/