200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 跟我一起走进内联汇编的世界

跟我一起走进内联汇编的世界

时间:2018-10-15 01:27:08

相关推荐

跟我一起走进内联汇编的世界

一、基本内联汇编:

内联汇编代码比较容易整合到c/c++的代码中,可以做一些对于单独使用c/c++来说笨重或者不可能完成的任务。而且它还可以访问寄存器的值、访问条件码、使用一些特殊的指令和特殊的内存地址等等。

内联汇编主要用于如下场合:

1.使用汇编语言编写特定的函数;

2.编写对速度要求非常高的代码;

3.设备驱动程序中直接访问硬件;

4.编写"Naked" Call的初始化和结束代码。

C/C++与汇编可以混合使用,在内联汇编可以使用C/C++的变量和很多其它C/C++的元素。在__asm__块中可以使用以下C/C++元素:

1.符号,包括标号、变量和函数名;

2.常量,包括符号常量和枚举型(enum)成员;

3.宏定义和预处理指示符;

4.注释,包括""和"//";

5.类型名,包括所有MASM中合法的类型

6.typedef名称, 像PTR、TYPE、特定的结构成员或枚举成员这样的通用操作符。

在__asm__块中,可以使用C/C++或ASM的基数计数法(比如: 0x100和100H是相等的)。

二、gcc定义嵌入式汇编

asm asm-qualifiers ( AssemblerTemplate:

OutputOperands[ :

InputOperands[ :

Clobbers/Modify] ])

解析:

asm:嵌入式汇编的关键字,但为了解决在有编译选项‘-std=gnu99’的情况下,asm关键字会无效的问题,实际都是使用__asm__。

用来声明一个内联汇编表达式,任何一个内联汇编表达式都是以它开头的,是必不可少的。

asm-qualifiers:限定符,当是volatile(实际用__volatile__)时,表示不要对嵌入式汇编进行优化。否则当你使用了优化选项(-O)进行编译时,GCC将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。

如果你不想让GCC的优化影响你的内联汇编代码,你最好在前面都加上__volatile__,而不要依赖于编译器的原则,因为即使你非常了解当前编译器的优化原则,你也无法保证这种原则将来不会发生变化。而__volatile__的含义却是恒定的。

AssemblerTemplate:汇编模板,名称虽然很奇怪,但没有输入输出参数的情况下,其内容就是普通的汇编语句。

也就是"Instruction List",它是汇编指令序列。它可以是空的。

其中()里面所有的汇编语句都属于AssemblerTemplate的内容。但是,如果需要输入输出参数时,则%0表示第一个参数,%n表示第n个参数。

比如:__asm__ __volatile__("");或__asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。

但并非所有Instruction List为空的内联汇编表达式都是没有意义的。

比如:__asm__ ("":::"memory");就非常有意义。

它向GCC声明:“我对内存作了改动”,GCC在编译的时候,会将此因素考虑进去。

注意的俩点:

第一就是所有寄存器都使用%%作为前缀,第二在这个部分新增了%0~%9的占位符来表示用户填充的数据。那么占位符,占的是什么位呢,谁来填充,怎么填充?%0~%9的占位符会用输出部分和输入部分指定的寄存器或变量按照出现的顺序依次填充。如果不够填充则会出现编译错误的情况。

invalid 'asm': operand number out of range

例如下面的汇编代码:

__asm__("movl %0,%%eax \n\t""movl %%eax,%1":"=m" (a):"m" (b));

其中%0会被变量a的内存地址替换掉,%1会被变量b的内存地址替换掉。

除了这两点外,还需要注意一点的就是多条指令如何分割,在这里需要使用\n\t进行分割,每条语句使用冒号包裹起来。有的文档提到还可以使用分号来分割指令但是经过我测试发现其实并不可以,使用分号分割指令编译是可以通过的,但是生成的汇编代码中会保留分号导致分号后面的代码都被注释了(汇编语言中分号代表注释)linux内核中使用内联汇编也是使用\n\t来进行分割的,所以这里我只推荐\n\t来进行分割多条指令。

