- 第一章 CPU 简介
- 第二章 Hello,world!
- 第三章 函数开始和结束
- 第四章 栈
- Chapter 5 printf() 与参数处理
- Chapter 6 scanf()
- CHAPER7 访问传递参数
- Chapter 8 一个或者多个字的返回值
- Chapter 9 指针
- Chapter 10 条件跳转
- 第 11 章 选择结构 switch()/case/default
- 第 12 章 循环结构
- 第 13 章 strlen()
- Chapter 14 Division by 9
- chapter 15 用 FPU 工作
- Chapter 16 数组
- Chapter 17 位域
- 第 18 章 结构体
- 19 章 联合体
- 第二十章 函数指针
- 第 21 章 在 32 位环境中的 64 位值
- 第二十二章 SIMD
- 23 章 64 位化
- 24 章 使用 x64 下的 SIMD 来处理浮点数
- 25 章 温度转换
- 26 章 C99 的限制
- 27 章 内联函数
- 第 28 章 得到不正确反汇编结果
- 第 29 章 花指令
- 第 30 章 16 位 Windows
- 第 31 章 类
- 三十二 ostream
- 34.2.2 MSVC
- 34.2.3 C++ 11 std::forward_list
- 34.3 std::vector
- 34.4 std::map and std::set
6.2 x86
6.2.1 MSVC
MVSC 2010 编译后得到下面代码
#!bash
CONST SEGMENT
$SG3831 DB ’Enter X:’, 0aH, 00H
$SG3832 DB ’%d’, 00H
35
6.2\. X86 CHAPTER 6\. SCANF()
$SG3833 DB ’You entered %d...’, 0aH, 00H
CONST ENDS
PUBLIC _main
EXTRN _scanf:PROC
EXTRN _printf:PROC
; Function compile flags: /Odtp
_TEXT SEGMENT
_x$ = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
push ecx
push OFFSET $SG3831 ; ’Enter X:’
call _printf
add esp, 4
lea eax, DWORD PTR _x$[ebp]
push eax
push OFFSET $SG3832 ; ’%d’
call _scanf
add esp, 8
mov ecx, DWORD PTR _x$[ebp]
push ecx
push OFFSET $SG3833 ; ’You entered %d...’
call _printf
add esp, 8
; return 0
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
X 是局部变量。
C/C++标准告诉我们它只对函数内部可见,无法从外部访问。习惯上,局部变量放在栈中。也可能有其他方法,但在 x86 中是这样。
函数序言后下一条指令 PUSH ECX 目的并不是要存储 ECX 的状态(注意程序结尾没有与之相对的 POP ECX)。
事实上这条指令仅仅是在栈中分配了 4 字节用于存储变量 x。
变量 x 可以用宏 _x$
来访问(等于-4),EBP 寄存器指向当前栈帧。
在一个函数执行完之后,EBP 将指向当前栈帧,就无法通过 EBP+offset
来访问局部变量和函数参数了。
也可以使用 ESP 寄存器,但由于它经常变化所以使用不方便。所以说在函数刚开始时,EBP 的值保存了此时 ESP 的值。
下面是一个非常典型的 32 位栈帧结构 ... ... EBP-8 local variable #2, marked in IDA as var_8 EBP-4 local variable #1, marked in IDA as var_4 EBP saved value of EBP EBP+4 return address EBP+8 argument#1, marked in IDA as arg_0 EBP+0xC argument#2, marked in IDA as arg_4 EBP+0x10 argument#3, marked in IDA as arg_8 ... ...
在我们的例子中,scanf() 有两个参数。
第一个参数是指向"%d"的字符串指针,第二个是变量 x 的地址。
首先, lea eax, DWORD PTR _x$[ebp]
指令将变量 x 的地址放入 EAX 寄存器。LEA 作用是"取有效地址",然而之后的主要用途有所变化(b.6.2)。
可以说,LEA 在这里只是把 EBP 的值与宏 _x$
的值相乘,并存储在 EAX 寄存器中。
lea eax, [ebp-4]
也是一样。
EBP 的值减去 4,结果放在 EAX 寄存器中。接着 EAX 寄存器的值被压入栈中,再调用 printf()
。
之后, printf()
被调用。第一个参数是一个字符串指针:" You entered %d …
"。
第二个参数是通过 mov ecx, [ebp-4]
使用的,这个指令把变量 x 的内容传给 ECX 而不是它的地址。
然后,ECX 的值放入栈中,接着最后一次调用 printf()
。
6.2.2 MSVC+OllyDbg
让我们在 OllyDbg 中使用这个例子。首先载入程序,按 F8 直到进入我们的可执行文件而不是 ntdll.dll。往下滚动屏幕找到 main()。点击第一条指令(PUSH EBP),按 F2,再按 F9,触发 main() 开始处的断点。
让我们来跟随到准备变量 x 的地址的位置。图 6.2
可以右击寄存器窗口的 EAX,再点击"堆栈窗口中跟随"。这个地址会在堆栈窗口中显示。观察,这是局部栈中的一个变量。我在图中用红色箭头标出。这里是一些无用数据(0x77D478)。PUSH 指令将会把这个栈元素的地址压入栈中。然后按 F8 直到 scanf() 函数执行完。在 scanf() 执行时,我们要在命令行窗口中输入,例如输入 123。
图 6.1 命令行输出
scanf() 在这里执行。图 6.3。scanf() 在 EAX 中返回 1,这意味着成功读入了一个值。现在我们关心的那个栈元素中的值是 0x7B(123)。
接下来,这个值从栈中复制到 ECX 寄存器中,然后传递给 printf()。图 6.4
图 6.2 OllyDbg:计算局部变量的地址
图 6.3:OllyDbg:scanf() 执行
图 6.4:OllyDbg:准备把值传递给 printf()
6.2.3 GCC
让我们在 Linux GCC 4.4.1 下编译这段代码
GCC 把第一个调用的 printf() 替换成了 puts(),原因在 2.3.3 节中讲过了。
和之前一样,参数都是用 MOV 指令放入栈中。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论