GCC内联汇编入门

原文为GCC-Inline-Assembly-HOWTO,在google上能够找到原文,欢迎指出翻译错误。linux

 

中文版说明express

因为译者水平有限,故译文出错之处,还请见谅。C语言的关键字不译,一些单词或词组(如colbber等)因为恐怕译后词不达意,故并不翻译,由下面的单词表代为解释,敬请见谅。编程

 

英文原文中的单词和词组:ide

operand:操做数,能够是寄存器,内存,当即数。函数

volatile:易挥发的,是C语言的关键字。学习

constraint: 约束。优化

register: 本文指CPU寄存器。ui

asm:“asm”和“__asm__”在C语言中是关键字。原文中常常出现这个单词,是指嵌入到C语言(或者其它语言)的汇编程序片段。google

basic inline assembly:指C语言中内联汇编程序的一种形式,和extended asm对应。基本格式以下:编码

asm("assembly code");

extended assembly:和basic inline assembly对应,比它多了一些特性,如能够指明输入,输出等。基本格式以下:

asm ( assembler template

   : output operands

   : input operands

   : list of clobbered registers

   );

clobber list:实际上就是被使用的寄存器的列表,用来告诉GCC它们已经被asm使用了,不要在asm程序外使用它们。否则可能带来不可预见的后果。

clobbered registers:它和clobber list对应。

assembler template:就是汇编模板,全部内联汇编代码都有按必定的格式。

见extended assembly的说明

 

做者:Sandeep.S

译者:吴遥

版本号 v0.1  2003年3月01日

翻译版更新日期 2008/06/11

 

这篇HOWTO解释GCC提供的内联汇编特性的用途和用法。学习这篇文章只须具有两个前提条件,显然那就是对x86汇编语言和C语言有基本的了解。

 

  

1. 前言

1.1版权与许可证

1.2回馈与更正

1.3感谢

2.简介

3.GCC汇编语法

4.基本内联汇编

5.扩展内联汇编

5.1汇编程序模板

5.2操做数

5.3 Clobber列表

5.4 Volatile … ?

6.更多关于约束条件

6.1 经常使用的约束

6.2 约束修饰符

7. 一些有用的诀窍

8. 结束语

9. 参考

 

 

 

1. 前言

 

1.1版权与许可证

 

版权全部 (c)2003 Sandeep S.

这篇文档是免费的,你能够在依据自由软件组织GNU通用公共许可证条款下从新发布或者修改它。不管是版本2的许可证仍是后来的版本(由你本身选择)。

这份文档的发布是但愿它有用,可是并无任何保证。

 

1.2回馈与更正

 

欢迎善意的回馈和批评,我感谢每个指出本文错误的人并尽快地更正错误。

 

1.3感谢

 

我向GNU开发者提供这个功能强大的特性表达最诚挚的感谢。感谢Mr.Pramode C E的帮助。感谢政府工程学院的朋友尤为是Nisha Kurur和Sakeeb S精神上的支持。感谢政府工程学院老师对个人帮助。

另外,还要感谢 Phillip、Brennan、Underwood 和 colin@nyx.net ,他们解决了不少难题。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2.简介

 

如今咱们开始学GCC内联汇编。内联意味着什么?

咱们能够指示编译器插入一个函数的代码到调用者的代码中,也就是实际上调用产生的地方。这样的函数就是内联函数。看上去很像宏?实际上它们很类似。

内联函数有什么好处呢?

内联的方法减小了函数调用的额外开销。并且若是有实际的参数值是常数,那么在编译的时候编译器知道可能充许参数值的单一化,因此并非全部的内联函数的代码都要

包含进来。对可执行代码大小的影响是不可预测的,它视乎对特定的状况。声明一个内联函数,咱们声明中使用关键字inline。

如今咱们站在一个位置来猜什么是内联汇编。它只是一些写在函数内的汇编语言的程序。在系统编程时候它们会显得很便利,快速,很是有用。咱们的主要目标是学习GCC

