内联汇编提供了可以在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;}
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, 4a: .long 11.globl b .align 4 .type b, @object .size b, 4b: .long 22 .comm result,4,4 .section .rodata.LC0: .string "The answer is %d\n" .text.globl main .type main, @functionmain: 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)定义把变量存放在哪里(对于输入值)或者从哪里传送变量(对于输出值)。使用它定义把变量存放在寄存器还是内存位置中。 约束是单一字符的代码,定义如下: ------------------------------------------------------------------------ 约束 描述 -------------------------------------------------------------------- a Use the %eax, %ax, or %al registers. b Use the %ebx, %bx, or %bl registers. c Use the %ecx, %cx, or %cl registers. d Use the %edx, %dx, or $dl registers. S Use the %esi or %si registers. D Use the %edi or %di registers. r Use any available general-purpose register. q Use either the %eax, %ebx, %ecx, or %edx register. A Use the %eax and the %edx registers for a 64-bit value. m Use the variable\u2019s memory location. o Use an offset memory location. V Use only a direct memory location. i Use an immediate integer value. n Use an immediate integer value with a known value. g Use 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);}
- 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)
- /************************************************************************* > 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;}
4)使用占位符
当有很多输入值时,上面的方法有点麻烦,于是提供了占位符(placeholder),可以在内联汇编中使用它引入输入和输出。这样可以在对于编译器方便的任何寄存器或者内存位置中声明输入和输出。 占位符是前面加%的数字。按照内联汇编中列出的每个输入值和输出值在列表中的顺序,每个值被赋予一个从0开始的数字,然后可以在汇编代码中使用占位符表示值。如:- asm ("assembly code" : "=r"(result) : "r"(data1), "r"(data2));
- imull %1, %2movl %2, %0
5)引用占位符
如果内联汇编代码中的输入和输出共享C变量,可以指定占位符作为约束值,可减少代码中需要的寄存器数量:- asm ("imull %1, %0" : "=r"(data2) : "r"(data1), "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" );
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 ("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) );
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) ); })