首页 > 代码库 > Linux中的汇编简介

Linux中的汇编简介

GNU as汇编语法

GNU汇编语法使用的是AT&T汇编它和Intel汇编的语法主要有以下一些不同:

  • AT&T汇编中的立即操作数前面要加上‘$‘,寄存器操作数名前要加上百分号‘%‘,绝对跳转操作数前要加上‘*‘,Intel的语法均不包含这些符号;
  • AT&T语法与Intel语法中使用的源操作数和目的操作数顺序正好相反,AT&T的源操作数和目的操作数是从左到右,Intel语法是从右到左,例如add eax, 4在AT&T语法中是addl $4, %eax;
  • AT&T语法中内存操作数长度由操作码的最后一位字符来确定,操作码后缀有b/w/l分别代表内存引用宽度为8位,16位和32位,Intel语法则通过在内存操作数前使用前缀byte ptr,word ptr和dword ptr来达到此目的。例如Intel中的mov al, byte ptr foo对应AT&T中的语句为movb $foo, %al;
  • AT&T语法中立即跳转和调用为ljmp/lcall $section, $offset,而Intel中的是jmp/call far section:offset,AT&T中的返回指令lret $stack-adjust对应Intel的ret far stack-adjust;
  • AT&T汇编器不支持多代码段程序,类UNIX系统要求所有代码在一个段中;

预处理:as汇编器能对汇编语言进行简单的预处理,包括删除多余的空格和制表符,删除注释语句,把字符常数转换为对应的数值,但此预处理不能处理宏,也没有处理包含文件的功能,如果需要这些功能,可以使用gcc的预处理器cpp来实现。

符号:GNU汇编语言中的符号是由字符组成的标识符,组成符号的字符包含大小写字母,数字和_.$三个字符,符号不允许以数字开始,且区分大小写。在汇编程序中符号长度没有限制,符号使用空格来界定开始和结束。语句以换行符或者行分割符(;)作为结束,文件最后必须以换行符作为结束,语句有0个或多个标号开始(Label),后面可跟随一个确定语句类型的关键符号,标号由符号后面跟随一个冒号构成,关键符号确定了余下部分语句的语义,如果关键符号以.开始,那么当前语句就是一个汇编命令,如果关键符号以字母开始,那么当前语句就是一条汇编语言指令语句,一it哦啊语句的通用格式为:

标号:汇编命令     注释语句或者标号:指令助记符 操作数1, 操作数2     注释语句

常数:常数是一个数字,分为字符常数和数字常数两类,字符常数分为单个字符和字符串,数字常数分为整数和浮点数。汇编语言的单个字符表示一版为在该字符前加上单引号,而字符串需要添加双引号。

指令:指令是CPU执行的操作,通常指令称为操作码,操作数是指令操作的对象,而地址是指令数据在内存中的位置,一条指令语句一版包括:标号,操作码(指令助记符),操作数,注释。

操作数:操作数包含立即操作数,寄存器和内存。间接操作数含有实际操作数的地址值,AT&T语法通过在操作数前加上*来表示间接操作数,只有跳转和调用指令才能使用间接操作数。立即操作数前面需要加上$符号,寄存器名前面需要加上%符号,内存操作数由变量名或者含有变量地址的寄存器指定,变量名隐含指出了变量的地址,并指示cpu引用改地址内存处的内容。

操作码:AT&T语法中指令操作码最后一个字符用来指明操作数的宽度,字符b,w和l分别制定byte,word和long类型的操作数。如果指令名没有带字符后缀,并且指定语句中不含内存操作数,那么as汇编器会根据寄存器操作数来尝试确定操作数宽度。操作码前缀用于修饰随后的操作码,他们用于重复字符串指令,提供区覆盖,执行总线锁定操作或者指定操作数和地址宽度。

