Ted's Blog

Happy coding

C语言中插入汇编语言

网上的搜集,不一定可用

#include   "stdio.h"  
   
  void   main()  
  {  
      int   a,b,c;  
      a=3;  
      b=4;  
      __asm  
      {  
              mov   eax,a  
              mov   ebx,b  
              add   eax,ebx  
              mov   c,eax  
      }  
      printf("c   is   %d\n", c);  
}

GCC中插入汇编语言
int   main(int   argc,char   **argv)  
  {  
      int   a=10,b=20;  
      int   c=0;  
          asm("movl   %1,%%eax;\  
                    addl   %2,%%eax;\  
                    movl   %%eax,%0;"  
                  :"=m"(c)  
                  :"r"(a),"r"(b)  
                  :"%eax");  
      printf("a+b=%d",c);  
  return   1;  
  }  
 
  complier   this   one   :  
    gcc     add.c   -o   add
   
   
这两个都正确,前者是vc里的嵌入汇编,功能较弱  
的汇编。  
后者是gcc的嵌入汇编,可以实现32位汇编,功能很强大。  
后一个程序是求c=a+b   的值。  
那段嵌入代码会扩展成与以下代码等价的东西:  
movl   $a,%eax  
addl   $b,%eax  
movl   %eax,$c  
你可以使用下面的编译指令生成汇编文件add.s  
gcc   -S   add.cTop


 开dos控制台部分汇编实现。  
#include   <windows.h>  
  #include   <winbase.h>  
  void   main()  
  {  
  LoadLibrary("msvcrt.dll");  
  __asm   {  
  mov   esp,ebp   ;把ebp的内容赋值给esp  
  push   ebp   ;保存ebp,esp-4  
  mov   ebp,esp   ;给ebp赋新值,将作为局部变量的基指针  
  xor   edi,edi   ;  
  push   edi   ;压入0,esp-4,;作用是构造字符串的结尾\0字符。    
  sub   esp,08h   ;加上上面,一共有12个字节,;用来放"command.com"。    
  mov   byte   ptr   [ebp-0ch],63h   ;  
  mov   byte   ptr   [ebp-0bh],6fh   ;  
  mov   byte   ptr   [ebp-0ah],6dh   ;  
  mov   byte   ptr   [ebp-09h],6Dh   ;  
  mov   byte   ptr   [ebp-08h],61h   ;  
  mov   byte   ptr   [ebp-07h],6eh   ;  
  mov   byte   ptr   [ebp-06h],64h   ;  
  mov   byte   ptr   [ebp-05h],2Eh   ;  
  mov   byte   ptr   [ebp-04h],63h   ;  
  mov   byte   ptr   [ebp-03h],6fh   ;  
  mov   byte   ptr   [ebp-02h],6dh   ;生成串"command.com".  
  lea   eax,[ebp-0ch]   ;  
  push   eax   ;串地址作为参数入栈  
  mov   eax,   0x7801AFC3   ;  
  call   eax   ;调用system  
  }  
  }  
  直接编译看看吧。给你一个上来就可以玩转的源码PP嘻嘻,注释加的多好啊

AT&T汇编伪指令

  最近一直在看OS方面的东西,其中要用到许多AT&T汇编,下面是在网上打到的关于它的伪指令方面的中英文对照版本。英文版本也可以输入如下命令来查看:
    info as   
    read the "Pseudo Ops" node

7 Assembler Directives
All assembler directives have names that begin with a period (‘.’). The rest
of the name is letters, usually in lower case.
This chapter discusses directives that are available regardless of the target
machine configuration for the gnu assembler. Some machine configurations provi
de additional directives. See Chapter 8 [Machine Dependencies], page 61.

7 汇编器命令
所有的汇编器命令名都由句号('.')开头。命令名的其余是字母,通常使用小写。
本章讨论可用命令,不理会gun汇编器针对目标机器配置。某些机器的配置提供附加的命令
。见第8章[机器相关性],第61页。

7.1 .abort
This directive stops the assembly immediately. It is for compatibility with ot
her assemblers. The original idea was that the assembly language source would
be piped into the assembler. If the sender of the source quit, it could use th
is directive tells as to quit also. One day .abort will not be supported.

7.1 .abort
本命令立即终止汇编过程。这是为了兼容其它的汇编器。早期的想法是汇编语言的源码会
被输送进汇编器。如果发送源码的程序要退出,它可以使用本命令通知as退出。将来可能
不再支持使用.abort


7.2 .ABORT
When producing COFF output, as accepts this directive as a synonym for ‘.abor
t’.
When producing b.out output, as accepts this directive, but ignores it.

7.2 .ABORT
当生成COFF输出时,汇编器把这条命令作为.abort接受。
当产成b.out输出时,汇编器允许使用这条命令,但忽略它。


7.3 .align abs-expr, abs-expr, abs-expr
Pad the location counter (in the current subsection) to a particular storage b
oundary. The first expression (which must be absolute) is the alignment requir
ed, as described below.
The second expression (also absolute) gives the fill value to be stored in the
padding bytes. It (and the comma) may be omitted. If it is omitted, the paddi
ng bytes are normally zero. However, on some systems, if the section is marked
as containing code and the fill value is omitted, the space is filled with no
-op instructions.
The third expression is also absolute, and is also optional. If it is present,
it is the maximum number of bytes that should be skipped by this alignment di
rective. If doing the alignment would require skipping more bytes than the spe
cified maximum, then the alignment is not done at all. You can omit the fill v
alue (the second argument) entirely by simply using two commas after the requi
red alignment; this can be useful if you want the alignment to be filled with
no-op instructions when appropriate.
The way the required alignment is specified varies from system to system. For
the a29k, hppa, m68k, m88k, w65, sparc, and Hitachi SH, and i386 using ELF for
mat, the first expression is the alignment request in bytes. For example ‘.al
ign 8’ advances the location counter until it is a multiple of 8. If the loca
tion counter is already a multiple of 8, no change is needed.
For other systems, including the i386 using a.out format, and the arm and stro
ngarm, it is the number of low-order zero bits the location counter must have
after advancement. For example ‘.align 3’ advances the location counter unti
l it a multiple of 8. If the location counter is already a multiple of 8, no c
hange is needed.
This inconsistency is due to the different behaviors of the various native ass
emblers for these systems which GAS must emulate. GAS also provides .balign an
d .p2align directives, described later, which have a consistent behavior acros
s all architectures (but are specific to GAS).


7.3 .align abs-expr, abs-expr, abs-expr
增加位置计数器(在当前的子段)使它指向规定的存储边界。第一个表达式参数(结果必须是
纯粹的数字)是必需参数:边界基准,见后面的描述。
第二个表达式参数(结果必须是纯粹的数字)给出填充字节的值,用这个值填充位置计数器
越过的地方。这个参数(和逗点)可以省略,如果省略它,填充字节的值通常是0。但在某些
系统上,如果本段标识为包含代码,而填充值被省略,则使用no-op指令填充这个空间。

第3个参数表达式的结果也必须是纯粹的数字,这个参数是可选的。如果存在第3个参数,
它代表本对齐命令允许越过字节数的最大值。如果完成这个对齐需要跳过的字节比指定的
最大值还多,则根本无法完成对齐。您可以在边界基准后简单地使用两个逗号,以省略填充
值参数(第二参数);如果您想在适当的时候,对齐操作自动使用no-op指令填充,这个方法
将非常奏效。
边界基准的定义因系统而有差异。a29k,hppa,m68k,m88k,w65,sparc,Hitachi SH,
和使用ELF格式的i386,第一个表达式是边界基准,单位是字节。例如‘.align 8’向后移
动位置计数器至8的倍数。如果地址已经是8的倍数,则无需移动。
有些其它系统,包括使用a.out格式的i386,ARM和strongarm,这代表位置计数器移动后,
计数器中连续为0的低序位数量。例如‘.align 3’向后移动位置计数器直至8的倍数(计
数器的最低的3位为0)。如果地址已经是8倍数,则无需移动。
之所以存在这样的区别,是因为GAS需要模仿各种汇编器的不同动作。GAS还提供.balign和
.p2align命令,在以后详细讲述,这两条命令在所有的机型上使用相同的动作 (但需要向
GAS明确说明机型)。


7.4 .ascii "string". . .
.ascii expects zero or more string literals (see Section 3.6.1.1 [Strings], pa
ge 19) separated by commas. It assembles each string (with no automatic traili
ng zero byte) into consecutive addresses.

7.4 .ascii "string"...
.ascii可不带参数或者带多个由逗点分开的字符串(见3.6.1.1节[Strings],第19页)。它把
汇编好的每个字符串(在字符串末不自动追加零字节)存入连续的地址。


7.5 .asciz "string". . .
.asciz is just like .ascii, but each string is followed by a zero byte. The “
z” in ‘.asciz’ stands for “zero”.

7.5 .asciz "string"...
.asciz类似与.ascii,但在每个字符串末自动追加一个零字节。‘.asciz’中的‘z’代表
“zero”。


7.6 .balign[wl] abs-expr, abs-expr, abs-expr
Pad the location counter (in the current subsection) to a particular storage b
oundary. The first expression (which must be absolute) is the alignment reques
t in bytes. For example ‘.balign 8’ advances the location counter until it i
s a multiple of 8. If the location counter is already a multiple of 8, no chan
ge is needed.
The second expression (also absolute) gives the fill value to be stored in the
padding bytes. It (and the comma) may be omitted. If it is omitted, the paddi
ng bytes are normally zero. However, on some systems, if the section is marked
as containing code and the fill value is omitted, the space is filled with no
-op instructions.
The third expression is also absolute, and is also optional. If it is present,
it is the maximum number of bytes that should be skipped by this alignment di
rective. If doing the alignment would require skipping more bytes than the spe
cified maximum, then the alignment is not done at all. You can omit the fill v
alue (the second argument) entirely by simply using two commas after the requi
red alignment; this can be useful if you want the alignment to be filled with
no-op instructions when appropriate.
The .balignw and .balignl directives are variants of the .balign directive. Th
e .balignw directive treats the fill pattern as a two byte word value. The .ba
lignl directives treats the fill pattern as a four byte longword value. For ex
ample, .balignw 4,0x368d will align to a multiple of 4. If it skips two bytes,
they will be filled in with the value 0x368d (the exact placement of the byte
s depends upon the endianness of the processor). If it skips 1 or 3 bytes, the
fill value is undefined.

7.6
.balign[wl] abs-expr, abs-expr, abs-expr
增加位置计数器(在当前子段)使它指向规定的存储边界。第一个表达式参数(结果必须是纯
粹的数字)是必需参数:边界基准,单位为字节。例如,‘.balign 8’向后移动位置计数器
直至计数器的值等于8的倍数。如果位置计数器已经是8的倍数,则无需移动。
第2个表达式参数(结果必须是纯粹的数字)给出填充字节的值,用这个值填充位置计数器越
过的地方。第2个参数(和逗点)可以省略。如果省略它,填充字节的值通常是0。但在某些
系统上,如果本段标识为包含代码,而填充值被省略,则使用no-op指令填充空白区。
第3个参数的结果也必须是纯粹的数字,这个参数是可选的。如果存在第3个参数,它代表
本对齐命令允许跳过字节数的最大值。如果完成这个对齐需要跳过的字节数比规定的最大
值还多,则根本无法完成对齐。您可以在边界基准参数后简单地使用两个逗号,以省略填充
值参数(第二参数);如果您在想在适当的时候,对齐操作自动使用no-op指令填充,本方法
将非常奏效。

.balignw和.balignl是.balign命令的变化形式。.balignw使用2个字节来填充空白区。.b
alignl使用4字节来填充。例如,.balignw 4,0x368d将地址对齐到4的倍数,如果它跳过2个
字节,GAS将使用0x368d填充这2个字节(字节的确切存放位置视处理器的存储方式而定)。
如果它跳过1或3个字节,则填充值不明确。


7.7 .byte expressions
.byte expects zero or more expressions, separated by commas. Each expression i
s assembled into the next byte.

7.7.byte expressions
.byte可不带参数或者带多个表达式参数,表达式之间由逗点分隔。每个表达式参数都被汇
编成下一个字节。


7.8 .comm symbol , length
.comm declares a common symbol named symbol. When linking, a common symbol in
one object file may be merged with a defined or common symbol of the same name
in another object file. If ld does not see a definition for the symbol–just
one or more common symbols–then it will allocate length bytes of uninitialize
d memory. length must be an absolute expression. If ld sees multiple common sy
mbols with the same name, and they do not all have the same size, it will allo
cate space using the largest size.
When using ELF, the .comm directive takes an optional third argument. This is
the desired alignment of the symbol, specified as a byte boundary (for example
, an alignment of 16 means that the least significant 4 bits of the address sh
ould be zero). The alignment must be an absolute expression, and it must be a
power of two. If ld allocates uninitialized memory for the common symbol, it w
ill use the alignment when placing the symbol. If no alignment is specified, a
s will set the alignment to the largest power of two less than or equal to the
size of the symbol, up to a maximum of 16.
The syntax for .comm differs slightly on the HPPA. The syntax is ‘symbol .com
m, length’;symbol is optional.

7.8 .comm symbol , length
.comm声明一个符号名为symbol的通用符号(common symbol)。当连接时,目标文件中的通
用符号可能被并入其它目标文件中已定义的符号,或者被并入其他目标文件中同名通用符
号。如果ld无法找到该符号的定义——只有一个或多个通用符号——则分配length个字节
的未初始化内存。Length必须是一个纯粹的表达式。如果ld发现多个同名的通用符号,并
且它们的长度不同,ld将按照它们之中最大的length值为符号分配内存。
当使用ELF格式时,.comm可以使用第3个参数。它代表符号需要对齐的边界基准(例如,边界
基准为16时意味着符号存放地址的最低4位应该是零)。第3个参数表达式结果必须是纯粹的
数字,而且一定是2的幂。当ld为通用符号分配未初始化内存时,在存放符号时要使用到这
个参数。如果没有规定边界基准,as将把边界基准设置成以2为底的该符号长度的对数,并
向下取整。最大值为16。
.comm的语法在HPPA上稍微有些不同。语法是‘symbol .comm, length’;其中参数symbol
是可选的。


7.9 .data subsection
.data tells as to assemble the following statements onto the end of the data s
ubsection numbered subsection (which is an absolute expression). If subsection
is omitted, it defaults to zero.

7.9 .data subsection
.data通知as汇编后续语句,将它们追加在编号为subsection(subsection必须是纯粹的表
达式)数据段末。如果参数subsection省略,则默认是0。


7.10 .def name
Begin defining debugging information for a symbol name; the definition extends
until the .endef directive is encountered. This directive is only observed wh
en as is configured for COFF format output; when producing b.out, ‘.def’ is
recognized, but ignored.

7.10 .def name
开始定义符号'name'的调试信息;定义区延伸至遇到.endef命令。本命令只在as被配置成C
OFF格式输出时才使用;当输出为b.out格式时,可以使用‘.def’命令,但被忽略。

7.11 .desc symbol, abs-expression
This directive sets the descriptor of the symbol (see Section 5.5 [Symbol Attr
ibutes],page 30) to the low 16 bits of an absolute expression.
The ‘.desc’ directive is not available when as is configured for COFF output
; it is only for a.out or b.out object format. For the sake of compatibility,
as accepts it, but produces no output, when configured for COFF.

7.11 .desc symbol, abs-expression
本命令用一个纯粹表达式的低16位的值设置符号symbol的描述符(见5.5[符号属性],第30页
)。当as被配置成COFF输出时,‘.desc’命令无效;它只适用于a.out或b.out目标格式。为
兼容起见,当配置为COFF时,as接受此命令,但不产生输出。


7.12 .dim
This directive is generated by compilers to include auxiliary debugging inform
ation in the symbol table. It is only permitted inside .def/.endef pairs.
‘.dim’ is only meaningful when generating COFF format output; when as is gen
erating b.out, it accepts this directive but ignores it.

7.12 .dim
这条命令由编译器生成的,以便在符号表中加入辅助调试信息。只可以在.def/.endef对之
间使用此命令。
'.dim'仅仅在生成COFF格式输出时是有意义的;当生成b.out时,as接受这条命令,但忽略它



7.13 .double flonums
.double expects zero or more flonums, separated by commas. It assembles floati
ng point numbers. The exact kind of floating point numbers emitted depends on
how as is configured. See Chapter 8 [Machine Dependencies], page 61.

7.13 .double flonums
.double后跟着零个或由逗点分开多个的浮点数。本指令汇编出浮点数字。生成的浮点数的
确切类型视as的配置而定。见第8章[机器相关性],第61页。


7.14 .eject
Force a page break at this point, when generating assembly listings.

7.14 .eject
当生成汇编清单时,强制清单页在此点中断。


7.15 .else
.else is part of the as support for conditional assembly; see Section 7.35 [.i
f], page 43. It marks the beginning of a section of code to be assembled if th
e condition for the preceding
.if was false.

7.15 .else
.else 是支持as进行的条件汇编指令之一;见7.35[.if],第43页。如果前面.if命令的条件
不成立,则表示需要汇编.else后的一段代码。


7.16 .elseif
.elseif is part of the as support for conditional assembly; see Section 7.35 [
.if],page 43. It is shorthand for beginning a new .if block that would otherwi
se fill the entire .else section.

7.16 .elseif
.elseif 是支持as进行的条件汇编指令之一。见7.35节 [.if],第43页。它可以在.esle段
中快速产生一个新的.if块。


7.17 .end
.end marks the end of the assembly file. as does not process anything in the f
ile past the .end directive.

7.17 .end
.end标记着汇编文件的结束。as不处理.end命令后的任何语句。


7.18 .endef
This directive flags the end of a symbol definition begun with .def.
‘.endef’ is only meaningful when generating COFF format output; if as is con
figured to generate b.out, it accepts this directive but ignores it.

7.18 .endef
这条命令标志着从.def开始的符号定义结束。
‘.endef’命令仅仅在生成COFF格式的输出有意义;如果as被配置为生成b.out输出,虽然a
s接受这条命令,但忽略它。


7.19 .endfunc
.endfunc marks the end of a function specified with .func.

7.19 .endfunc
.endfunc标志着一个由.func命令定义的函数的结束。


7.20 .endif
.endif is part of the as support for conditional assembly; it marks the end of
a block of code that is only assembled conditionally. See Section 7.35 [.if],
page 43.

7.20 .endif
.endif是支持as进行的条件汇编的指令之一.它标志着条件汇编代码块的结束。见7.35节[
.if],第43页。


7.21 .equ symbol, expression
This directive sets the value of symbol to expression. It is synonymous with ‘
.set’; see Section 7.68 [.set], page 53.
The syntax for equ on the HPPA is ‘symbol .equ expression’.

7.21 .equ symbol, expression
本命令把符号symbol值设置为expression。它等同与'.set'命令。见7.68[.set],第53页。

在HPPA上的equ语法是‘symbol .equ expression’。


7.22 .equiv symbol, expression
The .equiv directive is like .equ and .set, except that the assembler will sig
nal an error if symbol is already defined.
Except for the contents of the error message, this is roughly equivalent to

.ifdef SYM
.err
.endif
.equ SYM,VAL

7.22 .equiv symbol, expression
.equiv 类似与.equ & .set命令, 不同之处在于,如果符号已经定义过,as会发出错误信
号。
除了错误信息的内容之外,它大体上等价与:
.ifdef SYM
.err
.endif
.equ SYM,VAL

7.23 .err
If as assembles a .err directive, it will print an error message and, unless t
he -Z option was used, it will not generate an object file. This can be used t
o signal error an conditionally compiled code.

7.23 .err
如果as汇编一条.err命令, 将打印一条错误信息,除非使用了-Z 选项, as不会生成目标文
件。 可以在条件编译代码中使用它来发出错误信息。


7.24 .exitm
Exit early from the current macro definition. See Section 7.50 [Macro], page 4
7.

7.24 .exitm
从当前宏定义体中提前退出。见7.50 [Macro],第47页。

7.25 .extern
.extern is accepted in the source program—for compatibility with other assemb
lers—but it is ignored. as treats all undefined symbols as external.

7.25 .extern
.extern可以在源程序中使用--以便兼容其他的汇编器—但会被忽略。as将所有未定义的符
号都当作外部符号处理。


7.26 .fail expression
Generates an error or a warning. If the value of the expression is 500 or more
, as will print a warning message. If the value is less than 500, as will prin
t an error message. The message will include the value of expression. This can
occasionally be useful inside complex nested macros or conditional assembly.


7.26 .fail expression
生成一个错误(error)或警告(warning)。如果expression的值大于或等于500,as会打印一
条“警告”消息。如果expression的值小于500,as会打印一条“错误”消息。消息中包含
了expression的值。这在复杂的宏嵌套或条件汇编时偶尔用到。


7.27 .file string
.file tells as that we are about to start a new logical file. string is the ne
w file name.
In general, the filename is recognized whether or not it is surrounded by quot
es ‘"’; but if you wish to specify an empty file name, you must give the quo
tes–"". This statement may go away in future: it is only recognized to be com
patible with old as programs. In some configurations of as, .file has already
been removed to avoid conflicts with other assemblers. See Chapter 8 [Machine
Dependencies], page 61.

7.27 .file string
.file 通告as我们准备开启一个新的逻辑文件。 string 是新文件名。总的来说,文件名
是否使用引号‘"’都可以;但如果您希望规定一个空文件名时,必须使用引号-""。本语
句将来可能不再使用—允许使用它只是为了与旧版本的as程序兼容。在as的一些配置中,
已经删除了.file以避免与其它的汇编器冲突。见第8章 [Machine Dependencies], 第61页



7.28 .fill repeat , size , value
repeat, size and value are absolute expressions. This emits repeat copies of s
ize bytes. Repeat may be zero or more. Size may be zero or more, but if it is
more than 8, then it is deemed to have the value 8, compatible with other peop
le’s assemblers. The contents of each repeat bytes are taken from an 8-byte n
umber. The highest order 4 bytes are zero. The lowest order 4 bytes are value
rendered in the byte-order of an integer on the computer as is assembling for.
Each size bytes in a repetition is taken from the lowest order size bytes of
this number. Again, this bizarre behavior is compatible with other people’s a
ssemblers.
size and value are optional. If the second comma and value are absent, value i
s assumed zero. If the first comma and following tokens are absent, size is as
sumed to be 1.

7.28 .fill repeat , size , value
repeat, size 和value都必须是纯粹的表达式。本命令生成size个字节的repeat个副本。
Repeat可以是0或更大的值。Size 可以是0或更大的值, 但即使size大于8,也被视作8,以
兼容其它的汇编器。各个副本中的内容取自一个8字节长的数。最高4个字节为零,最低的
4个字节是value,它以as正在汇编的目标计算机的整数字节顺序排列。每个副本中的size
个字节都取值于这个数最低的size个字节。再次说明,这个古怪的动作只是为了兼容其他
的汇编器。
size参数和value参数是可选的。如果不存在第2个逗号和value参数,则假定value为零。
如果不存在第1个逗号和其后的参数,则假定size为1。


7.29 .float flonums
This directive assembles zero or more flonums, separated by commas. It has the
same effect as .single. The exact kind of floating point numbers emitted depe
nds on how as is configured. See Chapter 8 [Machine Dependencies], page 61.


7.29 .float flonums
本命令汇编0个或多个浮点数,浮点数之间由逗号分隔。它和.single的汇编效果相同。生
成的浮点数的确切类型视as的配置而定。见第8章 [Machine Dependencies], 61页。


7.30 .func name[,label]
.func emits debugging information to denote function name, and is ignored unle
ss the file is assembled with debugging enabled. Only ‘--gstabs’ is currentl
y supported. Label is the entry point of the function and if omitted name prep
ended with the ‘leading char’ is used. ‘leading char’ is usually _ or noth
ing, depending on the target. All functions are currently defined to have void
return type. The function must be terminated with .endfunc.

7.30 .func name[,label]
.func发出一个调试信息用以指示函数name,这个信息将被忽略,除非文件使用debugging
enabled方式的汇编。目前只支持‘--gstabs’。label是函数的入口点,如果name被省略
则使用预定的‘引导符’。‘引导符’通常可以是 _ 或者什么也没有,视目标机型而定。
所有函数现时被定义为void返回类型,函数体必须使用.endfunc来结束


