C语言内嵌汇编简介

l 简要介绍

n 关键字

u __asm__:必须的;__volatile__:非必须的

n 格式

u __asm__ __volatile__ (“instruction list” : output:input : clobber/modify);

u 上述除了四个部分都可以缺省,当后面三个部分没有的时候退化成基本内联汇编,否则为GCC内联汇编

n 各个操作数含义

u Instruction list:所有的内联操作定义

u Output:所有的输出变量

u Input:所有的输入变量

u Clobber/modify:对于可能被修改部分的声明

n 每个操作数的集合

u Instruction list:各种intel和AT&T汇编操作命令

u Output:输出操作数的变量名和所使用模式和寄存器/内存

u Input:输入操作数的变量名和所使用模式和寄存器/内存

u Clobber/modify:对寄存器或者内存可能改变的提醒声明

l 语法

n 寄存器引用

u Eg. % eax, % ebx

n 操作数的顺序

u 从左到右,eg.  “movl %eax(源操作数), % ebx(目的操作数)”

n 立即数

u 前面加上$,eg. “movl $0x04, % ebx” 或者 para=0x04 movl $para, % ebx,将立即数04h装入寄存器ebx

n 符号常数(直接引用)

u Eg. value: .long0x12a3f2de movl value, % ebx(将常数0x12a3f2dez装入寄存器ebx)

u Eg. “movl $value, % ebx”(将value的地址装入寄存器ebx)

n 操作数的长度

u 指令最后一个符号的含义:b,w,l,分别代表:byte,word,long,如果没有指定操作数长度,则按照目标操作数的长度来设置

n 符号扩展和零扩展指令(AT&T与Intel汇编指令中的不同部分)

u 需要指定源操作数和目的操作数的长度

u AT&T中的格式:movs(符号扩展)和movz(零扩展)

u Intel中的格式:movsx(符号扩展)和movzx(零扩展)

u Eg. movsbl意味着movs (from)byte (to)long; movsbl % al, %edx   (Intel类似)

u Eg. movsbw意味着movs (from)byte (to)word movsbw % al, %dx  (Intel类似)

u 其他的还有:cbw, cwde, cwd, cdq等(intel的),cbtw, cwtl, cwtd, cltd等(AT&T)

n 调用和跳转指令

u 段内调用和跳转:call, ret, jmp

u 段间调用和跳转:lcall, lret, ljmp

u 段间调用和跳转指令格式:”lcall/ljmp $section, $offset”

u 段间返回指令为:”lret $stack-adjust”

n 前缀

u 字符串重复操作(rep, repne)

u 指定被操作的段(cs,ds,ss,es,fs,gs)

u 进行总线枷锁(lock)

u 指定地址和操作的大小(data16,addr16)

u 在AT&T汇编语法中,前缀通常被单独放在一行,后面不跟任何操作数,例如,对于重复scas指令,其写法为:

repne

scas

u 操作码的意义和用法:

l 在AT&T语法中,只需要按照section:memory-operand的格式就指定了相应的段前缀

Eg.lcall % cs:realmode_switch

l “lock”是为了保证指令执行期间禁止一切中断,其作用于指令:ADD,ADC等

n 内存引用

u Intel语法的间接内存引用格式为:section:[base+index*scale+displacement]

u AT&T语法中对应形式为:section:displacement(base, index,scale)

u Base和index是任意的32-bit base和index寄存器

u Scale取值为:1,2,4,8,默认值为1

u Section可以指定任意的寄存器作为段前缀,默认的段寄存器在不同的情况下不一样(如果在指令中指定了默认的段前缀,编译器在目标代码中不会产生此段前缀代码)

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

u 其他指令参看《c和汇编混编语法》

l GCC内联汇编

n 基本内联汇编(只有instruction list,没有input/output/clobber)

u Eg.__asm__ (“movl % esp, % eax”);

u Eg.__asm__ (“movl $1, % eax xor % ebx, % ebx int$0x80”);

u Eg. __asm__(“movl $1, % eax\r\t” “xor % ebx, %ebx\r\t” “int $0x80”);

u 基本格式:__asm__ __volatile__(“instructionlist”);

l __asm__:是GCC关键字asm的宏定义,每一个内联汇编表达式的开头都是它,必不可少

l Instruction list:是汇编指令序列,可以是空的

l 可以将所有的指令放在一个引号中

n 可以将每一条执行放在一行

n 多条指令放在一行,需要用分号或换行符隔开(多数情况加一个\t)

l 也可以分开放在几个引号中

n 除了最后一对引号之外,前面所有引号里的最后一条指令之后都要有一个分号或\n或\t

l 原则总结:任意两个指令间要么被分号隔开,要么被放在两行(可以真的放,也可以加上\n)

l __volatile__:GCC内联关键字volatile的宏定义,可以不用,用了说明要保留每一条指令(不会在优化的时候被省略)

n 带有C/C++表达式的内联汇编

u 基本格式:__asm__ __volatile__ (“instruction list” : output: input : clobber);

l 基本规则:

n 如果clobber为空,前面的冒号必须省略

n 如果instruction list为空,则input,output,clobber可以为空也可以不为空

n 如果output,input,clobber都为空,output,input之前的冒号既可以省略也可以不省略(全部省略退化成基本内联汇编)

n C内联汇编中instruction list中的寄存器前面需要用两个百分号

n Input,clobber为空,output不为空,input前的冒号可以省略/不省略

n 后面不空,前面为空,则前面的冒号都必须保留

n Instruction list后面是否有冒号->是否为C内联汇编