内联汇编函数的基本格式和用法。

内联汇编之因此如此重要主要是由于它操做的能力和让它的输出在C语言变量中可见。(这个句话译得不太好)由于这样的能力,“asm”(译者:asm指内联函数)就像一个汇编指令和包含它的C语言程序之间的接口。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3.GCC汇编语法

 

GCC,即Linux平台下的GNU C语言编译器,它使用AT&T&sol(译者:应该是指AT&T语法,可是sol就不知道是什么);UNIX汇编语法。如今让咱们使用AT&T语法来进行汇编编码。若是你对AT&T语法不熟悉也不用担忧,我将会教你。这种语法和Intel语法有很大的不一样。如下我将给出主要的不一样。

一、来源地-目的地 定序

AT&T语法和Intel语法在操做数的方向上是相反的。Intel语法的第一个操做数是目的地,第二个是来源地。然而AT&T语法的第一个操做数是来源地,第二的是目的地。也即:

“Op-code dst src”在Intel语法中变为“Op-code src dst”AT&T语法。

 

二、寄存器命名

寄存器名字要有前缀“%”。也即若是寄存器eax被使用,应写做%eax。

 

三、当即操做数

AT&T当即操做数以前要有一个“$”符号。对于静态C语言变量也要有前缀“$”。在Intel语法里,十六进制常数要有“h”做为后缀。在AT&T语法里咱们用“0x”做为代

替。因此,对于十六进制的数,咱们看到一个“$”,而后一个“0x”,最后才是常数自己。

 

四、操做数大小

在AT&T语法里内存操做数的大小取决于操做码名字的最后一个字母。操做码后“b”,“w”和“l”分别指定byte(8字节长度),word(16字节长度)和long(32字节长度)的内存引用。Intel语法采用对内存操做数(不是操做码)加上前缀“tyte ptr”,“word ptr”和“dword ptr”的方法来实现。

这样,Intel语法的“mov al, byte ptr foo”等同于AT&T语法的“movb foo, %al”。

 

五、内存操做数

在Intel语法里寄存器包含在“[”和“]”里,而在AT&T语法里却改成“(”和“)”。另外,在Intel语法一个非直接内存引用是这样的:section:[base + index*scale + disp],而在AT&T语法里倒是这样的:section:disp(base, index, scale)。

有一点要记住的是当一个常数看成disp/scale时,“$”符号不能前缀。

如今咱们来看一下Intel语法和AT&T语法的主要不一样点。我只是写了不多的一部分。若是要了解所有的内容,请参考GNU汇编文档(GNU Assembler documentations)。如今让咱们看一些例子来帮助理解。

Intel Code

AT&T Code

mov     eax,1

movl    $1,%eax

mov     ebx,0ffh

movl    $0xff,%ebx

int     80h

int     $0x80

mov     ebx, eax

movl    %eax, %ebx

mov     eax,[ecx]

movl    (%ecx),%eax

mov     eax,[ebx+3]

movl    3(%ebx),%eax

mov     eax,[ebx+20h]

movl    0x20(%ebx),%eax

add     eax,[ebx+ecx*2h]

addl    (%ebx,%ecx,0x2),%eax

lea     eax,[ebx+ecx]

leal    (%ebx,%ecx),%eax

sub     eax,[ebx+ecx*4h-20h]

subl    -0x20(%ebx,%ecx,0x4),%eax

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4.基本内联汇编

 

基本内联汇编的格式是很是简单的,以下:

asm("assembly code");

 

例子以下:

asm("movl %ecx %eax");/*将ecx的值传给eax了/

__asm__("movb %bh (%eax)");/*将bh的值传到eax指向的内存处*/

 

你可能已经注意到在这里我使用asm和__asm__两个关键字。它们都是正确的。若是asm关键字与程序里的某些程序发生冲突,那么你可使用__asm__代替。若是咱们有不止一条指令,那么咱们每行在双引号里写一条指令,而且在指令最后加上一个'/n'和'/t'。这是由于gcc以字符串的形式发送每条指给as(GAS),而且经过使用newline&tab的方法发送正确的行格式给汇编器。例子:

