关闭

关闭

封号提示

内容

首页 GNU C中的嵌入式汇编.doc

GNU C中的嵌入式汇编.doc

GNU C中的嵌入式汇编.doc

上传者: 小小霸王仔二号 2014-02-14 评分1 评论0 下载1 收藏0 阅读量588 暂无简介 简介 举报

简介:本文档为《GNU C中的嵌入式汇编doc》,可适用于IT/计算机领域,主题内容包含GNUC中的嵌入式汇编::GNUC中的嵌入式汇编::分类:CC平时在读写代码过程中屡屡会遇到gcc编译环境下的嵌入式汇编问题觉得有必要总结一下。基于符等。

GNUC中的嵌入式汇编::GNUC中的嵌入式汇编::分类:CC平时在读写代码过程中屡屡会遇到gcc编译环境下的嵌入式汇编问题觉得有必要总结一下。基于gcc编译器中汇编嵌入C语言的规则以及一些个人体会现做一下总结以备以后查阅。在接下来的总结中我会根据:)局部变量的类型(堆栈寄存器))嵌入式汇编语言中C变量分配寄存器的方法(手工指定、gcc编译器自动指定)这两种情况的不同组合并结合elf文件的反汇编内容对比C程序来展开讨论。约定:下文中凡是提到局部变量之处指的是变量a和b凡是提到C变量之处指的是嵌入式汇编中的C语言变量。情况:局部变量位于堆栈中编译器自动绑定C变量到对应的寄存器所用到的C代码如程序列表所示:表情况下的C语言代码(mainc)#include"stdioh"intmain(void){registerintaasm("eax")registerintbasm("ebx")inta=intb=printf("a=db=dn",a,b)asmvolatile("nop")asmvolatile("add,nt":"r"(a):"r"(b))asmvolatile("nop")printf("a=db=dn",a,b)return}第行与行代码中嵌入式了两个nop指令其主要目的在于快速定位反汇编代码中对应C代码嵌入式汇编部分方便对比分析除此之外别无他途。局部变量a和b位于堆栈中在嵌入式汇编部分也没有明确为a和b绑定具体的寄存器。将程序列表中的代码编译(gccomainmainc)然后运行得到以下结果:billubuntu:~testasm$maina=b=a=b=然后再将可执行程序main反汇编(objdumpSmain>mainS)得到如程序列表所示的代码:表情况下main的反汇编代码(mainS)<main>:c:pushebpc:movesp,ebpc:and$xfffffff,espca:sub$x,espcd:movl$x,xc(esp)d:d:movl$x,x(esp)dc:dd:mov$xf,eaxe:movx(esp),edxe:movedx,x(esp)ea:movxc(esp),edxee:movedx,x(esp)f:moveax,(esp)f:callf<printfplt>fa:nopfb:movx(esp),edxff:movxc(esp),eax:addedx,eax:moveax,xc(esp):nopa:mov$xf,eaxf:movx(esp),edx:movedx,x(esp):movxc(esp),edxb:movedx,x(esp)f:moveax,(esp):callf<printfplt>:mov$x,eaxc:leaved:ret通过程序列表可以看出在两个nop之间的代码块即为嵌入式汇编部分。C变量a和b为堆栈变量在和行分别被赋值然后在嵌入式汇编部分gcc编译器将a和b值分别赋给寄存器eax和edx也就是所谓的load操作。然后进行加法操作eax既是输入寄存器也是输出寄存器操作的结果回写内存(堆栈)也就是store操作。在这里C语言中的嵌入式汇编部分没有明确指出为C变量a和b分配哪个寄存器gcc编译器自动将其绑定到eax和edx。虽然这里用到了eax和edx破坏了其原有值但是从汇编代码可以看出main函数中从头到尾没有对这两个寄存器进行push和pop操作这是为什么呢?!根据ABI(ApplicationBinaryInterface)规范在I(位)体系结构下eax、edx、ecx这三个寄存器是volatile寄存器可以供callee随便使用而无需为caller保存因此这里可以放心使用而无需备份原有内容。而对于ebx、edi、esi等寄存器则属于非volatile寄存器专属caller使用如果callee要使用则需要对其进行push和pop操作以备份和恢复其原有值。由于程序列表中只有两个局部变量只用了eax、edx两个volatile寄存器因此我们看不出gcc对非volatile寄存器的备份恢复操作针对这种情况下面将局部变量数目增加到个同样还是进行加法操作我们来看看会是什么情况。C代码如程序列表所示:表局部变量增加为个的C语言代码(mainc)#include"stdioh"intmain(void){registerintaasm("eax")registerintbasm("ebx")inta=intb=intc=intd=inte=intf=printf("a=db=dn",a,b)asmvolatile("nop")asmvolatile("add,nt""add,nt""add,nt":"r"(a),"r"(c),"r"(e):"r"(b),"r"(d),"r"(f))asmvolatile("nop")printf("a=db=dn",a,b)return}对程序列表生成的可执行文件main进行反汇编操作得到如程序列表所示的代码。可以看出嵌入式汇编部分新增了对寄存器edi、esi、ebx以及ecx的使用由于前三个寄存器属于非volatile寄存器因此callee要对其进行维护也就是main函数要对其进行压栈和弹栈操作。表局部变量增加为个的main反汇编代码c<main>:c:pushebpc:movesp,ebpc:and$xfffffff,espca:pushedicb:pushesicc:pushebxcd:sub$x,espd:movl$x,xc(esp)d:d:movl$x,x(esp)d:nope:movx(esp),ebx:movx(esp),esi:movx(esp),edia:movxc(esp),ecxe:movx(esp),edx:movxc(esp),eax:addebx,ecx:addesi,edxa:addedi,eaxc:movecx,xc(esp):movedx,x(esp):moveax,xc(esp):nop:mov$x,eaxb:add$x,espe:popebxf:popesi:popedi:movebp,esp:popebp:ret以上属于情况下的实验。下面来实验一下局部变量位于堆栈嵌入式汇编中手工为C变量绑定寄存器的情况。情况:局部变量位于堆栈中手工绑定C变量到对应的寄存器对应的C代码如程序列表所示:表情况下的C语言代码(mainc)#include"stdioh"intmain(void){registerintaasm("eax")registerintbasm("ebx")inta=intb=printf("a=db=dn",a,b)asmvlatile("nop")asmvolatile("add,nt":"a"(a):"c"(b))asmvolatile("nop")printf("a=db=dn",a,b)return}程序里表中将变量a和b分别绑定到寄存器eax和ecx。将程序列表中的代码反汇编得到如程序里表所示的代码:表情况下的反汇编代码c<main>:c:pushebpc:movesp,ebpc:and$xfffffff,espca:sub$x,espcd:movl$x,xc(esp)d:d:movl$x,x(esp)dc:dd:mov$xf,eaxe:movx(esp),edxe:movedx,x(esp)ea:movxc(esp),edxee:movedx,x(esp)f:moveax,(esp)f:callf<printfplt>fa:nopfb:movx(esp),edxff:movxc(esp),eax:movedx,ecx:addecx,eax:moveax,xc(esp)b:nopc:mov$xf,eax:movx(esp),edx:movedx,x(esp):movxc(esp),edxd:movedx,x(esp):moveax,(esp):callf<printfplt>:mov$x,eaxe:leavef:ret可以看出在程序列表中在具体计算中变量a和b的值确实赋给了寄存器eax和ecx对于变量b来说从堆栈取出的过程中其值先赋给了寄存器edx然后再由x赋给寄存器ecx看来是“多此一举”了其实我感觉这是gcc按着固定的套路来编译C代码的结果在对堆栈里面的局部变量进行操作之前gcc先不管下面如何操作固定的将堆栈里面的变量用寄存器序列eax,edx,ecx取出来至于后面如何使用这些变量以及将这些变量赋值给谁那是后话因此这里稍显机械存在优化空间。对于变量的回写过程同样也是机械的通过寄存器序列eax,edx,ecx来实施。在嵌入式汇编部分如果将变量b绑定edx则不存在这里的"多此一举"情况节省一条mov操作指令。同样如果为变量b绑定非volatile寄存器的话例如ebx除了无法节省这条mov操作指令之外反而还会增加一条push和一条pop操作指令用于维护ebx的内容。说倒取数(load)以及回写(store)这里不得不多说两句在取数的过程中也就是load过程gcc选择使用寄存器序列的顺序是eaxedxecx,也就是说如果有个变量要load的话就用eax同样如果有两个变load操作就用eax和edx依次类推如果有三个load操作就用eax、edx、ecx。这时候volatile寄存器序列已经用完如果有个load操作怎么办显然是用非volatile寄存器了这时候在使用之前就要实施push操作使用完之后要实施pop操作。同理对于回写操作也就是store操作按着同样规则使用相同的寄存器序列。在程序列表中虽然变量b绑定的是ecx但是这里仍然按着load两次参数的"死板"规则来取数先使用寄存器edx取b再使用寄存器eax取a值然后再将寄存器edx的值赋给寄存器ecx即手工绑定的寄存器。对于回写过程规则依然死板如果为变量a绑定寄存器ecx的话也就是说ecx要回写这时候gcc的规则现将ecx的值赋给eax然后再将eax的值回写堆栈。为什么要用eax前面已经说过因为这里只有一次store操作所以别无他选老老实实用eax回写吧。总结一下load过程使用寄存器序列的规则如下:)如果进行次load操作使用寄存器eax:“movx(xx)esp,eax”)如果进行次load操作使用寄存器edx,eax:“movx(xx)esp,edx”“movx(xx)esp,eax”)如果进行次load操作使用寄存器ecx,edx,eax:“movx(xx)esp,ecx”“movx(xx)esp,edx”“movx(xx)esp,eax”)如果进行次load操作使用寄存器ebx,ecx,edx,eax:“movx(xx)esp,ebx”“movx(xx)esp,ecx”“movx(xx)esp,edx”“movx(xx)esp,eax”)以下依次类推除了以上寄存器序列还会使用edi,esi等寄存器如果寄存器依然不够使用那么可能会就循环使用以上寄存器具体情况有待验证。在store过程中寄存器的使用规则如下:)如果进行次store操作使用寄存器eax:“moveax,x(xx)esp,”)如果进行次store操作使用寄存器edx,eax:“movedx,x(xx)esp”“moveax,x(xx)esp”)如果进行次store操作使用寄存器ecx,edx,eax:“movecx,x(xx)esp”“movedx,x(xx)esp”“moveax,x(xx)esp”)如果进行次store操作使用寄存器ebx,ecx,edx,eax:“movebx,x(xx)esp”“movecx,x(xx)esp”“movedx,x(xx)esp”“moveax,x(xx)esp”)以下依次类推出了以上寄存器序列还会使用edi,esi等寄存器如果寄存器依然不够使用那么可能会就循环使用以上寄存器具体情况有待验证。通过以上情况不难发现在使用堆栈局部变量的情况下对于嵌入式汇编里面的寄存其绑定问题还是交由gcc启动完成比较好可以获得较好的程序性能免得额外指令的浪费而且频繁的、不必要的访存操作是不被鼓励的(push,pop)。既然gcc的load和store操作有着自己的规则那么我们就少些不必要的干预让gcc获得更大的自主空间按着自己的规则进行优化。如果实在要干预的话理解以上规则对于程序的优化是有好处的。当然这些是我自己目前的初步总结更加详细准确的规则也许gccmannual里面已经有了不过我自己总结得来的认识更加深刻虽然有待完善。情况:局部变量位于寄存器中自动绑定C变量到寄存器C语言代码如程序列表所示:表情况下的C语言代码:#include"stdioh"intmain(void){registerintaasm("eax")=registerintbasm("ebx")=printf("a=db=dn",a,b)asmvolatile("add,nt":"r"(a):"r"(b))asmvolatile("nop")printf("a=db=dn",a,b)return}这里将c变量a和b分别位于寄存器eax和ebx中C代码中为这两个变量赋值。在嵌入式汇编部分让gcc自动为这两个变量绑定寄存器。将程序列表中的代码编译后执行得到如下所示结果:billubuntu:~testasm$maina=b=a=b=非常抱歉这个结果比较意外程序列表中明明执行的是ab操作结果存放于a中按道理来说a应该等于才是正如情况一的执行结果那样。可是这里偏偏却不是这样子。为什么???先对main程序进行反汇编我们来一看究竟反汇编结果如程序列表所示:表情况下的反汇编结果:c<main>:c:pushebpc:movesp,ebpc:and$xfffffff,espca:pushebxcb:sub$xc,espce:mov$x,eaxd:mov$x,ebxd:movebx,ecxda:mov$xe,edxdf:movecx,x(esp)e:moveax,x(esp)e:movedx,(esp)ea:callf<printfplt>ef:nopf:addebx,eaxf:nopf:movebx,edxf:mov$xe,ecxfa:movedx,x(esp)fe:moveax,x(esp):movecx,(esp):callf<printfplt>a:mov$x,eaxf:add$xc,esp:popebx:movebp,esp:popebp:ret从程序列表中我们明显可以看出局部变量的绑定成功了a绑定eaxb绑定ecx,两个nop指令之间的计算指令也是对的addebxeax结果保存于eax由于在嵌入式汇编部分的绑定采用的是自动方式因此gcc编译将计就计保持原有寄存器绑定不变可是打印出来的结果却偏偏不正确。在为a、b赋值和执行二者之间的加法操作的中间还有变故吗?有!确定有仔细看我们在执行加法指令之前曾经调用了一次printf函数总所周知eax充当默认的函数返回值寄存器也就是在调用了函数printf之后寄存器eax原有的值()已经被破坏掉了用作存放函数printf的返回值了所以当程序真正执行add指令时eax的值已经不再是因此得到的结果也不会是所以就有了刚才的错误执行结果。为此我们在c代码中稍作改动为了维持eax原有的值我们在第一次执行完printf操作之后对a再进行依次赋值操作。如程序列表所示:表修正后的C代码#include"stdioh"intmain(void){registerintaasm("eax")=registerintbasm("ebx")=printf("a=db=dn",a,b)a=asmvolatile("nop")asmvolatile("addl,nt":"r"(a):"r"(b))asmvolatile("nop")printf("a=db=dn",a,b)return}编译后得到的执行结果如下:billubuntu:~testasm$maina=b=a=b=结果正确恢复了正常我们在对其进行反汇编得到的代码如列表所示:表修正后的反汇编代码c<main>:c:pushebpc:movesp,ebpc:and$xfffffff,espca:pushebxcb:sub$xc,espce:mov$x,eaxd:mov$x,ebxd:movebx,ecxda:mov$xe,edxdf:movecx,x(esp)e:moveax,x(esp)e:movedx,(esp)ea:callf<printfplt>ef:mov$x,eaxf:nopf:addebx,eaxf:nopf:movebx,edxfa:mov$xe,ecxff:movedx,x(esp):moveax,x(esp):movecx,(esp)a:callf<printfplt>f:mov$x,eax:add$xc,esp:popebx:movebp,espa:popebpb:ret由程序列表可以见在第一次调用printf函数之后由对a绑定的寄存器重新赋值使得add执行结果正确。说道这里就不得不再提提关于volatile寄存器的问题。前面说过eax,edx,ecx属于volatile寄存器callee可以随意使用而无需维护其内容那么如果这三个寄存器里面有值需要维护怎么办呢显然由caller负责维护对于main函数本省来讲它属于callee可以随意自由使用这三个volatile寄存器但是相对printf函数来说main属于caller如过eaxedxecx有需要维护的值那么这个维护工作必须交由caller也就是main来完成。而程序列表中代码并没有维护eax寄存器的值因此产生错误。需要注意的是对于非volatile寄存器ebxgcc则自动添加了维护代码(push、pop)。在实验过程中还发现如果为局部变量绑定寄存器edx,ecx结果均为出现错误。而相反如果为局部变量绑定寄存器edi,esi,ebx则不会出现错误纠其原因还在于gcc会自动维护edi,esi,ebx这三个非volatile的值而且在做函数调用的时候也不会轻易使用这三个非volatile寄存器进行store操作往堆栈传递参数而是更多的使用eaxedxecx除非进行store操作的次数比较多才会使用非volatile寄存器edi,esi,ebx等在使用之前当然要push另外如果局部变量绑定了非volatile寄存器中那么在store的过程中就会避免使用这些已经被局部变量绑定的寄存器即使store过程中寄存器不够用而对于volatile寄存器则大不同既是前面已经绑定了局部变量但是在做store操作的时候照用不误因为他们是sotre和load操作的首选寄存器。这些寄存器是volatile属性“不靠谱”需要多加小心使用自己注意维护。因此对于volatile(eax,edx,ecx)和非volatile(edi,esi,ebx)寄存器总结如下:)volatile寄存器是进行load和store操作的首先寄存器也是函数调用的返回值寄存器如果前面将这些寄存器绑定了某个局部变量那这个变量在进行load或者store的过程中会被破坏掉在函数调用过程中也会被破坏掉因此需要注意维护。)非volatile寄存器只有在volatile寄存器不够用的情况下才在load、store操作中派出用场当然还是由gcc自动维护其值(push,pop)。如果前面将这些寄存器绑定了帮个变量那么在load和store的过程中不会再有已经绑定局部变量的寄存器出现。总之对于非volatile寄存器的使用采取的是比较“客气”的态度能不用则尽量不用gcc会在函数的开头和结尾进行push和pop操作维护其原有内容在使用的过程中不会破坏其中的既定值。而对voaltile寄存器的使用则要粗暴的多而且会尽量频繁的使用gcc不会在函数的开头和结尾进行push和pop操作也不会在使用过程中顾及其值是否被破坏。情况:局部变量位于寄存器中手工绑定C变量到寄存器这里将局部变量绑定某一寄存器而嵌入式汇编部分将对应C变量手工绑定到某个寄存器。代码如程序列表所示:表情况下的C代码:#include"stdioh"intmain(void){registerintaasm("edi")=registerintbasm("esi")=printf("a=db=dn",a,b)a=asmvolatile("nop")asmvolatile("addl,nt":"a"(a):"b"(b))asmvolatile("nop")printf("a=db=dn",a,b)return}鉴于情况的结果这里将局部变量绑定非volatile寄存器我们将焦点集中于嵌入式汇编中手工绑定C变量到寄存器的情况。程序编译运行结果正确如情况一样结果。通过实验发现在程序列表中的地、行无论如何制定寄存器其结构均正确因为两个nop之间的嵌入式汇编不会再出现其他代码使用voaltile寄存器的情况因此寄存器值的一致性可以得到保证。如果采用自动绑定形式gcc会维持原有的绑定如果手工绑定到具体寄存器gcc也会照搬只不过这样会增加一些指令用于在先前绑定(局部变量绑定)寄存器和后来绑定寄存器(嵌入式汇编中C变量绑定)之间传递变量值。程序列表中的代码反汇编结果如表所示:c<main>:c:pushebpc:movesp,ebpc:and$xfffffff,espca:pushedicb:pushesicc:pushebxcd:sub$x,espd:mov$x,edid:mov$x,esida:movesi,ecxdc:movedi,edxde:mov$xf,eaxe:movecx,x(esp)e:movedx,x(esp)eb:moveax,(esp)ee:callf<printfplt>f:nopf:movedi,eaxf:movesi,ebxf:addebx,eaxfa:moveax,edifc:nopfd:movesi,edxff:movedi,eax:mov$xf,ecx:movedx,x(esp)a:moveax,x(esp)e:movecx,(esp):callf<printfplt>:mov$x,eaxb:add$x,espe:popebxf:popesi:popedi:movebp,esp:popebp:ret由程序列表可以看出虽然在嵌入式汇编中重新对变量进行了寄存器绑定但是最终的计算结构还是要传回先前绑定的寄存器中edi。至此针对本文中示例代码的嵌入式汇编讨论告一段落。这里主要针对voaltile和非volatile两大类寄存器集在具体编译过程中的使用规则而展开讨论。由于讨论是限定在具体的程序代码之中难以顾及方方面面因此会存在这样或者那样的局限性仅仅是个人的一些浅显总结今后随着实验的深入而对本文进行扩充。如有错误之处还请指导交流。

