当前位置: 首页 > C, C/C++ > 正文

C语言內嵌汇编

说明:学校开设的《微机原理与接口》讲的是8086汇编是Inter 的汇编,可是GCC 使用的是AT&T汇编。
AT&T 和 Inter 汇编的一些区别

AT&T Inter
寄存器的命名规则

%eax eax

源目的操作顺序

%eax %ebx ebx eax
常数立即数的格式
%movl $_value ebx mov eax,_value
把value的地址放进eax寄存器
movl $0xd00d ,%ebx mov ebx,0xd00d
操作数长度标识
movw %ax,%bx mov bx,ax
寻值方式
immed32(basepointer,indexpointer,indexscale)
basepointer + indexpointer*indexscale + imm32

直接寻址
ATT: _boo
Inter: [_boo]

寄存器间接寻址
ATT : (%eax)
Intel: [eax]

变址寻址
ATT _variable(%eax)
Intel [eax+_variable]
ATT _array(,%eax,4)
Intel [eax*4+_array]
ATT _array(%ebx,%eax,8)
Intel [ebx+eax*8+_array]

GCC 内联汇编

例如:
asm(“statments”)
asm(“nop”);asm(“cli”)

asm和__asm__ 含义是一样的,如果有多行汇编都要加上“\\n\\t”.其中”\\n“是换行符,”\\t”是tab符。
在每条命令的结束加上着两个符号,是为了让gcc把内联汇编代码翻译成一般的汇编代码时,能够保证换行和留有一定的空格。
eg:
asm(“pushl %eax\n\t”
“movl S0.%eax\n\t”
“popl %eax”
);
实际上gcc 在处理汇编的时后,就是要把asm(…)打印到汇编文件中。
扩展GCC内联汇编
基本的格式如下
__asm__ __volatile__(“<asm routine>”:output:input:modify);

1.__asm__ 表示汇编代码开始,其后可以跟__volatile__ (可选项),其含义时避免asm 指令被删除移动组合,就是指令重组
2.如果执行的时候不希望GCC 优化就使用asm volatile(…); 等价于 __asm__ __volatile__(….);
3.”<asm routinue>” 为汇编指令的部分,数字前面加前缀%,
4.汇编指令部分中有几个操作数,就说明有几个变量需要与寄存器结合,由GCC 在编译后根据输出部分和输入部分的约束条件进行相应的处理,
由于这些样办操作数的前缀使用了“%”,因此,在用到具体的寄存器时就再前边加上两个% 。
5.输出部分用来规定输出变量和寄存器的结合,可以由多个约束,互相以逗号分开,每个以“=”开头,接着用一个字母来标示操作数的类型,然后是关于变量的约束。
:”=r”(__dummy)
“=r”表示相应的目标操作数(指令部分%0)可以使用任何一个通用寄存器并且变量存放在__dummy 存放在这个寄存器中
:”=m”(_dummy)
“= m”就表示相应的目标操作数是存放在内存单元__dummy 中,表示约束的字母很多,下标给出几个主要约束的字母及表示含义。

字母 含义
m,v,o 内存单元
R 任何通用寄存器
Q 寄存器eax,ebx,ecx,edx 之一
I,h 直接操作数
E,F 浮点数
G 任意
a,b,c,d 寄存器 eax/ax/al ,ebx/bx/bl,或 edx/dx/dl
S,D 寄存器esi 或 edi
I 常数(0~31)

6.输入部分类似输出部分 但是没有“=”
7.修改部分(Modify),以表示操作完成后内存中的内容已有改变

举个例子,一个简单的例子,没有实际意义,我们只是看看效果
首先是一个C源代码
#include<stdio.h>
int input,result;

void test(void){
input = 1;
__asm__ __volatile__(“movl %1,%0” : “=r”(result): “r”(input));
return;
}

然后GCC -c -S 看看汇编的结果

