汇编揭秘C中的参数传递

d1999xt 2008-03-07 01:41:33
汇编揭秘C中的参数传递

文章作者:怕冷的北极熊
很多学习汇编的朋友想必对C也比较了解,因为在当前大学的课程体系里,它很有可能就是你接触到的第一门编程语言。由于对计算机的理解不够,学习时必定会遇到各种问题。有些问题是你通过思考就可以解决的,而更多的问题则是你无从思考,就好像它天生就是这样,你只要记住就OK了。然而这样的学习方式是机械的,更是没有创造力的。只有真正理解了C语言,你才有能力去驾驭它,否则它和你之间永远会隔着一层窗户纸,虽然很薄,但是你永远也捅不透。这是为什么呢?其实道理很简单,就好比在一个公司有现成的代码库可以调用,有的程序员遇到问题时,他唯一可作的就是调用代码库中的功能模块,完事后就万事大吉。而有的程序员则是只要有时间宁可自己实现。即使没有时间,调用完代码库中的功能模块,他还会想,如果是自己,这个功能应该如何实现,代码库中的模块是否有不妥之处,进而对其功能不断进行改进和完善。这可能就是专业和非专业的重要区别。而那些不善于思考的程序员,将来很有可能就会成为我们眼中的“代码工人”。
那么如何才能真正理解C语言呢?答案就是汇编。汇编指令是机器指令的助记符表示,任何高级语言要想被计算机执行,都必须转化为一条条的机器指令,而它又与汇编指令一一对应,通过分析汇编指令,就能真正理解C语言在计算机中的运行机理,只有这样才算真正掌握了C语言,然而如何通过汇编指令分析C语言,很多朋友还不是很熟悉,或者根本就不知道。在有些人的脑子里,C语言是直接被CPU执行的,根本就不会想到还有汇编这一层。而对汇编只是懂点皮毛的,此时也只能是心有余而力不足。在此,我就用汇编语言来揭开C语言参数传递的真正面纱。首先我们来写一个最简单的C语言源程序t.c如下:
main(){}
然后我们在Turboc集成开发环境下生成可执行文件t.exe,接着我们用debug命令加载此文件,查看里面的汇编代码后发现
C:\c>debug t.exe
-u
0C1C:0000 BA720C MOV DX,0C72
0C1C:0003 2E CS:
0C1C:0004 8916F801 MOV [01F8],DX
0C1C:0008 B430 MOV AH,30
0C1C:000A CD21 INT 21
...
看后一头雾水,虽然我们知道每条指令所代表的具体含义,然而却不明白把t.c编译成此汇编代码的真正缘由。其实这是Turboc集成开发环境在作怪。C源程序要想生成exe文件,同样也要经历编译和链接两个阶段。而在Turboc下对应的编译器和链接器分别为Turboc根目录下的Tcc.exe和Tlink.exe。如果像我们所学汇编那样经过 tcc -c t.c(这里加入-c参数是要求只编译,否则它会自动调用链接程序),然后tlink t.obj,同样会生成t.exe,只是运行时不能正确返回,而Turboc集成开发环境为我们解决了这个问题。既然问题解决了,必然要加入相应的功能代码,因此
程序也就不容易读懂了。不过没关系,其实C语言中的函数调用就相当于汇编中call指令,而函数名则代表了功能函数在内存中的偏移地址。我们只要把函数名的值以十六进制形式打印一下就可以了。在C语言中,以十六进形式显示标记为%x。代码t.c如下:
main(){ printf("%x",main); }
执行后显示的结果就是main()函数在内存中的偏移地址。我的电脑打印的结果为1FA,因此我们用debug加载此程序后,通过u命令u 1fa得到的结果如下:
-u 1fa
0C1C:01FA B8FA01 MOV AX,01FA
0C1C:01FD 50 PUSH AX
0C1C:01FE B89401 MOV AX,0194
0C1C:0201 50 PUSH AX
0C1C:0202 E8B708 CALL 0ABC
0C1C:0205 59 POP CX
0C1C:0206 59 POP CX
0C1C:0207 C3 RET
这里有人可能会问到两个问题:
1:第一次打印的是1fa,就能保证第二次加载也是在1fa位置吗?
2:以上汇编指令怎么还是很难看懂呢?
其实对于问题1,多试验几次后你会发现,每次打印的结果都会一样。这和操作系统的内存管理有关,我们只要记住具体方法就可以了,因为我们当前的问题是要通过汇编语言来分析C语言的参数传递,与此问题无关的问题不便过多讨论。
对于问题2,真正的原因在于我们调用了库函数printf()所致。由于我们不知道此函数的具体实现,因此也无法理解
0C1C:01FA B8FA01 MOV AX,01FA
0C1C:01FD 50 PUSH AX
0C1C:01FE B89401 MOV AX,0194
0C1C:0201 50 PUSH AX
四条指令的的具体原因。分析问题要从最简单的开始入手,因此我们需要写一个能够说明问题的最简函数,尽量不去调用库函数。我的t.c如下:
int add(int,int);
main()
{
int a;
int b;
int c;
a=4;
b=5;
c=add(a,b);
}
int add(int a,int b)
{
return a+b;
}
它包括两个函数,主函数和相加函数。我们在Turboc集成开发环境下通过熟悉的快捷键F9就会生成t.exe。我们上面提到,main函数的在内存中的偏移地址为1fa(我的机子上的结果是1fa,不同的机子上可能是其它值),然后我们通过debug t.exe把程序加载到内存,通过u 1fa直接跳转到main()函数的起始位置,查看其对应的汇编代码如下:
C:\c>debug t2.exe
-u 1fa ------------------------------------------>以下对应main()
0C1C:01FA 55 PUSH BP
0C1C:01FB 8BEC MOV BP,SP
0C1C:01FD 83EC02 SUB SP,+02
0C1C:0200 56 PUSH SI
0C1C:0201 57 PUSH DI
0C1C:0202 BE0400 MOV SI,0004
0C1C:0205 BF0500 MOV DI,0005
0C1C:0208 57 PUSH DI
0C1C:0209 56 PUSH SI
0C1C:020A E80B00 CALL 0218
0C1C:020D 59 POP CX
0C1C:020E 59 POP CX
0C1C:020F 8946FE MOV [BP-02],AX
0C1C:0212 5F POP DI
0C1C:0213 5E POP SI
0C1C:0214 8BE5 MOV SP,BP
0C1C:0216 5D POP BP
0C1C:0217 C3 RET
-u --------------------------------------------->以下对应int add(int a,int b)
0C1C:0218 55 PUSH BP
0C1C:0219 8BEC MOV BP,SP
0C1C:021B 8B4604 MOV AX,[BP+04]
0C1C:021E 034606 ADD AX,[BP+06]
0C1C:0221 EB00 JMP 0223
0C1C:0223 5D POP BP
0C1C:0224 C3 RET
查看代码后我们发现,MOV SI,0004 和MOV DI,0005中的4、5正好对应我们t.c中的4和5。我们从头开始一步步执行,并观察栈中元素的变化如下:
PUSH BP   栈中元素为:BP
MOV BP,SP BP中保存当前栈顶位置,即指向栈中元素BP
SUB SP,+02 sp减2,相当于入栈操作,只是入栈元素为当前栈空间中残留字型数据,相当于开辟了一个字空间,此时栈中元素为:BP-残留数据
PUSH SI 此时栈中元素为:BP-残留数据-SI
PUSH DI 此时栈中元素为:BP-残留数据-SI-DI
MOV SI,0004 把4放入寄存器SI 
MOV DI,0005 把5放入寄存器DI
PUSH DI 此时栈中元素为:BP-残留数据-SI-DI-5
PUSH SI 此时栈中元素为:BP-残留数据-SI-DI-5-4
CALL 0218 调用子程序,对应c=add(a,b),段内近转移,CALL命令做的第一件事是把IP入栈,以便能正确返回。执行后栈中元素为:
BP-残留数据-SI-DI-5-4-020D
PUSH BP    函数add()的第一句汇编代码,为了还原现场,所以要重新使BP入栈。此时栈中元素为:BP-残留数据-SI-DI-5-4-020D-BP
MOV BP,SP 把当前栈顶位置赋给BP
MOV AX,[BP+04] 注意[BP+idata]默认的段寄存器应该是SS,因为SS:[SP]对应栈顶的BP,而BP==SP,所以 SS:[BP+4]应该对应栈中的4。
ADD AX,[BP+06] 由上部同样可推导出SS:[BP+6]应该对应栈中的5,对应return a+b; 把相加后的结果放在AX中。
JMP 0223 跳转到偏移地址0223处,对应指令POP BP,为何跳转,不予考虑。
POP BP 还原BP,执行后栈中元素为:BP-残留数据-SI-DI-5-4-020D
RET 回到调用函数(对应main函数) 执行后栈中元素为:BP-残留数据-SI-DI-5-4,而把IP赋值为020D。
POP CX 执行后栈中元素为:BP-残留数据-SI-DI-5, CX=4
POP CX 执行后栈中元素为:BP-残留数据-SI-DI, CX=5
MOV [BP-02],AX 由于main()对应的汇编指令初始时把栈顶偏移地址给了BP,所以此时的SS:[BP]应该对应栈中元素BP,而SS:[BP-2]则对应栈中的残留数据,由于add()对应的汇编代码ADD AX,[BP+06]把相加后的结果存放在了AX中,所以这里是用战中残留数据的空间存储了两个值相加后的结果。而此位置则对应了t.c中的变量c的存储位置。
POP DI 还原DI,执行后栈中元素为:BP-残留数据-SI
POP SI 还原SI,执行后栈中元素为:BP-残留数据
MOV SP,BP 还原SP,执行后栈中元素为:BP,因为main()对应的汇编指令的前两句PUSH BP,MOV BP,SP,结果就是SS:[BP]执向栈中元素元素BP。所以还原后SS:[BP]仍然指向栈中元素元素BP。
POP BP 还原BP,执行后栈中元素为:空
RET 函数调用完成,返回掉用main()的函数。
经过以上分析我们会发现,C语言经编译链接后生成的汇编程序并不复杂,每一条指令都是我们学过的。从中我们知道了,在函数间的参数传递以及在函数内部局部变量的声明都是通过栈来完成的。明白了这些,你是不是会恍然大悟,原来C语言最终是要以这种方式来执行。我们可以用同样的方法,去分析全局变量、指针、结构体、数组等C语言的各个知识点,到时候C语言会赤裸裸的暴露在我们面前,在我们的眼里,它将不再神秘。而此时,我们也许就已经具备了驾驭它的能力。