OutputOperands:表示嵌入式汇编的输出参数,和AssemblerTemplate中的%n对应。

输出部分是用于控制汇编语句中的输出,将寄存器的值输出到C语言中的变量中去。

例如:

__asm__("movq %%rcx,%0":"=a"(cr0));

这句汇编代码的含义则是把寄存器rcx的值赋给C语言中的变量cr0,那么”=a”的前缀是啥意思呢,a代表的就是rax/eax/ax/ah这类寄存器,会根据目标变量的大小选择对应的寄存器,这里我是64位的平台,并且rcx寄存器也是64位的,所以默认是将rax的值赋值给cr0。那么=号是啥意思呢?

这是输出部分必须要加的前缀,表示这个部分是输出,对应的变量值是只写的。那么在这里首先movq %%rcx,%0 会将rcx寄存器的值赋值给rax寄存器,然后再将rax寄存器的值赋值给C语言中的cr0变量,如果把=a换成=m,则意思变为将rcx寄存器的值赋值给c变量中的cr0变量,最终的结果都是一样,只不过中间过程不同而已,像这样的前缀类型还有很多,详细见下述,如果不清楚前缀的作用可以试图使用gcc -S 进行编译,然后查看编译后的汇编代码做了哪些改动,从而可以确定前缀的含义。

对于+号这一前缀来说也有一些其它的前缀,这些前缀有的只能用于输出部分,有的只能用于输入部分,例如+号就只能用于输出部分,详细见下述。这里再解释一个+号的含义,+号表示目标是可读也可写的,如果把=a换成+a那么含义就变成了先把C中变量cr0的值赋值给rax寄存器,然后再把rcx寄存器的值赋值给rax寄存器,最后再把rax寄存器的值赋值给变量cr0。

这里多出了一步,先把cr0变量的值赋值给rax寄存器。那么这就是+号的含义,显然在这段汇编中使用+号是没有意义的,因为rax寄存器紧跟着就被rcx寄存器的值覆盖了。

InputOperands:表示嵌入式汇编的输入参赛,和AssemblerTemplate中的%n对应。

输入部分是用于将C中的变量值作为汇编语句中的输入,例如:

__asm__ __volatile__("movq %0,%%rbx"::"a"(var));

这里是将C语言中变量var的值赋值给rbx寄存器,至于前缀”a”则表示,会先将变量var的值赋值给rax寄存器,然后再把rax寄存器的值赋值给rbx寄存器,如果把这里换成m则表示直接将var内存地址处的值赋值给rbx寄存器。前缀类型请见下述。

Clobbers/Modify:在嵌入式汇编的执行过程中,除了OutputOperands指定的参数会明确被改变之外,可能还会改变其他的一些寄存器或者内存。当Clobbers为"memory"时,表示告诉编译器,在嵌入式汇编执行的过程中,RAM可能被修改了,需要无效所有的Cache。

这个部分需要将修改的寄存器列表放在这里,但是你也可以不放,但是这样可能会造成意想不到的结果,因为寄存器是全局唯一的,某一时刻一个进程使用了寄存器ecx,并存入10,然后你执行你的程序修改了ecx寄存器的值,这将导致其他的程序出现问题,那么如果你将ecx加入破坏描述部分,gcc会在使用ecx寄存器前先push入栈,等使用完ecx后再pop回去。这就保证了ecx寄存器在使用过程中没有被修改。

具体文件中的内联汇编代码:

static ALWAYS_INLINE void CpuWait()

{

base::subtle::PauseCPU();//暂停CPU

}

static ALWAYS_INLINE void MemoryBarrier() {

base::subtle::MemoryBarrier();//提供没有内存访问的“barrier”语义

}

///提供编译器障碍。编译器不允许对内存重新排序

///通过这个访问(但是CPU可以)。这不会生成任何指令。

static ALWAYS_INLINE void CompilerBarrier()