内存引用:AT&T语法的间接内存引用形式为section:disp(base, index, scale),其中base和index是32位的基寄存器和索引寄存器,disp是可选的偏移值,scale是比例因子,scale乘上index用来表示操作数地址,section为内存操作数指定的段寄存器。

movl var, %eax                 #把内存地址var处的内容放入寄存器%eaxmovl %cs:var, %eax             #把代码段中的内存地址var处的内容放入%eaxmovb $0xa0, %es:(%ebx)         #把0xa0放入到es段的%ebx指定的偏移处movl $var, %eax                #把var的地址放入%eaxmovl array(%esi), %eax         #把array+%esi内存地址的内容放入eaxmovl (%ebx, %esi, 4), %eax     #把ebx+esi*4地址处的内容放到eaxmovl array(%ebx, %esi, 4), %eax#把array+ebx+esi*4地址的内容放到eaxmovl -4(%ebp), %eax            #把ebp-4地址处的内容放到eax

指令跳转:跳转指令用于把执行点转移到程序的另外的位置执行,跳转的目的位置通常用一个标号来表示。jmp是无条件跳转,可分为直接跳转和间接跳转,直接跳转语句的写法是给出跳转目标处的标号,间接跳转语句的写法是使用*作为操作指示符的前缀字符。

:段用于表示一个地址范围,它主要用来表示编译器生成的目标文件中的不同的信息区域。例如代码段,数据段,bss段。

符号:符号有很多作用,可以用来命名对象,连接器通过符号执行链接操作,调试器利用符号进行调试。标号是后面接着一个冒号的符号,该符号用来表示代码当前的位置。特殊符号.用来表示汇编的当前位置。除了名字之外每个符号都有值和类型属性。符号的值通常是32位的,链接器会对未定义的符号值做特殊处理,如果未定义的符号值为0则表示该符号在此汇编程序中没有定义,链接器会尝试使用其他链接的文件来确定此符号值,

as汇编器

as汇编器的命令行格式为: as [选项] [-o objfile] [srcfile.s],如果没有指定输出文件名,会默认输出a.out文件。可以再as命令行上给出0个或多个输入文件名,as会按照从左到右的顺序读取这些文件中的内容,在命令行上的参数如果没有实际意义将会被当做输入文件名看待,如果命令行上没有任何文件名,那么as将会试图从终端读取输入文件内容。

as的输出文件:由输入的汇编文件编译生成的二进制文件,目标文件最终作为连接器ld的输入文件。目标文件中包含汇编代码,协助ld产生可执行文件的信息,以及用于调试的符号信息。

内联汇编

内联汇编的基本格式为:

asm("汇编语句"      : 输出寄存器      : 输入寄存器      : 会被修改的寄存器)

除了汇编语句是必须的的以外,其他若不使用都可以省略。其中asm是内联汇编的关键词,汇编语句用于编写汇编指令,输出寄存器表示当这段汇编语句执行完之后,哪些寄存器用于存放输出数据,输入寄存器表示开始执行代码时,制定某些寄存器中应该放入的输入值,会被修改的寄存器表示对列出的寄存器中的值进行了修改。

常用的寄存器加载代码

代码  说明代码   说明
a使用寄存器eaxm使用内存地址
b使用寄存器ebxo使用内存地址并且可以加偏移值
c使用寄存器ecxI使用常数0~31
d使用寄存器edxJ使用常数0~63
S使用esiK使用常数0~255
D使用ediL使用常数0~65535
q使用动态分配字节可寻址寄存器M使用常数0~3
r使用任意动态分配寄存器N使用1字节常数
g使用通用有效地址O使用常数0~31
A使用eax和edx联合=输出操作数,输出值替换前值
+表示操作数可读可写&在使用完操作数之前内容会被修改

在执行代码时,如果不希望汇编语句被gcc优化,需要在asm符号后面添加关键字volatile。关键字volatile也可以放在函数名前来修饰函数,用来通知gcc该函数不会返回。

Linux中的汇编简介