发表日期:07/12/22
...全文
797 22 打赏 收藏 举报
写回复
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
WJN92 2011-08-22
  • 打赏
  • 举报
回复
这个是跟invoke是一样的吧
minitoy 2011-08-22
  • 打赏
  • 举报
回复
加密解密里的阐述的确十分十分详细.[Quote=引用 18 楼 buaa_dep6 的回复:]
我看了好几篇文章才搞明白参数的传递过程啊~~

头几篇文章看的一头雾水,后来还是看《加密解密》时彻悟的,呵呵,真是很菜:)
[/Quote]
yangxitomc 2011-08-20
  • 打赏
  • 举报
回复
变量申请用下面两行实现
0C1C:0202 BE0400 MOV SI,0004
0C1C:0205 BF0500 MOV DI,0005
可是如果申请了 a,b,c,d,e,... 而函数是add(aa,bb,cc,dd,ee,...)寄存器会不会不够用啊,
zawe333 2011-07-28
  • 打赏
  • 举报
回复
好文章,很支持
buaa_dep6 2008-09-27
  • 打赏
  • 举报
回复
我看了好几篇文章才搞明白参数的传递过程啊~~

头几篇文章看的一头雾水,后来还是看《加密解密》时彻悟的,呵呵,真是很菜:)
jweoweu 2008-09-16
  • 打赏
  • 举报
