`
universsky
  • 浏览: 99285 次
文章分类
社区版块
存档分类
最新评论

Linux中 x86的内联汇编

 
阅读更多

Linux中 x86的内联汇编


GCC为内联汇编提供特殊结构,它具有以下格式:

GCG 的 "asm"结构


asm
(
assembler
template
:
output
operands
(optional)
:
input
operands
(optional)
:
list
of
clobbered
registers


(optional)
);

本例中,汇编程序模板由汇编指令组成。输入操作数是充当指令输入操作数使用的 C表达式。输出操作数是将对其执行汇编指令输出的 C表

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

一个非常基本但很重要的区别在于简单内联汇编只包括指令,而扩展内联汇编包括操作数。要说明这一点,考虑以下示例:
内联汇编的基本要素


{


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"是操作数的约束,它指定将变量 "a"和 "b"存储在寄存器中。请注意,输出操作数约束应该带有一个约束修饰符 "=",指定它
是输出操作数。
·要在 "asm"内使用寄存器 %eax,%eax的前面应该再加一个 %,换句话说就%%eax,因为 "asm"使用 %0、%1等来标识
变量。任何带有一个 %的数都看作是输入/输出操作数,而不认为是寄存器。
·第三个冒号后的修饰寄存器 %eax告诉将在 "asm"中修改
GCC %eax的值,这样 GCC就不使用该寄存器存储任何其它的值。
· movl
%1,
%%eax将 "a"的值移到 %eax中, movl
%%eax,
%0将 %eax的内容移到 "b"中。因为 "b"被指定成输出
操作数,因此当 "asm"的执行完成后,它将反映出更新的值。换句话说,对 "asm"内 "b"所做的更改将在 "asm"外反映出来。
现在让我们更详细的了解每一项的含义。

汇编程序模板

汇编程序模板是一组插入到 C程序中的汇编指令(可以是单个指令,也可以是一组指令)。每条指令都应该由双引号括起,或者整组指令应
该由双引号括起。每条指令还应该用一个定界符结尾。有效的定界符为新行 (\n)和分号 (;)。 '\n'后可以跟一个 tab(\t)作为格式化符号,
增加 GCC在汇编文件中生成的指令的可读性。 指令通过数 %0、%1等来引用 C表达式(指定为操作数)。

如果希望确保编译器不会在 "asm"内部优化指令,可以在 "asm"后使用关键字 "volatile"。如果程序必须与 ANSI C兼容,则应该使用
__asm__和 __volatile__,而不是 asm和 volatile。

操作数

C表达式用作 "asm"内的汇编指令操作数。在汇编指令通过对 C程序的 C表达式进行操作来执行有意义的作业的情况下,操作数是内联汇
编的主要特性。

每个操作数都由操作数约束字符串指定,后面跟用括弧括起的 C表达式,例如:"constraint" (C expression)。操作数约束的主要功能是确
定操作数的寻址方式。

可以在输入和输出部分中同时使用多个操作数。每个操作数由逗号分隔开。

在汇编程序模板内部,操作数由数字引用。如果总共有 n个操作数(包括输入和输出),那么第一个输出操作数的编号为 0,逐项递增,最
后那个输入操作数的编号为 n
-1。总操作数的数目限制在 10,如果机器描述中任何指令模式中的最大操作数数目大于 10,则使用后者作为
限制。


修饰寄存器列表

如果 "asm"中的指令指的是硬件寄存器,可以告诉
GCC我们将自己使用和修改它们。这样,GCC就不会假设它装入到这些寄存器中的值
是有效值。通常不需要将输入和输出寄存器列为 clobbered,因为 GCC知道 "asm"使用它们(因为

它们被明确指定为约束)。不过,如果指令使用任何其它的寄存器,无论是明确的还是隐含的(,也不在输出约束列表中出现),寄存器都必
须被指定为修饰列表。修饰寄存器列在第三个冒号之后,其名称被指定为字符串。

至于关键字,如果指令以某些不可预知且不明确的方式修改了内存,则可能将 "memory"关键字添加到修饰寄存器表中。这样就告诉
GCC
不要在不同指令之间将内存值高速缓存在寄存器中。

操作数约束
前面提到过,"asm"中的每个操作数都应该由操作数约束字符串描述,后面跟用括弧括起的 C表达式。操作数约束
主要是确定指令中操作数的寻址方式。约束也可以指定:

·是否允许操作数位于寄存器中,以及它可以包括在哪些种类的寄存器中
·操作数是否可以是内存引用,以及在这种情况下使用哪些种类的地址
·操作数是否可以是立即数
约束还要求两个操作数匹配。
常用约束
在可用的操作数约束中,只有一小部分是常用的;下面列出了这些约束以及简要描述。有关操作数约束的完整列表,请参考 GCC和 GAS手
册。

寄存器操作数约束
(r)
使用这种约束指定操作数时,它们存储在通用寄存器中。请看下例:
asm
("movl
%%cr3,
%0\n"
:"=r"(cr3val));

这里,变量 cr3val保存在寄存器中,%cr3的值复制到寄存器上,cr3val的值从该寄存器更新到内存中。
指定 "r"约束时,GCC可以将变量 cr3val保存在任何可用的 GPR中。要指定寄存器,必须通过使用特定的寄存器约束直接指定寄存器名。

a
%eax
b
%ebx
c
%ecx
d
%edx
S
%esi
D
%edi

内存操作数约束
(m)
当操作数位于内存中时,任何对它们执行的操作都将在内存位置中直接发生,这与寄存器约束正好相反,后者先将值存储在要修改的寄存器
中,然后将它写回内存位置中。但寄存器约束通常只在对于指令来说它们是绝对必需的,或者它们可以大大提高进程速度时使用。当需要在
"asm"内部更新
C变量,而您又确实不希望使用寄存器来保存其值时,使用内存约束最为有效。例如,idtr的值存储在内存位置 loc中:

("sidt
%0\n"
:
:"m"(loc));

匹配(数字)约束
在某些情况下,一个变量既要充当输入操作数,也要充当输出操作数。可以通过使用匹配约束在 "asm"中指定这种情况。

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

在匹配约束的示例中,寄存器 %eax既用作输入变量,也用作输出变量。将 var输入读取到 %eax,增加后将更新的 %eax再次存储在
var中。这里的 "0"指定第
0个输出变量相同的约束。即,它指定 var的输出实例只应该存储在 %eax中。该约束可以用于以下情况:

·输入从变量中读取,或者变量被修改后,修改写回到同一变量中
·不需要将输入操作数和输出操作数的实例分开
使用匹配约束最重要的意义在于它们可以导致有效地使用可用寄存器。

一般内联汇编用法示例

以下示例通过各种不同的操作数约束说明了用法。有如此多的约束以至于无法将它们一一列出,这里只列出了最经常使用的那些约束类型。

"asm"和寄存器约束
"r"让我们先看一下使用寄存器约束
r的 "asm"。我们的示例显示了 GCC如

何分配寄存器,以及它如何更新输出变量的值。

int
main(void)
{
int
x
=
10,
y;


asm
("movl
%1,
%%eax;


"movl
%%eax,
%0;"
:"=r"(y)
/*
y
is
output
operand
*/
:"r"(x)
/*
x
is
input
operand
*/
:"%eax");
/*
%eax
is
clobbered
register
*/


}

在该例中,x的值复制为 "asm"中的 y。x和 y都通过存储在寄存器中传递给
"asm"。为该例生成的汇编代码如下:

main:
pushl
%ebp

movl
%esp,%ebp
subl
$8,%esp
movl
$10,-4(%ebp)
movl
-4(%ebp),%edx
/*
x=10
is
stored
in
%edx
*/
#APP
/*
asm
starts
here
*/
movl
%edx,
%eax
/*
x
is
moved
to
%eax
*/
movl
%eax,
%edx
/*
y
is
allocated
in
edx
and
updated
*/
#NO_APP
/*
asm
ends
here
*/
movl
%edx,-8(%ebp)
/*
value
of
y
in
stack
is
updated
with


the
value
in
%edx
*/

当使用 "r"约束时,GCC在这里可以自由分配任何寄存器。在我们的示例中,它选择
%edx来存储
x。在读取了 %edx中 x的值后,它为

y也分配了相同的寄存器。

因为 y是在输出操作数部分中指定的,所以 %edx中更新的值存储在 -8(%ebp),堆栈上 y的位置中。如果 y是在输入部分中指定的,

那么即使它在 y的临时寄存器存储值 (%edx)中被更新,堆栈上 y的值也不会更新。

因为 %eax是在修饰列表中指定的,GCC不在任何其它地方使用它来存储数据。

输入 x和输出
y都分配在同一个 %edx寄存器中,假设输入在输出产生之前被消耗。请注意,如果您有许多指令,就不是这种情况了。要确

保输入和输出分配到不同的寄存器中,可以指定 &约束修饰符。

下面是添加了约束修饰符的示例。

int
main(void)
{
int
x
=
10,
y;


asm
("movl
%1,
%%eax;


"movl
%%eax,
%0;"
:"=&r"(y)
/*
y
is
output
operand,
note
the


&
constraint
modifier.
*/
:"r"(x)
/*
x
is
input
operand
*/
:"%eax");
/*
%eax
is
clobbered
register
*/


}

以下是为该示例生成的汇编代码,从中可以明显地看出
x和 y存储在 "asm"中不同的寄存器中。

main:
pushl
%ebp
movl
%esp,%ebp
subl
$8,%esp
movl
$10,-4(%ebp)

movl
-4(%ebp),%ecx
/*
x,
the
input
is
in
%ecx
*/
#APP
movl
%ecx,
%eax
movl
%eax,
%edx
/*
y,
the
output
is
in
%edx
*/
#NO_APP
movl
%edx,-8(%ebp)

特定寄存器约束的使用

现在让我们看一下如何将个别寄存器作为操作数的约束指定。在下面的示例中,cpuid指令采用 %eax寄存器中的输入,然后在四个寄存器

中给出输出:%eax、%ebx、%ecx、%edx。对 cpuid的输

入(变量 "op")传递到 "asm"的 eax寄存器中,因为 cpuid希望它这样做。在输出中

使用 a、b、c和 d约束,分别收集四个寄存器中的值。

asm
("cpuid"
:
"=a"
(_eax),
"=b"
(_ebx),
"=c"
(_ecx),
"=d"
(_edx)
:
"a"
(op));

在下面可以看到为它生成的汇编代码(假设 _eax、_ebx等...变量都存储在堆栈上):

movl
-20(%ebp),%eax
/*
store
'op'
in
%eax
--input
*/
#APP


cpuid


#NO_APP
movl
%eax,-4(%ebp)
/*
store
%eax
in
_eax
--output
*/
movl
%ebx,-8(%ebp)
/*
store
other
registers
in
movl
%ecx,-12(%ebp)


respective
output
variables
*/
movl
%edx,-16(%ebp)


strcpy函数可以通过以下方式使用 "S"和 "D"约束来实现:

asm
("cld\n


rep\n


movsb"


:
/*
no
input
*/

:"S"(src),
"D"(dst),
"c"(count));


通过使用 "S"约束将源指针 src放入 %esi中,使用 "D"约束将目的指针 dst放入 %edi中。

因为 rep前缀需要 count值,所以将它放入 %ecx中。

在下面可以看到另一个约束,它使用两个寄存器 %eax和 %edx将两个 32位的值合并在一起,

然后生成一个64位的值:

#define
rdtscll(val)
\
__asm__
__volatile__
("rdtsc"
:
"=A"
(val))


The
generated
assembly
looks
like
this
(if
val
has
a
64
bit
memory
space).


#APP


rdtsc


#NO_APP


movl
%eax,-8(%ebp)
/*
As
a
result
of
A
constraint


movl
%edx,-4(%ebp)
%eax
and
%edx
serve
as
outputs
*/


Note
here
that
the
values
in
%edx:%eax
serve
as
64
bit
output.

使用匹配约束

在下面将看到系统调用的代码,它有四个参数:

#define
_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)
\
type
name
(type1
arg1,
type2
arg2,
type3
arg3,
type4
arg4)
\
{\
long
__res;
\
__asm__
volatile
("int
$0x80"
\
:
"=a"
(__res)
\
:
"0"
(__NR_##name),"b"
((long)(arg1)),"c"
((long)(arg2)),
\
"d"
((long)(arg3)),"S"
((long)(arg4)));
\
__syscall_return(type,__res);
\
}


在上例中,通过使用 b、c、d和 S约束将系统调用的四个自变量放入 %ebx、%ecx、%edx和 %esi中。

请注意,在输出中使用了 "=a"约束,这样,位于 %eax中的系统调用的返回值就被放入变量 __res中。通过将匹配约束
"0"用作输入部
分中第一个操作数约束,syscall号 __NR_##name被放入 %eax中,并用作对系统调用的输入。这样,这里的 %eax既可以用作输入
寄存器,又可以用作输出寄存器。没有其它寄存器用于这个目的。另请注意,输入(syscall号)在产生输出(syscall的返回值)之前被消耗
(使用)。


内存操作数约束的使用

请考虑下面的原子递减操作:

__asm__
__volatile__(
"lock;
decl
%0"
:"=m"
(counter)
:"m"
(counter));

为它生成的汇编类似于:

#APP


lock


decl
-24(%ebp)
/*
counter
is
modified
on
its
memory
location
*/
#NO_APP.

您可能考虑在这里为 counter使用寄存器约束。如果这样做,counter的值必须先复制到寄存器,递减,然后对其内存更新。但这样您会无

法理解锁定和原子性的全部意图,这些明确显示了使用内存约束的必要性。

使用修饰寄存器

请考虑内存拷贝的基本实现。

asm
("movl
$count,
%%ecx;


up:
lodsl;


stosl;


loop
up;"


:
/*
no
output
*/


:"S"(src),
"D"(dst)
/*
input
*/


:"%ecx",
"%eax"
);
/*
clobbered
list
*/

当 lodsl修改
%eax时,lodsl和 stosl指令隐含地使用它。%ecx寄存器明确装入 count。但 GCC在我们通知它以前是不知道这些的,

我们是通过将 %eax和 %ecx包括在修饰寄存器集中来通知
GCC的。

在完成这一步之前,GCC假设 %eax和 %ecx是自由的,它可能决定将它们用作存储其它的数据。请注意,%esi和 %edi由 "asm"使

用,它们不在修饰列表中。这是因为已经声明
"asm"将在输入操作数列表中使用它们。这里最低限度是,如果在 "asm"内部使用寄存器(无

论是明确还是隐含地),既不出现在输入操作数列表中,也不出现在输出操作数列表中,必须将它列为修饰寄存器。


www.linuxidc.com

分享到:
评论

相关推荐

    Linux中x86的内联汇编

    ### Linux中x86的内联汇编 #### GNU汇编程序简述 在深入了解Linux中x86的内联汇编之前,有必要先了解GCC(GNU Compiler Collection)中的汇编程序语法。GCC使用AT&T汇编语法,与Intel汇编语法有所不同。以下是AT&T...

    Linux 中 x86 的内联汇编1

    本文主要探讨了x86内联汇编的基础知识,以及如何在Linux内核编程中使用它。 首先,我们要了解GNU汇编语言的基本规则。由于GCC(GNU Compiler Collection)是Linux平台上的主要编译器,它使用AT&T汇编语法。在AT&T...

    Linux_中_x86_的内联汇编.pdf

    ### Linux 中 x86 的内联汇编技术详解 #### 一、内联汇编概述 内联汇编是在C/C++程序中嵌入汇编指令的一种方式,它允许程序员直接控制底层硬件,以实现更高效、更精确的代码执行。在Linux环境下,特别是对于x86架构...

    c语言asm汇编内嵌语法.pdf

    例如,在Linux系统中,使用内联汇编执行系统调用`exit`的方法如下: ```c __asm__("movl $1, %eax\n\t" /* SYS_exit 的系统调用号 */ "xorl %ebx, %ebx\n\t" /* 传入的参数为0,表示正常退出 */ "int $0x80"); /*...

    X86汇编教程

    - **内联汇编基本形式**:直接在C/C++代码中插入汇编指令。 - **扩展形式内联汇编**:提供更复杂的控制机制,如输入输出约束等。 - **深入理解约束**:了解如何正确使用约束以确保数据正确传递给汇编代码。 通过...

    X86简易汇编语言

    GCC支持在C/C++程序中嵌入汇编代码,称为内联汇编。 **2. 内联汇编基本形式** ```c asm("指令"); ``` **3. 扩展形式内联汇编** ```c asm("指令" : 输出操作数 : 输入操作数 : "副作用"); ``` - **输入操作数**...

    l-ia-pdf.pdf

    在Linux平台下使用x86内联汇编时,开发者主要使用的是GNU C编译器(GCC)。GCC采用的是AT&T汇编语法,因此开发者需要熟悉其基本语法规则。以下是内联汇编中一些重要的语法规则: 1. 寄存器命名:在AT&T语法中,...

    inline assembly code

    例如,Linux 内核中的许多部分就使用了内联汇编来实现对 I/O 端口的直接访问,或者为了提高性能而使用特定架构的优化指令。 ##### 4.2 性能优化 在追求极致性能的应用中,内联汇编可以用来替换原本效率较低的函数...

    简明X86汇编语言教程C

    - **扩展形式内联汇编**: - **输入/输出约束**:使用`:`来分隔输入输出变量。 - **示例**:`asm("addl %%eax, %%ebx" : "+r"(a) : "r"(b));`。 - **深入constra**: - **概念**:`constraint`用于指定变量应该被...

    GCC的内嵌汇编语法

    以下是一个简单的内联汇编示例,它使用x86架构的`inc`指令增加一个变量的值: ```c int x = 0; asm volatile ("inc %0" : "+r" (x)); ``` 在这个例子中,`"%0"`是一个占位符,对应于输出约束中的`"r"`(表示寄存器...

    Rootkit_on_Linux_x86_v2.6.pdf

    本文档概述了在Linux x86 v2.6环境中实现根工具包的技术细节。从简单的`sys_call_table`挂钩到内联挂钩、滥用调试寄存器以及构建实际的根工具包等多个方面进行了详细的介绍。这些技术不仅展示了根工具包的工作原理,...

    gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu.tar.gz

    Linaro定制的GCC版本通常会针对特定的ARM架构进行优化,比如Aarch64,这可能包括更好的指令集支持、优化的编译器内联汇编以及针对特定硬件特性的编译优化。这样的优化对于提升目标平台上的应用程序性能至关重要。 ...

    汇编语言教程

    GCC内联汇编允许在C/C++代码中嵌入汇编代码,实现特定的优化或硬件访问。理解GCC汇编格式,掌握内联汇编的基本和扩展形式,对于混合编程(结合高级语言和汇编语言)尤为重要。 总之,汇编语言不仅是计算机科学的...

    assembly64_ubuntu_assembly汇编linux_

    这涉及到使用系统调用号,通过内联汇编或者单独的汇编文件调用sysenter、sysexit或int 0x80指令来执行系统调用。例如,读取或写入标准输入/输出可以使用read和write系统调用。 在GAS中,指令的格式和语法是学习的...

    GNU gcc使用教程

    - **操作数**:内联汇编中操作数的表示。 - **修饰寄存器列表**:指定哪些寄存器可以被内联汇编修改。 - **操作数约束**:限定内联汇编指令的操作数类型。 - **示例**:实际的内联汇编代码示例。 #### 8. 不同的CPU...

    asm.rar_empty

    总的来说,这个话题涉及到了GCC编译器的内联汇编支持、空`clobbers`列表的语义,以及这些细节如何在特定的Linux内核版本中发挥作用。解决这样的问题可能需要深入理解汇编语言、GCC编译器的工作原理,以及Linux内核的...

    ProgrammingGroundUp-1-0-booksize Linux环境下汇编语言程序设计

    本书采用 Linux x86 汇编语言作为教学工具,带领读者逐步深入地理解计算机内部的工作原理以及如何在 Linux 环境下进行高效的程序设计。 #### 关键知识点 ##### 1. 处理器如何查看内存 (How the processor views ...

    汇编 学习 资源 超好用

    - 内联汇编是连接高级语言与低级汇编语言的桥梁,有助于提高程序性能。 ### 结论 汇编语言虽然在当今的软件开发中不如高级语言常见,但它仍然是理解和优化程序性能的重要工具。通过学习汇编语言,不仅可以深入...

Global site tag (gtag.js) - Google Analytics