__asm__ ("movl %eax, %ebx/n/t"

          "movl $56, %esi/n/t"

          "movl %ecx, $label(%edx,%ebx,$4)/n/t"

          "movb %ah, (%ebx)");

 

若是在咱们的代码中咱们改变了一些寄存器的值而且没有记下这些改变便返回,可能会致使错误的发生。这是由于GCC并不知道寄存器的值改变了,这会给咱们带来麻烦,尤为是编译器对程序进行一些优化处理时。假设有这样的状况发生:某些寄存器保存着某些变量的值,而咱们没有有告诉GCC便改变了它,程序会如常地运行。这就是咱们所的扩展功能的缘由。扩展asm提供咱们这种的功能。

 

 

 

 

 

 

 

 

 

 

5.扩展内联汇编

 

在基本汇编内联里,咱们只使用了指令。而在扩展汇编内联里,咱们可以指定操做数。它容许咱们指定输入寄存器,输出寄存器和一列clobbered registers(译者注:实际就是指一些被内联汇编使用的寄存器,不知道如何翻译,因此下文也是以英文写出)。没有强性规定必定要指定使用寄存器,咱们能够把头痛的事情留给GCC,而且这样可能更有利于GCC对程序优化。基本的格式以下:

asm ( assembler template

     : output operands                  /* optional */

   : input operands                   /* optional */

     : list of clobbered registers      /* optional */

     );

 

汇编程序模板(The assembler template)由汇编指令组成。每个操做数由在括号内的C语言表达式后的操做数约束字符串描述。第一个冒号把汇编程序模板和第一个输出操做数分开,第二个冒号则把最后一个输出操做数和第一个输入操做数分开,假设有这样的操做数。逗号则在每组中分开操做数。操做数的量最多为10,或者机器描述里最大操做数的的指令模式,这取决于那一个比较大。

 

若是没有输出操做数却有输入操做数,就要写上两个连续冒号,以说明没有输出操做数。例子:

   asm ("cld/n/t"

     "rep/n/t"

     "stosl"

     : /* no output registers */

     : "c" (count), "a" (fill_value), "D" (dest)

     : "%ecx", "%edi"

     );

 

如今,看看这代码都作了什么。上面的内联代码把fill_value的值写到edi指向的内存地址count次。而且告诉GCC寄存器eax和edi不能够再用了(也则是说被使用了)。让咱们看多一个例子来更好地理解。

int a=10, b;

asm ("movl %1, %%eax;

      movl %%eax, %0;"

     :"=r"(b)        /* output */

     :"r"(a)         /* input */

     :"%eax"         /* clobbered register */

     );   

 

这里咱们使用汇编指令让“b”的值等于“a”的值。下面是

一些要点:

“b”是一个输出操做数,经过%0联系起来;而“a”是一个输入操做数,经过%1联系起来。“r”是对操做数的一个约束。在后面咱们将会谈到关于约束的细节。“r”告诉GCC使用任何一个寄存器来存储操做数的值。输出操做数的约束必须包含一个约束修饰符“=”。这个修饰符说明输出操做数是只写的。

这里有两个“%”在寄存器以前。这样能够帮助GCC辨别操做数和寄存器,操做数只有一个“%”做为前缀。第三个冒号后的clobbered register %eax告诉GCC%eax的值将会在“asm”里被修改,因此GCC不会使用这个寄存器去存储其它的数值。

 

当“asm”程序执行完后,“b”会映射出更新后的值,由于它被指定为一个输出操做数。换句话说,在“asm”里对“b”的改变将会影响到“asm”的外面的程序。

 

如今咱们来看看各部分的细节。

 

5.1汇编程序模板

 

