会飞的鱼的专栏

我心随云,云随我心。集天地之灵,怀浩然之气。

会飞的鱼ID:flyfish30
32698次访问,排名3496(1)好友3人,关注者8
喜欢编写好用且好看的程序
flyfish30的文章
原创 21 篇
翻译 0 篇
转载 10 篇
评论 34 篇
最近评论
lytsing:楼主最后一段深有体会。武功招式的最高境界便是无招胜有招,无剑胜有剑,宣扬的是道家的思想,无人,无我。学过多年的数学,我们都知道,公理,定理难记么?不难,难的是要能是灵活运用。编程也如此,生活,艺术是息息相通的。

dxdclf:谢谢,好东东,正从事MTK相关工作,o(∩_∩)o...哈哈
ygliu_richway:你好,我正在从事mtk开发,有很急的问题想和你交流,谢谢!
我msn:ygliu_richway@live.cn
liusongliang68:你是第一个把道用数学公式表达的人!真牛。
adonis1620:收藏中。
文章分类
    收藏
      相册
      GTYPE类型系统分析用图
      MTK手机软件工程构建文件关系图
      VIM上下文字符串生成替换宏记录
      不变性与可变性用图
      LinuxMobile
      celinuxforum
      GPE
      handhelds
      Motorola linux phone DIY
      opensource.motorola
      tuxmobile
      编译链接技术
      GCC中文手册
      GNU Make 中文手册
      GNU make 指南
      汇编编程技术
      龙芯软件开发
      大坡3D软件开发
      软界高手
      Donald E. Knuth
      Jserv's blog
      友情链接
      absurd
      负暄琐话
      存档
      软件项目交易
      订阅我的博客
      XML聚合  FeedSky
      订阅到鲜果
      订阅到Google
      订阅到抓虾
      订阅到BlogLines
      订阅到Yahoo
      订阅到GouGou
      订阅到飞鸽
      订阅到Rojo
      订阅到newsgator
      订阅到netvibes

      转载 AT&T 汇编参考收藏

      新一篇: Makefile的几则实用技巧 | 旧一篇: ARM汇编的几个问题

       开发一个OS尽管绝大部分代码只需要用C/C++等高级语言就可以了但至少和硬件相关部分的代码需要使用汇编语言另外由于启动部分的代码有大小限制使用精练的汇 编可以缩小目标代码的尺寸另外对于某些需要被经常调用的代码使用汇编可以提高性
       
      能。所以我们必须了解汇编语言,即使你有可能并不喜欢它。
       
      如果你是计算机专业的话,在大学里你应该学习过Intel格式的8086/80386汇编,这 里就不再讨论。如果我们选择的OS开发工具GCC以及GAS的话,就必须了解AT&T 汇编语言语法,因为GCC/GAS只支持这种汇编语法。
       
      本书不会去讨论8086/80386的汇编编程,这类的书籍很多,你可以参考它们。这里只 会讨论AT&T的汇编语法,以及GCC的内嵌汇编语法。
       
       
      1. Syntax
       
       
       
      Register Reference
       
       
       
           引用寄存器要在寄存器号前加百分号%,“movl %eax, %ebx
       
           80386有如下寄存器:
       
           832-bit寄存 %eax%ebx%ecx%edx%edi%esi%ebp%esp
       
           816-bit寄存器,它们事实上是上832-bit寄存器的16位:%ax%bx
       
      %cx%dx%di%si%bp%sp
       
           88-bit寄存%ah%al%bh%bl%ch%cl%dh%dl。它们事实上 是寄存器%ax%bx%cx%dx 的高8位和低8位;
       
           6个段寄存器:%cs(code)%ds(data)%ss(stack),%es%fs%gs
       
           3个控制寄存器:%cr0%cr2%cr3
       
           6debug寄存器:%db0%db1%db2%db3%db6%db7
       
           2个测试寄存器:%tr6%tr7
       
           个浮点寄存器%st(0)%st(1)%st(2)%st(3)%st(4)%st(5)%st(6)
       
      %st(7)

       
       
      Operator Sequence
       
       
       
      操作数排列是从源(左)到目的(右,如“movl%eax(源),%ebx(目的)
       
       
      ImmediatelyOperator
       
       
       
      使用立即数,要在数前面加符号$,  “movl$0x04,%ebx”
       
      或者:
       
       
      para=0x04
       
      movl$para,%ebx
       
      指令执行的结果是将立即数04h装入寄存器ebx
       
       
      Symbol Constant
       
      符号常数直接引用如 value:.long0x12a3f2de movlvalue,%ebx
       
      指令执行的结果是将常数0x12a3f2de装入寄存器ebx
       
      引用符号地址在符号前加符号$,  “movl$value,%ebx则是将符号value的地址装入 寄存器ebx
       
       
      Length of Operator
       
       
       
      操作数的长度用加在指令后的符号表b(byte 8-bit), w(word, 16-bits), l(long,
       
      32-bits),如“movb %al, %bl“movw%ax,%bx“movl%eax,%ebx
       
      如果没有指定操作数长度的话编译器将按照目标操作数的长度来设置比如指令“mov
       
      %ax,%bx由于目标操作数bx的长度为word那么编译器将把此指令等同于“movw %ax,
       
      %bx。同样道理,指令“mov$4,%ebx等同于指令“movl$4,%ebx“push%al等同于
      “pushb%al。对于没有指定操作数长度,但编译器又无法猜测的指令,编译器将会报错,比如指令“push$4

       
       
      Sign and Zero Extension
       
       
      绝大多数面向80386AT&T汇编指令与Intel格式的汇编指令都是相同的符号扩展 指令和零扩展指令则是仅有的不同格式指令。
       
      符号扩展指令和零扩展指令需要指定源操作数长度和目的操作数长度即使在某些指令中这些操作数是隐含的。
       
      AT&T语法中,符号扩展和零扩展指令的格式,基本部分"movs""movz"(对应 Intel语法movsxmovzx后面跟上源操作数长度和目的操作数长度movsbl意味着 movsfrombytetolongmovbw意味着movsfrombytetowordmovswl 意味着movs  fromword  tolong。对movz指令也一样比如指令“movsbl %al,
       
      %edx意味着将al寄存器的内容进行符号扩展后放置到edx寄存器中。
       
      其它的Intel格式的符号扩展指令还有:
       
           cbw--sign-extendbytein%altowordin%ax
       
           cwde--sign-extendwordin%axtolongin%eax
       
           cwd--sign-extendwordin%axtolongin%dx:%ax
       
           cdq--sign-extenddwordin%eaxtoquadin%edx:%eax
       
      对应的AT&T语法的指令为cbtwcwtlcwtdcltd
       
       
      Call and Jump
       
       
      段内调用和跳转指令"call""ret""jmp"段间调用和跳转指令"lcall""lret"
       
      "ljmp"
       
      段间调用和跳转指令的格式为“lcall/ljmp$SECTION,$OFFSET,而段间返回指令则 为“lret$STACK-ADJUST
       
       
      Prefix
       
       
       
      操作码前缀被用在下列的情况:
           字符串重复操作指令(rep,repne)     指定被操作的段(cs,ds,ss,es,fs,gs)      进行总线加锁(lock)
       
           指定地址和操作的大小(data16,addr16)

      AT&T汇编语法中操作码前缀通常被单独放在一行后面不跟任何操作数例如, 对于重复scas指令,其写法为:
       
      repne
       
        scas
       
      上述操作码前缀的意义和用法如下:
       
           指定被操作的段前缀cs,ds,ss,es,fs,gs。在AT&T  语法中,只需要按section:memory-operan的格式就指定了相应的段前缀。如: lcall%cs:realmode_swtch
       
           操作数/地址大小前缀是“data16"addr16"它们被用来在32-bit操作数/地址 代码中指定16-bit的操作数/地址。
       
           总线加锁前缀“lock它是为了在多处理器环境中保证在当前指令执行期间禁止 一切中断。这个前缀仅仅对ADD,ADC,AND,BTC,BTR,BTS,CMPXCHG,DEC, INC,NEG,NOT,OR,SBB,SUB,XOR,XADD,XCHG指令有效,如果将Lock前 缀用在其它指令之前,将会引起异常。
       
           字符串重复操作前缀"rep","repe","repne"用来让字符串操作重复“%ecx次。
       
       
      Memory Reference
       
       
       
      Intel语法的间接内存引用的格式为:
       
      section:[base+index*scale+displacement]
       
      而在AT&T语法中对应的形式为:
       
       
      section:displacement(base,index,scale)
       
      其中baseindex是任意的32-bitbaseindex寄存器scale可以取1248。 如果不指定scale则默认值为1section可以指定任意的段寄存器作为段前缀默认的段寄存器在不同的情况下不一样如果你在指令中指定了默认的段前缀则编译器在目标代 码中不会产生此段前缀代码。
       
       
      下面是一些例子:
       
      -4(%ebp)base=%ebpdisplacement=-4section没有指定,由于base%ebp,所 以默认的section=%ssindex,scale没有指定index0
       
      foo(,%eax,4)index=%eaxscale=4displacement=foo其它域没有指定。这里默认 的section=%ds
       
      foo(,1)这个表达式引用的是指foo指向的地址所存放的值。注意这个表达式中没有

       
      baseindex,并且只有一个逗号,这是一种异常语法,但却合法。
       
      %gs:foo:这个表达式引用的是放置于%gs段里变量foo的值。
       
      如果calljump操作在操作数前指定前缀“*则表示是一个绝对地址调用/跳转也 就是说jmp/call指令指定的是一个绝对地址如果没有指定"*"则操作数是一个相对地址。
       
      任何指令如果其操作数是一个内存操作,则指令必须指定它的操作尺
       
      (byte,word,long,也就是说必须带有指令后(b,w,l)
       
       
      2. GCC Inline ASM
       
       
      GC 支持C/C++代码中嵌入汇编代码,这些汇编代码被称GCC Inline ASM— — GCC  内联汇编。这是一个非常有用的功能,有利于我们将一些C/C++语法无法 表达的指令直接潜入C/C++代码中另外也允许我们直接写C/C++代码中使用汇编编写简 洁高效的代码。
       
       
      2.1 EssentialInlineASM
       
       
       
      GCC中基本的内联汇编非常易懂,我们先来看两个简单的例子:
       
      __asm__("movl%esp,%eax"); //看起来很熟悉吧!
       
      或者是
       
       
      __asm__("
      movl$1,%eax   //SYS_exit xor%ebx,%ebx
       
      int $0x80
      ");
       
       
       
       
       
      __asm__(
       
      "movl$1,%eax\r\t"
      "xor%ebx,%ebx\r\t"
       
      "int$0x80"
       
      );
       
       
       
      基本内联汇编的格式是
       
       
      __asm____volatile__("InstructionList");

       
       
      1.__asm__
       
       
       
      __asm__GCC关键asm的宏定义:
       
      #define__asm__asm
       
      __asm__asm  用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以 它开头的,是必不可少的。
       
       
       
      2.InstructionList
       
       
       
      InstructionList是汇编指令序列。它可以是空的,比如:__asm__ __volatile__("");  
      __asm__("");都是完全合法的内联汇编表达式只不过这两条语句没有什么意义但并非所 有InstructionList为空的内联汇编表达式都是没有意义比如__asm__ ("":::"memory"); 就非常有意义,它向GCC  声明:我对内存作了改动GCC  在编译的时候,会将此因素 考虑进去。
       
      我们看一看下面这个例子:
       
       
      $catexample1.c
       
      int main(int__argc,char*__argv[])
       
      {
       
      int*__p=(int*)__argc;
      (*__p)=9999;
       
       
      //__asm__("":::"memory");
       
       
      if((*__p)==9999)
      return5;
       
       
      return(*__p);
       
      }
       
       
      在这段代码中那条内联汇编是被注释掉的在这条内联汇编之前内存指针__p所指 向的内存被赋值为9999,随即在内联汇编之后,一条if语句判断__p所指向的内存与9999 是否相等。很明显,它们是相等的。GCC  在优化编译的时候能够很聪明的发现这一点。我 们使用下面的命令行对其进行编译:
       
      $gcc-O-Sexample1.c
       
       
      选项-O表示优化编译我们还可以指定优化等级比如-O2表示优化等级为2选项-S
       
      表示将 C/C++源文件编译为汇编文件,文件名和 C/C++文件一样,只不过扩展名由.c  

       
      .s
       
      我们来查看一下被放在example1.s中的编译结果,我们这里仅仅列出了使用gcc 2.96 redhat7.3上编译后的相关函数部分汇编代为了保持清晰性无关的其它代码未被列 出。
      $catexample1.s main:
      push%ebp
       
      movl   %esp,%ebp
      movl   8(%ebp),%eax   #int*__p=(int*)__argc movl    $9999,(%eax)       # (*__p) = 9999
       
      movl   $5,%eax        #return5
      popl   %ebp ret
       
       
      参照一下C源码和编译出的汇编代码,我们会发现汇编代码中,没有if语句相关的代 码而是在赋值语句(*__p)=9999后直接return5这是因为GCC认为在(*__p)被赋值之后, 在if语句之前没有任何改(*__p)内容的操作,所以那条if语句的判断条件(*__p)==9999 肯定是为true  所以GCC就不再生成相关代码而是直接根据true的条件生return
       
      5的汇编代码(GCC使eax作为保存返回值的寄存器)。
       
      我们现在将example1.c中内联汇编的注释去掉,重新编译,然后看一下相关的编译结 果。
       
      $gcc-O-Sexample1.c
      $catexample1.s main:
       
      push%ebp
       
      movl   %esp,%ebp
      movl   8(%ebp),%eax   #int*__p=(int*)__argc movl $9999,(%eax) # (*__p) = 9999
       
      #APP
       
      #__asm__("":::"memory")
       
      #NO_APP
      cmpl   $9999,(%eax)    #(*__p)==9999
       
      jne    .L3              #false
       
      movl   $5,%eax        #true,return5
       
      jmp    .L2
       
      .L3:
      movl   (%eax),%eax
       
      .L2:
      popl   %ebp ret

       
      由于内联汇编语句__asm__("":::"memory")GCC  声明,在此内联汇编语句出现的位 置内存内容可能了改变,所以GCC  在编译时就不能像刚才那样处理。这次,GCC  老老实 实的将if语句生成了汇编代码。
       
      可能有人会质疑为什么要使用__asm__("":::"memory")GCC声明内存发生了变化? 明明“InstructionList是空的,没有任何对内存的操作,这样做只会增GCC生成汇编代 码的数量。
       
      确实那条内联汇编语句没有对内存作任何操作事实上它确实什么都没有做但影响 内存内容的不仅仅是你当前正在运行的程序比如如果你现在正在操作的内存是一块内存 映射映射的内容是外围I/O设备寄存那么操作这块内存的就不仅仅是当前的程序I/O 设备也会去操作这块内存既然两者都会去操作同一块内存那么任何一方在任何时候都不 能对这块内存的内容想当然。所以当你使用高级语言C/C++写这类程序的时候,你必须让 编译器也能够明白这一点,毕竟高级语言最终要被编译为汇编代码。
       
      你可能已经注意到了这次输出的汇编结果中有两个符号#APP#NO_APPGCC
       
      将内联汇编语句"Instruction List"所列出的指令放#APP  #NO_APP  间,
      __asm__("":::"memory")“InstructionList为空所以#APP#NO_APP中间也没有任何 内容。但我们以后的例子会更加清楚的表现这一点。
       
      关于为什么内联汇编__asm__("":::"memory")是一条声明内存改变的语句,我们后面会 详细讨论。
       
      刚才我们花了大量的内容来讨论"InstructionList"为空是的情况,但在实际的编程中,
       
      "InstructionList"