回复
顶下。。。
heyangbin 2008-09-13
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 cxdzxc 的回复:]
将C还原为用汇编去实现,对理解C的构成有重大意义,融会贯通之后创造出一种类似C的语言也不是没有可能的。
[/Quote]
所言极是
西北无影脚 2008-09-13
  • 打赏
  • 举报
回复
UP
疯哥哥 2008-09-10
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 BigCarrot 的回复:]
这种事情干嘛要做的那样愚蠢

明明有现成的ABI规定了这样的做法,compiler只是按照ABI实现而已

幸亏文章作者没有直接把0101的机器码show给我们看C中的参数传递
[/Quote]
呵呵,虽然高级语言调用规则是ABI规定的,但有时候学习这些东西,还是要亲自看看才好..
ckc 2008-09-10
  • 打赏
  • 举报
回复
绝大多数的c编译器都可以把c语言编译出汇编语言的源代码
里面可以清楚看到c的源代码和相对应的汇编代码
看起来比上面的那个机器码反汇编出来的东西清楚多了
事实上有时候有些非常古怪的bug解决不了的时候,看看这个也许会有帮助

tcc好象是-s参数
cl是/Fa参数
gcc是-S
huxiaolai 2008-09-10
  • 打赏
  • 举报
回复
挺好
学到知识