u Output

l Eg. __asm__(“movl % % cr0, % 0” :“=a”(cr0));

n 输出部分为:”=a”(rc0),是一个操作表达式,指定了一个输出操作

u (cr0):C/C++表达式,用来保存内联汇编的一个输出值,其操作就等于C/C++的相等赋值rc0 = output_value,也就是说它只能是一个可以合法地放在C/C++赋值操作=左边的表达式

u “=a”,称为“操作约束”,包含了两个约束:等号= 和字母a,其中等号=说明括号中左值表达式cr0是一个write-only,只能被当前内联汇编的输入,而不能作为输出,字母a是寄存器eax,ax,al的简写,说明cr0的值要从eax寄存器汇总获取,也就是cro=eax的意思,汇编指令为:movl % eax, address_of_cr0

u 关于”=”,等号(=)说明当前表达式为write-Only,如果是加号(+)说明当前表达式为read-write的,如果是缺省()说明当前表达式为read-only

n 多个output之间用逗号隔开

u input

l eg. __asm__(“movl % 0, % % db7”::”a”(cpu->db7));

n “a”(cpu->db7):成为“输入表达式”,两个部分”a”和(cpu->db7)是必不可少的

n Cpu->db7是一个C/C++表达式,不必是一个左值,还可以是一个右边表达式

n 引号中的是约束部分,和输出表达式不同,不允许指定加好和等号约束,默认就是read-only,需要指定一个寄存器约束,a表示输入变量通过寄存器eax输入到内联汇编中

u 操作约束

l 寄存器约束

n r :表示一个通用寄存器,由GCC在% eax% ax% al,% ebx% bx% bl,% ecx% cx% cl,% edx%dx% dl中选取一个GCC认为合适的

n q:表示一个通用寄存器,和r的意义相同

n a:表示使用% eax% ax% al

n b:表示使用% ebx% bx% bl

n c:表示使用% ecx% cx% cl

n d:表示使用% edx% dx% dl

n s:表示使用% esi% si

n f:表示使用浮点寄存器

n t:表示使用第一个浮点寄存器

n u:表示使用第二个浮点寄存器

l 内存约束

n Eg.__asm__(“lidt %0”:”=m”(__idt_addr));

n 内存方式进行输入输出的时候,由于不借助寄存器,所以GCC不会按照声明对其作出任何的输入输出处理,只是直接拿来用

n m:表示用系统所支持的任何一种内存方式,不需要借助寄存器

l 立即数约束

n Eg.__asm__ __volatile__(“movl % 0, % %eax”::”i”(100));

n i/I:表示输入表达式是一个立即数(整数),不需要借助任何寄存器

n f/F:表示输入表达式是一个立即数(浮点数),不需要借助任何寄存器

l 通用约束

n g:表示可以用通用寄存器,内存,立即数等任何一种处理方式

n 0,1,2,3…:表示和第n个操作数使用相同的寄存器/内存

n 一个带有C/C++表达式的内联汇编,其操作表达式被按照列出的顺序编号,最多允许有10个操作表达式

n 如果某个input操作表达式使用0-9中的数字作为它的约束,则等于想GCC声明“我要使用和编号为1的output操作表达式形同的寄存器或者内存地址”

l 修饰符

n 等号(=)和加号(+)用于对output操作表达式的修饰

n 符号(&)只能用于output操作表达式的修饰,声明“不得为任何input操作表达式分配与此output操作表达式相同的寄存器”,意味着,output操作表达式在所有的input操作表达式输入前输出,排除了output使用已经被input提前使用过的东西

n 百分号(% ):只能在input操作表达式中,声明“当前input操作表达式中的C/C++表达式可以和下一个input操作表达式中的C/C++表达式互换”,一般用于符号交换律运算

n 修饰符的意义

u =/+/&:output write-only/read-write/独占

u % :input 可互换

n 占位符

u % 0,% 1等,每个占位符在编译时候,会替换为对应的input/output操作表达式所指定的寄存器/内存地址/立即数

u 必须使用占位符的情况:

l Input做指定的为内存地址(“m”),则无法事先确定地址,只能使用占位符

l Output中使用了通用寄存器(“=r”),那么不知道用了哪一个,只能用占位符

u Clobber(通知GCC当前语句可能会对某些寄存器或者内存进行修改,希望能够在编译的时候将这一点考虑进去,那么就可以再clobber中声明这些)

l 一般发生情况:寄存器出现在“instruction list”,却不是由input/output所指定,也不是input/output使用”r”/”g”时由GCC为其选择,同时此寄存器被”instruction list”中的指令修改,而该寄存器只是当前内联汇编时使用

l 如果在一个内联汇编语句的clobber域向GCC声明某个寄存器内容发生了改变,GCC编译时,如果发现这个被声明的寄存器内容在此内联汇编语句之后还要继续使用那么会先将此寄存器的内容保存起来,然后再内联汇编语句之后,将其内容恢复。

l 如果一个寄存器被声明在了clobber中,那么,input/output中不能再使用

l 内存被修改:使用“memory”来声明内存可能发生变化,如果将内存的内容读入某个寄存器,那么,在执行完内联汇编之后,如果还需要这个内存的内容,不会直接给出寄存器的内容,而是重新进行内存读取

l 使用”cc”:当内联指令中包含影响eflags寄存器中的条件标志时,最好加入这个声明,因为条件标志很可能在内联汇编中被改变

l 0-9:声明第n个input/output操作表达式所使用的寄存器发生了变化


你可能感兴趣的:(c,linux)