7.31 .global symbol, .globl symbol
.global makes the symbol visible to ld. If you define symbol in your partial p
rogram, its value is made available to other partial programs that are linked
with it. Otherwise, symbol takes its attributes from a symbol of the same name
from another file linked into the same program.
Both spellings (‘.globl’ and ‘.global’) are accepted, for compatibility wi
th other assemblers.
On the HPPA, .global is not always enough to make it accessible to other parti
al programs. You may need the HPPA-only .EXPORT directive as well. See Section
8.8.5 [HPPA Assembler Directives], page 84.

7.31 .global symbol, .globl symbol
.global 使符号symbol对连接器ld可见。如果您在局部过程中定义符号symbol,其它和此
的局部过程都可以访问它的值。另外,symbol从连接到本过程的另一个文件中的同名符号
获取自己的属性。
两种写法都可以(‘.globl’ 和‘.global’),以便兼容多种汇编器。

在HPPA上, .global未必总能够使符号被其它局部过程访问。可能同时需要使用HPPA-only
.EXPORT命令。见8.8.5[HPPA Assembler Directives],84页。


7.32 .hidden names
This one of the ELF visibility directives. The other two are .internal (see Se
ction 7.39 [.internal], page 44) and .protected (see Section 7.58 [.protected]
, page 50).
This directive overrides the named symbols default visibility (which is set by
their binding: local, global or weak). The directive sets the visibility to h
idden which means that the symbols are not visible to other components. Such s
ymbols are always considered to be protected as well.

7.32 .hidden names
这是一条关于ELF可见度的命令。其它两条是.internal(见7.39[.internal],44页) 和 .p
rotected (见7.58 [.protected], 50页)。本命令取消指定符号的缺省可见度(可见度由其
他命令捆绑设定:local,global,weak)。本命令把可见度设置为hidden,这意味着本符号对
其他部分不可见。这最好是一些需要长期保护的符号。


7.33 .hword expressions
This expects zero or more expressions, and emits a 16 bit number for each.
This directive is a synonym for ‘.short’; depending on the target architectu
re, it may also be a synonym for ‘.word’.

7.33 .hword expressions
本命令后可以不带或带多个expressions,并且为每个参数生成一个16位数。
本命令等同与'.short'命令。在某些架构上,也可能等同与'.word'。


7.34 .ident
This directive is used by some assemblers to place tags in object files. as si
mply accepts the directive for source-file compatibility with such assemblers,
but does not actually emit anything for it.

7.34 .ident
本命令被某些汇编器用来在目标文件中加入标饰。为了使汇编源码文件兼容上述的汇编器
,as简单地接受本命令,但实际上不产生东西。


7.35 .if absolute expression
.if marks the beginning of a section of code which is only considered part of
the source program being assembled if the argument (which must be an absolute
expression) is nonzero. The end of the conditional section of code must be mar
ked by .endif (see Section 7.20 [.endif], page 40); optionally, you may includ
e code for the alternative condition, flagged by .else (see Section 7.15 [.els
e], page 40). If you have several conditions to check, .elseif may be used to
avoid nesting blocks if/else within each subsequent .else block.
The following variants of .if are also supported:
.ifdef symbol
Assembles the following section of code if the specified symbol has been defin
ed.
.ifc string1,string2
Assembles the following section of code if the two strings are the same. The s
trings may be optionally quoted with single quotes. If they are not quoted, th
e first string stops at the first comma, and the second string stops at the en
d of the line. Strings which contain whitespace should be quoted. The string c
omparison is case sensitive.
.ifeq absolute expression
Assembles the following section of code if the argument is zero.
.ifeqs string1,string2
Another form of .ifc. The strings must be quoted using double quotes.
.ifge absolute expression
Assembles the following section of code if the argument is greater than or equ
al to zero.
.ifgt absolute expression
Assembles the following section of code if the argument is greater than zero.

.ifle absolute expression
Assembles the following section of code if the argument is less than or equal
to zero.
.iflt absolute expression
Assembles the following section of code if the argument is less than zero.
.ifnc string1,string2.
Like .ifc, but the sense of the test is reversed: this assembles the following
section of code if the two strings are not the same.
.ifndef symbol
.ifnotdef symbol
Assembles the following section of code if the specified symbol has not been d
efined. Both spelling variants are equivalent.
.ifne absolute expression
Assembles the following section of code if the argument is not equal to zero (
in other words, this is equivalent to .if).
.ifnes string1,string2
Like .ifeqs, but the sense of the test is reversed: this assembles the followi
ng section of code if the two strings are not the same.

7.35 .if absolute expression
.if 标志着一段代码的开始,这段代码只有在参数absolute experession(必须是一个独立
的表达式)不为0时才进行汇编。这段条件汇编代码必须使用.endif标志结束。(见7.20[.e
ndif], 40页);另外,可以使用.esle来标记一个代码块(见7.15 [.else],40页),这个代码
块与前面那段代码只有一个会进行汇编。 如果您需要检查数个汇编条件,可以在使用.el
seif命令,以避免在.else代码块中进行if/else语句块的嵌套。
同样可以使用下面.if的变体:
.ifdef symbol
如果指定的符号symbol已经定义过,汇编下面那段代码。
.ifc string1,string2
如果两个字符串相同的话,汇编下面那段代码。 字符串可以可选地使用单引号。如果不使
用引号则第1个字符串在逗号处结束。第2个字符串在本行末结束。包含空白的字符串应该
使用引号标注。字符串比较时是区分大小写的。

.ifeq absolute expression
如果参数的值为0,汇编下面那段代码。

.ifeqs string1,string2
这是.ifc的另一种形式,字符串必须使用双引号标注。

.ifge absolute expression
如果参数的值大于等于0,汇编下面那段代码。

.ifgt absolute expression
如果参数的值大于0,汇编下面那段代码。

.ifle absolute expression
如果参数的值小于等于0,汇编下面那段代码。

.iflt absolute expression
如果参数的值小于0,汇编下面那段代码。

.ifnc string1,string2.
类似与.ifc,不过使用反向的测试: 如果两个字符串不相等的话,汇编下面那段代码。

.ifndef symbol
.ifnotdef symbol
如果指定的符号symbol不曾被定义过,汇编下面那段代码。 上面两种写法是等效的。


.ifne absolute expression
如果参数的值为不等于0,汇编下面那段代码。 (换句话说, 这是.if的另一种写法).

.ifnes string1,string2
类似与.ifeqs,不过使用反向的测试: 如果两个字符串不相等的话,汇编下面那段代码。



7.36 .incbin "file"[,skip[,count]]
The incbin directive includes file verbatim at the current location. You can c
ontrol the search paths used with the ‘-I’ command-line option (see Chapter
2 [Command-Line Options], page 11). Quotation marks are required around file.

The skip argument skips a number of bytes from the start of the file. The coun
t argument indicates the maximum number of bytes to read. Note that the data i
s not aligned in any way, so it is the user’s responsibility to make sure tha
t proper alignment is provided both before and after the incbin directive.

7.36 .incbin "file"[,skip[,count]]
这条incbin命令在当前位置逐字地引入file文件的内容。您可以使用命令行选项参数“-I
”来控制搜索路径。(见第2章[Command-Line Options], 11页)。文件名必须使用引号。

参数skip表示需要从文件头跳过的字节数目。参数count表示读入的最大字节数目。注意,
数据没有进行任何方式的对齐操作,所以用户需要在 .incbin命令的前后进行必要的边界
对齐。


7.37 .include "file"
This directive provides a way to include supporting files at specified points
in your source program. The code from file is assembled as if it followed the
point of the .include; when the end of the included file is reached, assembly
of the original file continues. You can control the search paths used with the
‘-I’ command-line option (see Chapter 2 [Command-Line Options], page 11). Q
uotation marks are required around file.

7.37 .include "file"
本命令提供在源程序中指定点引入支撑文件的手段。file中的代码如同紧跟.include后一
样被汇编。当引入文件汇编结束,继续汇编原来的文件。您可以使用命令行选项参数“-I
”来控制搜索路径(见第2章[Command-Line Options], 11页)。文件名必须使用引号来标注



7.38 .int expressions
Expect zero or more expressions, of any section, separated by commas. For each
expression, emit a number that, at run time, is the value of that expression.
The byte order and bit size of the number depends on what kind of target the
assembly is for.

7.38 .int expressions
可以不带参数或带多个expressions,参数之间由逗号分隔。每个expressions都生成一个数
字,这个数字等于表达式在目标机器运行时的值。字节顺序和数字的位数视汇编的目标机器
而定。


7.39 .internal names
This one of the ELF visibility directives. The other two are .hidden (see Sect
ion 7.32 [.hidden], page 42) and .protected (see Section 7.58 [.protected], pa
ge 50).
This directive overrides the named symbols default visibility (which is set by
their binding: local, global or weak). The directive sets the visibility to i
nternal which means that the symbols are considered to be hidden (ie not visib
le to other components), and that some extra, processor specific processing mu
st also be performed upon the symbols as well.

7.39 .internal names
这是一条与ELF可见度相关的命令。另外的两条是.hidden(见7.32[.hidden],42页) 和 .p
rotected (见7.58 [.protected],50页)。
本命令取消指定符号的缺省可见度(可见度由其他命令捆绑设定:local,global,weak)。本
命令把指定符号可见度设置为internal,这意味着此符号需要被隐藏(即对其他部分不可见
),另外,符号还必须经过处理器的特别的处理。




# 回复:linux下汇编的Directive Operands 2004-09-08 3:24 PM n9871009
定语:发现简单的把英文直译成中文有时产生会极大的混乱。向前和向后就是一例,我在
5.3节符号名发现这个问题,当时另选了两个词替代了向前和向后。看起来现在必须说明一
下。
向前(移动):向文件头的方向(移动)。
向后(移动):向文件尾的方向(移动)。
===========================================================
7.40 .irp symbol, values . . .
Evaluate a sequence of statements assigning different values to symbol. The se
quence of statements starts at the .irp directive, and is terminated by an .en
dr directive. For each value, symbol is set to value, and the sequence of stat
ements is assembled. If no value is listed, the sequence of statements is asse
mbled once, with symbol set to the null string. To refer to symbol within the
sequence of statements, use \symbol.
For example, assembling
.irp param,1,2,3
move d\param,sp@-
.endr
is equivalent to assembling
move d1,sp@-
move d2,sp@-
move d3,sp@-

7.40 .irp symbol,values . . .
加工一个需要用values替代symbol的语句序列。语句序列从.irp命令开始,在.endr命令前
结束。对于每个value都进行如下加工:用value替代Symbol,并对此语句序列进行汇编。
如果没有给出value,则用空字符串(null sting)替代symbol,并将此语句序列汇编一次。
使用\symbol, 把参数symbol提交给语句序列。
例如下列代码
.irp param,1,2,3
move d\param,sp@-
.endr
等同与
move d1,sp@-
move d2,sp@-
move d3,sp@-


7.41 .irpc symbol,values . . .
Evaluate a sequence of statements assigning different values to symbol. The se
quence of statements starts at the .irpc directive, and is terminated by an .e
ndr directive. For each character in value, symbol is set to the character, an
d the sequence of statements is assembled. If no value is listed, the sequence
of statements is assembled once, with symbol set to the null string. To refer
to symbol within the sequence of statements, use \symbol.
For example, assembling
.irpc param,123
move d\param,sp@-
.endr
is equivalent to assembling
move d1,sp@-
move d2,sp@-
move d3,sp@-

7.41 .irpc symbol,values. . .
加工一个需要用values替代symbol的语句序列。语句序列从.irpc命令开始,在.endr命令
前结束。对于value中的每个字符,都进行如下加工;用此字符替代symbol,并对此语句序
列进行汇编。如果没有给出value参数,则用空字符串(null sting)替代symbol,并将此语
句序列汇编一次。使用\symbol, 把参数symbol提交给语句序列。
例如下列代码
.irpc param,123
move d\param,sp@-
.endr
等同与
move d1,sp@-
move d2,sp@-
move d3,sp@-


7.42 .lcomm symbol , length
Reserve length (an absolute expression) bytes for a local common denoted by sy
mbol. The section and value of symbol are those of the new local common. The a
ddresses are allocated in the bss section, so that at run-time the bytes start
off zeroed. Symbol is not declared global (see Section 7.31 [.global], page 4
2), so is normally not visible to ld.
Some targets permit a third argument to be used with .lcomm. This argument spe
cifies the desired alignment of the symbol in the bss section.
The syntax for .lcomm differs slightly on the HPPA. The syntax is ‘symbol .lc
omm, length’; symbol is optional.

7.42 .lcomm symbol , length
为一个本地通用符号symbol预留length个字节的内存。symbol 的段(属性)和值(属性)被设
置为一个新的本地通用符号应有的属性:内存是在bss段中分配的,所以在运行时,这些字
节开始都是零。因为symbol没有被声明为全局性的符号,所以symbol对ld通常不可见。

某些目标格式允许在.lcomm命令中使用第3个参数。这个参数指出这个bss段中的符号对齐
操作所需要的边界基准。
.lcomm的语法在HPPA上稍有不同。表示为‘symbol .lcomm, length’; symbol 是可选的



7.43 .lflags
as accepts this directive, for compatibility with other assemblers, but ignore
s it.

7.43 .lflags
as接受本命令,以兼容其他的汇编器,但忽略之。


7.44 .line line-number
Change the logical line number. line-number must be an absolute expression. Th
e next line has that logical line number. Therefore any other statements on th
e current line (after a statement separator character) are reported as on logi
cal line number line-number - 1. One day as will no longer support this direct
ive: it is recognized only for compatibility with existing assembler programs.

Warning: In the AMD29K configuration of as, this command is not available; use
the synonym .ln in that context.
Even though this is a directive associated with the a.out or b.out object-code
formats, as still recognizes it when producing COFF output, and treats ‘.lin
e’ as though it were the COFF ‘.ln’ if it is found outside a .def/.endef pa
ir.
Inside a .def, ‘.line’ is, instead, one of the directives used by compilers
to generate auxiliary symbol information for debugging.

7.44 .line line-number
更改逻辑行号,参数line-number必须是个纯粹的表达式。本命令后的下一行将被赋予此逻
辑行号。因此在当前行之前任何其他的语句(在语句分隔符后)的逻辑行号将被视作line
-number - 1。以后 as将不在支持这条命令:只是为了兼容现存的汇编器而接受本命令。

Warning: 在为AMD29K目标机器配置的as中,不能使用本指令。在这种场合可以使用.ln命令

尽管这是与a. out或b. out目标代码格式相关的命令,在生成COFF输出时as仍然接受它,
并且如果‘.line’出现在.def/endef之外的话,就把它视为‘.ln’命令。
如果‘.line’在.def语句块中的话,.line命令则是一条编译器使用的命令,用来为调式
生成辅助符号信息。


7.45 .linkonce [type]
Mark the current section so that the linker only includes a single copy of it.
This may be used to include the same section in several different object file
s, but ensure that the linker will only include it once in the final output fi
le. The .linkonce pseudo-op must be used for each instance of the section. Dup
licate sections are detected based on the section name, so it should be unique
.
This directive is only supported by a few object file formats; as of this writ
ing, the only object file format that supports it is the Portable Executable f
ormat used on Windows NT.
The type argument is optional. If specified, it must be one of the following s
trings. For example:
.linkonce same_size
Not all types may be supported on all object file formats.
discard Silently discard duplicate sections. This is the default.
one_only Warn if there are duplicate sections, but still keep only one copy.

same_size Warn if any of the duplicates have different sizes.
same_contents
Warn if any of the duplicates do not have exactly the same contents.

7.45 .linkonce [type]
给当前段做一个标志,以便连接器只包含它的一个拷贝。这个命令可以用于几个不同的目标
文件中包含同样的段,但需要连接器在最终的输出文件中只包含一个这样的段。. linkou
ce伪操作必须在每个段的实例都中使用。对重复段的探测基于段名来进行,因此这个段将
是唯一的。
本命令只在少数目标格式文件中有效,到写本文为止,只有基于Windows NT的PE (Portab
le Executable)格式的目标文件支持本命令,
参数type是可选的,如果指定了此参数,它必须是下列字符串之一。例如
. Linkonce same_size
不是在所有的格式目标文件都可以使用所有类型的参数。
discard 静静地舍弃重复的段,这也是默认值。
one_only 如果存在重复的段则发出警告,但只保存一个拷贝。
same_size 如果重复的段有不同的大小则发出警告。
same_contents 如果重复段的内容不是精确的相符则发出警告。


7.46 .ln line-number
‘.ln’ is a synonym for ‘.line’.

7.46 .ln line-number
‘.ln’命令等同与‘.line’.


7.47 .mri val
If val is non-zero, this tells as to enter MRI mode. If val is zero, this tell
s as to exit MRI mode. This change affects code assembled until the next .mri
directive, or until the end of the file. See Section 2.8 [MRI mode], page 13.


7.47 .mri val
如果参数val是非零值,这将通知as进入MRI模式。如果参数val的值是零,这通知as退出M
RI模式。这个变化会影响汇编的结果,直到下个.mri命令,或者直到文件尾。见2.8 [MRI
mode], 13页。


7.48 .list
Control (in conjunction with the .nolist directive) whether or not assembly li
stings are generated. These two directives maintain an internal counter (which
is zero initially). .list increments the counter, and .nolist decrements it.
Assembly listings are generated whenever the counter is greater than zero.
By default, listings are disabled. When you enable them (with the ‘-a’ comma
nd line option; see Chapter 2 [Command-Line Options], page 11), the initial va
lue of the listing counter is one.

7.48 .list
控制(和.nolist命令配合)是否生成汇编清单。这两个命令维护一个内部的计数器(计数
器初始值为0).list命令增加计数器的值,.nolist减少计数器的值。当计数器的值大与0
时将汇编列表。
缺省状态汇编列表的生成是关闭的。当您打开它的时候(使用带-a选项的命令行)第2章
[Command-Line Options], 11页), 内部计数器的初始值为1。


7.49 .long expressions
.long is the same as ‘.int’, see Section 7.38 [.int], page 44.

7.49 .long expressions
.long是.int的等价命令,见7.38 [.int], 44页.

7.50 .macro
The commands .macro and .endm allow you to define macros that generate assembl
y output. For example, this definition specifies a macro sum that puts a seque
nce of numbers into memory:
.macro sum from=0, to=5
.long \from
.if \to-\from
sum "(\from+1)",\to
.endif
.endm
With that definition, ‘SUM 0,5’ is equivalent to this assembly input:
.long 0
.long 1
.long 2
.long 3
.long 4
.long 5
.macro macname
.macro macname macargs ...
Begin the definition of a macro called macname. If your macro definition requi
res arguments, specify their names after the macro name, separated by commas o
r spaces. You can supply a default value for any macro argument by following t
he name with ‘=deflt’. For example, these are all valid .macro statements:


.macro comm
Begin the definition of a macro called comm, which takes no arguments.
.macro plus1 p, p1
.macro plus1 p p1
Either statement begins the definition of a macro called plus1,which takes two
arguments; within the macro definition, write ‘\p’ or ‘\p1’ to evaluate t
he arguments.
.macro reserve_str p1=0 p2
Begin the definition of a macro called reserve_str, with two arguments. The fi
rst argument has a default value, but not the second. After the definition is
complete, you can call the macro either as ‘reserve_str a, b’ (with ‘\p1’
evaluating to a and ‘\p2’ evaluating to b), or as ‘reserve_str ,b’ (with ‘
\p1’ evaluating as the default, in this case ‘0’, and ‘\p2’ evaluating to
b).
When you call a macro, you can specify the argument values either by position,
or by keyword. For example, ‘sum 9,17’ is equivalent to ‘sum to=17, from=9
’.
.endm Mark the end of a macro definition.
.exitm Exit early from the current macro definition.
\@ as maintains a counter of how many macros it has executed in this pseudov-a
riable; you can copy that number to your output with ‘\@’, but only within a
macro definition.


7.50 .macro
本命令.macro和.endm命令允许您定义宏来生成汇编输出。例如,下面的语句定义了一个宏
sum,这个宏把一个数字序列放入内存。

.macro sum from=0, to=5
.long \from
.if \to-\from
sum "(\from+1)",\to
.endif
.endm
使用上述定义,'SUM 0,5'语句就等于输入下面的汇编语句:
.long 0
.long 1
.long 2
.long 3
.long 4
.long 5

.macro macname
.macro macname macargs ...
开始定义一个名为macname的宏。如果您的宏需要使用参数,则在宏的名字后指定他们的名
字,参数之间用逗号或空格分隔。您可以为任意的参数提供参数的缺省值,只需要在参数
后使用“=deflt”,。例如,下列都是合法的宏定义语句:
.macro comm
定义一个名为comm宏,不使用参数。
.macro plus1 p, p1
.macro plus1 p p1
两个语句都声明要定义一个名为plus1的宏,这个宏需要两个参数,在宏定义体内,使用'
\p'或'\p1'来引用参数的值。
.macro reserve_str p1=0 p2
声明要定义一个名为reserve_str的宏,使用两个参数。第一个参数有缺省值,第二个没有
缺省值。宏定义完成后,您可以通过‘reserve_str a, b’(宏体中‘\p1’引用a的值,‘
\p2’引用b值)或通过‘reserve_str ,b’(‘\p1’使用缺省值,在此为‘0’,‘\p2’引
用b的值)来调用这个宏。

当调用一个宏时,您既可以通过位置指定参数值,也可以通过关键字指定参数值。例如,
‘sum 9,17’和‘sum to=17, from=9’是等价的。
.endm 标志宏定义体的结束。
.exitm 提前从当前宏定义体中退出。
\@ 这个伪变量其实是as维护的一个计数器,用来统计执行了多少个宏。您可以通过使用\
@把这个数字复制到您的输出中,但仅限于在宏定义体中使用。


7.51 .nolist
Control (in conjunction with the .list directive) whether or not assembly list
ings are generated. These two directives maintain an internal counter (which i
s zero initially). .list increments the counter, and .nolist decrements it. As
sembly listings are generated whenever the counter is greater than zero.

7.51 .nolist
控制(和.list命令配合)是否生成汇编列表。这两个命令维护一个内部的计数器(计数器
初始值为0).list命令增加计数器的值,.nolist减少计数器的值。当计数器的值大与0时
将汇编列表。


7.52 .octa bignums
This directive expects zero or more bignums, separated by commas. For each big
num, it emits a 16-byte integer.
The term “octa” comes from contexts in which a “word” is two bytes; hence
octa-word for 16 bytes.

7.52 .octa bignums
本命令可以不带参数或多个由逗号分隔开的巨数bignum,针对每个巨数bignum,它生成一个
16个字节的整数。
术语"octa"来源:word为2个字节,故此octa-word为16个字节。


7.53 .org new-lc , fill
Advance the location counter of the current section to new-lc. new-lc is eithe
r an absolute expression or an expression with the same section as the current
subsection. That is, you can’t use .org to cross sections: if new-lc has the
wrong section, the .org directive is ignored. To be compatible with former as
semblers, if the section of new-lc is absolute, as issues a warning, then pret
ends the section of new-lc is the same as the current subsection.
.org may only increase the location counter, or leave it unchanged; you cannot
use .org to move the location counter backwards.
Because as tries to assemble programs in one pass, new-lc may not be undefined
. If you really detest this restriction we eagerly await a chance to share you
r improved assembler.
Beware that the origin is relative to the start of the section, not to the sta
rt of the subsection. This is compatible with other people’s assemblers.
When the location counter (of the current subsection) is advanced, the interve
ning bytes are filled with fill which should be an absolute expression. If the
comma and fill are omitted, fill defaults to zero.

7.53 .org new-lc , fill
向后移动当前段的位置计数器至new-lc。new-lc要么是一个纯粹的表达式,要么这个表达
式与当前子段在同一个段中。换句话说,就是您不能使用.org进行段超越。如果new-lc指
向错误的段,则忽略.org命令。为了兼容以前的汇编器,如果new-lc指向一个地址独立的
段,as发出一个警告,并假定new-lc指向当前子段。
.org 仅仅可以增大位置计数器,或者保持位置计数器不变;您不能使用.org命令把位置计
数器向回移动。
因为as尽量一次完成程序汇编,所以不能使用未定义的new-lc。如果您厌恶这个限制,我
们急切期待有机会分享经过您改进的汇编器。
注意起点相对于段的首地址,而不是子段的首地址。这与其他的汇编器相兼容。
当(当前语句块)位置计数器到达指定位置,用fill填充该字节,fill必须是纯粹的表达
式。如果没有给出逗号和fill,fill值缺省为0。


