X86汇编语言学习4498.docx
X86汇编语言学习手记X86汇编编语言学习习手记(11)1. 编译译环境 OSS: Soolariis 9 X86 CCompiiler: gccc 3.33.2 Liinkerr: Soolariis Liink EEditoors 55.x Deebug Tooll: mddb Ediitor: vi 注:关于编译译环境的安安装和设置置,可以参参考文章:Solaaris 上的开发发环境安装装及设置。 mmdb是SSolarris提供供的kerrnel debuug工具,这这里用它做做反汇编和和汇编语言言调试工具具。 如果在在Linuux平台可可以用gddb进行反反汇编和调调试。 2. 最简简C代码分分析 为为简化问题题,来分析析一下最简简的c代码码生成的汇汇编代码: # vvi teest1.c intt maiin() reeturnn 0; 编译译该程序,产产生二进制制文件: # gccc teest1.c -oo tesst1 # fille teest1 teest1: ELFF 32-bit LSB execcutabble 8803866 Verrsionn 1, dynaamicaally linkked, not striippedd ttest11是一个EELF格式式32位小小端(Liittlee Enddian)的可执行行文件,动动态链接并并且符号表表没有去除除。 这正正是Uniix/Liinux平平台典型的的可执行文文件格式。 用mdbb反汇编可可以观察生生成的汇编编代码: # mdbb tesst1 LLoadiing mmodulles: liibc.sso.1 > mainn:diis ; 反汇汇编maiin函数,mmdb的命命令一般格格式为 <地址>>:diis maain: pusshl %ebbp ; ebbp寄存器器内容压栈栈,即保存存mainn函数的上上级调用函函数的栈基基地址 mmain+1: moovl %eesp,%ebp ; eesp值赋赋给ebpp,设置mmain函函数的栈基基址 maain+33: ssubl $8,%eesp mmain+6: andll $0xff0,%eesp mmain+9: movll $0,%eax mainn+0xee: subbl %eaax,%eesp mmain+0x100: moovl $00,%eaax ; 设设置函数返返回值0 mainn+0x115: lleavee ; 将ebpp值赋给eesp,ppop先前前栈内的上上级函数栈栈的基地址址给ebpp,恢复原原栈基址 mainn+0x116: rret ; mainn函数返回回,回到上上级调用 > 注注:这里得得到的汇编编语言语法法格式与IIntell的手册有有很大不同同,Uniix/Liinux采采用AT&&T汇编格格式作为汇汇编语言的的语法格式式 如果果想了解AAT&T汇汇编可以参参考文章:Linuux ATT&T 汇汇编语言开开发指南 问问题:谁调调用了 mmain函函数? 在CC语言的层层面来看,mmain函函数是一个个程序的起起始入口点点,而实际际上,ELLF可执行行文件的入入口点并不不是maiin而是_starrt。 mdb也也可以反汇汇编_sttart: > _starrt:ddis ;从_starrt 的地地址开始反反汇编 _starrt: pushhl $0 _starrt+2: pushhl $0 _starrt+4: movll %espp,%ebbp _sstartt+6: ppushll %edx _staart+77: movvl $0xx805004b0,%eaxx _sttart+0xc: teestl %eeax,%eax _staart+00xe: je +0xxf <_sstartt+0x11d> _starrt+0xx10: pushhl $0x8805044b0 _starrt+0xx15: calll -0x775 <ateexit>> _sttart+0x1aa: adddl $44,%essp _sstartt+0x11d: mmovl $0x800607110,%eeax _starrt+0xx22: testtl %eaxx,%eaax _sstartt+0x224: jje +7 <<_staart+00x2b>> _sttart+0x266: caall -00x86 <aatexiit> _starrt+0xx2b: pushhl $0x8805066cd _starrt+0xx30: calll -0x990 <ateexit>> _sttart+0x355: moovl +88(%ebbp),%eax _staart+00x38: leaal +0xx10(%ebp,%eaxx,4),%edxx _sttart+0x3cc: moovl %eedx,00x806608044 _sttart+0x422: anndl $00xf0,%espp _sttart+0x455: suubl $44,%essp _sstartt+0x448: ppushll %edx _staart+00x49: leaal +0xxc(%eebp),%edxx _sttart+0x4cc: puushl %eedx _starrt+0xx4d: pushhl %eaxx _sttart+0x4ee: caall +00x1522 <_initt> _sstartt+0x553: ccall -0xa33 <<_fppstarrt> _starrt+0xx58: ccall +0xfbb <maiin> ;在这里里调用了mmain函函数 _sstartt+0x55d: aaddl $0xc,%espp _sttart+0x600: puushl %eeax _starrt+0xx61: calll -0xaa1 <exiit> _starrt+0xx66: pushhl $0 _starrt+0xx68: movll $1,%eax _staart+00x6d: lcaall $7,$0 _starrt+0xx74: hlt > 问问题:为什什么用EAAX寄存器器保存函数数返回值? 实际上上IA322并没有规规定用哪个个寄存器来来保存返回回值。但如如果反汇编编Solaaris/Linuux的二进进制文件,就就会发现,都都用EAXX保存函数数返回值。 这不是偶偶然现象,是是操作系统统的ABII(Appplicaationn Binnary Inteerfacce)来决决定的。 Solaaris/Linuux操作系系统的ABBI就是SSytemm V AABI。 概概念:SFFP (SStackk Fraame PPointter) 栈框架指指针 正正确理解SSFP必须须了解: IA322 的栈的的概念 CCPU 中中32位寄寄存器ESSP/EBBP的作用用 PUSSH/POOP 指令令是如何影影响栈的 CALLL/RETT/LEAAVE 等等指令是如如何影响栈栈的 如如我们所知知: 1)IA322的栈是用用来存放临临时数据,而而且是LIIFO,即即后进先出出的。栈的的增长方向向是从高地地址向低地地址增长,按按字节为单单位编址。 2) EEBP是栈栈基址的指指针,永远远指向栈底底(高地址址),ESSP是栈指指针,永远远指向栈顶顶(低地址址)。 33) PUUSH一个个longg型数据时时,以字节节为单位将将数据压入入栈,从高高到低按字字节依次将将数据存入入ESP-1、ESSP-2、EESP-33、ESPP-4的地地址单元。 4) PPOP一个个longg型数据,过过程与PUUSH相反反,依次将将ESP-4、ESSP-3、EESP-22、ESPP-1从栈栈内弹出,放放入一个332位寄存存器。 55) CAALL指令令用来调用用一个函数数或过程,此此时,下一一条指令地地址会被压压入堆栈,以以备返回时时能恢复执执行下条指指令。 66) REET指令用用来从一个个函数或过过程返回,之之前CALLL保存的的下条指令令地址会从从栈内弹出出到EIPP寄存器中中,程序转转到CALLL之前下下条指令处处执行 77) ENNTER是是建立当前前函数的栈栈框架,即即相当于以以下两条指指令: ppushll %ebp movll %espp,%ebbp 8) LEAAVE是释释放当前函函数或者过过程的栈框框架,即相相当于以下下两条指令令: moovl eebp eesp ppopl ebpp 如如果反汇编编一个函数数,很多时时候会在函函数进入和和返回处,发发现有类似似如下形式式的汇编语语句: pusshl %ebbp ; eebp寄存存器内容压压栈,即保保存maiin函数的的上级调用用函数的栈栈基地址 movll %espp,%ebbp ; essp值赋给给ebp,设设置 maain函数数的栈基址址 . ; 以以上两条指指令相当于于 entter 00,0 . lleavee ; 将ebbp值赋给给esp,ppop先前前栈内的上上级函数栈栈的基地址址给ebpp,恢复原原栈基址 ret ; maain函数数返回,回回到上级调调用 这这些语句就就是用来创创建和释放放一个函数数或者过程程的栈框架架的。 原原来编译器器会自动在在函数入口口和出口处处插入创建建和释放栈栈框架的语语句。 函函数被调用用时: 11) EIIP/EBBP成为新新函数栈的的边界 函函数被调用用时,返回回时的EIIP首先被被压入堆栈栈;创建栈栈框架时,上上级函数栈栈的EBPP被压入堆堆栈,与EEIP一道道行成新函函数栈框架架的边界 2) EEBP成为为栈框架指指针SFPP,用来指指示新函数数栈的边界界 栈框架架建立后,EEBP指向向的栈的内内容就是上上一级函数数栈的EBBP,可以以想象,通通过EBPP就可以把把层层调用用函数的栈栈都回朔遍遍历一遍,调调试器就是是利用这个个特性实现现 baccktraace功能能的 3) ESPP总是作为为栈指针指指向栈顶,用用来分配栈栈空间 栈栈分配空间间给函数局局部变量时时的语句通通常就是给给ESP减减去一个常常数值,例例如,分配配一个整型型数据就是是 ESPP-4 44) 函数数的参数传传递和局部部变量访问问可以通过过SFP即即EBP来来实现 由于栈框框架指针永永远指向当当前函数的的栈基地址址,参数和和局部变量量访问通常常为如下形形式: +8+xxx(%ebbp) ; 函数入入口参数的的的访问 -xx(%ebpp) ; 函数数局部变量量访问 假如函数数A调用函函数B,函函数B调用用函数C ,则函数数栈框架及及调用关系系如下图所所示: b:77711011bbb00下图有有点乱,因因此删去部部分内容,要要看原图可可参考我的的blogg/b:7711101bbbb0 +-+-> 高地址 | EIIP (上上级函数返返回地址) | +-+ | EEBP (上级函数数的EBPP) | +-+ | Loocal Variiablees | | . | +-+ | AArg nn(函数BB的第n个个参数) | +-+ | Arrg .(函数B的的第.个参参数) | +-+ | AArg 11(函数BB的第1个个参数) | +-+ | AArg 00(函数BB的第0个个参数) | +-+ EIPP (A函函数的返回回地址) | +-+ | EBBP (AA函数的EEBP) | +-+ | LLocall Varriablles | | . | +-+ | Arrg n(函数C的的第n个参参数) | +-+ | Arg .(函数数C的第.个参数) | +-+ | AArg 11(函数CC的第1个个参数) | +-+ | AArg 00(函数CC的第0个个参数) | +-+ | EIPP (B函函数的返回回地址) | +-+ | EBBP (BB函数的EEBP) | +-+ | Loocal Variiablees | | . | +-+-> 低地地址 图 1-1 再分析析testt1反汇编编结果中剩剩余部分语语句的含义义: # mdbb tesst1 LLoadiing mmodulles: liibc.sso.1 > mainn:diis ; 反反汇编maain函数数 maiin: pushhl %ebpp maiin+1: movll %espp,%ebbp ; 创创建Staack FFramee(栈框架架) maain+33: subll $8,%esp ; 通过ESSP-8来来分配8字字节堆栈空空间 maain+66: andll $0xff0,%eesp ; 使栈地址址16字节节对齐 mmain+9: movvl $0,%eaxx ; 无意义义 maiin+0xxe: ssubl %eax,%espp ; 无无意义 mmain+0x100: moovl $00,%eaax ; 设置mmain函函数返回值值 maiin+0xx15: leavve ; 撤撤销Staack FFramee(栈框架架) maain+00x16: rett ; mainn 函数返返回 > 以以下两句似似乎是没有有意义的,果果真是这样样吗? mmovl $0,%eeax subll %eaax,%eesp 用用gcc的的O2级优优化来重新新编译teest1.c: # gccc -O22 tesst1.cc -o testt1 # mdb testt1 > mainn:diis maain: pushhl %ebpp maiin+1: mmovl %esp,%ebpp maiin+3: ssubl $8,%eesp mmain+6: anddl $0xxf0,%esp mainn+9: xoorl %eeax,%eax ; 设设置maiin返回值值,使用xxorl异异或指令来来使eaxx为0 mmain+0xb: leaave mmain+0xc: rett > 新的反汇汇编结果比比最初的结结果要简洁洁一些,果果然之前被被认为无用用的语句被被优化掉了了,进一步步验证了之之前的猜测测。 提示示:编译器器产生的某某些语句可可能在程序序实际语义义上没有用用处,可以以用优化选选项去掉这这些语句。 问问题:为什什么用xoorl来设设置eaxx的值? 注意到优优化后的代代码中,eeax返回回值的设置置由 moovl $0,%eeax 变变为 xoorl %eax,%eaxx ,这是是因为IAA32指令令中,xoorl比mmovl有有更高的运运行速度。 概概念:Sttack aliggned 栈对齐 那么,以以下语句到到底是和作作用呢? subll $8,%esp aandl $0xf00,%essp ; 通过anndl使低低4位为00,保证栈栈地址166字节对齐齐 表面来来看,这条条语句最直直接的后果果是使ESSP的地址址后4位为为0,即116字节对对齐,那么么为什么这这么做呢? 原来,IIA32 系列CPPU的一些些指令分别别在4、88、16字字节对齐时时会有更快快的运行速速度,因此此gcc编编译器为提提高生成代代码在IAA32上的的运行速度度,默认对对产生的代代码进行116字节对对齐 aandl $0xff0,%eesp 的的意义很明明显,那么么 subbl $88,%essp 呢,是是必须的吗吗? 这里里假设在进进入maiin函数之之前,栈是是16字节节对齐的话话,那么,进进入maiin函数后后,EIPP和EBPP被压入堆堆栈后,栈栈地址最末末4位二进进制位必定定是 10000,eesp -8则恰好好使后4位位地址二进进制位为00000。看看来,这也也是为保证证栈16字字节对齐的的。 如如果查一下下gcc的的手册,就就会发现关关于栈对齐齐的参数设设置: -mpreeferrred-sstackk-bouundarry=n ; 希望栈栈按照2的的n次的字字节边界对对齐, nn的取值范范围是2-12 默默认情况下下,n是等等于4的,也也就是说,默默认情况下下,gccc是16字字节对齐,以以适应IAA32大多多数指令的的要求。 让让我们利用用-mprreferrred-stacck-booundaary=22来去除栈栈对齐指令令: # ggcc -mpreeferrred-sstackk-bouundarry=2 testt1.c -o ttest11 > mmain:diss maiin: pusshl %ebbp maain+11: moovl %eesp,%ebp mainn+3: movll $0,%eax mainn+8: leavve maain+99: reet > 可可以看到,栈栈对齐指令令没有了,因因为,IAA32的栈栈本身就是是4字节对对齐的,不不需要用额额外指令进进行对齐。 那么,栈栈框架指针针SFP是是不是必须须的呢? # gccc -mmprefferreed-sttack-bounndaryy=2 -fomiit-frrame-poinnter testt1.c -o ttest > maain:dis mainn: movll $0,%eax mainn+5: ret > 由由此可知,-fomiit-frrame-poinnter 可以去除除SFP。 问题:去去除SFPP后有什么么缺点呢? 1)增增加调式难难度 由于于SFP在在调试器bbackttracee的指令中中被使用到到,因此没没有SFPP该调试指指令就无法法使用。 2)降低低汇编代码码可读性 函数参数数和局部变变量的访问问,在没有有ebp的的情况下,都都只能通过过+xx(esp)的方式访访问,而很很难区分两两种方式,降降低了程序序的可读性性。 问题题:去除SSFP有什什么优点呢呢? 1)节省栈空空间 2)减少建立立和撤销栈栈框架的指指令后,简简化了代码码 3)使使ebp空空闲出来,使使之作为通通用寄存器器使用,增增加通用寄寄存器的数数量 4)以上3点点使得程序序运行速度度更快 概概念:Caallinng Coonvenntionn 调用用约定和 ABI (Appplicaationn Binnary Inteerfacce) 应应用程序二二进制接口口 函函数如何找找到它的参参数? 函函数如何返返回结果? 函数在在哪里存放放局部变量量? 那一一个硬件寄寄存器是起起始空间? 那一个个硬件寄存存器必须预预先保留? CCalliing CConveentioon 调调用约定对对以上问题题作出了规规定。Caallinng Coonvenntionn也是ABBI的一部部分。 因因此,遵守守相同ABBI规范的的操作系统统,使其相相互间实现现二进制代代码的互操操作成为了了可能。 例如:由由于Sollariss、Linnux都遵遵守Sysstem V的ABBI,Soolariis 100就提供了了直接运行行Linuux二进制制程序的功功能。 详详见文章:关注: Solaaris 10的110大新变变化 3. 小结 本本文通过最最简的C程程序,引入入以下概念念: SFFP 栈框框架指针 Stacck alligneed 栈对对齐 Caallinng Coonvenntionn 调用用约定 和和 ABII (Apppliccatioon Biinaryy Intterfaace) 应用程序序二进制接接口 今后后,将通过过进一步的的实验,来来深入了解解这些概念念。通过掌掌握这些概概念,使在在汇编级调调试程序产产生的coore ddump、掌掌握C语言言高级调试试技巧成为为了可能。X86汇编编语言学习习手记(22)这是作者在在学习X886汇编过过程中的学学习笔记,难难免有错误误和疏漏之之处,欢迎迎指正。作作者将随时时修改错误误并将新的的版本发布布在自己的的Blogg站点上。严严格说来,本本篇文档更更侧重于CC语言和CC编译器方方面的知识识,如果涉涉及到基本本的汇编语语言的内容容,可以参参考相关文文档。 自自X86 汇编语言言学习手记记(1)在在作者的BBlog上上发布以来来,得到了了很多网友友的肯定和和鼓励,并并且还有热热心网友指指出了其中中的错误,b:beea66dddae00作者已已经将文档档中已发现现的错误修修正后更新新在Bloog上。/b:bbea666ddaee0 上上一篇文章章通过分析析一个最简简的C程序序,引出了了以下概念念: Sttack Framme 栈框框架 和 SFP 栈框架指指针 Sttack aliggned 栈对齐 Callling Convventiion 调用约定定 和 AABI (Appllicattion Binaary IInterrfacee) 应用用程序二进进制接口 本章中,将将通过进一一步的实验验,来深入入了解这些些概念。如如果还不了了解这些概概念,可以以参考 XX86汇编编语言学习习手记(11)。 1. 局局部变量的的栈分配 上上篇文章已已经分析过过一个最简简的C程序序, 下面面我们分析析一下C编编译器如何何处理局部部变量的分分配,为此此先给出如如下程序: #vi ttest22.c iint mmain() int i; iint jj=2; i=3; i=+i; retuurn ii+j; 编编译该程序序,产生二二进制文件件,并利用用mdb来来观察程序序运行中的的stacck的状态态: #ggcc ttest22.c -o teest2 #mdbb tesst2 LLoadiing mmodulles: liibc.sso.1 > mainn:diis maain: puushl %eebp mmain+1: mmovl %esp,%ebpp ; mainn至maiin+1,创创建Staack FFramee maiin+3: subbl $8,%espp ; 为局局部变量ii,j分配配栈空间,并并保证栈116字节对对齐 maain+66: anndl $00xf0,%espp maiin+9: movvl $0,%eaxx maiin+0xxe: subbl %eaax,%eesp ; maain+66至maiin+0xxe,再次次保证栈116字节对对齐 mmain+0x100: mmovl $2,-88(%ebbp) ; 初始化局局部变量jj的值为22 maiin+0xx17: movvl $3,-4(%ebp) ; 给局局部变量ii赋值为33 maiin+0xx1e: leaal -4(%ebpp),%eeax ; 将局局部变量ii的地址装装入到EAAX寄存器器中 maain+00x21: inncl (%eax) ; ii+ mmain+0x233: mmovl -8(%eebp),%eaxx ; 将j的值值装入EAAX maain+00x26: adddl -44(%ebbp),%eax ; ii+j并将将结果存入入EAX,作作为返回值值 maiin+0xx29: leaave ; 撤销Sttack Framme mmain+0x2aa: rret ; maain函数数返回 >> > mainn+0x110:b ; 在地地址 maain+00x10处处设置断点点