{

__asm__ __volatile__("" : : : "memory");//声明内存改变

//这是个空嵌入式汇编语句,其中"memory"表示,告诉编译器内存的内容可能被更改了,需要无效所有Cache,并访问实际的内容,而不是Cache。

//该语句创建一个编译器层的存储屏障(memory barrier),告诉编译器不要越过该屏障优化存储器的访问顺序.举例来说,如果你要访问某地址需要特殊的顺序(可能因为那地址的数据是其它设备的返回值)就要告诉编译器不要优化你的访问顺序,防止编译器会考虑效率问题而优化你的读写顺序.

}

三、带有C/C++表达式的内联汇编格式:

__asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);

规范:

1.这4个部分都不是必须的,任何一个部分都可以为空

2.如果Clobbers/Modify为空,则其前面的冒号(:)必须省略。

3.如果AssemblerTemplate为空,则Input,Output,Clobber/Modify可以不为空,也可以为空。

比如:__asm__ ( " " : : : "memory" );和__asm__(" " : : );

4.如果Output,Input,Clobber/Modify都为空,Output,Input之前的冒号(:)既可以省略,也可以不省略。如果都省略,则 此汇编退化为一个基本内联汇编,否则,仍然是一个带有C/C++表达式的内联汇编,此时”Instruction List”中的寄存器写法要遵守相关规定,比如寄存器前必须使用两个百分号(%%),而不是像基本汇编格式一样在寄存器前只使用一个百分号(%)。

5.如果后面的部分不为空,而前面的部分为空,则前面的冒号(:)都必须保留,否则无法说 明不为空的部分究竟是第几部分。

比如:Clobber/Modify,Output为空,而Input不为空,则Clobber/Modify前的冒号必须省略(前面的规则),而Output 前的冒号必须为保留。

例如:__asm__( " mov %%eax, %%ebx" : : "a"(foo) )

如果Clobber/Modify不为空,而Input和Output都为空,则Input和Output前的冒号都必须保留。

例如:__asm__( " mov %%eax, %%ebx" : : : "ebx" )

四、操作约束

每一个Input和Output表达式都必须指定自己的操作约束Operation Constraint。

1.寄存器约束

当你当前的输入或输入需要借助一个寄存器时,你需要为其指定一个寄存器约束。

你可以直接指定一个寄存器的名字,也可以指定一个缩写,如果你指定一个缩写,比如字母a,则GCC将会根据当前操作表达式中C/C++表达式的宽度决定使用%eax,%ax或%al。

当设置a的宽度为__shrt,由于变量__shrt是16-bit short类型,则编译出来的汇编代码中,则会让此变量使用%ex寄存器。即__asm__ ("mov %0,%%bx" : : "a"(__shrt));

无论是Input,还是Output操作表达式约束,都可以使用寄存器约束。

下表中列出了常用的寄存器约束的缩写。

约束 Input/Output 的意义:

2.内存约束:

如果一个Input/Output操作表达式的C/C++表达式表现为一个内存地址,不想借助于任何寄存器,则可以使用内存约束。

比如:

__asm__("lidt %0": "=m"(__idt_addr)); 或 __asm__("lidt %0": :"m"(__idt_addr));

五、相关操作符和修饰符含义

操作符:

Output输出操作表达式的修饰符:

Input输入操作表达式的修饰符:

六、ATT 内联汇编和intel内联汇编的区别

内联汇编的重要性体现在它能够灵活操作,而且可以使其输出通过 C 变量显示出来。因为它具有这种能力,所以 “asm” 可以用作汇编指令和包含它的 C 程序之间的接口。

一个非常基本但很重要的区别在于简单内联汇编只包括指令,而扩展内联汇编包括操作数。

注意在内联汇编中:

即在“asm” 内使用寄存器 %eax,%eax 的前面应该再加一个 %,换句话说就是 %%eax,因为 “asm” 使用 %0、%1 等来标识变量。任何带有一个 % 的数都看作是输入/输出操作数,而不认为是寄存器。

AT&T汇编和Intel汇编,是两种不同汇编语言格式,与具体CPU关系不大,只是Intel汇编格式基本只用在自家的x86系列CPU上,而AT&T汇编格式在多种CPU上都可以使用(x86,power,VAX等等)。

两者的区别主要是CPU指令、寄存器、立即数、寻址方式等方面的表示方法不同,如果只习惯一种,那么看另一种那是相当的费劲,经常想错。

