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

时间:2012-11-30 11:01:54

标签: assembly printf scanf att

我想在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

(遵循与之前推送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

2 个答案:

答案 0 :(得分: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的偏移量。

你必须先分配空间。除非您要使用EBP相对地址解决本地变量,否则您不需要帧。 你可以推ebp - 4等 只需编译你的代码,看看它在任何调试器中是如何工作的(我使用ollydbg来检查你的代码); 最后,您可以要求C编译器生成asm列表并查看编译器如何执行此操作。

答案 1 :(得分: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]