首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Linux 中 x86 的内联汇编 将各个部分组合起来-2

Linux 中 x86 的内联汇编 将各个部分组合起来-2

一般内联汇编用法示例以下示例通过各种不同的操作数约束说明了用法。有如此多的约束以至于无法将它们一一列出,这里只列出了最经常使用的那些约束类型。
"asm" 和寄存器约束 "r"让我们先看一下使用寄存器约束 r 的 "asm"。我们的示例显示了 GCC如何分配寄存器,以及它如何更新输出变量的值。      
1
2
3
4
5
6
7
8
9
10
11
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"。为该例生成的汇编代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
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寄存器中,假设输入在输出产生之前被消耗。请注意,如果您有许多指令,就不是这种情况了。要确保输入和输出分配到不同的寄存器中,可以指定& 约束修饰符。下面是添加了约束修饰符的示例。
1
2
3
4
5
6
7
8
9
10
11
12
13
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" 中不同的寄存器中。
1
2
3
4
5
6
7
8
9
10
11
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约束,分别收集四个寄存器中的值。
1
2
3
4
5
6
asm ("cpuid"
: "=a" (_eax),
"=b" (_ebx),
"=c" (_ecx),
"=d" (_edx)
: "a" (op));




在下面可以看到为它生成的汇编代码(假设 _eax、_ebx 等...变量都存储在堆栈上):
1
2
3
4
5
6
7
8
9
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" 约束来实现:
1
2
3
4
5
6
7
8
9
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 位的值:
1
2
3
4
5
6
7
8
9
10
#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.




使用匹配约束在下面将看到系统调用的代码,它有四个参数:
1
2
3
4
5
6
7
8
9
10
#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 的返回值)之前被消耗(使用)。
内存操作数约束的使用请考虑下面的原子递减操作:
1
2
3
4
__asm__ __volatile__(
"lock; decl %0"
:"=m" (counter)
:"m" (counter));




为它生成的汇编类似于:
1
2
3
4
#APP
    lock
    decl -24(%ebp) /* counter is modified on its memory location */
#NO_APP.




您可能考虑在这里为 counter 使用寄存器约束。如果这样做,counter的值必须先复制到寄存器,递减,然后对其内存更新。但这样您会无法理解锁定和原子性的全部意图,这些明确显示了使用内存约束的必要性。
使用修饰寄存器请考虑内存拷贝的基本实现。
1
2
3
4
5
6
7
8
9
10
   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"内部使用寄存器(无论是明确还是隐含地),既不出现在输入操作数列表中,也不出现在输出操作数列表中,必须将它列为修饰寄存器。
返回列表