2

我想在汇编中编写以下 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
4

2 回答 2

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 中。然后推送两个地址,然后推送 fmt 的偏移量。

You have to allocate space first. And you do not need frame unless you are going to address local vars using EBP relative addresses. You can push ebp - 4 etc. Just compile your code and look how it works in any debugger (I used ollydbg to check your code); Finally, you can ask C compiler to generate asm listing and look, how compiler does this.

于 2012-11-30T18:59:56.637 回答
0

有了这个:

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]
于 2012-11-30T14:37:24.213 回答