有时侯我们会想这么写一条汇编语句:
/* 错误写法 */
asm volatile ("movl %0(%%esp), %x" : /* no outputs */ : "i" (4) );
但这样子gcc会报错:
Error: junk `(%esp)' after expression
原因是,%0是立即数(用"i"约束的),于是gcc会把这条汇编指令变成:
movl $4(%esp), x
于是它就会报错,因为多了一个$符号 -- 这在GNU as的寄存器间接寻址中是不允许的。需要改成:
asm volatile ("movl (%%esp), %x" : /* no outputs */ : "i" (4) );
例如下面的程序:
/* WARNING: WRONG! */
$ cat -n dollar.c
1 #include
2
3 int main()
4 {
5 long var = 0;
6
7 asm volatile
8 ("pushl $0x11223344\n\t"
9 "pushl $0x55667788\n\t"
10 "movl %1(%%esp), %0\n\t"
11 "addl $8, %%esp " /* 消减前面两条push指令导致的esp值改变 */
12
13 : "=r" (var)
14 : "i" (4)
15 : "cc", "esp"
16 );
17
18 printf("var is 0x%x\n", var);
19 return 0;
20 }
使用gcc编译:
$ gcc dollar.c -m32 -S
查看一下生成的dollar.s文件,我们看到inline asm被gcc转换成:
pushl $0x11223344
pushl $0x55667788
movl $4(%esp), x
addl $8, %esp
如上所说,"$4(%esp)"这种寻址方式是无法通过GNU as的,因为多了一个$符号。 GCC为我们提供了一些方法,
允许我们消除那个无用的$符号:
消除%0的$符号
%n0 消除%0的$符号,并添加负号。 (例如%0是4,那么%n0就是-4)
%l0 和%0相同,但是只用于跳转指令的目标
%0本来是一个地址,例如0x8048000,放在x中。 现在把它转换为寄存器间接寻址,亦即: (x)
%P0 相同,消除%0的$符号
让我们再回头来看上面的dollar.c程序。 我们把第10行改成:
"movl (%%esp), %0\n\t"
然后再次用gcc dollar.c -m32 -S编译,查看dollar.s中的相关代码,发现这条汇编语句变成了:
movl 4(%esp), x
这正是我们期待的结果。 至于%n0、%l0和%P0,留给读者自己试验一下。
看一下的例子:
$ cat a.c
#include
int main(void)
{
char c = 0;
asm volatile ("movb , %0"
: "=r" (c)
/* GNU ld 为IA32连接程序时,默认在从0x8048001开始的地方保存'E'、'L'、'F'三个字母 */
: "r" (0x8048001)
);
printf("c is %c\n", c);
return 0;
}
编译运行:
$ gcc -m32 a.c
$ ./a.out
c is E
成功的打印出了字母'E'。 倘若我们用-S生成*.s文件,再查看其中的相关汇编语句:
movb (x), %al
这说明gcc为0x8048001接合了eax寄存器,这就是%1;而,则把它转换为间接寻址,也就是『(%x)』。
阅读(2799) | 评论(0) | 转发(0) |