使用 scanf 进入全局或局部变量(在堆栈上),32 位调用约定
Posted
技术标签:
【中文标题】使用 scanf 进入全局或局部变量(在堆栈上),32 位调用约定【英文标题】:Using scanf into global or local variables (on the stack), 32-bit calling convention 【发布时间】:2011-12-16 04:34:07 【问题描述】:给定以下代码:
.section .rodata
str: .string "Hello World!\n"
input: .long 2
########
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
pushl $str
call printf
#return from printf:
movl $0, %eax
movl %ebp,%esp
popl %ebp
ret
输出将是“Hello World!”。
现在我尝试从用户那里获取一个数字,然后在屏幕上打印出来,但是 它不起作用(代码编译,但我做错了什么)。 我的错在哪里?
.section .rodata
input: .long 2
########
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
pushl %ebx
call scanf # call scanf to get number from the user
popl input # store the number entered by user in input (variable)
pushl input # push it back into the stack
call printf # print input
#return from printf:
movl $0, %eax
movl %ebp,%esp
popl %ebp
ret
【问题讨论】:
【参考方案1】:我不太确定您使用的是哪种汇编程序,但是我可以让您的代码使用 gcc 编译,所以我坚持使用您的格式样式(不是在谈论 AT&T 语法)。
无论如何,您应该检查documentation 中的scanf
并意识到它需要一个格式字符串 和指针 指向内存中存储值的位置读入。它还返回成功读取的项目数,而不是读取的内容。
现在做同样的事情并检查documention 是否有printf
。您会看到需要格式字符串才能以可读的形式打印您的号码。一个合适的格式字符串是"%d\n"
来打印数字和换行符。
您的代码现在可能看起来像这样(使用 gcc 对我来说编译和工作正常):
.section .rodata
input_format: .string "%d"
output_format: .string "%d\n"
.section .bss
input: .long 0 # reserve 4 bytes of space
.section .text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
pushl $input # push the ADDRESS of input to have the value stored in it
pushl $input_format # give scanf the ADDRESS of the format string
call scanf # call scanf to get number from the user
addl $8, %esp # clean up the stack
# Note the return value of scanf is passed through eax (same for printf)
pushl input # pass the number to printf BY VALUE
pushl $output_format # pass the ADDRESSS of the output format string to printf
call printf # print input
#return 0 from main:
movl $0, %eax
movl %ebp,%esp
popl %ebp
ret
请注意,我通常会使用db/dw/dd
在.(ro)data
和.bss
部分分配内存,而不是.string
和.long
,所以如果这部分做的有点不对,你可以修复它。
您也可以使用堆栈空间来存储数字,但是您已经声明了input
,我想让代码尽可能与您的代码相似。 scanf
和 printf
之前和之后的所有其他内容也是如此,我只是将其作为您的代码。
编辑:这是使用堆栈创建局部变量的示例,而不是在.bss
或.data
段中声明变量:
.section .rodata
input_format: .string "%d"
output_format: .string "%d\n"
.section .text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp # allocate 4 bytes on the stack for a local variable
# The local variable will be at -4(%ebp)
leal -4(%ebp), %eax # get the ADDRESS of our local variable
pushl %eax # push the ADDRESS of the variable on the stack
pushl $input_format # give scanf the ADDRESS of the format string
call scanf # call scanf to get number from the user
addl $8, %esp # clean up the stack
# Note the return value of scanf is passed through eax (same for printf)
pushl -4(%ebp) # pass the number to printf BY VALUE
pushl $output_format # pass the ADDRESSS of the output format string to printf
call printf # print the input
#return from printf:
movl $0, %eax
movl %ebp,%esp
popl %ebp
ret
【讨论】:
首先 - 谢谢!其次,如何在不使用全局变量(即“输入”变量)的情况下使用堆栈来代替?再次非常感谢!! @ron:您可以将堆栈指针减 4 以“分配”一个大小为 4 字节的变量,并将堆栈上该位置的地址传递给scanf
。我会用一个例子来更新我的答案。
input: .long
保留零字节空间。您希望在您的版本中使用全局变量 .long 0
(已为您修复)。
在您使用局部变量(堆栈空间)的版本中,您可以简单地push %esp
将指针推送到您保留的空间。但是使用 LEA 相对于帧指针可能会更好地显示这个概念,特别是如果您根本不打算将 EBP 设置为帧指针。 (顺便说一句,请注意 Linux 上 i386 System V ABI 的现代版本要求/保证保持 16 字节堆栈对齐,而不仅仅是 4。即在函数进入后对 ESP 进行 3 x 4 字节推送/调整,这与它正在做的不同。不过,在 GNU/Linux 上 printf 通常不会因此出错)。【参考方案2】:
您对scanf
的参数不正确,您需要同时推送扫描格式和缓冲区以保存您要查找的类型,然后,如果它们不是字符串,您需要将新的格式化字符串推送到printf
,在本例中为 "%d"
。
看起来有点像这样(对不起,它是 Intel/MASM 格式):
SUB ESP,4 ;make stack space for an int
LEA EAX,[ESP]
PUSH EAX
PUSH offset NumberString ;"%d"
CALL scanf
PUSH [ESP+8] ;our scanned number
PUSH offset NumberString ;"%d"
CALL printf
ADD ESP,20 ;clean up for the cdecl funcs and the alloc'ed stack space
【讨论】:
请注意,您可以push esp
(保证存储原始值:felixcloutier.com/x86/push),并且lea eax, [esp]
是mov eax, esp
的愚蠢低效替代方案。以上是关于使用 scanf 进入全局或局部变量(在堆栈上),32 位调用约定的主要内容,如果未能解决你的问题,请参考以下文章