7.54 .p2align[wl] abs-expr, abs-expr, abs-expr
Pad the location counter (in the current subsection) to a particular storage b
oundary. The first expression (which must be absolute) is the number of low-or
der zero bits the location counter must have after advancement. For example ‘
.p2align 3’ advances the location counter until it a multiple of 8. If the lo
cation counter is already a multiple of 8, no change is needed.
The second expression (also absolute) gives the fill value to be stored in the
padding bytes. It (and the comma) may be omitted. If it is omitted, the paddi
ng bytes are normally zero. However, on some systems, if the section is marked
as containing code and the fill value is omitted, the space is filled with no
-op instructions.
The third expression is also absolute, and is also optional. If it is present,
it is the maximum number of bytes that should be skipped by this alignment di
rective. If doing the alignment would require skipping more bytes than the spe
cified maximum, then the alignment is not done at all. You can omit the fill v
alue (the second argument) entirely by simply using two commas after the requi
red alignment; this can be useful if you want the alignment to be filled with
no-op instructions when appropriate.
The .p2alignw and .p2alignl directives are variants of the .p2align directive.
The .p2alignw directive treats the fill pattern as a two byte word value. The
.p2alignl directives treats the fill pattern as a four byte longword value. F
or example, .p2alignw 2,0x368d will align to a multiple of 4. If it skips two
bytes, they will be filled in with the value 0x368d (the exact placement of th
e bytes depends upon the endianness of the processor). If it skips 1 or 3 byte
s, the fill value is undefined.

7.54 .p2align[wl] abs-expr, abs-expr, abs-expr
增加位置计数器(在当前的子段)使它指向规定的存储边界。第一个表达式参数(结果必须是
纯粹的数字) 代表位置计数器移动后,计数器中连续为0的低序位数量。例如‘.align 3’
向后移动位置指针直至8的倍数(指针的最低的3位为0)。如果地址已经是8倍数,则无需
移动。
第二个表达式参数(结果必须是纯粹的数字)给出填充字节的值。用这个值填充位置计数器
越过的地方。这个参数(和逗点)可以省略。如果省略它,填充字节的值通常默认为0。但在
某些系统上,如果本段标识为包含代码,而填充值被省略,则使用no-op指令填充填充区。

第3个参数表达式的结果也必须是纯粹的数字,这个参数是可选的。如果存在第3个参数,
它代表本对齐命令允许越过字节数的最大值。如果完成这个对齐需要跳过的字节比指定的
最大值还多,则根本无法完成对齐。您可以在边界基准后简单地使用两个逗号,以省略填充
值参数(第二参数);如果您想在适当的时候,对齐操作自动使用no-op指令填充,这个方法
将非常奏效。
.p2alignw和.p2alignl是.p2align命令的变化形式。.p2alignw 使用2个字节来填充填充区
。.p2alignl使用4字节来填充。例如,. .p2alignw 2,0x368d将地址对齐到4的倍数,如果
它跳过2个字节,GAS将使用0x368d填充这2个字节(字节的准确的位置视处理器的存储方式
而定)。如果它跳过1或3个字节,填充值则不明确。


7.55 .previous
This is one of the ELF section stack manipulation directives. The others are .
section (see Section 7.66 [Section], page 52), .subsection (see Section 7.79 [
SubSection], page 56), .pushsection (see Section 7.61 [PushSection], page 50),
and .popsection (see Section 7.56 [PopSection], page 50).
This directive swaps the current section (and subsection) with most recently r
eferenced section (and subsection) prior to this one. Multiple .previous direc
tives in a row will flip between two sections (and their subsections).
In terms of the section stack, this directive swaps the current section with t
he top section on the section stack.

7.55 .previous
这是一个ELF段堆栈操作命令。其他的段堆栈操作命令还有.section (见 7.66 [Section]
, 52页), .subsection (见 7.79 [SubSection], 56页),.pushsection (见 7.61 [PushS
ection], 50页), 和 .popsection (见 7.56 [PopSection], 50页)。
本命令交换当前段(及其子段)和最近访问过的段(及其子段)。多个连续的.previous命
令将使当前位置两个段(及其子段)之间反复切换。
用段堆栈的术语来说,本命令使当前段和堆顶段交换位置。


7.56 .popsection
This is one of the ELF section stack manipulation directives. The others are .
section (see Section 7.66 [Section], page 52), .subsection (see Section 7.79 [
SubSection], page 56), .pushsection (see Section 7.61 [PushSection], page 50),
and .previous (see Section 7.55 [Previous], page 49).
This directive replaces the current section (and subsection) with the top sect
ion (and subsection) on the section stack. This section is popped off the stac
k.

7.56 .popsection
这是一个ELF段堆栈操作命令。其他的段堆栈操作命令还有.section(见 7.66 [Section],
52页), .subsection (见 7.79 [SubSection], 56页),.pushsection (见 7.61 [PushSe
ction], 50页), 和 .previous (见 7.55 [Previous], 49页).
本命令用堆栈顶段(及其子段)替代当前段(及其子段)。堆栈顶段出栈。

7.57 .print string
as will print string on the standard output during assembly. You must put stri
ng in double quotes.

7.57 .print string
as会在标准输出上打印string字符串。String必须使用双引号。


7.58 .protected names
This one of the ELF visibility directives. The other two are .hidden (see Sect
ion 7.32 [Hidden], page 42) and .internal (see Section 7.39 [Internal], page 4
4).
This directive overrides the named symbols default visibility (which is set by
their binding: local, global or weak). The directive sets the visibility to p
rotected which means that any references to the symbols from within the compon
ents that defines them must be resolved to the definition in that component, e
ven if a definition in another component would normally preempt this.

7.58 .protected names
这是一条ELF可见度的相关命令。其它两条是.hidden (参见 7.32 [Hidden], 42页)和 .i
nternal (参见 7.39 [Internal], 44页)。
本命令将取消指定符号的可见度缺省值(可见度由其他命令捆绑设定:local, global, w
eak)本命令将可见度设置为protected,这个可见度意味着:在定义此符号的部件内对此符
号的任何访问,都必须解析到这个部件内的定义体。即使其他部件中存在一个正常情况下
比此优先的定义体。

7.59 .psize lines, columns
Use this directive to declare the number of lines—and, optionally, the number
of columns—to use for each page, when generating listings.
If you do not use .psize, listings use a default line-count of 60. You may omi
t the comma and columns specification; the default width is 200 columns.
as generates formfeeds whenever the specified number of lines is exceeded (or
whenever you explicitly request one, using .eject).
If you specify lines as 0, no formfeeds are generated save those explicitly sp
ecified with .eject.

7.59 .psize lines , columns
当生成清单列表时,使用本命令声明每页的行数—还可以可选地声明列数。
如果您不使用本命令,清单列表的行数为默认的60行。可以省略逗号和列参数:默认值为
200列。
当指定的行数过多的话,as会产生进纸操作。(如果您确实需要一个进纸动作,可以使用
.eject命令)
如果您指定行数为0,则不产生进纸操作,除非您明确地使用了.eject命令。


7.60 .purgem name
Undefine the macro name, so that later uses of the string will not be expanded
. See Section 7.50 [Macro], page 47.

7.60 .purgem name
取消name的宏定义,后面使用字符串name不会被宏扩展。参见 7.50 [Macro], 47页。


7.61 .pushsection name , subsection
This is one of the ELF section stack manipulation directives. The others are .
section (see Section 7.66 [Section], page 52), .subsection (see Section 7.79 [
SubSection], page 56), .popsection (see Section 7.56 [PopSection], page 50), a
nd .previous (see Section 7.55 [Previous], page 49).
This directive is a synonym for .section. It pushes the current section (and s
ubsection) onto the top of the section stack, and then replaces the current se
ction and subsection with name and subsection.

7.61 .pushsection name , subsection
本命令是一个ELF段堆栈操作命令。其余的几个是.section (参见 7.66 [Section], 52页
) , .subsection (参见7.79 [SubSection], 56页),.popsection (参见 7.56 [PopSecti
on], 50页), 和 .previous (参见 7.55 [Previous], 49页)。
本命令与.section命令是等价的。它将当前段(及子段)推入段堆栈的顶部。并使用name
和subsection来替代当前段和子段。

7.62 .quad bignums
.quad expects zero or more bignums, separated by commas. For each bignum, it e
mits an 8-byte integer. If the bignum won’t fit in 8 bytes, it prints a warni
ng message; and just takes the lowest order 8 bytes of the bignum.
The term “quad” comes from contexts in which a “word” is two bytes; hence
quad-word for 8 bytes.

7.62 .quad bignums
.quad 可带0或多个bignum参数,每个参数由逗号分隔。对于每个bignum都汇编成一个8字
节的整数。如果某个bignum用8字节无法表示,则给出警告信息;只汇编这个bignum的最低
8字节。
术语“quad”源于一个“word”代表2个字节,所以quad-word代表8个字节。

7.63 .rept count
Repeat the sequence of lines between the .rept directive and the next .endr di
rective count times.
For example, assembling
.rept 3
.long 0
.endr
is equivalent to assembling
.long 0
.long 0
.long 0

7.63 .rept count
汇编.rept和.endr之间的语句count次。
如, 汇编下列语句:
.rept 3
.long 0
.endr
与下列语句是等价的:
.long 0
.long 0
.long 0

7.64 sbttl "subheading"
Use subheading as the title (third line, immediately after the title line) whe
n generating assembly listings.
This directive affects subsequent pages, as well as the current page if it app
ears within ten lines of the top of a page.

7.64 sbttl "subheading"
当生成汇编清单时,使用subheading作为标题(第3行,紧跟在标题行之后)。
本命令对清单的后续页起作用,如果它位于当前页的前10行内,则对当前页也起作用。



7.65 .scl class
Set the storage-class value for a symbol. This directive may only be used insi
de a .def/.endef pair. Storage class may flag whether a symbol is static or ex
ternal, or it may record further symbolic debugging information.
The ‘.scl’ directive is primarily associated with COFF output; when configur
ed to generate b.out output format, as accepts this directive but ignores it.


7.65 .scl class
设置一个符号的存储类型值(storage-class value)。本命令只能在.def/.endef之间使
用。符号的存储类型可以表明符号是static类型或是external类型,或者进一步记录符号
的调试信息。
‘.scl’命令主要与在COFF输出有关,当生成b.out输出格式时,as接受本命令,但忽略本
命令。




7.66 .section name (COFF version)
Use the .section directive to assemble the following code into a section named
name.
This directive is only supported for targets that actually support arbitrarily
named sections; on a.out targets, for example, it is not accepted, even with
a standard a.out section name.
For COFF targets, the .section directive is used in one of the following ways:

.section name [, "flags"]
.section name [, subsegment]
If the optional argument is quoted, it is taken as flags to use for the sectio
n. Each flag is a single character. The following flags are recognized:
b bss section (uninitialized data)
n section is not loaded
w writable section
d data section
r read-only section
x executable section
s shared section (meaningful for PE targets)
If no flags are specified, the default flags depend upon the section name. If
the section name is not recognized, the default will be for the section to be
loaded and writable. Note the n and w flags remove attributes from the section
, rather than adding them, so if they are used on their own it will be as if n
o flags had been specified at all.
If the optional argument to the .section directive is not quoted, it is taken
as a subsegment number (see Section 4.4 [Sub-Sections], page 25).

7.66 .section name (COFF 版本)
使用.section命令将后续的代码汇编进一个定名为name的段。
本命令只能在目标格式真正支持任意命名段时使用;例如,汇编一个a.out目标格式时,即
使name是一个标准的a.out段名,本命令也不被接受。
当目标格式为COFF时,.section命令的使用为下面某一种格式:
.section name[, "flags"]
.section name[, subsegment]
如可选参数使用了引号,它将被视为该段的标志(flags)。每个标记是单个的字符。下列是
认可的标志。
b bss 段 (未初始化的数据)
n 未装入内存的段
w 可写的段
d 数据段
r 只读段
x 代码段 (executable section)
s 共享段 (目标为PE格式有意义)
如果本命令没有指定标志,则依靠段名来确定标志缺省值。如果该段名没有使用标准段名
,则默认该段已装入内存并且可写。注意在使用n和w标志组合时,不是增加这组属性,而
是删除该段的属性。所以如果只存在这两个标志,就代表该段没有指定任何标志。
如果本命令的可选参数没有使用引号,参数将被视为子段的编号。(参见 4.4 [Sub-Secti
ons], 25页)。



7.67 .section name (ELF 版本)
This is one of the ELF section stack manipulation directives. The others are .
subsection (see Section 7.79 [SubSection], page 56), .pushsection (see Section
7.61 [PushSection], page 50), .popsection (see Section 7.56 [PopSection], pag
e 50), and .previous (see Section 7.55 [Previous], page 49).
For ELF targets, the .section directive is used like this:
.section name [, "flags"[, @type]]
The optional flags argument is a quoted string which may contain any combinati
on of the following characters:
a section is allocatable
w section is writable
x section is executable
The optional type argument may contain one of the following constants:
@progbits section contains data
@nobits section does not contain data (i.e., section only occupies space)

If no flags are specified, the default flags depend upon the section name. If
the section name is not recognized, the default will be for the section to hav
e none of the above flags: it will not be allocated in memory, nor writable, n
or executable. The section will contain data.
For ELF targets, the assembler supports another type of .section directive for
compatibility with the Solaris assembler:
.section "name"[, flags...]
Note that the section name is quoted. There may be a sequence of comma separat
ed flags:
#alloc section is allocatable
#write section is writable
#execinstr section is executable
This directive replaces the current section and subsection. The replaced secti
on and subsection are pushed onto the section stack. See the contents of the g
as testsuite directory gas/testsuite/gas/elf for some examples of how this dir
ective and the other section stack directives work.

7.67 .section name (ELF 版本)
本命令是ELF的段堆栈操作命令之一,其他的段堆栈命令为.subsection (见 Section 7.7
9 [SubSection], page 56), .pushsection (见Section 7.61 [PushSection], page 50)
, .popsection (见 Section 7.56 [PopSection], page 50), and .previous (见 Secti
on 7.55 [Previous], page 49).
当目标格式为ELF时,.section命令应如下使用:
.section name [, "flags"[, @type]]
可选参数flags是被引号包围的字符串,可以由下列字符的任意组合:
a 可分配的段(allocatable)
w 可写段
x 代码段
可选的参数type可以包含下列的任一常量:
@progbits 包含数据的段
@nobits 不包含数据的段(只占用空间的段)
如果本命令没有指定标志,则依靠段名来确定标志缺省值。如果段名不是标准的段名,则
默认的该段不包含上述标志:该段不可分配内存,不可写,不可执行。该段是包含数据的
段。
当目标格式为ELF时,as还支持另一种形式的.section命令,以便兼容Solaris的汇编器:

.section "name"[, flags...]
注意段名是使用引号包围的,可能存在一系列由逗号分隔分隔的标志:
#alloc 可分配的段(section is allocatable)
#write 可写的段
#execinstr 可执行的段
本命令将(用段名为name的段)替代当前段和子段。被替换的段将被推入段堆栈。参见ga
s的测试套件目录gas/testsuite/gas/elf,可以找到一些本命令和其他段堆栈操作命令的
例子。

7.68 .set symbol, expression
Set the value of symbol to expression. This changes symbol’s value and type t
o conform to expression. If symbol was flagged as external, it remains flagged
(see Section 5.5 [Symbol Attributes], page 30).
You may .set a symbol many times in the same assembly.
If you .set a global symbol, the value stored in the object file is the last v
alue stored into it.
The syntax for set on the HPPA is ‘symbol .set expression’.

7.68 .set symbol, expression
设置symbol为expression。这将改变symbol的值域和类型领域以符合expression参数。如
果symbol已被标志为external,则symbol保持它的标志。(见 5.5 [Symbol Attributes],
30页)。
您可以在同一个汇编程序中多次使用.set命令来设置同一个符号。
如果设置一个全局符号,该符号在目标文件中值为最后设定的值。
在HPPA上的语法是‘symbol .set expression’。

7.69 .short expressions
.short is normally the same as ‘.word’. See Section 7.92 [.word], page 59.

In some configurations, however, .short and .word generate numbers of differen
t lengths; see Chapter 8 [Machine Dependencies], page 61.

7.69 .short expressions
本命令通常和’.word’命令一样,见7.92 [.word], 59页.
然而在某些配置中,.short和.word命令生成的数字长度却不相同;见第8章 [Machine De
pendencies], 61页.



7.70 .single flonums
This directive assembles zero or more flonums, separated by commas. It has the
same effect as .float. The exact kind of floating point numbers emitted depen
ds on how as is configured. See Chapter 8 [Machine Dependencies], page 61.

7.70 .single flonums
本命令可以汇编0个或多个浮点参数,各个参数之间使用逗号分隔。它的作用和.float相同
。生成浮点数的具体类型视as的配置而定。见第8章 [Machine Dependencies], 61页。






--------------------
一切有为法 如梦幻泡影




# 回复:linux下汇编的Directive Operands 2004-09-08 3:25 PM n9871009
Re: 7 汇编器命令(下) [re: amtb]



位组合:bit pattern,想不出有什么特别的意义,大概指的是有限个数的0和1所有的组合
吧。Fix me.
sleb128/uleb128: 基于128位的低地址结尾带/无符号的数。您有什么好建议?
==========================下==================================
7.71 .size (COFF 版本)
This directive is generated by compilers to include auxiliary debugging inform
ation in the symbol table. It is only permitted inside .def/.endef pairs.
‘.size’ is only meaningful when generating COFF format output; when as is ge
nerating b.out, it accepts this directive but ignores it.

7.71 .size (COFF 版本)
本命令一般由编译器生成,以在符号表中加入辅助调试信息。本命令只能在.def/.endef命
令对之间使用。
本命令只在生成COFF格式的输出文件有意义。当as生成b.out时,as接受本命令但忽略之。




7.72 .size name , expression (ELF 版本)
This directive is used to set the size associated with a symbol name. The size
in bytes is computed from expression which can make use of label arithmetic.
This directive is typically used to set the size of function symbols.
本命令经常用来设置符号name的内存大小。内存大小的单位是字节, 通过计算参数expres
sion得到,参数expression中可以使用标签进行计算。本命令常用来设置函数符号的长度


7.73 .sleb128 expressions
sleb128 stands for “signed little endian base 128.” This is a compact, varia
ble length representation of numbers used by the DWARF symbolic debugging form
at. See Section 7.86 [Uleb128], page 58.

7.73 .sleb128 expressions
sleb128代表“signed little endian base 128”(低地址结尾的带符号128位基数)。这是
一个紧凑的,变长的数字表示方法,当使用DWARF符号调试格式时使用。参见7.86 [Uleb1
28], 58页。



7.74 .skip size , fill
This directive emits size bytes, each of value fill. Both size and fill are ab
solute expressions. If the comma and fill are omitted, fill is assumed to be z
ero. This is the same as ‘.space’.

7.74 .skip size , fill
本命令生成size个字节,每个字节的值都是fill。参数size和fill都必须是纯粹的表达式
。如果省略逗号和fill,则默认fill的值为0。这与’.space’相同。



7.75 .space size , fill
This directive emits size bytes, each of value fill. Both size and fill are ab
solute expressions. If the comma and fill are omitted, fill is assumed to be z
ero. This is the same as ‘.skip’.
Warning: .space has a completely different meaning for HPPA targets; use .bloc
k as a substitute. See HP9000 Series 800 Assembly Language Reference Manual (H
P 92432-90001) for the meaning of the .space directive. See Section 8.8.5 [HPP
A Assembler Directives], page 84, for a summary.
On the AMD 29K, this directive is ignored; it is accepted for compatibility wi
th other AMD 29K assemblers.
Warning: In most versions of the gnu assembler, the directive .space has the e
ffect of .block See Chapter 8 [Machine Dependencies], page 61.

7.75 .space size , fill
本命令生成size个字节,每个字节的值都是fill。参数size和fill都必须是纯粹的表达式
。如果省略了逗号和fill,则默认fill的值为0。这与’.skip’相同。
警告:在生成HPPA目标格式时,.space的意义完全不同。应该使用.block命令替代本命令
。在HP9000系列800汇编语言参考手册(HP 92432-90001),可以找到.space命令的用法。参
见 8.8.5 [HPPA Assembler Directives],84页, 可以找到使用摘要。
在AMD 29K上,本命令将被忽略。出于兼容其它一些AMD 29K汇编器的目的,as接受本命令

警告:在gnu汇编器大多数版本中,这个.space命令和.block命令等效。见第8章 [Machin
e Dependencies], 61页。

7.76 .stabd, .stabn, .stabs
There are three directives that begin ‘.stab’. All emit symbols (see Chapter
5 [Symbols], page 29), for use by symbolic debuggers. The symbols are not ent
ered in the as hash table:they cannot be referenced elsewhere in the source fi
le. Up to five fields are required:
string This is the symbol’s name. It may contain any character except ‘\000’
, so is more general than ordinary symbol names. Some debuggers used to code a
rbitrarily complex structures into symbol names using this field.
type An absolute expression. The symbol’s type is set to the low 8 bits of th
is expression. Any bit pattern is permitted, but ld and debuggers choke on sil
ly bit patterns.
other An absolute expression. The symbol’s “other” attribute is set to the
low 8 bits of this expression.
desc An absolute expression. The symbol’s descriptor is set to the low 16 bit
s of this expression.
Value An absolute expression that becomes the symbol’s value.
If a warning is detected while reading a .stabd, .stabn, or .stabs statement,
the symbol has probably already been created; you get a half-formed symbol in
your object file. This is compatible with earlier assemblers!
.stabd type , other , desc
The “name” of the symbol generated is not even an empty string. It is a null
pointer, for compatibility. Older assemblers used a null pointer so they didn
’t waste space in object files with empty strings.
The symbol’s value is set to the location counter, relocatably. When your pro
gram is linked, the value of this symbol is the address of the location counte
r when the .stabd was assembled.
.stabn type , other , desc , value
The name of the symbol is set to the empty string "".
.stabs string , type , other , desc , value
All five fields are specified.

7.76 .stabd, .stabn, .stabs
有3个以.stab开头的命令。它们都用来产生符号,(参见第5章 [Symbols], 29页),供符号
调试器使用。这些符号没有收入as的散列表中:这些符号不能被源文件其他地方所访问。
它们至少需要5个属性域:
string 这是符号的名字。它可以包含除‘\000’之外的任何字符,故此可用名比普通符号
名更广泛。很多调试器经常利用这个空间,把任意复杂的结构编码为符号名。
type 这是一个纯粹的表达式。符号的类型属性由这个表达式的低8位设定。任何的位组合
(bit pattern)都可以,但连接器和调试器会被没有义的位组合所中断。
other 这是一个纯粹的表达式。由这个表达式的低8位设定此符号的“其它”属性。
desc 这是一个纯粹的表达式。由这个表达式的低16位设定此符号的描述符。
Value 这个纯粹的表达式将作为符号的值。

如果汇编.stabd, .stabn, 或 .stabs语句时引发了一个警告,该符号有可能已经被创建;
在目标文件中存在一个半成品的符号。这样做兼容于早期的汇编器!

.stabd type , other , desc
生成符号的“名字”甚至不是空字符串,而是一个空指针(null),这样安排是出于对兼
容性要求。早期的汇编器经常使用空指针,以避免空字符串在目标文件中浪费空间。
这个符号的值(值域)在重定位时设置为位置计数器的值。当程序连接之后,这个符号的
值是.stabd命令汇编时位置计数器的地址。
.stabn type , other , desc , value
这个符号的名字被设置为空字符串“”。
.stabs string , type , other , desc , value
5个属性域全部指定好。



7.77 .string "str"
Copy the characters in str to the object file. You may specify more than one s
tring to copy, separated by commas. Unless otherwise specified for a particula
r machine, the assembler marks the end of each string with a 0 byte. You can u
se any of the escape sequences described in Section 3.6.1.1 [Strings], page 19
.