cxdzxc 2008-09-07
  • 打赏
  • 举报
回复
将C还原为用汇编去实现,对理解C的构成有重大意义,融会贯通之后创造出一种类似C的语言也不是没有可能的。
Andy1990zx 2008-09-07
  • 打赏
  • 举报
回复
反正要知道C中子函數傳遞參數是用的push/pop

所以在使用過后空間就被釋放了
larrycheng 2008-09-07
  • 打赏
  • 举报
回复
感谢分享
weidianmeng1301 2008-03-20
  • 打赏
  • 举报
回复
有些人不求甚解,还认为别人犯傻!
4楼的兄弟啊!
这篇文章让我们知道,知识地学习是需要相互贯通的。
研究行为能让我们更深入的了解某些机制和原理。
别把学习表面化,任何捷径都是在自毁前程。
ironxxx 2008-03-09
  • 打赏
  • 举报
回复
好东西啊谢谢!
dongyi940333 2008-03-08
  • 打赏
  • 举报
回复
谢谢LZ,帮顶一个,收藏
guoxyj 2008-03-08
  • 打赏
  • 举报
回复
不错
BigCarrot 2008-03-08
  • 打赏
  • 举报
回复
这种事情干嘛要做的那样愚蠢

明明有现成的ABI规定了这样的做法,compiler只是按照ABI实现而已

幸亏文章作者没有直接把0101的机器码show给我们看C中的参数传递
智能卡_Snooper 2008-03-07
  • 打赏
  • 举报