接下来简单介绍一下:

ATT内联汇编 转 intel内联汇编的转换规则:

1.先用asm(“.intel_syntax noprefix/n”)声明一下

2.编译的时候加上 -masm=intel 参数

3.Intel汇编代码命名原则不同(省略了寄存器名字前面的‘%’符号):

比如:intel是esp,而不是%esp。

4.Intel汇编代码源/目的地操作数顺序不同(目的操作数在前,源操作数在后):

比如:intel是mov ebx, eax,而不是movl %eax, %ebx。

5.Intel汇编代码立即操作数/常数的格式(省略了前缀的‘$’符号):

比如:intel是mov eax,_value而不是mov $_value,%ebx。

规则同样适用于十六进制

6.在 Intel 汇编代码中,操作数的字长是用 “byte ptr” 和 “word ptr” 等前缀来表示的,而不是由操作符的最后一个字母决定,后缀’b’、’w’、’l’分别表示操作数为字节(byte,8 比特)、字(word,16 比特)和长字(long,32比特)

比如:intel是mov al, byte ptr val,而不是movb val, %al

7.在 Intel 汇编代码中,绝对转移和调用指令(jump/call)的操作数不需要加上’*’作为前缀,并且远程转移、子调用、返回指令的操作码格式为“jmp far”,“call far”,“ret far”而不是“ljump”,“lcall”,“lret”.

8.内存操作符的寻址方式不同,也就是不同的方式来描述寄存器中的位置:

在 Intel 汇编代码寻址方式格式中是[basepointer + indexpointer*indexscale + imm32),而不是imm32(basepointer,indexpointer,indexscale)。

比如:intel是dword ptr [ebp+8],而不是8(%ebp)。

七、ATT,intel,ARM寄存器的区别

1.ATT的寄存器:

16个常用寄存器

%rax, %rbx, %rcx, %rdx, %rsi, %rdi, %rbp, %rsp

%r8, %r9, %r10, %r11, %r12, %r13, %r14, %r15

寄存器的具体用途

%rax做为函数的返回值(同8086汇编的ax)

%rsp指向栈顶(同8086汇编的ss:sp)

%rdi、%rsi、%rdx、%rcx、%r8、%r9 等寄存器用于存放函数参数

8 个 32-bit t通用寄存器 %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 位;

2.intel的寄存器(类似于ATT寄存器):

通用寄存器:8个,分别为EAX,EBX,ECX,EDX,ESP,EBP,ESI,EDI

标志寄存器:1个,EFLAGS

控制寄存器:5个,分别为CR0-CR4

调试寄存器:8个,分别为DR0-DR7

系统地址寄存器:4个,GDTR、IDTR、LDTR和TR

16位段寄存器:6个,分别为CS,DS,ES,FS,GS,SS

其他寄存器:EIP、TSC等

3.ARM处理器中R0-R15共16个寄存器,它们的用途是一些约定的习惯,并依据这些用途定义了别名:

寄存器的使用情况如下:

1)子程序间通过寄存器R0-R3来传递参数,这时可以使用它们的别名A0-A3,被调用的子程序返回前无须重复R0-R3的内容。

例如:void fun(int a, int b, int c){

// a---> r0//b----> r1//c----> r2}

2)在子程序中,使用R4-R11来保存局部变量,这时可以使用它们的别名V1-V8,如果在子程序中使用了它们的某些寄存器,子程序进入时要保存这些寄存器的值,返回时再次恢复它们;

对于子程序中没有使用到的寄存器,则不必进行这些操作,在Thumb指令中,通常只能使用寄存器R4-R7来保存局部变量。

3)寄存器R12用作子程序间scratch寄存器,别名为IP。

4)寄存器R13用作数据栈指针,别名SP,在子程序中寄存器R13不能用作它用,它的值在进入、退出子程序时必须相等。

5)寄存器R14称为链接寄存器,别名LR,它用于保存子程序的返回地址。

如果在子程序中保存了返回地址,R14可用作它用。

6)寄存器R15是程序计数器,别名PC,没用别的用途

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。