GNU C内联汇编(AT&T语法)
转:http://www.linuxso.com/linuxbiancheng/40050.html
内联汇编提供了可以在C或C++代码中创建汇编语言代码,不必连接额外的库或程序。这种方法对最终程序在汇编语言级别如何实现特定的函数,给予程序员更多的控制权。
1.基本的内联汇编
1)asm格式
GNU的C编译器使用asm关键字指出使用汇编语言编写的源代码段落。基本格式:
asm("assembly code");
括号中的汇编格式:指令必须在引号里;指令超过一条,必须使用新行字符分隔。如:
asm ( "movl $1, %eax\n\t"
"movl $0, %ebx\n\t"
"int $0x80" );
2)使用全局C变量
如何将数据传递和传出汇编语言呢?一种方法是使用C语言的全局变量,并且只有全局的变量才能在基本的内联汇编代码内使用。
示例:
/*************************************************************************
> File: use_global_var.c
> Author: 孤舟钓客
> Mail: guzhoudiaoke@126.com
> Time: 2012年12月23日 星期日 11时33分25秒
************************************************************************/ #include<stdio.h> int a = 11;
int b = 22;
int result; int main()
{
asm ( "pusha\n\t"
"movl a, %eax\n\t"
"movl b, %ebx\n\t"
"imull %ebx, %eax\n\t"
"movl %eax, result\n\t"
"popa" );
printf ("The answer is %d\n", result);
return 0;
}
运行结果:
liury@liury-laptop:~/program/asm/inline_assembly/use_global_var$ ls
use_global_var.c
liury@liury-laptop:~/program/asm/inline_assembly/use_global_var$ gcc -o use_global_var use_global_var.c
liury@liury-laptop:~/program/asm/inline_assembly/use_global_var$ ./use_global_var
The answer is 242
注释:
反汇编:
可以发现a和b在.data段中,并且类型、对齐方式等的设置。result没有初始化,故声明为.comm值。
注意开头和结尾的PUSHA,POPA。因为后面的C代码可能用到寄存器,而内联汇编中可能改变了它们,会发生不可预料的后果,故要在开始的位置保存它们,最后恢复它们。
3)volatile修饰符
编译器会试图优化生成的汇编代码以提高性能。但对内联汇编来说,优化有时并不是好事。如果不希望编译器处理内联汇编代码,可以明确地说明。用volatile修饰符可以完成这个请求:
asm volatile ("assembly code");
4)__asm__替换关键字
ANSI C 规范把关键字asm用于其他用途,不能将它用于内联汇编语句。如果希望使用ANSI C 约定编写代码,必须使用关键字__asm__替换一般的关键字asm。汇编代码段则与asm一样。__asm__可以使用__volatile__进行修饰。
2.扩展的asm
基本的asm格式简单,但有局限:所有输入输出必须使用全局C变量;必须注意不改变任何寄存器的值。
扩展格式提供附加选项。
1)扩展asm格式
扩展asm提供附加的特性,格式:
asm ("assembly code" : output locations : input operands : changed registers);
assembly code:汇编代码,同基本的asm
output locations:输出位置,包含内联汇编代码的输出值的寄存器和内存位置的列表
input operands: 输入操作数,包含内联汇编代码的输入值的寄存器和内存位置的列表
changed registers:改动的寄存器,内联代码改变的任何其他寄存器列表
若不生成输出值:asm ("assembly code" : : input operands : changed registers);
若不改动任何寄存器: asm ("assembly code" : output locations : input operands);
.file "use_global_var.c"
.globl a
.data
.align 4
.type a, @object
.size a, 4
a:
.long 11
.globl b
.align 4
.type b, @object
.size b, 4
b:
.long 22
.comm result,4,4
.section .rodata
.LC0:
.string "The answer is %d\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
#APP
# 16 "use_global_var.c" 1
pusha
movl a, %eax
movl b, %ebx
imull %ebx, %eax
movl %eax, result
popa
# 0 "" 2
#NO_APP
movl result, %edx
movl $.LC0, %eax
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"
.section .note.GNU-stack,"",@progbits
2)指定输入和输出
扩展格式中,可从寄存器和内存位置给输入、输出赋值,输入、输出列表的格式:
"constraint" (variable)
variable 是C变量。扩展asm中,局部和全局变量都可以用。约束(constraint)定义把变量存放在哪里(对于输入值)或者从哪里传送变量(对于输出值)。使用它定义把变量存放在寄存器还是内存位置中。
约束是单一字符的代码,定义如下:
------------------------------------------------------------------------
约束描述
--------------------------------------------------------------------
aUse the %eax, %ax, or %al registers.
bUse the %ebx, %bx, or %bl registers.
cUse the %ecx, %cx, or %cl registers.
dUse the %edx, %dx, or $dl registers.
SUse the %esi or %si registers.
DUse the %edi or %di registers.
rUse any available general-purpose register.
qUse either the %eax, %ebx, %ecx, or %edx register.
AUse the %eax and the %edx registers for a 64-bit value.
mUse the variable\u2019s memory location.
oUse an offset memory location.
VUse only a direct memory location.
iUse an immediate integer value.
nUse an immediate integer value with a known value.
gUse any register or memory location available.
-------------------------------------------------------------------------
除了这些约束外,输出值还包含一个约束修饰符,它指示编译器如何处理输出值:
---------------------------------------------------------------------
输出修饰符描述
---------------------------------------------------------------
+可以读取和写入操作数
=只能写入操作数
%如果必要,操作数可以和下一个操作数切换
&在内联函数完成前,可以删除或者重新使用操作数
----------------------------------------------------------------------
示例:
asm ("assembly code" : "=a"(result) : "d"(data1) : "c"(data2));
把C语言变量data1放到EDX中,data2放到ECX中,结果存放到EAX中然后传送给result。
3)使用寄存器
如果输入值和输出变量被赋值给寄存器,那么在内联汇编中几乎可以像平常一样使用寄存器。
示例:
/*************************************************************************
> File: use_registers.c
> Author: 孤舟钓客
> Mail: guzhoudiaoke@126.com
> Time: 2012年12月23日 星期日 13时56分38秒
************************************************************************/ #include<stdio.h> int main()
{
int data1 = 11;
int data2 = 22;
int result; __asm__ ("imull %%edx, %%ecx\n\t"
"movl %%ecx, %%eax"
: "=a"(result)
: "d"(data1), "c"(data2)); printf("The result is %d\n", result);
}
运行:
liury@liury-laptop:~/program/asm/inline_assembly/use_registers$ gcc -o use_registers use_registers.c
liury@liury-laptop:~/program/asm/inline_assembly/use_registers$ ls
use_registers use_registers.c use_registers.s
liury@liury-laptop:~/program/asm/inline_assembly/use_registers$ ./use_registers
The result is 242
注释:
为了使用占位符见下面,使用寄存器时要写两个%
"=a" 使用等号符号修饰输出寄存器表明汇编代码只能写入它,这是对内联汇编代码中所有输出值的要求。
反汇编:
movl $11, 28(%esp)
movl $22, 24(%esp)
movl 28(%esp), %eax
movl 24(%esp), %ecx
movl %eax, %edx
#APP
# 16 "use_registers.c" 1
imull %edx, %ecx
movl %ecx, %eax
# 0 "" 2
#NO_APP
movl %eax, 20(%esp)
可见,编译器把C局部变量栈上的值加载到了寄存器中,并通过把EAX中的结果输出给栈上的变量result。
不一定要在内联汇编中指定输出值,一些汇编指令已经假设输入值包含输出值。比如MOVS指令输入值包含输出位置。
示例:
/*************************************************************************
> File: only_input.c
> Author: 孤舟钓客
> Mail: guzhoudiaoke@126.com
> Time: 2012年12月23日 星期日 14时15分12秒
************************************************************************/ #include<stdio.h> int main()
{
char input[30] = "Hello inline assembly.\n";
char output[30];
int len = 24; __asm__ __volatile__ (
"cld\n\t"
"rep movsb"
:
: "S"(input), "D"(output), "c"(len)); printf("%s", output);
return 0;
}
运行:
liury@liury-laptop:~/program/asm/inline_assembly/use_registers$ gcc -o only_input only_input.c
liury@liury-laptop:~/program/asm/inline_assembly/use_registers$ ./only_input
Hello inline assembly.
注释:
程序把MOVS 需要的三个输入值作为输入,要复制的字符串的位置存放在ESI中,目标位置存放在EDI中,要复制的字符串长度存放在ECX中,
输出值已被定义为输入值之一,所以在扩展格式中没有专门定义输出值。
此时volatile很重要,否则编译器或许会认为这个asm段是不必要的而删除它,因为它不生成输出。
4)使用占位符
当有很多输入值时,上面的方法有点麻烦,于是提供了占位符(placeholder),可以在内联汇编中使用它引入输入和输出。这样可以在对于编译器方便的任何寄存器或者内存位置中声明输入和输出。
占位符是前面加%的数字。按照内联汇编中列出的每个输入值和输出值在列表中的顺序,每个值被赋予一个从0开始的数字,然后可以在汇编代码中使用占位符表示值。如:
asm ("assembly code"
: "=r"(result)
: "r"(data1), "r"(data2));
将生成如下的占位符:
%0: 表示包含变量值result的寄存器
%1: 表示包含变量值data1的寄存器
%2: 表示包含变量值data2的寄存器
使用占位符:
imull %1, %2
movl %2, %0
5)引用占位符
如果内联汇编代码中的输入和输出共享C变量,可以指定占位符作为约束值,可减少代码中需要的寄存器数量:
asm ("imull %1, %0"
: "=r"(data2)
: "r"(data1), "0"(data2));
0标记通知编译器使用第一个命名的寄存器存放输出值data2.
6)替换占位符
当输入输出很多时,数字型的占位符会很混乱,新的(3.1开始)GNU编译器允许声明替换的名称作为占位符,格式:
%[name] "constraint" (variable)
示例:
asm ("imull %[val1], %[val2]"
: [val2] "=r"(data2)
: [val1] "r"(data1), "0"(data2));
7)改动的寄存器列表
前面的例子中没有指定改动的寄存器,为何? 编译器默认输入值和输出值使用的寄存器都会被改动,并做了相应处理,所以不需要指定这些是改动了的寄存器,而若指定了,会产生错误信息
正确方法:如果内联汇编代码使用了没有被初始地声明为输入输出的任何其他寄存器,则要通知编译器。编译器必须知道这些寄存器,以便避免使用它们。
示例:
asm ("movl %1, %%eax\n\t"
"addl %%eax, %0"
: "=r"(result)
: "r"(data1), "0"(result)
: "%eax" );
在改变的寄存器中指明要使用%eax,则当用"r"指定要使用一个寄存器时就不会选%eax了。
如果在内联汇编中使用了没有在输入输出中定义的任何内存位置,必须标记为被破坏的。在改动的寄存器列表中使用”memory“通知编译器这个内存位置在内联汇编中被改动。
8)使用内存位置
在内联汇编代码中使用寄存器比较快,但也可以直接使用C变量的内存位置。约束m用于引用输入输出的内存位置。
示例:
asm ("divb %2\n\t"
"movl %eax, %0"
: "=m"(result)
: "a"(dividend), "m"(divisor));
9)跳转
内联汇编代码中也可以包含定义位置标签,实现跳转。
示例:
int a = 11;
int b = 22;
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) );
内联汇编中使用标签的两个限制:
只能跳转到相同的asm段内的标签;
内联汇编也被编码到最终的汇编代码中,如果有另一个asm段,就不能再次使用相同的标签,否则会出错。另外如果试图整合使用C关键字(如函数名称或全局变量)的标签,也会出错。
解决办法:
在不同的asm段中也不用用过的标签;
使用局部标签。
条件分支和无条件分支都运行指定一个数字加上方向标志作为标签,方向标志指出处理器应该向哪个方向查找数字型标签,第一个遇到的标签会被采用。
示例:
asm ("cmp %1, %2\n\t"
"jge 0f\n\t"
"movl %1, %0\n\t"
"jmp 1f\n"
"0:\n\t"
"movl %2, %0\n"
"1:"
: "=r"(result)
: "r"(a), "r"(b) );
其中f(forward)指出从跳转指令向前(即到后面的代码)查找标签,b(backword)则相反,到向后(到前面的代码)找标签。
3.内联汇编用作宏函数
1)C宏函数
#define NAME expression
示例:
#define SUM(a, b, result) \
((result) = (a) + (b))
2)内联汇编宏函数
示例:
#define GREATER(a, b, result) ( { asm ( \
"cmp %1, %2\n\t" \
"jge 0f\n\t" \
"movl %1, %0\n\t" \
"jmp 1f\n\t" \
"0:\n\t" \
"movl %2, %0\n\t" \
"1:\n\t" \
: "=r"(result) \
: "r"(a), "r"(b) ); })
GNU C内联汇编(AT&T语法)的更多相关文章
- GNU C 内联汇编介绍
GNU C 内联汇编介绍 简介 1.很早之前就听说 C 语言能够直接内嵌汇编指令.但是之前始终没有去详细了解过.最近由于某种需求,看到了相关的 C 语言代码.也就自然去简单的学习了一下如何在 C 代码 ...
- x86平台转x64平台关于内联汇编不再支持的解决
x86平台转x64平台关于内联汇编不再支持的解决 2011/08/25 把自己碰到的问题以及解决方法给记录下来,留着备用! 工具:VS2005 编译器:cl.exe(X86 C/C+ ...
- 最牛X的GCC 内联汇编
导读 正如大家知道的,在C语言中插入汇编语言,其是Linux中使用的基本汇编程序语法.本文将讲解 GCC 提供的内联汇编特性的用途和用法.对于阅读这篇文章,这里只有两个前提要求,很明显,就是 x86 ...
- Linux 中 x86 的内联汇编
工程中需要用到内联汇编,找到一篇不错的文章,趁机学习下. 原文地址:http://www.ibm.com/developerworks/cn/linux/sdk/assemble/inline/ 如果 ...
- GCC内联汇编入门
原文为GCC-Inline-Assembly-HOWTO,在google上可以找到原文,欢迎指出翻译错误. 中文版说明 由于译者水平有限,故译文出错之处,还请见谅.C语言的关键字不译,一些单词或词组( ...
- [翻译] GCC 内联汇编 HOWTO
目录 GCC 内联汇编 HOWTO 原文链接与说明 1. 简介 1.1 版权许可 1.2 反馈校正 1.3 致谢 2. 概览 3. GCC 汇编语法 4. 基本内联 5. 扩展汇编 5.1 汇编程序模 ...
- 推荐一篇讲arm架构gcc内联汇编的文章
这是来自ethernut网站的一篇文章,原文链接: http://www.ethernut.de/en/documents/arm-inline-asm.html 另外,据说nut/os是个不错的开源 ...
- C内联汇编
用C写程序比直接用汇编写程序更简洁,可读性更好,但效率可能不如汇编程序,因为C程序毕竟要经由编译器生成汇编代码,尽管现代编译器的优化已经做得很好了,但还是不如手写的汇编代码.另外,有些平台相关的指令必 ...
- C语言的本质(32)——C语言与汇编之C语言内联汇编
用C写程序比直接用汇编写程序更简洁,可读性更好,但效率可能不如汇编程序,因为C程序毕竟要经由编译器生成汇编代码,尽管现代编译器的优化已经做得很好了,但还是不如手写的汇编代码.另外,有些平台相关的指令必 ...
随机推荐
- Linux时间子系统之一:clock source(时钟源)【转】
转自:http://blog.csdn.net/droidphone/article/details/7975694 clock source用于为linux内核提供一个时间基线,如果你用linux的 ...
- UVALive 7040 Color
题目链接:LA-7040 题意为用m种颜色给n个格子染色.问正好使用k种颜色的方案有多少. 首先很容易想到的是\( k * (k-1)^{n-1}\),这个算出来的是使用小于等于k种颜色给n个方格染色 ...
- 2015多校第6场 HDU 5354 Bipartite Graph CDQ,并查集
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5354 题意:求删去每个点后图是否存在奇环(n,m<=1e5) 解法:很经典的套路,和这题一样:h ...
- ACdream 1157 Segments CDQ分治
题目链接:https://vjudge.net/problem/ACdream-1157 题意: Problem Description 由3钟类型操作: 1)D L R(1 <= L < ...
- vue数据绑定方式:
1,{{ }} 2,v-text 3,v-html 前两种接受普通变量,第三种绑定带有标签的内容,但是严禁使用,这个会有 XSS危险,(将字符串解析成源代码) 4,v-bind:title=‘m ...
- [转]6个HelloWorld
原文地址:点击打开链接 转这个帖子,是因为看了这个帖子使我明白了一个道理:一旦你发散自己的思维,激发自己的创意,就会发现原来编程是这么的好玩. 原文标题为<6个变态的C语言Hello World ...
- Django内置信号
阅读目录(Content) Django中内置的signal 自定义信号 1.定义信号 2.注册信号 3.触发信号 回到顶部(go to top) Django中内置的signal Django中提供 ...
- 使用GitLab进行落地项目的管理,并且自动更新、重启、回滚
Gitlab 清空项目历史commit,节省空间 http://blog.csdn.net/dounine/article/details/77840416?locationNum=6&f ...
- Run Rancher server on windows
软件环境:WIN 10 一.首先安装Docker for Windows,Cmder(我用这个执行Docker 命令) 二.右键右下角Docker 图标--> Daemon ,在Registry ...
- LoadRunner 执行单句SQL语句
LoadRunner 执行单句SQL语句 Action() { int NumRows=0; int i=1; //建立数据库连接 lr_db_connect("StepName=Datab ...