回复
以前,有一段时间,俺还蒙昧阶段,就被要求做这个,那时俺已经对汇编有很深的理解了,于是想在汇编中构造一个填充程序,用C写了一段,看了好长时间也没弄成。
加载更多回复(1)
相关推荐
内容简介   《揭秘数据解密的关键技术》[1]是一本以游戏资源文件格式为研究对象的数据逆向工程的技术书籍,主要讲解如何分析和研究自定义文件格式的数据结构。《揭秘数据解密的关键技术》内容包含反汇编的阅读和理解,数据在计算机的存储原理,常用媒体格式的解析,加密和解密的识别和分析,数据压缩的特征识别,打包文件格式的识别和游戏窗口化的方法。《揭秘数据解密的关键技术》对每一个问题都给出了详细和完整的分析过程,力求用最通俗和简单的方法让读者学会分析和研究自定义文件格式。《揭秘数据解密的关键技术》适合对数据解密、游戏资源提取、软件逆向工程感兴趣的读者以及广大编程爱好者阅读。 编辑本段作者简介   刘颖东,网名“小猫”,擅长逆向工程与游戏开发,从接触反汇编开始便一发不可收拾,对操作系统底层控制有强烈的征服欲望,现致力于嵌入式操作系统的开发。 编辑本段编辑推荐   《揭秘数据解密的关键技术》告诉您:。《揭秘数据解密的关键技术》内容包含反汇编的阅读和理解,数据在计算机的存储原理,常用媒体格式的解析,加密和解密的识别和分析,数据压缩的特征识别,打包文件格式的识别和游戏窗口化的方法。《揭秘数据解密的关键技术》对每一个问题都给出了详细和完整的分析过程,力求用最通俗和简单的方法让读者学会分析和研究自定义文件格式 编辑本段目录   第1章 走进数据解密   1.1 数据解密是什么   1.1.1 代码逆向工程和数据逆向工程   1.2 数据解密的方法   1.2.1 黑盒分析法   1.2.2 白盒分析法   1.2.3 黑盒分析法与白盒分析法的比较   1.3 万能的汇编语言   1.3.1 为什么选择汇编语言   1.3.2 16位和32位的80x86汇编语言   1.4 通用寄存器   1.4.1 EAX、EBX、ECX和EDX寄存器   1.4.2 EAX、EBX、ECX和EDX寄存器的用途   1.5 变址寄存器   1.5.1 ESI和EDI寄存器   1.5.2 ESI和EDI寄存器的用途   1.6 指针寄存器   1.6.1 EBP和ESP寄存器   1.6.2 EBP和ESP寄存器的用途   1.7 标志寄存器   1.7.1 EFLAGS寄存器   1.7.2 EFLAGS寄存器的用途   1.8 灵活的寻址方式   1.8.1 寻址方式的分类   1.8.2 高级语言的数据结构和80386寻址方式的关系   1.9 80386指令   1.9.1 Intel格式和AT&T格式的指令   1.9.2 数据传送指令MOV、XCHG、PUSH、POP   1.9.3 地址传送指令   1.9.4 算数运算指令   1.9.5 逻辑运算指令   1.9.6 移位指令   1.9.7 条件转移指令   1.9.8 函数调用指令   1.1 0函数调用约定   1.1 0.1 3种常用的调用约定   1.1 0.2 调用约定的参数传递顺序   1.1 1字节码   1.1 1.1 代码和数据的区别   1.1 1.2 PE文件   第2章 识别汇编代码的高级模式   2.1 汇编的常量、指针和变量——C语言的常量、指针和变量   2.1.1 常量、指针和变量的定义   2.1.2 常量、指针和变量的实现机制   2.2 汇编的字符串——C语言的字符串   2.2.1 字符串的定义   2.2.2 字符串的实现机制   2.3 汇编的数组——C语言的数组   2.3.1 数组的定义   2.3.2 数组的实现机制   2.3.3 二维数组的实现机制   2.4 汇编的结构体——C语言的结构体   2.4.1 结构体的定义   2.4.2 结构体的实现机制   2.5 汇编的条件分支语句——C语言的条件分支语句   2.5.1 条件分支语句的定义   2.5.2 if的实现机制   2.5.3 包含复杂表达式的if语句的实现机制   2.5.4 switch语句的实现机制   2.6 汇编的循环——C语言的循环   2.6.1 循环的定义   2.6.2 while语句的实现机制   2.6.3 do...while语句实现机制   2.6.4 for语句的实现机制。。。。。。。
发帖
汇编语言

2.1w+

社区成员

汇编语言(Assembly Language)是任何一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。
社区管理员
  • 汇编语言
加入社区
帖子事件
创建了帖子
2008-03-07 01:41
社区公告
暂无公告