7.77 .string "str"
将参数str中的字符复制到目标文件中去。您可以指定多个字符串进行复制,之间使用逗号
分隔。除非另外指定了具体的机器,汇编器将在每个字符串后追加一个0字节作为标记。您
可以使用任意的逃逸序列,参见19页中3.6.1.1 [Strings]的描述。



7.78 .struct expression
Switch to the absolute section, and set the section offset to expression, whic
h must be an absolute expression. You might use this as follows:
.struct 0
field1:
.struct field1 + 4
field2:
.struct field2 + 4
field3:
This would define the symbol field1 to have the value 0, the symbol field2 to
have the value 4, and the symbol field3 to have the value 8. Assembly would be
left in the absolute section, and you would need to use a .section directive
of some sort to change to some other section before further assembly.

7.78 .struct expression
切换到独立地址段,并用expression设定段的偏移量,expression必须是个纯粹的表达式
。您可以如下使用:
.struct 0
field1:
.struct field1 + 4
field2:
.struct field2 + 4
field3:

定义符号field1的值为0,符号field2的值为4,符号field3的值为8。这段汇编程序将保存
在独立地址段中,在进行下一步汇编前,您需要使用一个某种类型的.section命令,以切
换到相应的段。



7.79 .subsection name
This is one of the ELF section stack manipulation directives. The others are .
section (see Section 7.66 [Section], page 52),.pushsection (see Section 7.61 [
PushSection], page 50), .popsection (see Section 7.56 [PopSection], page 50),
and .previous (see Section 7.55 [Previous], page 49).
This directive replaces the current subsection with name. The current section
is not changed. The replaced subsection is put onto the section stack in place
of the then current top of stack subsection.

7.79 .subsection name
本命令是一个ELF段堆栈操作命令。其它的几个命令是(参见 7.66 [Section], 52页),
.pushsection (参见 7.61 [PushSection],50页), .popsection (参见 7.56 [PopSectio
n], 50页), and .previous (参见7.55 [Previous], 49页)。
本命令用name子段替换当前子段。当前段并不改变。被替换的子段入段堆栈,成为段堆栈
的新栈顶。



7.80 .symver
Use the .symver directive to bind symbols to specific version nodes within a s
ource file. This is only supported on ELF platforms, and is typically used whe
n assembling files to be linked into a shared library. There are cases where i
t may make sense to use this in objects to be bound into an application itself
so as to override a versioned symbol from a shared library.
For ELF targets, the .symver directive can be used like this:
.symver name, name2@nodename
If the symbol name is defined within the file being assembled, the .symver dir
ective effectively creates a symbol alias with the name name2@nodename, and in
fact the main reason that we just don’t try and create a regular alias is th
at the @ character isn’t permitted in symbol names. The name2 part of the nam
e is the actual name of the symbol by which it will be externally referenced.
The name name itself is merely a name of convenience that is used so that it i
s possible to have definitions for multiple versions of a function within a si
ngle source file, and so that the compiler can unambiguously know which versio
n of a function is being mentioned. The nodename portion of the alias should b
e the name of a node specified in the version script supplied to the linker wh
en building a shared library. If you are attempting to override a versioned sy
mbol from a shared library, then nodename should correspond to the nodename of
the symbol you are trying to override.
If the symbol name is not defined within the file being assembled, all referen
ces to name will be changed to name2@nodename. If no reference to name is made
, name2@nodename will be removed from the symbol table.
Another usage of the .symver directive is:
.symver name, name2@@nodename
In this case, the symbol name must exist and be defined within the file being
assembled. It is similar to name2@nodename. The difference is name2@@nodename
will also be used to resolve references to name2 by the linker.
The third usage of the .symver directive is:
.symver name, name2@@@nodename
When name is not defined within the file being assembled, it is treated as nam
e2@nodename. When name is defined within the file being assembled, the symbol
name, name, will be changed to name2@@nodename.

7.80 .symver
使用.symver命令把符号装订到在源文件里指定的节点。本命令只在ELF平台上可用,如果
当前汇编的文件被连接到一个共享库中时常常用到。有些情况下应该在目标文件中使用本
命令,把目标文件自我装订进某个应用软件中,从而取代共享库中旧版本符号。
对于ELF目标,.symver命令可以这样使用:
.symver name, name2@nodename
如果符号name的定义在当前正在汇编的文件中,这个.symver命令实际用name2@nodename创
建一个符号别名,而且我们不打算创建一个正规的别名,因为在符号名中是不允许存在‘
@’这个字符的。别名中name2才是符号的真正名字,外部访问是通过这个名字进行的。符
号自己的名字name仅仅为了使用上的方便,这样在同一个源文件中的一个函数才可能有多
个定义体;编译器才能够清楚当前使用的函数是哪个具体的定义。别名中的nodename部分
应是某个节点的名字,这个节点的名字是在建立共享库时,提供给连接器的版本脚本中指
定的。如果您想覆盖共享库中的旧版本符号,则nodename应该是将被取代符号的节点名。

如果符号name的定义不在当前正在汇编的文件中,则所有对name的访问都变为对name2@no
dename的访问。如果根本没有对name的访问,将会把name2@nodename从符号表中删除。

.symver命令的另一种用法:
.symver name, name2@@nodename
在这种情况下,符号name必须存在,并且它必须在当前正在汇编的文件中被定义。这类似
与name2@nodename。区别是name2@@nodename还被连接器用来解析对name2的访问。//注:
对name2的访问被转向到nodename
.symver命令的第3种用法:
.symver name, name2@@@nodename
如果name不是在当前正在汇编的文件中被定义的时候,对符号的处理就如同name2@nodena
me。如果name是当前正在汇编的文件中定义的,符号的名字name,会被转换为name2@@nod
ename。



7.81 .tag structname
This directive is generated by compilers to include auxiliary debugging inform
ation in the symbol table. It is only permitted inside .def/.endef pairs. Tags
are used to link structure definitions in the symbol table with instances of
those structures.
‘.tag’ is only used when generating COFF format output; when as is generatin
g b.out, it accepts this directive but ignores it.

7.81 .tag structname
本命令由编译器生成,用来在符号表中增加调试辅助的信息。本命令只允许在.def/.ende
f语句对内使用。标饰(tags)常用来连接符号表中的结构定义和该结构实例。
‘.tag’只能在生成COFF格式的输出文件时使用。当as生成b.out格式的输出文件时,接受
本命令但忽略之。



7.82 .text subsection
Tells as to assemble the following statements onto the end of the text subsect
ion numbered subsection, which is an absolute expression. If subsection is omi
tted, subsection number zero is used.

7.82 .text subsection
通知as把后续语句汇编到编号为subsection的正文子段的末尾,subsection是一个纯粹的
表达式。如果省略了参数subsection,则使用编号为0的子段。


7.83 .title "heading"
Use heading as the title (second line, immediately after the source file name
and page number) when generating assembly listings.
This directive affects subsequent pages, as well as the current page if it app
ears within ten lines of the top of a page.

7.83 .title "heading"
当生成汇编清单时,把heading作为标题使用(标题在第2行,紧跟在源文件名和页号后)

如果这个命令出现在某页的前10行中,它不但作用影响到后续的页,也同样影响到当前页



7.84 .type int (COFF version)
This directive, permitted only within .def/.endef pairs, records the integer i
nt as the type attribute of a symbol table entry.
‘.type’ is associated only with COFF format output; when as is configured fo
r b.out output, it accepts this directive but ignores it.

7.84 .type int (COFF 版本)
本命令紧允许在.def/.endef 命令对之间使用,把整数int作为类型属性记录进符号表表项

‘.type’只和COFF格式的输出有关,当as配置生成b.out输出格式时,as接受本命令但忽
略之。

7.85 .type name , type description (ELF version)
This directive is used to set the type of symbol name to be either a function
symbol or an object symbol. There are five different syntaxes supported for th
e type description field, in order to provide compatibility with various other
assemblers. The syntaxes supported are:

.type <name>,#function
.type <name>,#object

.type <name>,@function
.type <name>,@object

.type <name>,%function
.type <name>,%object

.type <name>,"function"
.type <name>,"object"

.type <name> STT_FUNCTION
.type <name> STT_OBJECT

7.85 .type name , type description (ELF 版本)
本命令经常用来设置符号name的类型(属性)为函数符号或是目标符号两者之一。type d
escription部分允许使用5种不同的语法,以兼容众多的汇编器。这些语法是:

.type <name>,#function
.type <name>,#object

.type <name>,@function
.type <name>,@object

.type <name>,%function
.type <name>,%object

.type <name>,"function"
.type <name>,"object"

.type <name> STT_FUNCTION
.type <name> STT_OBJECT


7.86 .uleb128 expressions
uleb128 stands for “unsigned little endian base 128.” This is a compact, var
iable length representation of numbers used by the DWARF symbolic debugging fo
rmat. See Section 7.73 [Sleb128], page 54.

7.86 .uleb128 expressions
uleb128代表“unsigned little endian base 128”(低地址结尾的无符号128位基数)。这
是一个紧凑的,变长的数字表示方法,当使用DWARF符号调试格式时使用。参见7.83 [Sle
b128], 54页。



7.87 .val addr
This directive, permitted only within .def/.endef pairs, records the address a
ddr as the value attribute of a symbol table entry.
‘.val’ is used only for COFF output; when as is configured for b.out, it acc
epts this directive but ignores it.

7.87 .val addr
本命令只能在.def/.endef命令对之间使用,把addr的地址作为值属性存入符号表的表项中

‘.val’命令只能在COFF输出时使用;当as被配置成生成b.out输出时,接受本命令但忽略
之。



7.88 .version "string"
This directive creates a .note section and places into it an ELF formatted not
e of type NT VERSION. The note’s name is set to string.

7.88 .version "string"
本命令创建一个.note段,并把一个NT VERSION类型ELF格式的note放入该.note段。Note的
名字被设置为string。



7.89 .vtable_entry table, offset
This directive finds or creates a symbol table and creates a VTABLE_ENTRY relo
cation for it with an addend of offset.

7.89 .vtable_entry table, offset
本命令寻找或创建一个符号表,并用offset作偏移量的增量,为此符号表产生一个VTABLE
_ENTRY重定位。



7.90 .vtable_inherit child, parent
This directive finds the symbol child and finds or creates the symbol parent a
nd then creates a VTABLE_INHERIT relocation for the parent whose addend is the
value of the child symbol. As a special case the parent name of 0 is treated
as refering the *ABS* section.

7.90 .vtable_inherit child, parent
本命令寻找符号child, 并寻找或创建符号parent,为符号parent产生一个VTABLE_INHERI
T重定位,parent的偏移量增量为符号child的值。一个特例,如果parent的名字为0,则将
它交给*ABS*段处理。



7.91 .weak names
This directive sets the weak attribute on the comma separated list of symbol n
ames. If the symbols do not already exist, they will be created.

7.91 .weak names
本命令设置names中每个符号(由逗号分隔)的weak属性。如果这些符号尚不存在,则创建
这些符号。


7.92 .word expressions
This directive expects zero or more expressions, of any section, separated by
commas.
The size of the number emitted, and its byte order, depend on what target comp
uter the assembly is for.
Warning: Special Treatment to support Compilers
Machines with a 32-bit address space, but that do less than 32-bit addressing,
require the following special treatment. If the machine of interest to you do
es 32-bit addressing (or doesn’t require it; see Chapter 8 [Machine Dependenc
ies], page 61), you can ignore this issue.
In order to assemble compiler output into something that works, as occasionall
y does strange things to ‘.word’ directives. Directives of the form ‘.word
sym1-sym2’ are often emitted by compilers as part of jump tables. Therefore,
when as assembles a directive of the form ‘.word sym1-sym2’, and the differe
nce between sym1 and sym2 does not fit in 16 bits, as creates a secondary jump
table, immediately before the next label. This secondary jump table is preced
ed by a short-jump to the first byte after the secondary table. This short-jum
p prevents the flow of control from accidentally falling into the new table. I
nside the table is a long-jump to sym2. The original ‘.word’ contains sym1 m
inus the address of the long-jump to sym2.
If there were several occurrences of ‘.word sym1-sym2’ before the secondary
jump table, all of them are adjusted. If there was a ‘.word sym3-sym4’, that
also did not fit in sixteen bits, a long-jump to sym4 is included in the seco
ndary jump table, and the .word directives are adjusted to contain sym3 minus
the address of the long-jump to sym4; and so on, for as many entries in the or
iginal jump table as necessary.

7.92 .word expressions
本命令可不带表达式或带多个表达式,这些表达式可以属于任意段,每个表达式由逗号分
隔。
汇编生成的数字的大小,字节顺序视生成程序运行的目标机器而定。
警告:支持编译器的特殊处理
有些机器具有32位地址空间,但不能完全进行32位寻址,需要下列的特殊处理。如果您关
心的机器能够进行32位寻址,(或者根本不需要32位寻址;见第8章[机器相关性],61页)
则可以忽略这个问题。
为了使由编译器产生源码的汇编结果能够正确地运行,as偶尔会对'.word'命令进行些奇怪
的操作。编译器在跳转表部分经常生成类似'.word sym1-sym2'形式的命令。所以,当as汇
编一条形如'.word sym1-sym2'的命令,且sym1和sym2之间的偏移量大于16位时,as会在下
个标签前创建一个'次级跳转表',在'次级跳转表'前面加插上一个短-跳转指令,这个短-跳
转指令的目的地址是'次级跳转表'之后的第一个字节。 这个短跳转防止控制流程意外地落
入新的跳转表(次级跳转表)。在'次级跳转表'内是个目的地址为sym2的长-跳转指令。原
来的'.word'命令调整为sym1减去到sym2的长-跳转指令地址,。
如果在次级跳转表前出现了几个'.word sym1-sym2',这些命令都要进行调整。如果存在一
个'.word sym3-sym4',且地址差也大与16位,次级跳转表中将包含一个至sym4的长-跳转
指令,且.word命令将被调整为包含sym3减去到sym4长-跳转指令的地址; 如是类推,处理
原始跳转表中的需要处理的各个表项。



7.93 Deprecated Directives
One day these directives won’t work. They are included for compatibility with
older assemblers.
.abort
.line

7.93 不赞成使用的命令
将来下列命令可能不再被支持,它们的存在只是为了与老版本的汇编器相兼容。
.abort
.line

AT&T汇编指令

GAS中每个操作都是有一个字符的后缀,表明操作数的大小。

C声明

GAS后缀

大小(字节)

char

b

1

short

w

2

(unsigned) int / long / char*

l

4

float

s

4

double

l

8

long double

t

10/12

注意:GAL使用后缀“l”同时表示4字节整数和8字节双精度浮点数,这不会产生歧义因为浮点数使用的是完全不同的指令和寄存器。

 

 

操作数格式:

格式

操作数值

名称

样例(GAS = C语言)

$Imm

Imm

立即数寻址

$1 = 1

Ea

R[Ea]

寄存器寻址

%eax = eax

Imm

M[Imm]

绝对寻址

0x104 = *0x104

Ea

M[R[Ea]]

间接寻址

%eax= *eax

Imm(Ea)

M[Imm+R[Ea]]

(基址+偏移量)寻址

4(%eax) = *(4+eax)

Ea,Eb

M[R[Ea]+R[Eb]]

变址

(%eax,%ebx) = *(eax+ebx)

ImmEa,Eb

M[Imm+R[Ea]+R[Eb]]

寻址

9(%eax,%ebx)= *(9+eax+ebx)

(,Ea,s)

M[R[Ea]*s]

伸缩化变址寻址

(,%eax,4)= *(eax*4)

Imm(,Ea,s)

M[Imm+R[Ea]*s]

伸缩化变址寻址

0xfc(,%eax,4)= *(0xfc+eax*4)

(Ea,Eb,s)

M(R[Ea]+R[Eb]*s)

伸缩化变址寻址

(%eax,%ebx,4) = *(eax+ebx*4)

Imm(Ea,Eb,s)

M(Imm+R[Ea]+R[Eb]*s)

伸缩化变址寻址

8(%eax,%ebx,4) = *(8+eax+ebx*4)

注:M[xx]表示在存储器中xx地址的值,R[xx]表示寄存器xx的值,这种表示方法将寄存器、内存都看出一个大数组的形式。

 

 

数据传送指令:

指令

效果

描述

movl S,D

D <-- S

传双字

movw S,D

D <-- S

传字

movb S,D

D <-- S

传字节

movsbl S,D

D <-- 符号扩展S

符号位填充(字节->双字)

movzbl S,D

D <-- 零扩展S

零填充(字节->双字)

pushl S

R[%esp] <-- R[%esp] – 4;

M[R[%esp]] <-- S

压栈

popl D

D <-- M[R[%esp]]

R[%esp] <-- R[%esp] + 4;

出栈

注:均假设栈往低地址扩展。

 

 

算数和逻辑操作地址:

指令

效果

描述

leal S,D

D = &S

movl地版,S地址入DD仅能是寄存器

incl D

D++

1

decl D

D--

1

negl D

D = -D

取负

notl D

D = ~D

取反

addl S,D

D = D + S

subl S,D

D = D – S

imull S,D

D = D*S

xorl S,D

D = D ^ S

异或

orl S,D

D = D | S

andl S,D

D = D & S

sall k,D

D = D << k

左移

shll k,D

D = D << k

左移(sall)

sarl k,D

D = D >> k

算数右移

shrl k,D

D = D >> k

逻辑右移

 

 

特殊算术操作:

指令

效果

描述

imull S

R[%edx]:R[%eax] = S * R[%eax]

无符号64位乘

mull S

R[%edx]:R[%eax] = S * R[%eax]

有符号64位乘

cltd S

R[%edx]:R[%eax] = 符号位扩展R[%eax]

转换为4字节

idivl S

R[%edx] = R[%edx]:R[%eax] % S;

R[%eax] = R[%edx]:R[%eax] / S;

有符号除法,保存余数和商

divl S

R[%edx] = R[%edx]:R[%eax] % S;

R[%eax] = R[%edx]:R[%eax] / S;

无符号除法,保存余数和商

注:64位数通常存储为,高32位放在edx,低32位放在eax

 

 

条件码:

条件码寄存器描述了最近的算数或逻辑操作的属性。

CF:进位标志,最高位产生了进位,可用于检查无符号数溢出。

OF:溢出标志,二进制补码溢出——正溢出或负溢出。

ZF:零标志,结果为0

SF:符号标志,操作结果为负。

 

 

比较指令:

指令

基于

描述

cmpb S2,S1

S1 – S2

比较字节,差关系

testb S2,S1

S1 & S2

测试字节,与关系

cmpw S2,S1

S1 – S2

比较字,差关系

testw S2,S1

S1 & S2

测试字,与关系

cmpl S2,S1

S1 – S2

比较双字,差关系

testl S2,S1

S1 & S2

测试双字,与关系

 

 

访问条件码指令:

指令

同义名

效果

设置条件

sete D

setz

D = ZF

相等/

setne D

setnz

D = ~ZF

不等/非零

sets D

 

D = SF

负数

setns D

 

D = ~SF

非负数

setg D

setnle

D = ~(SF ^OF) & ZF

