汇编(AT&T 32 位)scanf 问题

Posted

技术标签:

【中文标题】汇编(AT&T 32 位)scanf 问题【英文标题】:Assembly (AT&T 32 bit) scanf questions 【发布时间】:2012-11-30 11:01:53 【问题描述】:

我想在Assembly中编写如下C代码:

int main(void)

    int x,y;
    scanf("%d%d",&x,&y);
    printf("%d%d",x,y);
    return 0;

首先我尝试了只扫描/打印一个整数:

.section    .rodata #read only data section
fmt:    .string "%d%d"
    .text   
.globl  main    
    .type   main, @function
main:   
pushl   %ebp    #save the old frame pointer
movl    %esp,   %ebp    #create the new frame pointer

pushl %esp #location of x
pushl $fmt
call scanf

    #stack should now have exactly the scanned number x and then the format, as needed for printf.
call printf

movl    $0, %eax
movl    %ebp,   %esp    #restore the old stack pointer - release all used memory.
popl    %ebp    #restore old frame pointer (the caller function frame)
ret 

但它不起作用。出于某种原因,以下技巧使它起作用(在 printf 之前添加):

addl $4,%esp #pop format
pushl 4(%esp)
pushl $fmt

我不明白为什么 pushl 4(%esp) 会使它起作用,所以对于我的第一个问题,我要求对此事进行澄清。 然后我尝试对两个变量做同样的事情:

fmt:    .string "%d%d"
[...]
    pushl %esp #location of x
    pushl %esp #location of y
    pushl $fmt
    call scanf

但它导致了分段错误。它甚至没有到达 printf 部分,我会尝试这样的事情:

addl $4,%esp #pop format    
pushl 8(%esp)
pushl 8(%esp)
pushl $fmt

call printf

(遵循与之前的 pushl 4(%esp) 相同的逻辑。 所以我的第二个问题是如何使它与两个变量一起工作。 谢谢!

编辑:为什么下面的代码不能用于扫描两个变量?

subl $8,%esp #leave place for two vars
pushl -4(%ebp) #location of x
pushl -8(%ebp) #location of y
pushl $fmt
call scanf

【问题讨论】:

【参考方案1】:

"它应该将 %esp 减去 4,然后将 %esp 保存在 esp 位置 指向”

这将在 8086 CPU 上发生。从 80286 开始,它在内部存储 ESP,然后减去,然后写入内部存储的值。您必须首先分配变量(一次推送或一次 esp-4),然后存储该变量的地址(第二次推送)。对于第一个问题,您必须进行 3 次推送或 1 次推送和 2 次推送。在您的情况下, esp 指向存储旧 EBP 的堆栈位置。 你可以使用

push eax
push esp
push fmt

这也可以。

另外,关于第二个问题,你提到了那些没有 甚至重要,

哦,是的,我复制了错误的代码行,抱歉。我指的是这篇文章:

pushl %esp #location of x
pushl %esp #location of y
pushl $fmt
call scanf

我指出了,为什么您的代码不正确。您必须推送 2 个变量地址。相反,您推送旧 EBP 的地址,然后使用 prev 参数(指向旧 EBP)的堆栈中的单元地址;结果,在读取一个参数时会弄乱,接收您在 scanf 上输入的值。当它想写入另一个值而不是单元格的地址时,它具有先前的 int。

最后,您能解释一下您建议的代码吗?为什么我要移动 edx 和 eax 进入 esp,我什至不知道里面有什么

抱歉,这是 Intel 语法,因此 mov eax, esp 表示“将 esp 写入 eax”。 确实,这不是一个很好的代码。只是一个例子。

我在堆栈上分配一个变量,在 eax 中获取它的地址。然后分配另一个 var,将其地址存储在 edx 中。然后push两个地址,再push fmt的偏移量。

你必须先分配空间。除非您打算使用 EBP 相对地址来处理本地变量,否则您不需要框架。 您可以推送 ebp - 4 等。 只需编译您的代码并查看它在任何调试器中的工作方式(我使用 ollydbg 来检查您的代码); 最后,你可以让C编译器生成asm列表,看看编译器是怎么做的。

【讨论】:

现在我什么都明白了,除了为什么要费心推动 %eax 之类的。为什么不简单地在 esps 之前添加 -4(即让代码 pushl -4(%esp) #location of x(对于 y 也一样,在 scanf 之前)就可以了? 因为它不能“解决问题”。如果你推 esp - 4,你仍然没有为变量分配内存。 那么,如果我现在理解了这个问题,如果给 scanf 的地址指向它自己,scanf 不能覆盖地址吗? 另外,为什么我编辑的问题末尾的代码不起作用? (虽然它可以工作,但前提是 scanf 只写入该位置一次 - 在第一次写入后,它会被值覆盖,因此,不安全)你必须分配 2 个 dwords 然后获取这些地址。如果我没记错的话,esp - val 没有操作码,所以你的汇编程序必须将它转换为 2 补码。所以 push reg - val 需要 6 - 8 个字节, push eax - 1 个字节。对于您的解决方案,在 scanf (如果有效)之后,堆栈将具有价值,而不是地址。所以你必须为后续的 printf 调用弄清楚。【参考方案2】:

有了这个:

pushl %esp #location of x
pushl $fmt
call scanf

你覆盖 EBP。

首先,CPU 记住寄存器的值(旧的 esp 值),然后减去 4,然后保存旧的 ESP 值。在这种情况下,这是旧的 EBP。当你第一次减去 4 时,你会在堆栈上分配一个变量(更好的 PUSH EAX - 它更短,只有 1 个字节);

第二种情况的类似问题:

addl $4,%esp #pop format    
pushl 8(%esp)
pushl 8(%esp)
pushl $fmt

这里第一个参数指向的不是X,而是第二个参数。第二点指向 EBP。 您必须先在堆栈上分配变量:

push ebp
mov ebp, esp
push eax
mov edx, esp
push eax
mov eax, esp
push eax
push edx
push offset fmt
call xyz

还有更多:如果您不使用本地变量,则无需推送 ebp,无需创建帧指针。或者,在堆栈上分配变量后,您可以使用:

LEA eax, [EBP - 4]
LEA edx, [EBP - 8]

【讨论】:

我不明白,为什么 pushl %esp 会覆盖 %ebp?它应该将 %esp 减去 4,然后将 %esp 保存在 esp 指向的位置。另外,关于第二个问题,您提到了甚至无关紧要的行,因为分段错误更早被触发,所以这不是问题。最后,你能解释一下你建议的代码吗?为什么我要把 edx 和 eax 移到 esp 中,我什至不知道它们里面有什么......谢谢! 作为另一个答案发布,因为它大于允许的评论。 @nodwj: push %esp 不会自身覆盖保存的 EBP 值(来自 push %ebp)。但它会向 scanf 传递一个指向它的指针,要求 scanf 覆盖它。

以上是关于汇编(AT&T 32 位)scanf 问题的主要内容,如果未能解决你的问题,请参考以下文章

英特尔 AT&T 汇编程序的逐步执行?

用不到 4 行汇编编写这个练习 AT&T

AT&T x86_32 汇编_003_数据段

AT&T x86_32 汇编_004_数据传递

在 AT&T 汇编中从 ascii 转换为整数

AT&T x86_32 汇编_001_一个示例程序.md