类似资料

编辑推荐

陕西古代史.pdf

人类与大地母亲.pdf

趣味博弈学.pdf

技术型无形资产评估研究.pdf

从结构到解构:法国20世纪思想主潮·下卷 (法)弗朗索瓦·多斯着.pdf

职业精品

精彩专题

结婚彩礼真有那么重要吗?

原创于西周而后沿袭至今的彩礼,虽然被一部分家长奉为圭臬,但越来越多的年轻人对结婚必须要彩礼不以为然。彩礼引发的社会矛盾越来越受到关注,结婚是自己的事,如人饮水冷暖自知,至于要不要彩礼或者要多少彩礼,因人而异,因财力而已,不可一概而论。

用户评论

0/200
    暂无评论
上传我的资料

精选资料

热门资料排行换一换

  • 机械设计发展史.doc

  • 人教版一年级写字教案部编本【全册…

  • LDPC编码的最优化HARQ方案…

  • 河洛文化与阎连科小说.doc

  • 关于管理学团队沟通的课堂小游戏,…

  • 华中科技大学非全日制研究生公共政…

  • 《变形铝合金热处理规范》 - 中…

  • 美国房地产发展史.doc

  • 临床药师培训之药历&#40;抗感…

  • 资料评价:

    / 15
    所需积分:1 立即下载

    意见
    反馈

    返回
    顶部