.file “GCC_SUM1.c” /*源文件名*/
.comm input,4,4 /*common 段*/
.comm result,4,4
.text
.globl test
.type test, @function
test: /*test 函数*/
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $1, input(%rip) /*将1写入变量input放在寄存器rip 中*/
movl input(%rip), %eax /*将 rip 的值写入eax中,这行是GCC 自动增加的代码*/
#APP /*GCC 插入的注释,表示内嵌汇编的开始*/
# 13 “GCC_SUM1.c” 1
movl %eax,%eax /*我们的内嵌汇编语句,由于没有指定使用的寄存器,GCC 都使用了eax 寄存器*/
# 0 “” 2
#NO_APP /*表示内嵌汇编结束*/
movl %eax, result(%rip) /*将eax 的值写进result,这行也是GCC自动增加的代码*/
nop
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size test, .-test
.ident “GCC: (GNU) 4.9.2 20150212 (Red Hat 4.9.2-6)”
.section .note.GNU-stack,””,@progbits
可以看出,首先将1写进了input.此时默认的寄存器是rip,接着我们的内嵌汇编开始了,由于我们并没有指定寄存器,所以GCC 自动选定了eax,生成了movl %eax %eax,但是很明显这是一句“废话”,但是也并没有优化这个部分,就直接打印出来了,之前说过了“=”是输出变量,所以我们这句内嵌汇编的本意就是将input 写到 result 中去。所以最终生成 movl %eax result(%rip)这句将input 写进了 result.

总结以上的一个小例子

#include<stdio.h>

int main(){
int a = 10;
int b = 22;
__asm__ __volatile__(“movl %1,%0”:”=r”(b):”r”(a));
printf(“%d %d\n”,a,b);

}

输出结果如我们所料就是:10 10
接下来我们来探讨探讨下比较通用的内嵌汇编写法。
内嵌汇编语法如下:

__asm__ (
汇编语言模板:
输出部分:
输入部分:
破坏描述部分)

共四个部分:汇编语句模板,输出部分,输入部分,破坏描述部分,每个部分使用: 隔开,汇编模板部分必不可少,其他三部分可选,如果使用了后面的部分,前面部分为空,也需要使用:格开,相应部分为空。
__asm__ __volatile__(“cil”:::”memory”)
汇编语句模板

汇编语句模板由汇编语句序列组成,使用; \n \n\t 分开,指令中的操作数可以使用占位符引用C语言变量,操作数占位符最多10个,%0,%1,%2,%3…..指令中的占位符总被视为long 4B 大小,但对其时加的操作根据指令可以是字或者是字节,一般默认是低字,或者低字节,其实可以指明它的情况,方法是在%和序号之间插入一个字母,b 代表低字节,h 代表高字节。%h1

输出部分:每个输出操作数由字符串和C语言变量组成,其中每个变量的字符串必须有“=”。描述字符串表示对该变量的条件限制,这样GCC 就可以根据这些条件决定如和分配寄存器,如何产生必要的代码处理操作数与C表达式或C变量之间的联系。

输入部分:输入部分描述输入操作符,不同的操作数描述符之间使用逗号隔开,每个操作数描述符限定字符串和C语言表达式或者C语言变量组成。

bitops.h
static __inline__ void __set_bit(int nr,volatile void *addr){
__asm__(
“btsl %1,%0”:
“=m”(addr):
“lr”(nr));
}

这个函数的作用是将addr 的对应第nr位设置成1。

限制字符以及一些小问题:
当我们需要movl a , eax ;但是如果eax已经被使用了呢?GCC 会将它入栈,到代码结束的时候在出栈。
使用“r“限制输入的变量,GCC 首先会分配一个寄存器,然后将值读入寄存器,最后使用该寄存器替换占位符;
使用“r”限制输入的变量,GCC 会分配一个寄存器然后用该寄存器替换占位符,但是在使用该寄存器以前并不将变量值先读入寄存器,GCC 认为所有输出在最终完成运算之前都是没有用的,所以不读入寄存器。最后GCC 插入代码,将寄存器的值写回变量;
输入变量使用的寄存器在最后一处使用它的指令后,就可以挪做它用。
最新的GCC 增加了“+”操作符,直接使一个操作数变成“读写型”。
#include<stdio.h>
int main(){
int result = 3;
int input = 1;
__asm__ __volatile__(“addl %1,%0”:”+r”(result):”m”(input));
printf(“the result = %d\n”,result);
return 0;
}

&限制符在内核中使用的较多,它表示输入输出操作数不能使用相同的寄存器。
memory 由两个作用:
1.不要将该段内嵌汇编指令与前面的指令重新排序;也就是执行内嵌汇编代码之前,他面前的指令全都执行完毕。
2.不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变量可以以一种不可预知的方式发生改变,因此GCC插入必要的代码先将缓存在寄存器的值写回内存。

参考:Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1054411

本文固定链接: http://zmrlinux.com/2016/01/19/c%e8%af%ad%e8%a8%80%e5%85%a7%e5%b5%8c%e6%b1%87%e7%bc%96/ | Kernel & Me

该日志由 root 于2016年01月19日发表在 C, C/C++ 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: C语言內嵌汇编 | Kernel & Me