汇编程序模板包含被插入到C语言程序里的汇编指令的集合。它的格式像这样:每个指令必须包括在双引号里面,或者所有的指令包括在双引号里面。每个指令还必须以一个定界符(delimiter)结束。合法的定界符能够是newline(/n)和semicolon(;)。'/n'能够接一个tab(/t)。咱们都知道了使用newline/tab的理由了吧?对应于C语言表达式的操做数被表示为%0,%1...等等。

 

5.2操做数

 

C语言表达式在“asm”里做为汇编指令的操做数。每个操做数首先要有一个在双引号里的一个操做数约束。对于输出操做数,还必需要有一个约束修饰符(它也是在引号里面),最后才是一个C语言表达式表示这个操做数。也就是说,"constraint"(C expression)是通常的形式,对于输出操做数则会有一个额外的修饰符。约束主要用来决定操做数的寻址模式。约束一样可以用来指定使用哪一个寄存器。

若是咱们使用不止一个操做数,那么它们用逗号分开。

在汇编程序模板里,每个操做数经过号码来引用。编码方式以下。若是总共有n个操做数(包括输入和输出),那么第一个输出操做数编号为0,,第二个编号为1,以此类推,最后一个输入操做数编号为n-1。最大的操做数号如上一节所说的。

 

输出操做数必须是值。而输入操做数则没有这么严格,它们能够是表达式。扩展asm属性常常用于机器指令,编译器自己并不知道它的存在。若是输出表达式不能直接地寻址(例如,它是一个bit范围的值),咱们的约束就必须容许使用一个寄存器。那样的话,GCC将会使用这个寄存器做为asm的输出,而后把这个寄存器的内容保存到输出。

 

正如上面所说的,普通的输出操做数必须是只写的(write-only);GCC将会假设在指令以前的这样的操做数的值是没有用的,而且不需要被产生。扩展asm也支持input-output或者read-write操做数。

 

如今让咱们专一于一些例子。咱们想要一个数乘于5,咱们使用lea指令。

asm ("leal (%1,%1,4), %0"

     : "=r" (five_times_x)

     : "r" (x)

     );

 

这里咱们的输入是“x”。我没有指定使用哪个寄存器。GCC会选择某个寄存器来做为输入,另外某个来做为输出。若是咱们想输入和输出都在同一个寄存器,咱们能够命令GCC这样作。这里咱们使用读写类型(types of read write)的操做数。经过指定合适的约束,下面咱们实现它:

asm ("leal (%0,%0,4), %0"

     : "=r" (five_times_x)

     : "0" (x)

     );

 

如今输入和输出操做数都在同一个寄存器里,可是咱们仍是不知道是哪个寄存器。若是咱们想指定寄存器,能够这样:

asm ("leal (%%ecx,%%ecx,4), %%ecx"

     : "=c" (x)

     : "c" (x)

     );

 

在以上的三个例子中,咱们没有在clobber列表上指定任何寄存器。为何呢?在前两个例子中,GCC决定了使哪一个寄存器而且它知道什么改变了?在最后一个例子中,咱们也不用在clobber列表加入ecx,GCC知道它和x之间传递数值。因此GCC知道ecx的值,不用考虑把它加入clobber列表。

 

5.3 Clobber列表

 

若是指令连续使用一些寄存器。咱们必须把这么寄存器列在clobber列表之中,也则是内联汇编程序里第三个冒号后的范围。这样是为了告诉GCC咱们本身将会使用而且修改它们。这样GCC将不会认为这些寄存器里的值是可用的。咱们没有必要列出用于输入和输出的寄存器。由于GCC知道asm程序使用到它们(由于它们在约束中明显地指出来)。若是指令使用任何其它的寄存器,不管是显式仍是隐式的指出(同是这样的寄存器也没有在输入或者输出约束列表中出现),那么这些寄存器必须在clobber列表中出现。

若是咱们的指令会改变寄存器的值,咱们必须加上"cc"到clobber列表上。