大于(有符号>

setge D

setnl

D = ~(SF ^OF)

小于等于(有符号>=)

setl D

setnge

D = SF ^ OF

小于(有符号<)

setle D

setng

D = (SF ^ OF) | ZF

小于等于(有符号<=)

seta D

setnbe

D = ~CF & ~ZF

超过(无符号>)

setae D

setnb

D = ~CF

超过或等于(无符号>=)

setb D

setnae

D = CF

低于(无符号<)

setbe D

setna

D = CF | ZF

低于或等于(无符号<=)

 

 

跳转指令:

指令

同义名

跳转条件

描述

jmp   Label

 

1

直接跳转

jmp   *Operand

 

1

间接跳转

je     Label

jz

ZF

等于/

jne    Label

jnz

~ZF

不等/非零

js     Label

 

SF

负数

jnz    Label

 

~SF

非负数

jg     Label

jnle

~(SF^OF) & ~ZF

大于(有符号>)

jge    Label

jnl

~(SF ^ OF)

大于等于(有符号>=)

jl     Label

jnge

SF ^ OF

小于(有符号<

jle     Label

jng

(SF ^ OF) | ZF

小于等于(有符号<=)

ja     Label

jnbe

~CF & ~ZF

超过(无符号>)

jae    Label

jnb

~CF

超过或等于(无符号>=)

jb     Label

jnae

CF

低于(无符号<)

jbe    Label

jna

CF | ZF

低于或等于(无符号<=)

 

 

转移控制指令:(函数调用):

指令

描述

call    Label

过程调用,返回地址入栈,跳转到调用过程起始处,返回地址是call后面那条指令的地址

call    *Operand

leave

为返回准备好栈,为ret准备好栈,主要是弹出函数内的栈使用及%ebp

 

 

GCCC中潜入汇编代码:

asm( code-string [:output-list [ : input-list [ :overwrite-list]]]);

注意,后面的参数(如overwrite-list)如果为空则不要相应的“:”,而如果前面参数(如output-list)为空则需要用“:”占位。

如:

asm ("..."

    :                    //output需要占位

    : "r" (src)       //后面的Overwrites不能写,我测试的结果是写了编译不过

};


如:

Int ok_umul(unsigned x,unsigned y,unsigned *dest)

{

  int result;

asm(“movl %2 , %%eax; mull %3; movl %%eax,%0;\

           setae %dl; movzbl %%dl,%1”

           :  “=r” (*dest)  ,  “=r” (result)         //output

           :  “r” (x)  ,  “r” (y)                         //inputs

           :  “%ebx”  , “%edx”                        //Overwrites

);

 

return result;

}

我们用%0--%n表示输入的参数,”r”表示整数寄存器,”=”表示对其进行了赋值。%eax要写成%%eax,这是c语言字符串的规则,别忘了code-string就是一个c语言的字符串。

linux汇编常见问题

1.gcc嵌入汇编
(1). 在gcc嵌入汇编中输入输出使用相同的寄存器?

static void * __memcpy(void * to, const void * from, size_t n)
{
 int d0,d1,d2;
 __asm__ __volatile__(
  "rep;movsl\n\t"
  "testb $2,%b4\n\t"
  "je 1f\n\t"
  "movsw\n"
  "1:\ttestb $1,%b4\n\t"
  "je 2f\n\t"
  "movsb\n"
  "2:"
  :"=&c" (d0), "=&D" (d1), "=&S" (d2)
  :"0" (n/4), "q" (n), "1" ((long) to), "2" ((long) from)
  :"memory");
 return (to);
}

操作数0,1,2和3,5,6使用相同的寄存器的理解:
a. 3,5,6在输入过程中将值n/4,to和from的地址读入ecx,edi,esi寄存器中;
b. 0,1,2在输出过程中将ecx,edi,esi寄存器中的值存入d0,d1,d2内存变量中。
c. 注意在上面的语句中也有"&"限定符,但输入和输出仍使用相同的寄存器,如操作数0和3都使用寄存器ecx,这是因为"0"限定的结果,如果

把"0" (n/4)换成"c" (n/4),则因为"=&c"的限定而使得编译时报错。

(2). 关于gcc嵌入式汇编中"&"限定符的作用?
"&"限定符用于输出操作数,使其唯一的使用某寄存器

int bar,foo;
__asm__ __volatile__(
 "call func \n\t"
 "mov ebx,%1"
 :"=a" (foo)
 :"r" (bar));

在gcc编译时默认会让bar也使用eax寄存器,如果把"=a"改为"=&a",那么foo将唯一使用eax,而让bar使用其它的寄存器。

(3). _start和main的关系?
main是gcc看到的程序入口点,而ld和as所看到的程序入口点其实是_start。libc库中的_start会调用main函数。
a. 编译带_start的汇编程序时的步骤:as -o a.o a.s,ld -o a a.o; gcc -g -c a.s,ld -o a a.o。(使用gcc编译可以增加调试选项-g,这

时不能直接用gcc -o a a.s编译的原因是gcc会默认在程序中查找main函数,并且会将libc中的_start加入进来。如果直接用gcc -o a a.s编译

,则会报两个错误"重复的_start"和"没找到main")
b. 编译带main的汇编程序或C程序时的步骤:gcc -o a a.s;gcc -o a a.c。

(4).section和.previous
将这两个.section和.previous中间的代码汇编到各自定义的段中,然后跳回去,将这之后的的代码汇编到上一个section中(一般是.text段),

也就是自定义段之前的段。.section和.previous必须配套使用。

2. AT&T汇编
(1).data,.section等都是伪操作,不能直接翻译成机器码,只有相应的assembler才能识别
(2).section将程序分成几个片断,如.data,.text,.bss
(3).globl 函数名表示该函数被export,并可以被其它文件中的函数调用
(4).bss可以用来申请一块空间,但不需要对其进行初始化
(5)使用内核定义函数如open,read等可以通过int $80中断来完成
(6)MOVL $FOO,%EAX是把FOO在内存中的地址放到EAX中,而MOVL FOO,%EAX是把FOO这个变量的内容放入EAX
(7).include "文件名",将其它文件包含进来
(8)如何在汇编中表示结构?如c语言中的如下结构:
struct para
{
 char Firstname[40];
 char Lastname[40];
 char Address[240];
 long Age;//4 bytes
}
在汇编中可以表示成:
.section data
record1:
.ascii "Fredrick\0"
.rept 31 #Padding to 40 bytes
.byte 0
.endr
.ascii "Bartlett\0"
.rept 31 #Padding to 40 bytes
.byte 0
.endr
.ascii "4242 S Prairie\nTulsa, OK 55555\0"
.rept 209 #Padding to 240 bytes
.byte 0
.endr
.long 45
其中.rept n和.endr表示重复两者之间的序列n次,可用于填充数据
(9)当汇编程序由多个文件构成时,可以采用以下方式编译与连接:
as write-records.s -o write-records.o (gcc -g -c write-records.s)
as write-record.s -o write-record.o (gcc -g -c write-record.s)
ld write-record.o write-records.o -o write-records
(10)如何在汇编语言中使用动态库中的函数?
#helloworld-lib.s
.section .data
helloworld:
.ascii "hello world\n\0"
.section .text
.globl _start
_start:
pushl $helloworld
call printf
pushl $0
call exit

as helloworld-lib.s -o helloworld-lib.o
ld -dynamic-linker /lib/ld-linux.so.2 -o helloworld-lib helloworld-lib.o -lc
产生动态库:ld -shared write-record.o read-record.o -o librecord.so
(11)使用汇编文件生成动态库
as write-record.s -o write-record.o
as read-record.s -o read-record.o
ld -shared write-record.o read-record.o -o librecord.so
as write-records.s -o write-records.o
ld -L . -dynamic-linker /lib/ld-linux.so.2 -o write-records -lrecord write-records.o
记得运行write-records时还需要将动态库路径加到/etc/ld.so.conf中,并运行ldconfig
(12)编译汇编文件时如何产生调试符号
as --gstabs a.s -0 a.o 或者gcc -g -c a.s

附录:
(1)函数调用时栈的情况
#Parameter #N <--- N*4+4(%ebp)
#...
#Parameter 2 <--- 12(%ebp)
#Parameter 1 <--- 8(%ebp)
#Return Address <--- 4(%ebp)
#Old %ebp <--- (%ebp)
#Local Variable 1 <--- -4(%ebp)
#Local Variable 2 <--- -8(%ebp) and (%esp)

(2)例子
 .include "external_func.s"

 .section .data
data_array:     #定义long型数组
 .long 3,67,34,0        
data_strings:    #定义字符串
 .ascii "Hello there\0"
data_long: #定义long型变量
 .long 5

 .section .bss
 .lcomm my_buffer, 500   #申请一块500字节的内存

 .section .text
 .equ LINUX_SYSCALL, 0x80 #定义符号LINUX_SYSCALL的值为0x80
 .globl _start
_start:
 pushl %edx
 movl data_long,%edx     #将data_long变量的值放入edx寄存器
 movl $data_long,%edx    #将data_long的地址放入edx寄存器
 popl %edx

 pushl $3      #push second argument
 pushl $2      #push first argument
 call power    #call the function
 addl $8, %esp #move the stack pointer back
 pushl %eax    #save the first answer before,calling the next function

 movl $1, %eax #exit (%ebx is returned)
 int $LINUX_SYSCALL    

 .type power, @function #定义函数power
power:
 pushl %ebp         #save old base pointer
 movl %esp, %ebp    #make stack pointer the base pointer
 subl $4, %esp      #get room for our local storage
 movl 8(%ebp), %eax #put first argument in %eax
 movl 12(%ebp), %ebx #put second argument in %ebx
 imull %ebx,%eax
 movl %ebp, %esp    #restore the stack pointer
 popl %ebp          #restore the base pointer
 ret

LINUX汇编(汇编语言程序设计读书笔记)

#############################################
# 一, IA-32 硬件特性
#############################################

寄存器:
1, 通用寄存器, 用于存放正在处理的数据
EAX 用于操作数和结果数的累加器
EBX 指向数据内存断中的数据的指针
ECX 字符串和循环操作的计数器
EDX IO指针
EDI 用于字符串操作的目标的数据指针
ESI 用于字符串操作的源的数据指针
ESP 堆栈指针
EBP 堆栈数据指针

其中寄存器EAX, EBX, ECX, EDX又可以通过16位和8位寄存器名称引用
如EAX, AX 引用EAX低16位, AL 引用EAX低8位, AH 引用AL之后的高8位



2, 段寄存器:
IA-32平台允许使用3中内存模型: 平坦内存模式 分段内存模式 实地址模式

平坦内存: 把全部的系统内存表示为连续的地址空间, 通过线性地址的特定地址
访问内存位置.

分段内存: 把系统内存划分为独立的段组, 通过位于寄存器中的指针进行引用. 每
个段用于包含特定类型的数据。 一个段用于包含指令码, 另一个段包
含数据元素, 第三个段包含数据堆栈。
段中的内存位置是通过逻辑地址引用的, 逻辑地址是由段地址加上偏移
量构成, 处理器把逻辑地址转换为相应的线性地址以便访问。


段寄存器:
CS 代码段
DS 数据段
SS 堆栈段
ES 附加段指针
FS 附加段指针
GS 附加段指针

每个段寄存器都是16位的, 包含指向内存特定段起始位置的指针,程序不能
显示加载或改变CS寄存器, DS, ES, FS, GS都用于指向数据段, 通过4个独立
的段, 程序可以分隔数据元素, 确保他们不会重叠, 程序必须加载带有段的
正确指针值的数据段寄存器, 并且使用偏移值引用各个内存的位置。
SS段寄存器用于指向堆栈段, 堆栈包含传递给函数和过程的数据值。

实地址: 如果实地址模式, 所有段寄存器都指向线性0地址, 并且都不会被程序改动,
所有的指令码 数据元素 堆栈元素 都是通过他们的线性地址直接访问的。



3, 指令指针寄存器
是EIP寄存器, 它跟踪要执行程序的下一条指令代码, 应用程序不能修改指令指针本身,不
能指定内存地址把它拖放EIP寄存器中,相反必须通过一般的跳转指令来改变预存取缓存的
下一条指令。

在平坦内存模型中, 指令指针包含下一条指令码的线性地址, 在分段模型中指令指针包含
逻辑地址指针, 通过CS寄存器的内存引用。



4, 控制寄存器
CRO 控制操作模式 和 处理器当前状态的系统标志
CR1 当前没有使用
CR2 内存页面错误信息
CR3 内存页面目录信息
CR4 支持处理器特性和说明处理器特性能力的标志

不能直接访问控制寄存器, 但是能把控制寄存器中的值传递给通用寄存器,如果必须改动控制
寄存器的标志, 可以改动通用寄存器的值, 然后把内容传递给控制寄存器。





标志:
IA-32使用单一的寄存器来包含一组状态控制和系统标志, EFLAGS寄存器包含32位标志信息

1, 状态标志
标志 位 说明
CF 0 进位标志, 如果无符号数的数学操作产生最高有效位的进位或者借位, 此时值为1
PF 2 奇偶校验标志, 用于表明数学操作的结果寄存器中的是否包含错误数据
AF 4 辅助进位标志, 用于二进制编码的10进制(BCD)的数学操作中, 如果用于运算的
寄存器的第三位发生进位或借位, 该值为1
ZF 6 0标志, 如果操作为0, 则该值为1
SF 7 符号标志, 设置为结果的最高有效位, 这一位是符号位表明结果是正值还是负值
OF 11 溢出标志

2, 控制标志
当前只定义了一个控制标志DF即方向标志, 用于控制处理器处理字符串的方式
如果设置为1, 字符串指令自动递减内存地址以便到达字符串中的下一字节。
反之。

3, 系统标志
标志 位 说明
TF 8 陷阱标志, 设置为1时启用单步模式, 在单步模式下处理器每次只执行一条命令。
IF 9 中断使能标志, 控制处理器如响应从外部源接收到的信号。
IOPL 12和13 IO特权级别标志, 表明当前正在运行任务的IO特权级别, 它定义IO地址空间的
特权访问级别, 该值必须小于或者等于访问I/O地址空间的级别; 否则任何访问
IO空间的请求都会被拒绝!
NT 14 嵌套任务标志控制当前运行的任务是否连接到前一个任务, 它用于连接被中断
和被调用的任务.
RF 16 恢复标志用于控制在调试模式中如何响应异常。
VM 17 虚拟8086模式, 表明处理器在虚拟8086模式中而不是保护模式或者实模式。
AC 18 对准检查标志, 用于启用内存引用的对准检查
VIF 19 虚拟中断标志, 当处理器在虚拟模式中操作时, 该标志起IF标志的作用.
VIP 20 虚拟中断挂起标志, 在虚拟模式操作时用于表示一个中断正在被挂起。
ID 21 表示CPU是否支持cpuid指令, 如果处理器能够设置或者清零这个标志, 表示
处理器支持该指令。






################################################################################################
# 二,GNU汇编工具系列
################################################################################################
1, 二进制工具系列
addr2line 把地址转换成文件名或者行号

ar 创建 修改或者展开文件存档

as 把汇编语言代码汇编成目标代码
常用选项:
-a -> 指定输出中包含那些清单
-D -> 包含它用于向下兼容 但是被忽略
--defsym -> 在汇编代码之前定义符号和值
-f -> 快速汇编跳过注释和空白
--gstabs -> 包含每行源代码的调试信息
--gstats+ -> 包含gdb专门的调试信息
-I -> 指定包含文件的目录
-J -> 不警告带符号溢出
-L -> 在符号表中保存本地符号
-o -> 给定输出目标名
-R -> 把数据段合并进文本段
--statistics -> 显示汇编使用的最大空间和总时间
-v -> 显示as的版本号
-W -> 不显示警告信息

c++filt 还原c++符号的过滤器

gprof 显示程序简档信息的程序

ld 把目标代码文件转换成可执行文件的转换器
常用选项:
-d -> 指定目标代码输入文件的格式
-Bstatic -> 只使用静态库
-Bdynamic -> 只使用动态库
-Bsymbolic-> 把引用捆绑到共享库中的全局符号
-c -> 从指定的命令文件读取命令
-cref -> 创建跨引用表
-defsym -> 在输出文件中创建指定的全局符号
-demangle -> 在错误消息中还原符号名称
-e -> 使用指定的符号作为程序的初始执行点
-E -> 对于elf文件把所有的符号添加到动态符号表
-share -> 创建共享库
-Ttext -> 使用指定的地址作为文本段的起始点
-Tdata -> 使用指定的地址作为数据段的起始点
-Tbss -> 使用指定的地址作为bss段的起始点
-L -> 把指定的路径添加到库搜索清单
-O -> 生成优化的输出文件
-o -> 指定输出名
-oformat -> 指定输出文件的二进制格式
-R -> 从指定的文件读取符号和地址
-rpath -> 把指定的位置添加到运行时库搜索路径
-rpath-link-> 指定搜索运行时共享库的路径
-X -> 删除本地所有临时符号
-x -> 删除本地所有符号

nm 列出目标文件中的符号

objcopy 复制或翻译目标文件

objdump 显示来自目标文件的信息

ranlib 生成存档文件内容的索引

readelf 按照elf格式显示目标文件信息

size 列出目标文件或者存档文件的段长度

strings 显示目标文件中可打印字符串

strip 丢弃符号

windres 编译Microsoft Windows资源文件

2, GNU编译器
gcc
常用选项:
-c 编译或者汇编代码但不进行连接
-S 编译后停止但不进行汇编
-E 预处理后停止但不进行编译
-o 指定输出文件名
-v 显示每个编译阶段使用的命令
-std 指定使用的语言标准
-g 生成调试信息
-pg 生成gprof制作简档要使用的额外代码
-O 优化可执行代码
-W 设置编译器警告级别
-I 指定包含文件清单
-L 指定库文件目录
-D 预定义源代码中使用的宏
-U 取消任何定义了的宏
-f 指定控制编译器行为的选项
-m 指定与硬件相关的选项

3, GNU调试程序
gdb
常用选项:
-d 指定远程调试时串行接口的线路速度
-batch 以批处理模式运行
-c 指定要分析的核心转储文件
-cd 指定工作目录
-d 指定搜索源文件的目录
-e 指定要执行的文件
-f 调试时以标准格式输出文件名和行号
-q 安静模式
-s 指定符号的文件名
-se 指定符号和要执行的文件名
-tty 设置标准输出和输入设备
-x 从指定的文件执行gdb命令

由于gnu调试时忽略开始处断点, 需要在开始标签处执行一个空指令
如:
.globl _start
_start:
nop
此时断点可以设置成 break *_start+1
查看寄存器状态info registers
使用print命令查看特定寄存器或者变量的值, 加上修饰符可以得到不同的输出格式:
print/d 显示十进制数字
print/t 显示二进制数字
print/x 显示16进制数字
使用x命令可以查看特定内存的值:
x/nyz
其中 n为要显示的字段数
y时输出格式, 它可以是:
c 用于字符, d用于十进制, x用于16进制
z是要显示的字段长度, 它可以是:
b用于字节, h用于16字节, w用于32位字
如:
x/42cb 用于显示前42字节


################################################################################################
# 三, GNU汇编语言结构
################################################################################################
主要包括三个常用的段:
data 数据段 声明带有初始值的元素
bss 数据段 声明使用0或者null初始化的元素
text 正文段 包含的指令, 每个汇编程序都必须包含此段

使用.section 指令定义段, 如:
.section .data
.section .bss
.section .text

起始点:
gnu汇编器使用_start标签表示默认的起始点, 此外如果想要汇编内部的标签能够被外部程序访问,
需要使用.globl 指令, 如:.globl _start


使用通用库函数时可以使用:
ld -dynamic-linker /lib/ld-linux.so.2




################################################################################################
# 四, 数据传递
################################################################################################
1, 数据段
使用.data声明数据段, 这个段中声明的任何数据元素都保留在内存中并可以被汇编程序的指令读取,
此外还可以使用.rodata声明只读的数据段, 在声明一个数据元素时, 需要使用标签和命令:

标签:用做引用数据元素所使用的标记, 它和c语言的变量很相似, 它对于处理器是没有意义的, 它
只是用做汇编器试图访问内存位置时用做引用指针的一个位置。

指令:这个名字指示汇编器为通过标签引用的数据元素保留特定数量的内存, 声明命令之后必须给出
一个或多个默认值。

声明指令:
.ascii 文本字符串
.asciz 以空字符结尾的字符串
.byte 字节值
.double 双精度浮点值
.float 单精度浮点值
.int 32位整数
.long 32位整数, 和int相同
.octa 16字节整数
.quad 8字节整数
.short 16位整数
.single 单精度浮点数(和float相同)


例子:
output:
.ascii "hello world."

pi:
.float 2.14

声明可以在一行中定义多个值, 如:
ages:
.int 20, 10, 30, 40


定义静态符号:
使用.equ命令把常量值定义为可以在文本段中使用的符号,如:
.section .data
.equ LINUX_SYS_CALL, 0x80
.section .text
movl $LINUX_SYS_CALL, %eax



2, bss段
和data段不同, 无需声明特定的数据类型, 只需声明为所需目的保留的原始内存部分即可。
GNU汇编器使用以下两个命令声明内存区域:
.comm 声明为未初始化的通用内存区域
.lcomm 声明为未初始化的本地内存区域

两种声明很相似, 但.lcomm是为不会从本地汇编代码之外进行访问的数据保留的, 格式为:
.comm/.lcomm symbol, length

例子:
.section .bss
.lcomm buffer, 1000
该语句把1000字节的内存地址赋予标签buffer, 在声明本地通用内存区域的程序之外的函数是
不能访问他们的.(不能在.globl命令中使用他们)


在bss段声明的好处是, 数据不包含在可执行文件中。在数据段中定义数据时, 它必须被包含在
可执行程序中, 因为必须使用特定值初始化它。 因为不使用数据初始化bss段中声明的数据区域,
所以内存区域被保留在运行时使用, 并且不必包含在最终的程序中




3, 传送数据
move 指令:
格式 movex 源操作数, 目的操作数。 其中x为要传送数据的长度, 取值有:
l 用于32位的长字节
w 用于16位的字
b 用于8位的字节值


立即数前面要加一个$符号, 寄存器前面要加%符号。

8个通用的寄存器是用于保存数据的最常用的寄存器, 这些寄存器的内容可以传递
给其他的任何可用的寄存器。 和通用寄存器不同, 专用寄存器(控制, 调试, 段)
的内容只能传送给通用寄存器, 或者接收从通用寄存器传过来的内容。


在对标签进行引用时:
例:
.section .data
value:
.int 100
_start:
movl value, %eax
movl $value, %eax
movl %ebx, (%edi)
movl %ebx, 4(%edi)

其中:movl value, %eax 只是把标签value当前引用的内存值传递给eax
movl $value, %eax 把标签value当前引用的内存地址指针传递给eax
movl %ebx, (%edi) 如果edi外面没有括号那么这个指令只是把ebx中的
值加载到edi中, 如果有了括号就表示把ebx中的内容
传送给edi中包含的内存位置。
movl %ebx, 4(%edi) 表示把edi中的值放在edi指向的位置之后的4字节内存位置中
movl %ebx, -4(%edi) 表示把edi中的值放在edi指向的位置之前的4字节内存位置中



cmove 指令(条件转移):
cmovex 源操作数, 目的操作数. x的取值为:
无符号数:
a/nbe 大于/不小于或者等于
ae/nb 大于或者等于/不小于
nc 无进位
b/nae 小于/不大于等于
c 进位
be/na 小于或等于/不大于
e/z 等于/零
ne/nz 不等于/不为零
p/pe 奇偶校验/偶校验
np/po 非奇偶校验/奇校验

有符号数:
ge/nl 大于或者等于/不小于
l/nge 小于/不大于或者等于
le/ng 小于或者等于/不大于
o 溢出
no 未溢出
s 带符号(负)
ns 无符号(非负)





交换数据:
xchg 在两个寄存器之间或者寄存器和内存间交换值
如:
xchg 操作数, 操作数, 要求两个操作数必须长度相同且不能同时都是内存位置
其中寄存器可以是32,16,8位的


bswap 反转一个32位寄存器的字节顺序

如: bswap %ebx


xadd 交换两个值 并把两个值只和存储在目标操作数中

如: xadd 源操作数,目标操作数
其中源操作数必须是寄存器, 目标操作数可以是内存位置也可以是寄存器
其中寄存器可以是32,16,8位的

cmpxchg
cmpxchg source, destination
其中source必须是寄存器, destination可以是内存或者寄存器, 用来比较
两者的值, 如果相等,就把源操作数的值加载到目标操作数中, 如果不等就把
目标操作数加载到源操作数中,其中寄存器可以是32,16,8位的, 其中源操作
数是EAX,AX或者AL寄存器中的值


cmpxchg8b 同cmpxchg, 但是它处理8字节值, 同时它只有一个操作数
cmpxchg8b destination
其中destination引用一个内存位置, 其中的8字节值会与EDX和EAX寄存器中
包含的值(EDX高位寄存器, EAX低位寄存器)进行比较, 如果目标值和EDX:EAX
对中的值相等, 就把EDX:EAX对中的64位值传递给内存位置, 如果不匹配就把
内存地址中的值加载到EDX:EAX对中



4, 堆栈
ESP 寄存器保存了当前堆栈的起始位置, 当一个数据压入栈时, 它就会自动递减,
反之其自动递增

压入堆栈操作:
pushx source, x取值为:
l 32位长字
w 16位字

弹出堆栈操作:
popx source
其中source必须是16或32位寄存器或者内存位置, 当pop最后一个元素时ESP值应该
和以前的相等


5,压入和弹出所有寄存器
pusha/popa 压入或者弹出所有16位通用寄存器
pushad/popad 压入或者弹出所有32位通用寄存器
pushf/popf 压入或者弹出EFLAGS寄存器的低16位
pushfd/popfd 压入或者弹出EFLAGS寄存器的全部32位


6,数据地址对齐
gas 汇编器支持.align 命令, 它用于在特定的内存边界对准定义的数据元素, 在数据段
中.align命令紧贴在数据定义的前面




################################################################################################
# 五,控制流程
################################################################################################
无条件跳转:
1, 跳转
jmp location 其中location为要跳转到的内存地址, 在汇编中为定义的标签

2,调用
调用指令分为两个部分:
1, 调用call address 跳转到指定位置
2, 返回指令ret, 它没有参数紧跟在call指令后面的位置

执行call指令时,它把EIP的值放到堆栈中, 然后修改EIP以指向被调用的函数地址, 当被调用
函数完成后, 它从堆栈获取过去的EIP的值, 并把控制权返还给原始程序。

3,中断
由硬件设备生成中断。 程序生成软件中断
当一个程序产生中断调用时, 发出调用的程序暂停, 被调用的程序接替它运行, 指令指针被转移到
被调用的函数地址, 当调用完成时使用中断返回指令可以返回调原始程序。



条件跳转:
条件跳转按照EFLAGS中的值来判断是否该跳转, 格式为:

jxx address, 其中xx是1-3个字符的条件代码, 取值如下:
a 大于时跳转
ae 大于等于
b 小于
be 小于等于
c 进位
cxz 如果CX寄存器为0
ecxz 如果ECS寄存器为0
e 相等
na 不大于
nae 不大于或者等于
nb 不小于
nbe 不小于或等于
nc 无进位
ne 不等于
g 大于(有符号)
ge 大于等于(有符号)
l 小于(有符号)
le 小于等于(有符号)
ng 不大于(有符号)
nge 不大于等于(有符号)
nl 不小于
nle 不小于等于
no 不溢出
np 不奇偶校验
ns 无符号
nz 非零
o 溢出
p 奇偶校验
pe 如果偶校验
po 如果奇校验
s 如果带符号
z 如果为零


条件跳转不支持分段内存模型下的远跳转, 如果在该模式下进行
程序设计必须使用程序逻辑确定条件是否存在, 然后实现无条件
跳转, 跳转前必须设置EFLAGS寄存器



比较:
cmp operend1, operend2


进位标志修改指令:
CLC 清空进位标志(设置为0)
CMC 对进位标志求反(把它改变为相反的值)
STC 设置进位标志(设置为1)


循环:
loop 循环直到ECX寄存器为0
loope/loopz 循环直到ecx寄存器为0 或者没有设置ZF标志
loopne/loopnz 循环直到ecx为0或者设置了ZF标志

指令格式为: loopxx address 注意循环指令只支持8位偏移地址



################################################################################################
# 六,数字
################################################################################################

IA-32平台中存储超过一字节的数都被存储为小尾数的形式但是把数字传递给寄存器时, 寄存器里面保存是按照大尾数
的形式存储


把无符号数转换成位数更大的值时, 必须确保所有的高位部分都被设置为零

把有符号数转换成位数更大的数时:
intel 提供了movsx指令它允许扩展带符号数并保留符号, 它与movzx相似, 但是它假设要传送的字节是带符号数形式


浮点数:
fld 指令用于把浮点数字传送入和传送出FPU寄存器, 格式:
fld source
其中source可以为32 64或者80位整数值


IA-32使用FLD指令用于把存储在内存中的单精度和双精度浮点值FPU寄存器堆栈中, 为了区分这两种长度GNU汇编器使用
FLDS加载单精度浮点数, FLDL加载双精度浮点数

类似FST用于获取FPU寄存器堆栈中顶部的值, 并且把这个值放到内存位置中, 对于单精度使用FSTS, 对于双精度使用FSTL



################################################################################################
# 七,基本数学运算
################################################################################################
1, 加法
ADD source, destination 把两个整数相加
其中source可以是立即数内存或者寄存器, destination可以是内存或者寄存器, 但是两者不能同时都是内存位置


ADC 和ADD相似进行加法运算, 但是它把前一个ADD指令的产生进位标志的值包含在其中, 在处理位数大于32(如64)
位的整数时, 该指令非常有用

2, 减法
SUB source, destination 把两个整数相减
NEG 它生成值的补码
SBB 指令, 和加法操作一样, 可以使用进位情况帮助执行大的无符号数值的减法运算. SBB在多字节减法操作中利用
进位和溢出标志实现跨数据边界的的借位特性

3,递增和递减
dec destination 递减
inc destination 递增

其中dec和inc指令都不会影响进位标志, 所以递增或递减计数器的值都不会影响程序中涉及进位标志的其他任何运算

4, 乘法
mul source 进行无符号数相乘
它使用隐含的目标操作数, 目标位置总是使用eax的某种形式, 这取决与源操作数的长度, 因此根据源操作数的长度,
目标操作数必须放在AL, AX, EAX中。 此外由于乘法可能产生很大的值, 目标位置必须是源操作数的两倍位置, 源为
8时, 应该是16, 源为16时, 应该为32, 但是当源为16位时intel为了向下兼容, 目标操作数不是存放在eax中, 而
是分别存放在DX:AX中, 结果高位存储在DX中, 地位存储在AX中。对于32位的源, 目标操作数存储在EDX:EAX中, 其中
EDX存储的是高32位, EAX存储的是低32位

imul source 进行有符号数乘法运算, 其中的目标操作数和mul的一样

imul source, destination 也可以执行有符号乘法运算, 但是此时可以把目标放在指定的位置, 使用这种格式的缺陷
在与乘法的操作结果被限制为单一目标寄存器的长度.

imul multiplier, source, destination
其中multiplier是一个立即数, 这种方式允许一个值与给定的源操作数进行快速的乘法运算, 然后把结果存储在通用
寄存器中


5, 除法
div divisor 执行无符号数除法运算
除数的最大值取决与被除数的长度, 对于16位被除数 ,除数只能为8位, 32或64位同上
被除数 被除数长度 商 余数
AX 16位 AL AH
DX:AX 32位 AX DX
EDX:EAX 64位 EAX EDX

idiv divisor 执行有符号数的除法运算, 方式和div一样

6, 移位
左移位:
sal 向左移位
sal destination 把destination向左移动1位
sal %cl, destination 把destination的值向左移动CL寄存器中指定的位数
sal shifter, destination 把destination的值向左移动shifter值指定的位数
向左移位可以对带符号数和无符号数执行向左移位的操作, 移位造成的空位用零填充, 移位造成的超过数据长度的任何位
都被存放在进位标志中, 然后在下一次移位操作中被丢弃

右移位:
shr向右移位
sar向右移位
SHR指令清空移位造成的空位, 所以它只能对无符号数进行移位操作
SAR指令根据整数的符号位, 要么清空, 要么设置移位造成的空位, 对于负数, 空位被设置为1

循环移位:
和移位指令类似, 只不过溢出的位被存放回值的另一端, 而不是丢弃
ROL 向左循环移位
ROR 向右循环移位
RCL 向左循环移位, 并且包含进位标志
RCR 向右循环移位, 并且包含进位标志

7, 逻辑运算
AND OR XOR
这些指令使用相同的格式:
and source, destination
其中source可以是8位 16 位或者32位的立即值 寄存器或内存中的值, destination可以是8位 16 位或者
32位寄存器或内存中的值, 不能同时使用内存值作为源和目标。 布尔逻辑功能对源和目标执行按位操作。
也就是说使用指定的逻辑功能按照顺序对数据的元素的每个位进行单独比较。

NOT指令使用单一操作数, 它即是源值也是目标结果的位置
清空寄存器的最高效方式是使用OR指令对寄存器和它本身进行异或操作.当和本身进行XOR操作时, 每个设置为
1的位就变为0, 每个设置为0的位也变位0。

位测试可以使用以上的逻辑运算指令, 但这些指令会修改destination的值, 因此intel提供了test指令, 它不
会修改目标值而是设置相应的标志



################################################################################################
# 八,字符串处理
################################################################################################
1, 传送字符串
movs 有三种格式
movsb 传送单一字节
movsw 传送一个字
movsl 传送双字

movs指令使用隐含的源和目的操作数, 隐含的源操作数是ESI, 隐含的目的操作数是EDI, 有两种方式加载内存地址到
ESI和EDI, 第一种是使用标签间接寻址 movl $output, %ESI, 第二种是使用lea指令, lea指令加载对象的地址到指定
的目的操作数如lea output, %esi, 每次执行movs指令后, 数据传送后ESI和EDI寄存器会自动改变,为另一次传送做
准备, ESI和EDI可能随着标志DF的不同自动递增或者自动递减, 如果DF标志为0则movs指令后ESI和EDI会递增, 反之会
递减, 为了设置DF标志, 可以使用一下指令:
CLD 将DF标志清零
STD 设置DF标志

2,rep前缀
REP 指令的特殊之处在与它不执行什么操作, 这条指令用于按照特定次数重复执行字符串指令, 有ECX寄存器控制,
但不需要额外的loop指令, 如rep movsl

rep的其他格式:
repe 等于时重复
repne 不等于时重复
repnz 不为零时重复
repz 为零时重复

3, 存储和加载字符串
LODS 加载字符串, ESI为源, 当一次执行完lods时会递增或递减ESI寄存器, 然后把字符串值存放到EAX中

STOS 使用lods把字符串值加载到EAX后, 可以使用它把EAX中的值存储到内存中去:
stos使用EDI作为目的操作数, 执行stos指令后, 会根据DF的值自动递增或者递减EDI中的值

4, 比较字符串
cmps 和其他的操作字符串的指令一样, 隐含的源和目标操作数都为ESI和EDI, 每次执行时都会根据DF的值把
ESI和EDI递增或者递减, cmps指令从目标字符串中减去源字符串, 执行后会设置EFLAGS寄存器的状态.


5,扫描字符串
scas 把EDI作为目标, 它把EDI中的字符串和EAX中的字符串进行比较 ,然后根据DF的值递增或者递减EDI



################################################################################################
# 九,使用函数
################################################################################################
GNU汇编语言定义函数的语法:
.type 标签(也就是函数名), @function
ret 返回到调用处



################################################################################################
# 十,linux系统调用
################################################################################################
linux系统调用的中断向量为0x80

1, 系统调用标识存放在%eax中
2, 系统调用输入值:
EBX 第一个参数
ECX 第二个参数
EDX 第三个参数
ESI 第四个参数
EDI 第五个参数

需要输入超过6个输入参数的系统调用, EBX指针用于保存指向输入参数内存位置的指针, 输入参数按照连续的的顺序
存储, 系统调用的返回值存放在EAX中

################################################################################################
# 十一,汇编语言的高级功能
################################################################################################
1,gnu内联汇编的语法:
asm或__asm__("汇编代码");
指令必须包含在引号里
如果包含的指令超过一行 必须使用新行分隔符分隔


使用c全局变量, 不能在内联汇编中使用局部变量, 注意在汇编语言代码中值被用做内存位置, 而不是立即数值

如果不希望优化内联汇编, 则可以volatile修饰符如:__asm__ volatile("code");

2,GCC内联汇编的扩展语法
__asm__("assembly code":output locations:input operands:changed registers);
第一部分是汇编代码
第二部分是输出位置, 包含内联汇编代码的输出值的寄存器和内存位置列表
第三部分是输入操作数,包含内联汇编代码输入值的寄存器和内存位置的列表
第四部分是改动的寄存器, 内联汇编改变的任何其他寄存器的列表
这几个部分可以不全有, 但是没有的还必须使用:分隔

1, 指定输入值和输出值, 输入值和输出值的列表格式为:
"constraint"(variable), 其中variable是程序中声明的c变量, 在扩展asm格式中, 局部和全局变量都可以使用,
使用constrant(约束)定义把变量存放到哪(输入)或从哪里传送变量(输出)
约束使用单一的字符, 如下:
约束 描述
a 使用%eax, %ax, %al寄存器
b 使用%ebx, %bx, %bl寄存器
c 使用%ecx, %cx, %cl寄存器
d 使用%edx, %dx, %dl寄存器
S 使用%esi, %si寄存器
D 使用%edi, %di寄存器
r 使用任何可用的通用寄存器
q 使用%eax, %ebx, %ecx,%edx之一
A 对于64位值使用%eax, %edx寄存器
f 使用浮点寄存器
t 使用第一个(顶部)的浮点寄存器
u 使用第二个浮点寄存器
m 使用变量的内存位置
o 使用偏移内存位置
V 只使用直接内存位置
i 使用立即整数值
n 使用值已知的立即整数值
g 使用任何可用的寄存器和内存位置


除了这些约束之外, 输出值还包含一个约束修饰符:
输出修饰符 描述
+ 可以读取和写入操作数
= 只能写入操作数
% 如果有必要操作数可以和下一个操作数切换
& 在内联函数完成之前, 可以删除和重新使用操作数

如:
__asm__("assembly code": "=a"(result):"d"(data1),"c"(data2));
把c变量data1存放在edx寄存器中, 把c变量data2存放到ecx寄存器中, 内联汇编的结果
将存放在eax寄存器中, 然后传送给变量result


在扩展的asm语句块中如果要使用寄存器必须使用两个百分号符号


不一定总要在内联汇编代码中指定输出值, 一些汇编指令假定输入值包含输出值, 如movs指令


其他扩展内联汇编知识:
1, 使用占位符
输入值存放在内联汇编段中声明的特定寄存器中, 并且在汇编指令中专门使用这些寄存器.
虽然这种方式能够很好的处理只有几个输入值的情况, 但对于需要很多输入值的情况, 这
中方式显的有点繁琐. 为了帮助解决这个问题, 扩展asm格式提供了占位符, 可以在内联
汇编代码中使用它引用输入和输出值.

占位符是前面加上百分号的数字, 按照内联汇编中列出的每个输入和输出值在列表中的位置,
每个值被赋予从0开始的地方. 然后就可以在汇编代码中引用占位符来表示值。


如果内联汇编代码中的输入和输出值共享程序中相同的c变量, 则可以指定使用占位符作为
约束值, 如:
__asm__("imull %1, %0"
: "=r"(data2)
: "r"(data1), "0"(data2));
如输入输出值中共享相同的变量data2, 而在输入变量中则可以使用标记0作为输入参数的约束

2, 替换占位符
如果处理很多输入和输出值, 数字型的占位符很快就会变的很混乱, 为了使条理清晰 ,GNU汇编
器(从版本3.1开始)允许声明替换的名称作为占位符.替换的名称在声明输入值和输出值的段中
定义, 格式如下:
%[name]"constraint"(variable)
定义的值name成为内联汇编代码中变量的新的占位符号标识, 如下面的例子:
__asm__("imull %[value1], %[value2]"
: [value2] "=r"(data2)
: [value1] "r"(data1), "0"(data2));

3, 改动寄存器列表
编译器假设输入值和输出值使用的寄存器会被改动, 并且相应的作出处理。程序员不需要在改动的
寄存器列表中包含这些值, 如果这样做了, 就会产生错误消息. 注意改动的寄存器列表中的寄存器
使用完整的寄存器名称, 而不像输入和输出寄存器定义的那样仅仅是单一字母。 在寄存器名称前面
使用百分号符号是可选的。

改动寄存器列表的正确使用方法是, 如果内联汇编代码使用了没有被初始化地声明为输入或者输出
值的其他任何寄存器 , 则要通知编译器。编译器必须知道这些寄存器, 以避免使用他们。如:
int main(void) {
int data1 = 10;
int result = 20;

__asm__("movl %1, %%eax\n\t"
"addl %%eax, %0"
: "=r"(result)
: "r"(data1), "0"(result)
: "%eax");
printf("The result is %d\n", result);
return 0;
}

4, 使用内存位置
虽然在内联汇编代码中使用寄存器比较快, 但是也可以直接使用c变量的内存位置。 约束m用于引用输入值
和输出值中的内存位置。 记住, 对于要求使用寄存器的汇编指令, 仍然必须使用寄存器, 所以不得不定义
保存数据的中间寄存器。如:
int main(void) {
int dividentd = 20;
int divisor = 5;
int result;

__asm__("divb %2\n\t"
"movl %%eax, %0"
: "=m"(result)
: "a"(dividend), "m"(divisor));
printf("The result is %d\n", result);
return 0;
}

5, 处理跳转
内联汇编语言代码也可以包含定义其中位置的标签。 可以实现一般的汇编条件分支和无条件分支, 如:
int main(void) {
int a = 10;
int b = 20;
int result;

__asm__("cmp %1, %2\n\t"
"jge greater\n\t"
"movl %1, %0\n\t"
"jmp end\n"
"greater:\n\t"
"movl %2, %0\n"
"end:"
:"=r"(result)
:"r"(a), "r"(b));
printf("The larger value is %d\n", result);
return 0;
}

在内联汇编代码中使用标签时有两个限制。 第一个限制是只能跳转到相同的asm段内的标签,
不能从-个asm段跳转到另一个asm段中的标签。第二个限制更加复杂一点。 以上程序使用
标签greater和end。 但是, 这样有个潜在的问题, 查看汇编后的代码清单, 可以发现内联
汇编标签也被编码到了最终汇编后的代码中。 这意味着如果在c代码中还有另一个asm段, 就
不能再次使用相同的标签, 否则会因为标签重复使用而导致错误消息。还有如果试图整合使用
c关键字(比如函数名称或者全局变量)的标签也会导致错误。



################################################################################################
# 十二,优化你的代码
################################################################################################
GNU编译器提供-O选项供程序优化使用:
-O 提供基础级别的优化
-O2 提供更加高级的代码优化
-O3 提供最高级的代码优化
不同的优化级别使用的优化技术也可以单独的应用于代码。 可以使用-f命令行选项引用每个
单独的优化技术。

1, 编译器优化级别1
在优化的第一个级别执行基础代码的优化。 这个级别试图执行9种单独的优化功能:
-fdefer-pop: 这种优化技术与汇编语言代码在函数完成时如何进行操作有关。 一般
情况下, 函数的输入值被保存在堆栈种并且被函数访问。 函数返回时, 输入值还在
堆栈种。 一般情况下, 函数返回之后, 输入值被立即弹出堆栈。这样做会使堆栈种
的内容有些杂乱。

-fmerge-constans: 使用这种优化技术, 编译器试图合并相同的常量. 这一特性有
时候会导致很长的编译时间, 因为编译器必须分析c或者c++程序中用到的每个常量,
并且相互比较他们.

-fthread-jumps: 使用这种优化技术与编译器如果处理汇编代码中的条件和非条件
分支有关。 在某些情况下, 一条跳转指令可能转移到另一条分支语句。 通过一连串
跳转, 编译器确定多个跳转之间的最终目标并且把第一个跳转重新定向到最终目标。

-floop-optimize: 通过优化如何生成汇编语言中的循环, 编译器可以在很大程序上
提高应用程序的性能。 通常, 程序由很多大型且复杂的循环构成。 通过删除在循环
内没有改变值的变量赋值操作, 可以减少循环内执行指令的数量, 在很大程度上提高
性能。 此外优化那些确定何时离开循环的条件分支, 以便减少分支的影响。

-fif-conversion: if-then语句应该是应用程序中仅次于循环的最消耗时间的部分。
简单的if-then语句可能在最终的汇编语言代码中产生众多的条件分支。 通过减少
或者删除条件分支, 以及使用条件传送 设置标志和使用运算技巧来替换他们, 编译
器可以减少if-then语句中花费的时间量。

-fif-conversion2: 这种技术结合更加高级的数学特性, 减少实现if-then语句所
需的条件分支。

-fdelayed-branch: 这种技术试图根据指令周期时间重新安排指令。 它还试图把
尽可能多的指令移动到条件分支前, 以便最充分的利用处理器的治理缓存。

-fguess-branch-probability: 就像其名称所暗示的, 这种技术试图确定条件分支最可
能的结果, 并且相应的移动指令, 这和延迟分支技术类似。 因为在编译时预测代码的安排,
所以使用这一选项两次编译相同的c或者c++代码很可能会产生不同的汇编语言代码, 这取决
于编译时编译器认为会使用那些分支。 因为这个原因, 很多程序员不喜欢采用这个特性, 并且
专门地使用-fno-guess-branch-probability选项关闭这个特性

-fcprop-registers: 因为在函数中把寄存器分配给变量, 所以编译器执行第二次检查以便减少
调度依赖性(两个段要求使用相同的寄存器)并且删除不必要的寄存器复制操作。

2, 编译器优化级别2
结合了第一个级别的所有优化技术, 再加上一下一些优化:
-fforce-mem: 这种优化再任何指令使用变量前, 强制把存放再内存位置中的所有变量都复制到寄存器
中。 对于只涉及单一指令的变量, 这样也许不会有很大的优化效果. 但是对于再很多指令(必须数学操作)
中都涉及到的变量来说, 这会时很显著的优化, 因为和访问内存中的值相比 ,处理器访问寄存器中的值要
快的多。

-foptimize-sibling-calls: 这种技术处理相关的和/或者递归的函数调用。 通常, 递归的函数调用
可以被展开为一系列一般的指令, 而不是使用分支。 这样处理器的指令缓存能够加载展开的指令并且
处理他们, 和指令保持为需要分支操作的单独函数调用相比, 这样更快。

-fstrength-reduce: 这种优化技术对循环执行优化并且删除迭代变量。 迭代变量是捆绑到循环计数器
的变量, 比如使用变量, 然后使用循环计数器变量执行数学操作的for-next循环。

-fgcse: 这种技术对生成的所有汇编语言代码执行全局通用表达式消除历程。 这些优化操作试图分析
生成的汇编语言代码并且结合通用片段, 消除冗余的代码段。如果代码使用计算性的goto, gcc指令推荐
使用-fno-gcse选项。

-fcse-follow-jumps: 这种特别的通用子表达式消除技术扫描跳转指令, 查找程序中通过任何其他途径都不会到达的目标代码。 这种情况最常见的例子就式if-then-else语句的else部分。

-frerun-cse-after-loop: 这种技术在对任何循环已经进行过优化之后重新运行通用子表达式消除例程。
这样确保在展开循环代码之后更进一步地优化还编代码。

-fdelete-null-pointer-checks: 这种优化技术扫描生成的汇编语言代码, 查找检查空指针的代码。 编译器假设间接引用空指针将停止程序。 如果在间接引用之后检查指针, 它就不可能为空。

-fextensive-optimizations: 这种技术执行从编译时的角度来说代价高昂的各种优化技术,但是它可能
对运行时的性能产生负面影响。

-fregmove: 编译器试图重新分配mov指令中使用的寄存器, 并且将其作为其他指令操作数, 以便最大化
捆绑的寄存器的数量。

-fschedule-insns: 编译器将试图重新安排指令, 以便消除等待数据的处理器。 对于在进行浮点运算时有
延迟的处理器来说, 这使处理器在等待浮点结果时可以加载其他指令。

-fsched-interblock: 这种技术使编译器能够跨越指令块调度指令。 这可以非常灵活地移动指令以便等待
期间完成的工作最大化。

-fcaller-saves: 这个选项指示编译器对函数调用保存和恢复寄存器, 使函数能够访问寄存器值, 而且不必
保存和恢复他们。 如果调用多个函数, 这样能够节省时间, 因为只进行一次寄存器的保存和恢复操作, 而
不是在每个函数调用中都进行。

-fpeephole2: 这个选项允许进行任何计算机特定的观察孔优化。

-freorder-blocks: 这种优化技术允许重新安排指令块以便改进分支操作和代码局部性。

-fstrict-aliasing: 这种技术强制实行高级语言的严格变量规则。 对于c和c++程序来说, 它确保不在数据类型之间共享变量. 例如, 整数变量不和单精度浮点变量使用相同的内存位置。

-funit-at-a-time: 这种优化技术指示编译器在运行优化例程之前读取整个汇编语言代码。 这使编译器可以
重新安排不消耗大量时间的代码以便优化指令缓存。 但是, 这会在编译时花费相当多的内存, 对于小型计算机可能
是一个问题。

-falign-functions: 这个选项用于使函数对准内存中特定边界的开始位置。 大多数处理器按照页面读取内存,并且确保全部函数代码位于单一内存页面内, 就不需要叫化代码所需的页面。

-fcrossjumping: 这是对跨越跳转的转换代码处理, 以便组合分散在程序各处的相同代码。 这样可以减少
代码的长度, 但是也许不会对程序性能有直接影响。

3, 编译器优化级别3
它整合了第一和第二级别中的左右优化技巧, 还包括一下优化:
-finline-functions: 这种优化技术不为函数创建单独的汇编语言代码, 而是把函数代码包含在调度程序的代码中。 对于多次被调用的函数来说, 为每次函数调用复制函数代码。 虽然这样对于减少代码长度不利, 但是通过最充分的利用指令缓存代码, 而不是在每次函数调用时进行分支操作, 可以提高性能。

-fweb: 构建用于保存变量的伪寄存器网络。 伪寄存器包含数据, 就像他们是寄存器一样, 但是可以使用各种其他优化技术进行优化, 比如cse和loop优化技术。

-fgcse-after-reload: 这中技术在完全重新加载生成的且优化后的汇编语言代码之后执行第二次gcse优化,帮助消除不同优化方式创建的任何冗余段。

Linux 汇编语言开发指南

汇编语言的优点是速度快,可以直接对硬件进行操作,这对诸如图形处理等关键应用是非常重要的。Linux 是一个用 C 语言开发的操作系统,这使得很多程序员开始忘记在 Linux 中还可以直接使用汇编这一底层语言来优化程序的性能。本文为那些在Linux 平台上编写汇编代码的程序员提供指南,介绍 Linux 汇编语言的语法格式和开发工具,并辅以具体的例子讲述如何开发实用的Linux 汇编程序。

一、简介

作为最基本的编程语言之一,汇编语言虽然应用的范围不算很广,但重要性却勿庸置疑,因为它能够完成许多其它语言所无法完成的功能。就拿 Linux 内核来讲,虽然绝大部分代码是用 C 语言编写的,但仍然不可避免地在某些关键地方使用了汇编代码,其中主要是在 Linux 的启动部分。由于这部分代码与硬件的关系非常密切,即使是 C 语言也会有些力不从心,而汇编语言则能够很好扬长避短,最大限度地发挥硬件的性能。

大多数情况下 Linux 程序员不需要使用汇编语言,因为即便是硬件驱动这样的底层程序在 Linux 操作系统中也可以用完全用 C 语言来实现,再加上 GCC 这一优秀的编译器目前已经能够对最终生成的代码进行很好的优化,的确有足够的理由让我们可以暂时将汇编语言抛在一边了。但实现情况是 Linux 程序员有时还是需要使用汇编,或者不得不使用汇编,理由很简单:精简、高效和 libc 无关性。假设要移植 Linux 到某一特定的嵌入式硬件环境下,首先必然面临如何减少系统大小、提高执行效率等问题,此时或许只有汇编语言能帮上忙了。

汇编语言直接同计算机的底层软件甚至硬件进行交互,它具有如下一些优点:

  • 能够直接访问与硬件相关的存储器或 I/O 端口;
  • 能够不受编译器的限制,对生成的二进制代码进行完全的控制;
  • 能够对关键代码进行更准确的控制,避免因线程共同访问或者硬件设备共享引起的死锁;
  • 能够根据特定的应用对代码做最佳的优化,提高运行速度;
  • 能够最大限度地发挥硬件的功能。

同时还应该认识到,汇编语言是一种层次非常低的语言,它仅仅高于直接手工编写二进制的机器指令码,因此不可避免地存在一些缺点:

  • 编写的代码非常难懂,不好维护;
  • 很容易产生 bug,难于调试;
  • 只能针对特定的体系结构和处理器进行优化;
  • 开发效率很低,时间长且单调。

Linux 下用汇编语言编写的代码具有两种不同的形式。第一种是完全的汇编代码,指的是整个程序全部用汇编语言编写。尽管是完全的汇编代码,Linux 平台下的汇编工具也吸收了 C 语言的长处,使得程序员可以使用 #include、#ifdef 等预处理指令,并能够通过宏定义来简化代码。第二种是内嵌的汇编代码,指的是可以嵌入到C语言程序中的汇编代码片段。虽然 ANSI 的 C 语言标准中没有关于内嵌汇编代码的相应规定,但各种实际使用的 C 编译器都做了这方面的扩充,这其中当然就包括 Linux 平台下的 GCC。



回页首

二、Linux 汇编语法格式

绝大多数 Linux 程序员以前只接触过DOS/Windows 下的汇编语言,这些汇编代码都是 Intel 风格的。但在 Unix 和 Linux 系统中,更多采用的还是 AT&T 格式,两者在语法格式上有着很大的不同:

  1. 在 AT&T 汇编格式中,寄存器名要加上 '%' 作为前缀;而在 Intel 汇编格式中,寄存器名不需要加前缀。例如:

    AT&T 格式 Intel 格式
    pushl %eax push eax
  2. 在 AT&T 汇编格式中,用 '$' 前缀表示一个立即操作数;而在 Intel 汇编格式中,立即数的表示不用带任何前缀。例如:

    AT&T 格式 Intel 格式
    pushl $1 push 1
  3. AT&T 和 Intel 格式中的源操作数和目标操作数的位置正好相反。在 Intel 汇编格式中,目标操作数在源操作数的左边;而在 AT&T 汇编格式中,目标操作数在源操作数的右边。例如:

    AT&T 格式 Intel 格式
    addl $1, %eax add eax, 1
  4. 在 AT&T 汇编格式中,操作数的字长由操作符的最后一个字母决定,后缀'b'、'w'、'l'分别表示操作数为字节(byte,8 比特)、字(word,16 比特)和长字(long,32比特);而在 Intel 汇编格式中,操作数的字长是用 "byte ptr" 和 "word ptr" 等前缀来表示的。例如:

    AT&T 格式 Intel 格式
    movb val, %al mov al, byte ptr val
  5. 在 AT&T 汇编格式中,绝对转移和调用指令(jump/call)的操作数前要加上'*'作为前缀,而在 Intel 格式中则不需要。
  6. 远程转移指令和远程子调用指令的操作码,在 AT&T 汇编格式中为 "ljump" 和 "lcall",而在 Intel 汇编格式中则为 "jmp far" 和 "call far",即:

    AT&T 格式 Intel 格式
    ljump $section, $offset jmp far section:offset
    lcall $section, $offset call far section:offset

    与之相应的远程返回指令则为:

    AT&T 格式 Intel 格式
    lret $stack_adjust ret far stack_adjust
  7. 在 AT&T 汇编格式中,内存操作数的寻址方式是

    section:disp(base, index, scale)
    

    而在 Intel 汇编格式中,内存操作数的寻址方式为:

    section:[base + index*scale + disp]
    

    由于 Linux 工作在保护模式下,用的是 32 位线性地址,所以在计算地址时不用考虑段基址和偏移量,而是采用如下的地址计算方法:

    disp + base + index * scale
    

    下面是一些内存操作数的例子:

    AT&T 格式 Intel 格式
    movl -4(%ebp), %eax mov eax, [ebp - 4]
    movl array(, %eax, 4), %eax mov eax, [eax*4 + array]
    movw array(%ebx, %eax, 4), %cx mov cx, [ebx + 4*eax + array]
    movb $4, %fs:(%eax) mov fs:eax, 4



回页首

三、Hello World!

真不知道打破这个传统会带来什么样的后果,但既然所有程序设计语言的第一个例子都是在屏幕上打印一个字符串 "Hello World!",那我们也以这种方式来开始介绍 Linux 下的汇编语言程序设计。

在 Linux 操作系统中,你有很多办法可以实现在屏幕上显示一个字符串,但最简洁的方式是使用 Linux 内核提供的系统调用。使用这种方法最大的好处是可以直接和操作系统的内核进行通讯,不需要链接诸如 libc 这样的函数库,也不需要使用 ELF 解释器,因而代码尺寸小且执行速度快。

Linux 是一个运行在保护模式下的 32 位操作系统,采用 flat memory 模式,目前最常用到的是 ELF 格式的二进制代码。一个 ELF 格式的可执行程序通常划分为如下几个部分:.text、.data 和 .bss,其中 .text 是只读的代码区,.data 是可读可写的数据区,而 .bss 则是可读可写且没有初始化的数据区。代码区和数据区在 ELF 中统称为 section,根据实际需要你可以使用其它标准的 section,也可以添加自定义 section,但一个 ELF 可执行程序至少应该有一个 .text 部分。 下面给出我们的第一个汇编程序,用的是 AT&T 汇编语言格式:

例1. AT&T 格式

#hello.s 
.data                    # 数据段声明
        msg : .string "Hello, world!\\n" # 要输出的字符串
        len = . - msg                   # 字串长度
.text                    # 代码段声明
.global _start           # 指定入口函数
        
_start:                  # 在屏幕上显示一个字符串
        movl $len, %edx  # 参数三:字符串长度
        movl $msg, %ecx  # 参数二:要显示的字符串
        movl $1, %ebx    # 参数一:文件描述符(stdout) 
        movl $4, %eax    # 系统调用号(sys_write) 
        int  $0x80       # 调用内核功能
        
                         # 退出程序
        movl $0,%ebx     # 参数一:退出代码
        movl $1,%eax     # 系统调用号(sys_exit) 
        int  $0x80       # 调用内核功能

初次接触到 AT&T 格式的汇编代码时,很多程序员都认为太晦涩难懂了,没有关系,在 Linux 平台上你同样可以使用 Intel 格式来编写汇编程序:

例2. Intel 格式

; hello.asm 
section .data            ; 数据段声明
        msg db "Hello, world!", 0xA     ; 要输出的字符串
        len equ $ - msg                 ; 字串长度
section .text            ; 代码段声明
global _start            ; 指定入口函数
_start:                  ; 在屏幕上显示一个字符串
        mov edx, len     ; 参数三:字符串长度
        mov ecx, msg     ; 参数二:要显示的字符串
        mov ebx, 1       ; 参数一:文件描述符(stdout) 
        mov eax, 4       ; 系统调用号(sys_write) 
        int 0x80         ; 调用内核功能
                         ; 退出程序
        mov ebx, 0       ; 参数一:退出代码
        mov eax, 1       ; 系统调用号(sys_exit) 
        int 0x80         ; 调用内核功能

上面两个汇编程序采用的语法虽然完全不同,但功能却都是调用 Linux 内核提供的 sys_write 来显示一个字符串,然后再调用 sys_exit 退出程序。在 Linux 内核源文件 include/asm-i386/unistd.h 中,可以找到所有系统调用的定义。



回页首

四、Linux 汇编工具

Linux 平台下的汇编工具虽然种类很多,但同 DOS/Windows 一样,最基本的仍然是汇编器、连接器和调试器。

1.汇编器

汇编器(assembler)的作用是将用汇编语言编写的源程序转换成二进制形式的目标代码。Linux 平台的标准汇编器是 GAS,它是 GCC 所依赖的后台汇编工具,通常包含在 binutils 软件包中。GAS 使用标准的 AT&T 汇编语法,可以用来汇编用 AT&T 格式编写的程序:

[xiaowp@gary code]$ as -o hello.o hello.s

Linux 平台上另一个经常用到的汇编器是 NASM,它提供了很好的宏指令功能,并能够支持相当多的目标代码格式,包括 bin、a.out、coff、elf、rdf 等。NASM 采用的是人工编写的语法分析器,因而执行速度要比 GAS 快很多,更重要的是它使用的是 Intel 汇编语法,可以用来编译用 Intel 语法格式编写的汇编程序:

[xiaowp@gary code]$ nasm -f elf hello.asm

2.链接器

由汇编器产生的目标代码是不能直接在计算机上运行的,它必须经过链接器的处理才能生成可执行代码。链接器通常用来将多个目标代码连接成一个可执行代码,这样可以先将整个程序分成几个模块来单独开发,然后才将它们组合(链接)成一个应用程序。 Linux 使用 ld 作为标准的链接程序,它同样也包含在 binutils 软件包中。汇编程序在成功通过 GAS 或 NASM 的编译并生成目标代码后,就可以使用 ld 将其链接成可执行程序了:

[xiaowp@gary code]$ ld -s -o hello hello.o

3.调试器

有人说程序不是编出来而是调出来的,足见调试在软件开发中的重要作用,在用汇编语言编写程序时尤其如此。Linux 下调试汇编代码既可以用 GDB、DDD 这类通用的调试器,也可以使用专门用来调试汇编代码的 ALD(Assembly Language Debugger)。

从调试的角度来看,使用 GAS 的好处是可以在生成的目标代码中包含符号表(symbol table),这样就可以使用 GDB 和 DDD 来进行源码级的调试了。要在生成的可执行程序中包含符号表,可以采用下面的方式进行编译和链接:

[xiaowp@gary code]$ as --gstabs -o hello.o hello.s
[xiaowp@gary code]$ ld -o hello hello.o

执行 as 命令时带上参数 --gstabs 可以告诉汇编器在生成的目标代码中加上符号表,同时需要注意的是,在用 ld 命令进行链接时不要加上 -s 参数,否则目标代码中的符号表在链接时将被删去。

在 GDB 和 DDD 中调试汇编代码和调试 C 语言代码是一样的,你可以通过设置断点来中断程序的运行,查看变量和寄存器的当前值,并可以对代码进行单步跟踪。图1 是在 DDD 中调试汇编代码时的情景:


图1 用 DDD 中调试汇编程序

汇编程序员通常面对的都是一些比较苛刻的软硬件环境,短小精悍的ALD可能更能符合实际的需要,因此下面主要介绍一下如何用ALD来调试汇编程序。首先在命令行方式下执行ald命令来启动调试器,该命令的参数是将要被调试的可执行程序:

[xiaowp@gary doc]$ ald hello
Assembly Language Debugger 0.1.3
Copyright (C) 2000-2002 Patrick Alken
hello: ELF Intel 80386 (32 bit), LSB, Executable, Version 1 (current)
Loading debugging symbols...(15 symbols loaded)
ald>

当 ALD 的提示符出现之后,用 disassemble 命令对代码段进行反汇编:

ald> disassemble -s .text
Disassembling section .text (0x08048074 - 0x08048096)
08048074  BA0F000000                 mov edx, 0xf
08048079  B998900408                 mov ecx, 0x8049098
0804807E  BB01000000                 mov ebx, 0x1
08048083  B804000000                 mov eax, 0x4
08048088  CD80                       int 0x80
0804808A  BB00000000                 mov ebx, 0x0
0804808F  B801000000                 mov eax, 0x1
08048094  CD80                       int 0x80

上述输出信息的第一列是指令对应的地址码,利用它可以设置在程序执行时的断点:

ald> break 0x08048088
Breakpoint 1 set for 0x08048088

断点设置好后,使用 run 命令开始执行程序。ALD 在遇到断点时将自动暂停程序的运行,同时会显示所有寄存器的当前值:

ald> run
Starting program: hello
Breakpoint 1 encountered at 0x08048088
eax = 0x00000004 ebx = 0x00000001 ecx = 0x08049098 edx = 0x0000000F
esp = 0xBFFFF6C0 ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
ds  = 0x0000002B es  = 0x0000002B fs  = 0x00000000 gs  = 0x00000000
ss  = 0x0000002B cs  = 0x00000023 eip = 0x08048088 eflags = 0x00000246
Flags: PF ZF IF
08048088  CD80                       int 0x80

如果需要对汇编代码进行单步调试,可以使用 next 命令:

ald> next
Hello, world!
eax = 0x0000000F ebx = 0x00000000 ecx = 0x08049098 edx = 0x0000000F
esp = 0xBFFFF6C0 ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
ds  = 0x0000002B es  = 0x0000002B fs  = 0x00000000 gs  = 0x00000000
ss  = 0x0000002B cs  = 0x00000023 eip = 0x0804808F eflags = 0x00000346
Flags: PF ZF TF IF
0804808F  B801000000                 mov eax, 0x1

若想获得 ALD 支持的所有调试命令的详细列表,可以使用 help 命令:

ald> help
Commands may be abbreviated.
If a blank command is entered, the last command is repeated.
Type `help <command>' for more specific information on <command>.
General commands
attach         clear          continue       detach         disassemble
enter          examine        file           help           load
next           quit           register       run            set
step           unload         window         write
Breakpoint related commands
break          delete         disable        enable         ignore
lbreak         tbreak



回页首

五、系统调用

即便是最简单的汇编程序,也难免要用到诸如输入、输出以及退出等操作,而要进行这些操作则需要调用操作系统所提供的服务,也就是系统调用。除非你的程序只完成加减乘除等数学运算,否则将很难避免使用系统调用,事实上除了系统调用不同之外,各种操作系统的汇编编程往往都是很类似的。

在 Linux 平台下有两种方式来使用系统调用:利用封装后的 C 库(libc)或者通过汇编直接调用。其中通过汇编语言来直接调用系统调用,是最高效地使用 Linux 内核服务的方法,因为最终生成的程序不需要与任何库进行链接,而是直接和内核通信。

和 DOS 一样,Linux 下的系统调用也是通过中断(int 0x80)来实现的。在执行 int 80 指令时,寄存器 eax 中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器 ebx,ecx,edx,esi,edi 中,当系统调用完成之后,返回值可以在寄存器 eax 中获得。

所有的系统调用功能号都可以在文件 /usr/include/bits/syscall.h 中找到,为了便于使用,它们是用 SYS_<name> 这样的宏来定义的,如 SYS_write、SYS_exit 等。例如,经常用到的 write 函数是如下定义的:

ssize_t write(int fd, const void *buf, size_t count);

该函数的功能最终是通过 SYS_write 这一系统调用来实现的。根据上面的约定,参数 fb、buf 和 count 分别存在寄存器 ebx、ecx 和 edx 中,而系统调用号 SYS_write 则放在寄存器 eax 中,当 int 0x80 指令执行完毕后,返回值可以从寄存器 eax 中获得。

或许你已经发现,在进行系统调用时至多只有 5 个寄存器能够用来保存参数,难道所有系统调用的参数个数都不超过 5 吗?当然不是,例如 mmap 函数就有 6 个参数,这些参数最后都需要传递给系统调用 SYS_mmap:

void  *  mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);

当一个系统调用所需的参数个数大于 5 时,执行int 0x80 指令时仍需将系统调用功能号保存在寄存器 eax 中,所不同的只是全部参数应该依次放在一块连续的内存区域里,同时在寄存器 ebx 中保存指向该内存区域的指针。系统调用完成之后,返回值仍将保存在寄存器 eax 中。

由于只是需要一块连续的内存区域来保存系统调用的参数,因此完全可以像普通的函数调用一样使用栈(stack)来传递系统调用所需的参数。但要注意一点,Linux 采用的是 C 语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即最后一个参数先入栈,而第一个参数则最后入栈。如果采用栈来传递系统调用所需的参数,在执行int 0x80 指令时还应该将栈指针的当前值复制到寄存器 ebx中。



回页首

六、命令行参数

在 Linux 操作系统中,当一个可执行程序通过命令行启动时,其所需的参数将被保存到栈中:首先是 argc,然后是指向各个命令行参数的指针数组 argv,最后是指向环境变量的指针数据 envp。在编写汇编语言程序时,很多时候需要对这些参数进行处理,下面的代码示范了如何在汇编代码中进行命令行参数的处理:

例3. 处理命令行参数

# args.s
.text
.globl _start
        
_start:
        popl	%ecx		# argc
vnext:
        popl	%ecx		# argv
        test 	%ecx, %ecx      # 空指针表明结束
        jz	exit
        movl	%ecx, %ebx
        xorl	%edx, %edx
strlen:
        movb	(%ebx), %al
        inc	%edx
        inc	%ebx
        test	%al, %al
        jnz	strlen
        movb	$10, -1(%ebx)
        movl	$4, %eax        # 系统调用号(sys_write) 
        movl	$1, %ebx        # 文件描述符(stdout) 
        int	$0x80
        jmp	vnext
exit:
        movl	$1,%eax         # 系统调用号(sys_exit) 
        xorl	%ebx, %ebx      # 退出代码
        int 	$0x80
		
        ret



回页首

七、GCC 内联汇编

用汇编编写的程序虽然运行速度快,但开发速度非常慢,效率也很低。如果只是想对关键代码段进行优化,或许更好的办法是将汇编指令嵌入到 C 语言程序中,从而充分利用高级语言和汇编语言各自的特点。但一般来讲,在 C 代码中嵌入汇编语句要比"纯粹"的汇编语言代码复杂得多,因为需要解决如何分配寄存器,以及如何与C代码中的变量相结合等问题。

GCC 提供了很好的内联汇编支持,最基本的格式是:

__asm__("asm statements");

例如:

__asm__("nop"); 

如果需要同时执行多条汇编语句,则应该用"\\n\\t"将各个语句分隔开,例如:

__asm__( "pushl %%eax \\n\\t"
         "movl $0, %%eax \\n\\t"
         "popl %eax");

通常嵌入到 C 代码中的汇编语句很难做到与其它部分没有任何关系,因此更多时候需要用到完整的内联汇编格式:

__asm__("asm statements" : outputs : inputs : registers-modified);

插入到 C 代码中的汇编语句是以":"分隔的四个部分,其中第一部分就是汇编代码本身,通常称为指令部,其格式和在汇编语言中使用的格式基本相同。指令部分是必须的,而其它部分则可以根据实际情况而省略。

在将汇编语句嵌入到C代码中时,操作数如何与C代码中的变量相结合是个很大的问题。GCC采用如下方法来解决这个问题:程序员提供具体的指令,而对寄存器的使用则只需给出"样板"和约束条件就可以了,具体如何将寄存器与变量结合起来完全由GCC和GAS来负责。

在GCC内联汇编语句的指令部中,加上前缀'%'的数字(如%0,%1)表示的就是需要使用寄存器的"样板"操作数。指令部中使用了几个样板操作数,就表明有几个变量需要与寄存器相结合,这样GCC和GAS在编译和汇编时会根据后面给定的约束条件进行恰当的处理。由于样板操作数也使用'%'作为前缀,因此在涉及到具体的寄存器时,寄存器名前面应该加上两个'%',以免产生混淆。

紧跟在指令部后面的是输出部,是规定输出变量如何与样板操作数进行结合的条件,每个条件称为一个"约束",必要时可以包含多个约束,相互之间用逗号分隔开就可以了。每个输出约束都以'='号开始,然后紧跟一个对操作数类型进行说明的字后,最后是如何与变量相结合的约束。凡是与输出部中说明的操作数相结合的寄存器或操作数本身,在执行完嵌入的汇编代码后均不保留执行之前的内容,这是GCC在调度寄存器时所使用的依据。

输出部后面是输入部,输入约束的格式和输出约束相似,但不带'='号。如果一个输入约束要求使用寄存器,则GCC在预处理时就会为之分配一个寄存器,并插入必要的指令将操作数装入该寄存器。与输入部中说明的操作数结合的寄存器或操作数本身,在执行完嵌入的汇编代码后也不保留执行之前的内容。

有时在进行某些操作时,除了要用到进行数据输入和输出的寄存器外,还要使用多个寄存器来保存中间计算结果,这样就难免会破坏原有寄存器的内容。在GCC内联汇编格式中的最后一个部分中,可以对将产生副作用的寄存器进行说明,以便GCC能够采用相应的措施。

下面是一个内联汇编的简单例子:

例4.内联汇编

/* inline.c */
int main()
{
    int a = 10, b = 0;
    __asm__ __volatile__("movl %1, %%eax;\\n\\r"
                         "movl %%eax, %0;"
                         :"=r"(b)      /* 输出 */    
                         :"r"(a)       /* 输入 */
                         :"%eax");     /* 不受影响的寄存器 */
    
    printf("Result: %d, %d\\n", a, b);
}

上面的程序完成将变量a的值赋予变量b,有几点需要说明:

  • 变量b是输出操作数,通过%0来引用,而变量a是输入操作数,通过%1来引用。
  • 输入操作数和输出操作数都使用r进行约束,表示将变量a和变量b存储在寄存器中。输入约束和输出约束的不同点在于输出约束多一个约束修饰符'='。
  • 在内联汇编语句中使用寄存器eax时,寄存器名前应该加两个'%',即%%eax。内联汇编中使用%0、%1等来标识变量,任何只带一个'%'的标识符都看成是操作数,而不是寄存器。
  • 内联汇编语句的最后一个部分告诉GCC它将改变寄存器eax中的值,GCC在处理时不应使用该寄存器来存储任何其它的值。
  • 由于变量b被指定成输出操作数,当内联汇编语句执行完毕后,它所保存的值将被更新。

在内联汇编中用到的操作数从输出部的第一个约束开始编号,序号从0开始,每个约束记数一次,指令部要引用这些操作数时,只需在序号前加上'%'作为前缀就可以了。需要注意的是,内联汇编语句的指令部在引用一个操作数时总是将其作为32位的长字使用,但实际情况可能需要的是字或字节,因此应该在约束中指明正确的限定符:

限定符 意义
"m"、"v"、"o" 内存单元
"r" 任何寄存器
"q" 寄存器eax、ebx、ecx、edx之一
"i"、"h" 直接操作数
"E"和"F" 浮点数
"g" 任意
"a"、"b"、"c"、"d" 分别表示寄存器eax、ebx、ecx和edx
"S"和"D" 寄存器esi、edi
"I" 常数(0至31)



回页首

八、小结

Linux操作系统是用C语言编写的,汇编只在必要的时候才被人们想到,但它却是减少代码尺寸和优化代码性能的一种非常重要的手段,特别是在与硬件直接交互的时候,汇编可以说是最佳的选择。Linux提供了非常优秀的工具来支持汇编程序的开发,使用GCC的内联汇编能够充分地发挥C语言和汇编语言各自的优点。

参考资料

  1. 在网站 http://linuxassembly.org上可以找到大量的Linux汇编资源。
  2. 软件包binutils提供了as和ld等实用工具,其相关信息可以在网站 http://sources.redhat.com/binutils/上找到。
  3. NASM是Intel格式的汇编器,其相关信息可以在网站 http://nasm.sourceforge.net上找到。
  4. ALD是一个短小精悍的汇编调试器,其相关信息可以在网站 http://dunx1.irt.drexel.edu/~psa22/ald.html上找到。
  5. intel2gas是一个能够将Intel汇编格式转换成AT&T汇编格式的小工具,其相关信息可以在网站 http://www.niksula.cs.hut.fi/~mtiihone/intel2gas/上找到。
  6. IBM developerWorks上有一篇介绍GCC内联汇编的文章( http://www.ibm.com/developerworks/cn/linux/sdk/assemble/inline/index.shtml)。
  7. 本文代码下载: 代码

AT&T汇编语法

0.3.1 Overview 

 

开 发一个OS,尽管绝大部分代码只需要用C/C++等高级语言就可以了,但至少和硬件相关部分的代码需要使用汇编语言,另外,由于启动部分的代码有大小限 制,使用精练的汇编可以缩小目标代码的Size。另外,对于某些需要被经常调用的代码,使用汇编来写可以提高性能。所以我们必须了解汇编语言,即使你有可 能并不喜欢它。

 

如果你是计算机专业的话,在大学里你应该学习过Intel格式的8086/80386汇编,这里就不再讨论。如果我们选择的OS开发工具是GCC以及GAS的话,就必须了解AT&T汇编语言语法,因为GCC/GAS只支持这种汇编语法。

 

本书不会去讨论8086/80386的汇编编程,这类的书籍很多,你可以参考它们。这里只会讨论AT&T的汇编语法,以及GCC的内嵌汇编语法。


0.3.2 Syntax 

1.寄存器引用

引用寄存器要在寄存器号前加百分号%,如“movl %eax, %ebx”。

80386有如下寄存器:

  • 8个32-bit寄存器 %eax,%ebx,%ecx,%edx,%edi,%esi,%ebp,%esp;
  • 8个16-bit寄存器,它们事实上是上面8个32-bit寄存器的低16位:%ax,%bx,%cx,%dx,%di,%si,%bp,%sp;
  • 8个8-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;
  • 6个debug寄存器:%db0,%db1,%db2,%db3,%db6,%db7;
  • 2个测试寄存器:%tr6,%tr7;
  • 8个浮点寄存器栈:%st(0),%st(1),%st(2),%st(3),%st(4),%st(5),%st(6),%st(7)。

2. 操作数顺序

操作数排列是从源(左)到目的(右),如“movl %eax(源), %ebx(目的)”

3. 立即数

使用立即数,要在数前面加符号$, 如“movl $0x04, %ebx”

或者:

para = 0x04

movl $para, %ebx

指令执行的结果是将立即数04h装入寄存器ebx。

4. 符号常数

符号常数直接引用 如

value: .long 0x12a3f2de

movl value , %ebx

指令执行的结果是将常数0x12a3f2de装入寄存器ebx。

引用符号地址在符号前加符号$, 如“movl $value, % ebx”则是将符号value的地址装入寄存器ebx。

5. 操作数的长度

操作数的长度用加在指令后的符号表示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”。

6. 符号扩展和零扩展指令

绝大多数面向80386的AT&T汇编指令与Intel格式的汇编指令都是相同的,符号扩展指令和零扩展指令则是仅有的不同格式指令。

符号扩展指令和零扩展指令需要指定源操作数长度和目的操作数长度,即使在某些指令中这些操作数是隐含的。

在AT&T语法中,符号扩展和零扩展指令的格式为,基本部分 "movs"和"movz"(对应Intel语法的movsx和movzx),后面跟上源操作数长度和目的操作数长度。movsbl意味着movs (from)byte (to)long;movbw意味着movs (from)byte (to)word;movswl意味着movs (from)word (to)long。对于movz指令也一样。比如指令“movsbl %al, %edx”意味着将al寄存器的内容进行符号扩展后放置到edx寄存器中。

其它的Intel格式的符号扩展指令还有:

  • cbw -- sign-extend byte in %al to word in %ax;
  • cwde -- sign-extend word in %ax to long in %eax;
  • cwd -- sign-extend word in %ax to long in %dx:%ax;
  • cdq -- sign-extend dword in %eax to quad in %edx:%eax;

对应的AT&T语法的指令为cbtw,cwtl,cwtd,cltd。

7. 调用和跳转指令

段内调用和跳转指令为"call","ret"和"jmp",段间调用和跳转指令为"lcall","lret"和"ljmp"。

段间调用和跳转指令的格式为“lcall/ljmp $SECTION, $OFFSET”,而段间返回指令则为“lret $STACK-ADJUST”。

8. 前缀

操作码前缀被用在下列的情况:

  • 字符串重复操作指令(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-operand的格式就指定了相应的段前缀。比如: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”次。

9. 内存引用

Intel语法的间接内存引用的格式为:

section:[base+index*scale+displacement]

而在AT&T语法中对应的形式为:

section:displacement(base,index,scale)

其中,base和index是任意的32-bit base和index寄存器。scale可以取值1,2,4,8。如果不指定scale值,则默认值为1。section可以指定任意的段寄存器作为段前 缀,默认的段寄存器在不同的情况下不一样。如果你在指令中指定了默认的段前缀,则编译器在目标代码中不会产生此段前缀代码。

下面是一些例子:

-4(%ebp):base=%ebp,displacement=-4,section没有指定,由于base=%ebp,所以默认的section=%ss,index,scale没有指定,则index为0。

foo(,%eax,4):index=%eax,scale=4,displacement=foo。其它域没有指定。这里默认的section=%ds。

foo(,1):这个表达式引用的是指针foo指向的地址所存放的值。注意这个表达式中没有base和index,并且只有一个逗号,这是一种异常语法,但却合法。

%gs:foo:这个表达式引用的是放置于%gs段里变量foo的值。

如果call和jump操作在操作数前指定前缀“*”,则表示是一个绝对地址调用/跳转,也就是说jmp/call指令指定的是一个绝对地址。如果没有指定"*",则操作数是一个相对地址。

任何指令如果其操作数是一个内存操作,则指令必须指定它的操作尺寸(byte,word,long),也就是说必须带有指令后缀(b,w,l)。

AT&T汇编

在阅读linux内核源代码的时候,必须先掌握汇编,大家都知道,内核代码用的编译器是gcc,而gcc采用的是AT&T的汇编格式,与MS的intel有些区别。

一 AT&T的基本语法

    语法上主要有以下几个不同. 

★ 寄存器命名原则 

AT&T: %eax Intel: eax 

★ 源/目的操作数顺序 

AT&T: movl %eax,%ebx Intel: mov ebx,eax 

★ 常数/立即数的格式 

AT&T: movl $_value,%ebx Intel: mov eax,_value 

把_value的地址放入eax寄存器 

AT&T: movl $0xd00d,%ebx Intel: mov ebx,0xd00d 

★ 操作数长度标识 

AT&T: movw %ax,%bx Intel: mov bx,ax 

★寻址方式 

AT&T: immed32(basepointer,indexpointer,indexscale) 

Intel: [basepointer + indexpointer*indexscale + imm32) 

Linux工作于保护模式下,用的是32位线性地址,所以在计算地址时 

不用考虑segment:offset的问题.上式中的地址应为: 

imm32 + basepointer + indexpointer*indexscale 

下面是一些例子: 

★直接寻址 

AT&T: _booga ; _booga是一个全局的C变量 

注意加上$是表示地址引用,不加是表示值引用. 

注:对于局部变量,可以通过堆栈指针引用. 

Intel: [_booga] 

★寄存器间接寻址 

AT&T: (%eax) 

Intel: [eax] 

★变址寻址 

AT&T: _variable(%eax) 

Intel: [eax + _variable] 

AT&T: _array(,%eax,4) 

Intel: [eax*4 + _array] 

AT&T: _array(%ebx,%eax,8) 

Intel: [ebx + eax*8 + _array] 


二 基本的行内汇编 

    基本的行内汇编很简单,一般是按照下面的格式 

asm("statements"); 

例如:asm("nop"); asm("cli"); 

asm 和 __asm__是完全一样的. 

如果有多行汇编,则每一行都要加上 "\n\t" 

例如: 

asm( "pushl %eax\n\t" 

"movl $0,%eax\n\t" 

"popl %eax"); 

实际上gcc在处理汇编时,是要把asm(...)的内容"打印"到汇编 

文件中,所以格式控制字符是必要的. 

再例如: 

asm("movl %eax,%ebx"); 

asm("xorl %ebx,%edx"); 

asm("movl $0,_booga); 

在上面的例子中,由于我们在行内汇编中改变了edx和ebx的值,但是 

由于gcc的特殊的处理方法,即先形成汇编文件,再交给GAS去汇编, 

所以GAS并不知道我们已经改变了edx和ebx的值,如果程序的上下文 

需要edx或ebx作暂存,这样就会引起严重的后果.对于变量_booga也 

存在一样的问题.为了解决这个问题,就要用到扩展的行内汇编语法. 


三 扩展的行内汇编 

    扩展的行内汇编类似于Watcom. 

基本的格式是: 

asm ( "statements" : output_regs : input_regs : clobbered_regs); 

clobbered_regs指的是被改变的寄存器. 

下面是一个例子(为方便起见,我使用全局变量): 

int count=1; 

int value=1; 

int buf[10]; 

void main() 

asm( 

"cld \n\t" 

"rep \n\t" 

"stosl" 

:  "c" (count), "a" (value) , "D" (buf[0]) 

:  "%ecx","%edi" ); 

得到的主要汇编代码为: 

movl count,%ecx 

movl value,%eax 

movl buf,%edi 

#APP 

cld 

rep 

stosl 

#NO_APP 

cld,rep,stos就不用多解释了. 

这几条语句的功能是向buf中写上count个value值. 

冒号后的语句指明输入,输出和被改变的寄存器. 

通过冒号以后的语句,编译器就知道你的指令需要和改变哪些寄存器, 

从而可以优化寄存器的分配. 

其中符号"c"(count)指示要把count的值放入ecx寄存器 

类似的还有: 

a eax 

b ebx 

c ecx 

d edx 

S esi 

D edi 

I 常数值,(0 - 31) 

q,r 动态分配的寄存器 

g eax,ebx,ecx,edx或内存变量 

A 把eax和edx合成一个64位的寄存器(use long longs) 

我们也可以让gcc自己选择合适的寄存器. 

如下面的例子: 

asm("leal (%1,%1,4),%0" 

:  "=r" (x) 

:  "0" (x) ); 

这段代码实现5*x的快速乘法. 

得到的主要汇编代码为: 

movl x,%eax 

#APP 

leal (%eax,%eax,4),%eax 

#NO_APP 

movl %eax,x 

几点说明: 

1.使用q指示编译器从eax,ebx,ecx,edx分配寄存器. 

使用r指示编译器从eax,ebx,ecx,edx,esi,edi分配寄存器. 

2.我们不必把编译器分配的寄存器放入改变的寄存器列表,因为寄存器 

已经记住了它们. 

3."="是标示输出寄存器,必须这样用. 

4.数字%n的用法: 

数字表示的寄存器是按照出现和从左到右的顺序映射到用"r"或"q"请求 

的寄存器.如果我们要重用"r"或"q"请求的寄存器的话,就可以使用它们. 

5.如果强制使用固定的寄存器的话,如不用%1,而用ebx,则 

asm("leal (%%ebx,%%ebx,4),%0" 

:  "=r" (x) 

:  "0" (x) ); 

注意要使用两个%,因为一个%的语法已经被%n用掉了. 

下面可以来解释letter 4854-4855的问题: 

1、变量加下划线和双下划线有什么特殊含义吗? 

加下划线是指全局变量,但我的gcc中加不加都无所谓. 

2、以上定义用如下调用时展开会是什么意思? 

#define _syscall1(type,name,type1,arg1) \ 

type name(type1 arg1) \ 

{ \ 

long __res; \ 

/* __res应该是一个全局变量 */ 

__asm__ volatile ("int $0x80" \ 

/* volatile 的意思是不允许优化,使编译器严格按照你的汇编代码汇编*/ 

:  "=a" (__res) \ 

/* 产生代码 movl %eax, __res */ 

:  "0" (__NR_##name),"b" ((long)(arg1))); \ 

/* 如果我没记错的话,这里##指的是两次宏展开. 

  即用实际的系统调用名字代替"name",然后再把__NR_...展开. 

  接着把展开的常数放入eax,把arg1放入ebx */ 

if (__res >= 0) \ 

return (type) __res; \ 

errno = -__res; \ 

return -1; \ 


////////////////////////////////////////////////////////////////////////

四.AT&T汇编与Intel汇编的比较

Intel和AT&T语法的区别
Intel和AT&T汇编语言的语法表面上各不相同,这将导致刚刚学会INTEL汇编的人第一次见到AT&T汇编时
会感到困惑,或者反之。因此让我们从基础的东西开始。

前缀
在Intel汇编中没有寄存器前缀或者立即数前缀。而在AT&T汇编中寄存器有一个“%”前缀,立即数有
一个“$”前缀。Intel语句中十六进制和二进制数据分别带有“h”和“b”后缀,并且如果十六进制
数字的第一位是字母的话,那么数值的前面要加一个“0”前缀。
例如,
Intex Syntax
mov    eax,1
mov    ebx,0ffh
int    80h

AT&T Syntax
movl    $1,%eax
movl    $0xff,%ebx
int     $0x80
就像你看到的,AT&T非常难懂。[base+index*scale+disp] 看起来比disp(base,index,scale)更好理解。
 

操作数的用法
intel语句中操作数的用法和AT&T中的用法相反。在Intel语句中,第一个操作数表示目的,第二个
操作数表示源。然而在AT&T语句中第一个操作数表示源而第二个操作数表示目的。在这种情形下AT&T语法
的好处是显而易见的。我们从左向右读,也从左向右写,这样比较自然。
例如,
Intex Syntax
instr    dest,source
mov    eax,[ecx]
    
AT&T Syntax
instr     source,dest
movl    (%ecx),%eax

存储器操作数
如同上面所看到的,存储器操作数的用法也不相同。在Intel语句中基址寄存器用“[”和“]”括起来
而在AT&T语句中是用“(”和“)”括起来的。
例如,
Intex Syntax
mov    eax,[ebx]
mov    eax,[ebx+3]
AT&T Syntax
movl    (%ebx),%eax
movl    3(%ebx),%eax
AT&T语法中用来处理复杂的操作的指令的形式和Intel语法中的形式比较起来要难懂得多。在Intel语句
中这样的形式是segreg:[base+index*scale+disp]。在AT&T语句中这样的形式是
%segreg:disp(base,index,scale)。
Index/scale/disp/segreg 都是可选并且可以去掉的。Scale在本身没有说明而index已指定的情况下
缺省值为1。segreg的确定依赖于指令本身以及程序运行在实模式还是pmode。在实模式下它依赖于
指令本身而pmode模式下它是不需要的。在AT&T语句中用作scale/disp的立即数不要加“$”前缀。
例如
Intel Syntax
instr     foo,segreg:[base+index*scale+disp]
mov    eax,[ebx+20h]
add    eax,[ebx+ecx*2h]
lea    eax,[ebx+ecx]
sub    eax,[ebx+ecx*4h-20h]    
AT&T Syntax
instr    %segreg:disp(base,index,scale),foo
movl    0x20(%ebx),%eax
addl    (%ebx,%ecx,0x2),%eax
leal    (%ebx,%ecx),%eax
subl    -0x20(%ebx,%ecx,0x4),%eax

后缀
就像你已经注意到的,AT&T语法中有一个后缀,它的意义是表示操作数的大小。“l”代表long,
“w”代表word,“b”代表byte。Intel语法中在处理存储器操作数时也有类似的表示,
如byte ptr, word ptr, dword ptr。"dword" 显然对应于“long”。这有点类似于C语言中定义的
类型,但是既然使用的寄存器的大小对应着假定的数据类型,这样就显得不必要了。
例子:
Intel Syntax
mov    al,bl
mov    ax,bx
mov    eax,ebx
mov    eax, dword ptr [ebx]    
AT&T Syntax
movb    %bl,%al
movw    %bx,%ax
movl    %ebx,%eax
movl    (%ebx),%eax

注意:从此开始所有的例子都使用AT&T语法
系统调用
本节将介绍linux中汇编语言系统调用的用法。系统调用包括位于/usr/man/man2的手册里第二部分所有
的函数。这些函数也在/usr/include/sys/syscall.h中列出来了。一个重要的关于这些函数的列表是
http://www.linuxassembly.org/syscall.html里。这些函数通过linux中断服务:int $0x80来被执行
小于六个参数的系统调用
对于所有的系统调用,系统调用号在%eax中。对于小于六个参数的系统调用,参数依次存放
在%ebx,%ecx,%edx,%esi,%edi中,系统调用的返回值保存在%eax中。
系统调用号可以在/usr/include/sys/syscall.h中找到。宏被定义成SYS_的形式,
如SYS_exit, SYS_close等。
例子:(hello world 程序)
参照write(2)的帮助手册,写操作被声明为ssize_t write(int fd, const void *buf, size_t count);
这样,fd应存放在%ebx中,buf放在 %ecx, count 放在 %edx , SYS_write 放在 %eax中,紧跟着是
int $0x80语句来执行系统调用。系统调用的返回值保存在%eax中。
$ cat write.s
.include "defines.h"
.data
hello:
    .string "hello world\n"

.globl    main
main:
    movl    $SYS_write,%eax
    movl    $STDOUT,%ebx
    movl    $hello,%ecx
    movl    $12,%edx
    int    $0x80

    ret
$
少于5个参数的系统调用的处理也是这样的。只是没有用到的寄存器保持不变罢了。象open或者fcntl这样
带有一个可选的额外参数的系统调用也就知道怎么用了。
大于5个参数的系统调用
参数个数大于五个的系统调用仍然把系统调用号保存在%eax中,但是参数存放在内存中,并且指向第一个
参数的指针保存在%ebx中。
如果你使用栈,参数必须被逆序压进栈里,即按最后一个参数到第一个参数的顺序。然后将栈的指针拷贝
到%ebx中。或者将参数拷贝到一块分配的内存区域,然后把第一个参数的地址保存在%ebx中。
例子:(使用mmap作为系统调用的例子)。在C中使用mmap():
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude

#define STDOUT    1

void main(void) {
    char file[]="mmap.s";
    char *mappedptr;
    int fd,filelen;

    fd=fopen(file, O_RDONLY);
    filelen=lseek(fd,0,SEEK_END);
    mappedptr=mmap(NULL,filelen,PROT_READ,MAP_SHARED,fd,0);
    write(STDOUT, mappedptr, filelen);
    munmap(mappedptr, filelen);
    close(fd);
}
mmap()参数在内存中的排列:
%esp    %esp+4    %esp+8    %esp+12    %esp+16    %esp+20
00000000    filelen    00000001    00000001    fd    00000000
等价的汇编程序:
$ cat mmap.s
.include "defines.h"

.data
file:
    .string "mmap.s"
fd:
    .long     0
filelen:
    .long     0
mappedptr:
    .long     0

.globl main
main:
    push    %ebp
    movl    %esp,%ebp
    subl    $24,%esp

//    open($file, $O_RDONLY);

    movl    $fd,%ebx    // save fd
    movl    %eax,(%ebx)

//    lseek($fd,0,$SEEK_END);

    movl    $filelen,%ebx    // save file length
    movl    %eax,(%ebx)

    xorl    %edx,%edx

//    mmap(NULL,$filelen,PROT_READ,MAP_SHARED,$fd,0);
    movl    %edx,(%esp)
    movl    %eax,4(%esp)    // file length still in %eax
    movl    $PROT_READ,8(%esp)
    movl    $MAP_SHARED,12(%esp)
    movl    $fd,%ebx    // load file descriptor
    movl    (%ebx),%eax
    movl    %eax,16(%esp)
    movl    %edx,20(%esp)
    movl    $SYS_mmap,%eax
    movl    %esp,%ebx
    int    $0x80

    movl    $mappedptr,%ebx    // save ptr
    movl    %eax,(%ebx)
        
//     write($stdout, $mappedptr, $filelen);
//    munmap($mappedptr, $filelen);
//    close($fd);
    
    movl    %ebp,%esp
    popl    %ebp

    ret
$
注意:上面所列出的源代码和本文结束部分的例子的源代码不同。上面列出的代码中没有说明其它的
系统调用,因为这不是本节的重点,上面列出的源代码仅仅打开mmap.s文件,而例子的源代码要读
命令行的参数。这个mmap的例子还用到lseek来获取文件大小。
Socket系统调用
Socket系统调用使用唯一的系统调用号:SYS_socketcall,它保存在%eax中。Socket函数是通过位于
/usr/include/linux/net.h的一个子函数号来确定的,并且它们被保存在%ebx中。指向系统调用参数
的一个指针存放在%ecx中。Socket系统调用也是通过int $0x80来执行的。
$ cat socket.s
.include "defines.h"

.globl    _start
_start:
    pushl    %ebp
    movl    %esp,%ebp
    sub    $12,%esp

//    socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    movl    $AF_INET,(%esp)
    movl    $SOCK_STREAM,4(%esp)
    movl    $IPPROTO_TCP,8(%esp)

    movl    $SYS_socketcall,%eax
    movl    $SYS_socketcall_socket,%ebx
    movl    %esp,%ecx
    int    $0x80

    movl     $SYS_exit,%eax
    xorl     %ebx,%ebx
    int     $0x80

    movl    %ebp,%esp
    popl    %ebp
    ret
$

命令行参数
在linux中执行的时候命令行参数是放在栈上的。先是argc,跟着是一个由指向命令行中各字符串的
指针组成的数组(**argv)并以空指针结束。接下来是一个由指向环境变量的指针组成的
数组(**envp)。这些东西在asm中都可以很容易的获得,并且在例子代码(args.s)中有示范。

 
GCC内联汇编
本节中GCC内联汇编仅涉及x86的应用程序。操作数约束会和其它处理器上的有所不同。关于这部分
的说明放在本文的最后。
gcc中基本的内联汇编非常易懂,如
__asm__("movl    %esp,%eax");    // look familiar ?

或者是
__asm__("
            movl    $1,%eax        // SYS_exit
            xor    %ebx,%ebx
            int    $0x80
    ");
如果指定了用作asm的输入、输出数据并指出哪一个寄存器会被修改,会使程序的执行效率提高。
input/output/modify都不是必需的。格式如下:
__asm__("" : output : input : modify);
output和input中必须包含一个操作数约束字符串,并紧跟一个用圆括号括起来的C语言表达式。
输出操作数约束的前面必须有一个“=”,表示这是一个输出。可能会有多个输出,多个输入和
多个修改过的寄存器。每个“入口”应该用“,”分隔开,并且入口的总数不多有10个。
操作数约束字符串可以是包含整个寄存器的名称也可以是简写。
Abbrev Table
Abbrev    Register
a    %eax/%ax/%al
b    %ebx/%bx/%bl
c    %ecx/%cx/%cl
d    %edx/%dx/%dl
S    %esi/%si
D    %edi/%di
m    memory
例如:

    __asm__("test    %%eax,%%eax", : /* no output */ : "a"(foo));


或者是

    __asm__("test    %%eax,%%eax", : /* no output */ : "eax"(foo));
你可以在__asm__后使用关键字__volatile__:“你可以利用在__asm__后使用关键字__volatile__的
方法防止一条‘asm’指令被删除、移动或者被重新组合。”(出自gcc的info文件中"Assembler
Instructions with C Expression Operands" 部分)
$ cat inline1.c
#i nclude

int main(void) {
    int foo=10,bar=15;
    
    __asm__ __volatile__ ("addl     %%ebxx,%%eax"
        : "=eax"(foo)         // ouput
        : "eax"(foo), "ebx"(bar)// input
        : "eax"            // modify
    );
    printf("foo+bar=%d\n", foo);
    return 0;
}
$
你可能已经注意到现在寄存器使用“%%”前缀而不是“%”。这在使用output/input/modify域时是必要的,
这是因为此时基于其它域的寄存器的别名的使用。我马上来讨论这个问题。
你可以很简单的指定“a”而不是写“eax”或者强制使用一个特殊寄存器如"eax"、"ax"、"al",
这同样适用于其它一般用途的寄存器(在Abbrev表中列出的)。当你在当前的代码中使用特殊的寄存器
时这好像毫无用处,因此gcc提供了寄存器别名。最多有10个别名(%0—%9),这也是为什么只允许10个
输入/输出的原因。
$ cat inline2.c
int main(void) {
    long eax;
    short bx;
    char cl;

    __asm__("nop;nop;nop"); // to separate inline asm from the rest of
                // the code
    __volatile__ __asm__("
        test    %0,%0
        test    %1,%1
        test    %2,%2"
        : /* no outputs */
        : "a"((long)eax), "b"((short)bx), "c"((char)cl)
    );
    __asm__("nop;nop;nop");
    return 0;
}
$ gcc -o inline2 inline2.c
$ gdb ./inline2
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnulibc1"...
(no debugging symbols found)...
(gdb) disassemble main
Dump of assembler code for function main:
... start: inline asm ...
0x8048427 : nop
0x8048428 : nop
0x8048429 : nop
0x804842a : mov 0xfffffffc(%ebp),%eax
0x804842d : mov 0xfffffffa(%ebp),%bx
0x8048431 : mov 0xfffffff9(%ebp),%cl
0x8048434 : test %eax,%eax
0x8048436 : test %bx,%bx
0x8048439 : test %cl,%cl
0x804843b : nop
0x804843c : nop
0x804843d : nop
... end: inline asm ...
End of assembler dump.
$
就像你看到的,由内联汇编生成的代码将变量的值放入它们在input域中指定的寄存器中,然后继续
执行当前的代码。编译器自动根据变量的大小来侦测操作数的大小,这样相应的寄存器就被
别名%0, %1 和 %2代替了(当使用寄存器别名时在存储器里指定操作数的大小回导致编译时发生错误)
在操作数约束里也可以使用别名。这不允许你在输入/输出域中指定多于10个的入口。我能想到的这样
做的唯一用法是在你指定操作数约束为“q”以便让编译器在a,b,c,d寄存器之间进行选择的时候。
当这个寄存器被修改时,我们不会知道选中了那个寄存器,因而不能在modify域中指定它。
这种情况下你只需指定""。
例子:
$ cat inline3.c
#i nclude

int main(void) {
    long eax=1,ebx=2;

    __asm__ __volatile__ ("add %0,%2"
        : "=b"((long)ebx)
        : "a"((long)eax), "q"(ebx)
        : "2"
    );
    printf("ebx=%x\n", ebx);
    return 0;
}