[英]My (AT&T) assembly (x86-x64) code should increment but doesn't
我正在尝试组装一个小程序(用于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
它现在运行并递增,但是,当输出递增的值时,该值之后会出现一些奇怪的符号。
编辑:没关系,上面只发生过一次,现在没有输出增量值,只有怪异的迹象。
这是有关如何正确调用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
(编者注:最好是mov $formatstr, %edi
Linux非PIE可执行文件中的mov $formatstr, %edi
,或者更可移植的与位置无关的lea formatstr(%rip), %rdi
将静态存储中的字符串地址放入寄存器中)。
到目前为止,您的代码是正确的(除非您没有正确对齐堆栈,但是现在不必担心, scanf
可能会让您摆脱它)。 更新:现代版本的glibc确实存在scanf,该错误会在未对齐的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标签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
请在此处密切注意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编译器执行与您不同的操作时,这可能意味着C编译器知道您不知道的内容,因此您应该了解该内容。
或更简单地说, 在Godbolt编译器资源管理器中查看它
回复:更新的代码:
它现在运行并递增,但是,当输出递增的值时,该值之后会出现一些奇怪的符号。
传递arg的寄存器被调用。 您可以在不将格式字符串放入%rdi
情况下调用printf
,而在scanf
返回后必须假定该字符串保留垃圾。
使用调试器单步执行代码。 使用ni
结束gdb中的call
。 (有关GDB技巧,请参见x86标签Wiki的底部)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.