若是咱们的指令在不可预知的状况修改了内存,则要增长这个到clobber 列表增长”memory”。这样的话GCC在执行汇编指令时就不会在寄存器中保存这个内存的值。若是对内存的影响并无列在input或者output中,那么咱们还要增长volatile这个关键字。

咱们能够读写无限屡次clobber寄存器。考虑到模板里多指令的例子,程序假设子程序_foo从寄存器eax和ebx接收参数:

asm ("movl %0,%%eax;

      movl %1,%%ecx;

      call _foo"

      : /* no outputs */

      : "g" (from), "g" (to)

      : "eax", "ecx"

);

 

5.4 Volatile … ?

 

若是你熟悉内核代码之类的代码,你必定常常看到许多函数被声明为 volatile 或者 __volatile__,这个声明在asm或者__asm__后面。前面咱们说到了不少关于asm和__asm__。那么什么是volatile呢?

若是咱们写的汇编语句一个要在咱们写的地方执行(例如,必定不能够为了优化从一个循环里移出来),那么把关键字volatile放在asm和括号之间。这样就可以避免移动,删除代码。声明以下:

asm volatile ( ... : ... : ... : ...);

 

使用__volatile__关键字时咱们必须很当心。

若是咱们写的汇编语句只是为了作一些计算,而且对外面不形成任何影响,那么最好仍是不要用volatile关键字。这样作有利于gcc对代码的优化和美化。对于这一章的一些有用的技巧,我已经提供了不少内联asm函数的例子。详细内容能够查看clobber列表。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

6.更多关于约束条件

 

到目前为止,你可能已经知道了约束跟内联汇编关系密切。可是咱们尚未详细地说到约束。约束能够用来讲明一个操做数是否存放在一个寄存器中,而且在哪一个寄存器中;也能够用来讲明是否在内存中,而且是什么类型的地址;说明是不是一个当即数,而且可能取什么值(好比取值范围)等等。

 

6.1 经常使用的约束

 

约束有不少种,可是经常使用的比较很少,如今让咱们来了解一下这些约束。

1.      寄存器操做数约束(r)

当操做数被指定使用如下的约束时,它们就会被保存在通用寄存器(General Purpose Registers),请看下面的例子:

asm ("movl %%eax, %0/n" :"=r"(myval));

变量myval被保存在寄存器里,寄存器eax的值被复制到这个寄存器,接着变量myval的值从这个寄存器传到内存中,更新内存的值。当"r"约束声明时,gcc会保存变量的值到任何一个可用的通用寄存器中。想要指定某个寄存器,你必须使用专用的寄存器约束符直接指定寄存器的名称。这些约束符以下所示:

r

Register(s)

a

%eax, %ax, %al

b

%ebx, %bx, %bl

c

%ecx, %cx, %cl

d

%edx, %dx, %dl

S

%esi, %si

D

%edi, %di

 

2.      内存操做数约束(m)

对于寄存器操做数约束,先保存要运算的值到一个寄存器,运算后再把值传到内存中去。内丰操做数约束与相反,当操做数在内存中时,任何运算都会直接在内存中执行。寄存器约束常常在指令可以大大地提升进程运行的速度时被使用。而在C语言变量需要在内联汇编语句中更新和不需要使用寄存器保存这个变量值时,内存约束则是最有效的。以下面的例子,idtr的值被保存在内存loc中:

asm("sidt %0/n" : :"m"(loc));

3.      匹配(数字)约束

有时候,一个变量同时做为输入和输出的变量。这样的状况在内联汇编里能够用“匹配约束”来讲明:

    asm ("incl %0" :"=a"(var):"0"(var));

上一节,咱们看到了一些关于操做数的例子。而这个例子是为了说明匹配约束,寄存器%eax同时做为输入和输出的变量。输入的var被读到%eax里,在%eax自增后,%eax的值又保存在var里。在这里"0"说明和第0个输入变量使用一样的约束。也便是var的值只保存在%eax。这种约束可以用在:

1。输入和输出是同一个变量

2。输入和输出的操做数实例是不重要的。

