首页
社区
课程
招聘
[旧帖] HelloWorld的迷惑 0.00雪花
发表于: 2010-11-22 21:57 6917

[旧帖] HelloWorld的迷惑 0.00雪花

2010-11-22 21:57
6917
#include <stdio.h>

int main()
{
   printf("Hello,World\n");
   return 0;
}

上面这段代码,用VC  debug 版本进行编译,得到如下汇编代码:
main        PROC NEAR                                        ; COMDAT

; 4    : {///////////////////////////这里开始的代码//////////////////////////////

  00000        55                 push         ebp
  00001        8b ec                 mov         ebp, esp
  00003        83 ec 40         sub         esp, 64                        ; 00000040H
  00006        53                 push         ebx
  00007        56                 push         esi
  00008        57                 push         edi
  00009        8d 7d c0         lea         edi, DWORD PTR [ebp-64]
  0000c        b9 10 00 00 00         mov         ecx, 16                        ; 00000010H
  00011        b8 cc cc cc cc         mov         eax, -858993460                ; ccccccccH
  00016        f3 ab                 rep stosd
/////////////////////////////////////////////////这里结束的代码////////////////////////////////////
; 5    :    printf("Hello,World\n");

  00018        68 00 00 00 00         push         OFFSET FLAT:??_C@_0N@OJDJ@Hello?0World?6?$AA@ ; `string'
  0001d        e8 00 00 00 00         call         _printf
  00022        83 c4 04         add         esp, 4

; 6    :    return 0;

  00025        33 c0                 xor         eax, eax

; 7    : }

  00027        5f                 pop         edi
  00028        5e                 pop         esi
  00029        5b                 pop         ebx
  0002a        83 c4 40         add         esp, 64                        ; 00000040H
  0002d        3b ec                 cmp         ebp, esp
  0002f        e8 00 00 00 00         call         __chkesp
  00034        8b e5                 mov         esp, ebp
  00036        5d                 pop         ebp
  00037        c3                 ret         0
_main        ENDP

上面我标示 出的代码是干什么用的呢???release版本编译的就没有上面我标出的那段

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (16)
雪    币: 2134
活跃值: (14)
能力值: (RANK:170 )
在线值:
发帖
回帖
粉丝
2
两个基本知识:
1、操作是对栈数据置cc。
2、cc是软断点,

debug模式会作上面的操作,如果数据执行出错的话,比如堆栈乱了,cdecl, stdcall之类的返回错误,可以直接抛出一个断点异常,容易定位问题,不会把问题放大。

release模式则为了效率和优化考虑,把这部分代码去掉。
2010-11-22 22:21
0
雪    币: 1157
活跃值: (847)
能力值: ( LV8,RANK:150 )
在线值:
发帖
回帖
粉丝
3
那debug程序为什么能定位到源代码,而release版本的却不能呢??再说了,数据怎么会被执行呢??
2010-11-23 20:55
0
雪    币: 599
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
debug忠于源码且包含调试信息,release要优化。数据执行那个,你应该了解一点缓冲区溢出的基本知识。
2010-11-23 21:18
0
雪    币: 50
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
哦,这样啊...明白个大概,我还新的很啊..
2010-11-23 22:57
0
雪    币: 31
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
00000  55     push   ebp
  00001  8b ec     mov   ebp, esp
这俩句,我也挺迷糊的,我是把它理解成,使ebp和esp指向栈底,栈的大小为空。

  00003  83 ec 40   sub   esp, 64      ; 00000040H
//这里应该是分配堆栈空间,栈的结构从上到下是由低址到高址,所以sub esp,64是esp向上移动64,即分配空间大小为64

下面这段我的理解是,因为要调用main()函数,所以要保存下寄存器内容,以便函数返回时还原。
  00006  53     push   ebx
  00007  56     push   esi
  00008  57     push   edi
//这三条,把寄存器内容如栈保存。
  00009  8d 7d c0   lea   edi, DWORD PTR [ebp-64]
//这条使edi指向栈顶。
  0000c  b9 10 00 00 00   mov   ecx, 16      ; 00000010H
  00011  b8 cc cc cc cc   mov   eax, -858993460    ; ccccccccH
  00016  f3 ab     rep stosd
//这三条是把ccccccccH存储到edi指向的堆栈中。这我也不太明白。
这大概就是一楼说的那个东东吧?
1、操作是对栈数据置cc。
2、cc是软断点,
//这里的CC软断点应该就是上面的ccccccccH吧!

debug模式会作上面的操作,如果数据执行出错的话,比如堆栈乱了,cdecl, stdcall之类的返回错误,可以直接抛出一个断点异常,容易定位问题,不会把问题放大。

release模式则为了效率和优化考虑,把这部分代码去掉。
呵呵,我所理解的就这些了,希望对你有帮助!!
2010-11-24 16:14
0
雪    币: 60
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
路过学习 6个字啊
2010-11-25 09:42
0
雪    币: 379
活跃值: (40)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
数据是可以被执行的,忘记这是冯诺依曼体系的特点还是微软搞的,所谓的数据和指令都是二进制码,区别在于CPU怎么对待
2010-11-25 10:20
0
雪    币: 379
活跃值: (40)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
9
门前小雪解释的基本差不多。建议楼主搞本天书夜读,书的一开始对这些都做了详细的分析
2010-11-25 10:27
0
雪    币: 83
活跃值: (40)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
6楼的解释好像只是解释单条指令,没解释作用,我等下有时间写一下
①先说开头和结尾,这是个基本结构
  00000  55     push   ebp
  00001  8b ec     mov   ebp, esp
  00003  83 ec 40   sub   esp, 64      ; 00000040H
跟后面的
  0002a  83 c4 40   add   esp, 64      ; 00000040H
  0002d  3b ec     cmp   ebp, esp
  0002f  e8 00 00 00 00   call   __chkesp
  00034  8b e5     mov   esp, ebp
  00036  5d     pop   ebp
  00037  c3     ret   0
是配对的,一般的主程序或者子程序都是这样的结构,
一般程序运行前都要保存重要寄存器的值,运行完返回时再恢复
sub   esp, 64 是给主程序分配内存空间
00003这条指令会改变esp的值,esp是栈顶的地址,所以要用mov   ebp, esp 先把esp保存在ebp里面,结束后用mov   esp, ebp将esp恢复
00001这条指令会改变ebp的值,所以要push   ebp将ebp存入堆栈,后面再用pop   ebp恢复ebp
执行pop   ebp后堆栈的下一条指令就是程序的返回地址,ret   0从栈顶取出返回地址返回
这个过程中ebp存着原来的栈顶地址,如果ebp被意外的改变了,程序就不能返回了,
所以
  0002d  3b ec     cmp   ebp, esp
  0002f  e8 00 00 00 00   call   __chkesp是做检查

② 00006  53     push   ebx
00007  56     push   esi
00008  57     push   edi
是保存这3个寄存器的值
后面的
  00027  5f     pop   edi
  00028  5e     pop   esi
  00029  5b     pop   ebx
是把这3个寄存器改回去,这6条指令也是对应的,一个push对一个pop,为了堆栈平衡而且可以恢复这3个寄存器的原始值

③然后再来说中间的,中间就是要输出Hello,World这个字符串,连回车符是12个字符,当然还要先做一些初始化工作
lea   edi, DWORD PTR [ebp-64]
把ebp-64的地址存入edi中,刚才00003的指令是分配的64字节的空间,[ebp-64]就是那个空间的起始地址,用C语言的话说就是让edi指向空间的起始地址
  0000c  b9 10 00 00 00   mov   ecx, 16      ; 00000010H
  00011  b8 cc cc cc cc   mov   eax, -858993460    ; ccccccccH
  00016  f3 ab     rep stosd
这3条指令的效果就是用cc填充刚才的64字节的数据空间,至于为什么是cc二楼说得很清楚了,ecx用作循环的计数器,表示后面的rep stosd循环16次
00011的指令表示将eax用0xcccccccc填充,00016的指令表示将eax的值填入那个的空间,循环16次,一次填充4字节,16*4=64

④  00018  68 00 00 00 00   push   OFFSET FLAT:??_C@_0N@OJDJ@Hello?0World?6?$AA@ ; `string'
  0001d  e8 00 00 00 00   call   _printf
  00022  83 c4 04   add   esp, 4
  00025  33 c0     xor   eax, eax
把Hello,World的字符串地址压入栈顶,然后调用printf,一般调用函数都是被参数依次放入堆栈中,然后call
push使堆栈增加了4,所以esp减少了4,add   esp, 4是为了堆栈平衡,
printf的返回值在eax中,所有函数都是用eax保存返回值的,我们不用printf函数的返回值,所以用xor   eax, eax清空

整个程序就分析完了,小弟初学汇编,如果有不对的地方请大家指正
2010-11-25 12:15
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
因为debug版本的文件中有调试的段,realease里的则没有。
2010-11-25 15:03
0
雪    币: 1708
活跃值: (586)
能力值: ( LV15,RANK:670 )
在线值:
发帖
回帖
粉丝
12
release也能调试的,加入调试信息就可以了。
2010-11-25 15:04
0
雪    币: 180
活跃值: (76)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
mov ebp,esp是将原栈底设成新顶....
一个函数调用过程大概如下:
push 参数2
push 参数1
call 函数

call操作隐含了push eip

进函数后:
push   ebp
mov ebp,esp
........
sub esp,64
此时的栈结构大概就像下面:

.
.
.
函数内部的变量空间
64字节空间
.
.
.
ebp
eip
参数1
参数2

栈是从高到低增长的.函数之前的开辟空间\保存返回值\把EBP之类的压栈操作称为"函数序言"
2010-11-25 15:22
0
雪    币: 291
活跃值: (27)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
00000  55     push   ebp          ;保存基址
  00001  8b ec     mov   ebp, esp  ;打开堆栈 可以通过 ebp+- 来访问变量
  00003  83 ec 40   sub   esp, 64  ;为分配局部内存 一般都是64
  00006  53     push   ebx ;一般按照函数间调用的约定,函数中可以自由使用eax,ecx,edx
  00007  56     push   esi  ;其它寄存器如果需要使用则需要保存,用完时恢复;
                           ;也就是寄存器的使用约定; 这也使函数调用约定的一部分
  00008  57     push   edi    ;在函数中调用了别的函数后,eax,ecx,edx很可能已经改变
                                       ;这三条 保存寄存器的值
  00009  8d 7d c0   lea   edi, DWORD PTR [ebp-64]   ;装载局部内存地址
  0000c  b9 10 00 00 00   mov   ecx, 16      ; 00000010H ;计数
  00011  b8 cc cc cc cc   mov   eax, -858993460    ; ccccccccH
  00016  f3 ab     rep stosd  ;把al/ ax/ eax的内容存储到edi指向的内存单元中,同时edi的值根据方向标志的值增加或者减少。
前面开辟的(16*4)byte局部空间全部填充0xCC
注意: 0xCC是调试中断(__asm int 3)的指令码,所以可以想象,当程序错误的跳转到这个区域进行执行时将产生调试中断
2010-11-26 14:21
0
雪    币: 15
活跃值: (48)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
VC编译出的程序 多少会有点冗余的吧~~
如果直接用汇编 会省去许多代码的~
2010-11-26 21:07
0
雪    币: 101
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
00000  55     push   ebp
  00001  8b ec     mov   ebp, esp
  00003  83 ec 40   sub   esp, 64      ; 00000040H
  00006  53     push   ebx
  00007  56     push   esi
  00008  57     push   edi
  00009  8d 7d c0   lea   edi, DWORD PTR [ebp-64]
  0000c  b9 10 00 00 00   mov   ecx, 16      ; 00000010H
  00011  b8 cc cc cc cc   mov   eax, -858993460    ; ccccccccH
  00016  f3 ab     rep stosd

均是编译器约定的,
2010-12-1 10:11
0
雪    币: 1644
活跃值: (53)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
结合实际例子,浅显易懂,赞一个。
2011-5-18 19:27
0
游客
登录 | 注册 方可回帖
返回
//