使用匹配约束最重要的影响就是这样可以更加高效地利用可用的寄存器。

 

其它被使用的约束有:

1. "m":容许使用一个内存操做数,一般这个内存的地址能够是机器支持的任何值。

2. "o":容许使用一个内存操做数,不过这个地址必须是可移位的。例如,这个地址增长一个较小的位移后,这个地址仍是可用的。

3. "V":一个内存操做数,它是不可移位的。

4. "i":容许使用一个当即整型数(一个常数),这包括一个符号常量,它的值在汇编时知道。

5. "n":容许使用一个当即整型数,它是一个已知的数字值。

6. "g":容许使用任意通用寄存器、内存和当即数。

 

下面的约束是x86专用的:

1.      “r”:寄存器操做数据约束;

2.      “q”:寄存器a、b、c、d;

3.      “I”: 常量0到31;

4.      “J”: 常量0到63;

5.      “K”:0xff;

6.      “L”:0xffff;

7.      “M”:0, 1, 2, 3;

8.      “N”:0到255的常量;

9.      “f”:浮点数寄存器;

10.  “t”:第一个浮点数寄存器;

11.  “u”:第二个浮点数寄存器;

12.  “A”:指定为寄存器a或者d,主要用于64位的值;

 

6.2 约束修饰符

 

使用约束时,为了使对约束效果的控制更加精确,GCC为咱们提供了约束修饰符。经常使用的约束修饰符以下:

 

1. "="  : 意味着操做数对于这个指令只写,以前的值被放弃而且被输入值代替。

2. "&" : 意味着操做数在clobber列表中,而且在被用来做为输入操做数以前就已经被改过。所以操做数不能做为输入操做数存放在寄存器里。

 

上面的列表和说明并不彻底,学习例子可以更好地了解到内联汇编的使用方法。下一章咱们将会学习一些例子,这些例子里有更多关于clobber列表和约束。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

7. 一些有用的诀窍

 

上面所说的覆盖了GCC内联汇编的基本原理,如今咱们开始关注一些简单的例子。用宏来写内联汇编函数老是方便的。咱们能在内核代码里找到不少内联汇编函数。(/usr/src/linux/include/asm/*.h)

 

一、首先咱们以一个简单的例子开始。咱们要写一个程序对两个数据进行相加。

int main(void)

{

        int foo = 10, bar = 15;

        __asm__ __volatile__("addl  %%ebx,%%eax"

                             :"=a"(foo)

                             :"a"(foo), "b"(bar)

                             );

        printf("foo+bar=%d/n", foo);

        return 0;

}

 

上面的例子咱们把foo存放到%eax,bar到%ebx,而且把结果放到%eax。符号"="说明它是一个输出寄存器。咱们也能够用另外一种方式给变量加一个数值:

__asm__ __volatile__(

                  "   lock       ;/n"

                  "   addl %1,%0 ;/n"

                  : "=m"  (my_var)

                  : "ir"  (my_int), "m" (my_var)

                  :  /* no clobber-list */

                  );

这是一个原子式的加法。咱们能够去除指令"lock"来去除它的原子性。在输出区域,"=m"说明my_var是一个输出而且存放在内存里。一样的,"ir"说明my_int是一个数字而且应该放在某些寄存器里。clobber列表上没有寄存器。

 

二、如今咱们执行一些程序在变量或者寄存器上而且比较它们的值。

__asm__ __volatile__(  "decl %0; sete %1"

                  : "=m" (my_var), "=q" (cond)

                  : "m" (my_var)

                  : "memory"

                     );

 

my_var的值自减1,若是自减的结果是0,那么变量cond的值被设置。咱们可以加入一句"lock;/n/t"来增长程序的原子性。

 

同理,咱们可使用"incl %0"来代替"decl %0",来自增my_var。

 

说明:

(i)my_var是在内存里的一个变量

(ii)cond是寄存器eax, ebx, ecx和edx里的任一个

(iii)咱们能看到"memorg"是在clobber列表上。例如:代码修改内存的内容。

 

三、怎么设置,请空一个寄存器的任一个二进制位。做为一个技巧,请看下面的例子:

__asm__ __volatile__(   "btsl %1,%0"

                  : "=m" (ADDR)

                  : "Ir" (pos)

                  : "cc"

                  );

       在上面的例子里,内存“ADDR”的第pos个位被设置为1。咱们能够用“btrl”代替“btsl”来清空这个位。pos的约束符“Ir”说明pos在一个寄存器里面,而且它的值的范围为是0到31。

 

四、如今咱们来看看一些复杂可是有用的功能——字符串拷贝。

static inline char * strcpy(char * dest,const char *src)

{

int d0, d1, d2;

__asm__ __volatile__(  "1:/tlodsb/n/t"

                       "stosb/n/t"

                       "testb %%al,%%al/n/t"

                       "jne 1b"

                     : "=&S" (d0), "=&D" (d1), "=&a" (d2)

                     : "0" (src),"1" (dest)

                     : "memory");

return dest;

}

       拷贝源的地址存放到esi,拷贝目标的地址到edi。紧接着开始拷贝。遇到0时,拷贝结束。约束符”&S”, ”&D”, ”&a”说明寄存器esi, edi, eax是以前被用到的clobber列表。也便是说,在函数结束以前它们的内容会被改变。内存在clobber列表的缘由也是显而易见的。

       下面是一个相似的函数移动一块双字节,函数被声明为宏:

#define mov_blk(src, dest, numwords) /

__asm__ __volatile__ (                                          /

                       "cld/n/t"                                /

                       "rep/n/t"                                /

                       "movsl"                                  /

                       :                                        /

                       : "S" (src), "D" (dest), "c" (numwords)  /

                       : "%ecx", "%esi", "%edi"                 /

                       )

 

五、在Linux内核里,系统调用就是用内联汇编来实现的。如今咱们来看看一个系统调用是怎么实现的。全部的系统调用都被写成宏(在文件Linux/unistd.h里)。例如,一个被定义为宏的有三个参数的系统调用以下:

#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) /

type name(type1 arg1,type2 arg2,type3 arg3) /

{ /

long __res; /

__asm__ volatile (  "int $0x80" /

                  : "=a" (__res) /

                  : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), /

                    "d" ((long)(arg3))); /

__syscall_return(type,__res); /

}

       系统调用号被放到寄存器eax里,而后三个参数放到ebx, ecx, edx里。最后是指令”int $0x80”调用这个系统调用。返回值被放到eax。

 

       每个系统调用均可以用相似的方法实现。”Exit”是一个只有一个参数的系统调用,咱们来看看它的代码是怎样的:

{

        asm("movl $1,%%eax;         /* SYS_exit is 1 */

             xorl %%ebx,%%ebx;      /* Argument is in ebx, it is 0 */

             int  $0x80"            /* Enter kernel mode */

             );

}

       它的调用号是1,参数是0。因此执行”int $0x80”时,咱们将eax设置为1,ebx为0。

 

 

 

8. 结束语

 

这篇文章介绍GCC内联汇编的基础。只要你能理解它的基本概念,就不难靠本身一步步学习。咱们已经学习了一些有助于理解GCC内联汇编特性的经常使用例子。

GCC内联是一个很大的内容,同时这个文章在这个意义上并不完整。关于语法的更多的细节能够在GNU Assembler的官方文档上找到。

固然,Linux内核里大量地用到了GCC内联汇编。因此咱们能够在源代码上找到各类各样的例子。它们能为我带来不少的帮助。

若是你找到任何的排版出错,或者过期的信息,请让咱们都知道。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

9. 参考

1. Brennan’s Guide to Inline Assembly

2. Using Assembly Language in Linux

3. Using as, The GNU Assembler

4. Using and Porting the GNU Compiler Collection (GCC)

5. Linux Kernel Source

相关文章
相关标签/搜索