首先是这么一段代码(例一):
#include<stdio.h>
void main()
{
char c;
//int tmp;
char *s="abcdefg";
asm("movb %1,%0\n\t"
:"=d"(c)
:"m"(*s));
printf("out:%c\n",c);
}
这段代码运行后会出现什么结果呢?很显然,是out:a
但是"m"(*s)是什么意思呢?s是字符串指针,现在是把*s字符串传进去了。
事实上,对于"a"、"b"、"c"这些约束符他们都特别指定eax,ebx,ecx寄存器,我们使用它们时是把内存里的值传到寄存器,在输出时再把寄存器值传给内存变量。而"m"则直接指示原始内存位置。也就是说在内联汇编里面对于"m"标示的内存变量将直接对其进行修改。例如,在输出语句指定:"m"(var),那么在asm语句中对于var的修改将直接作用到它的内存位置。
对于上面的代码,看起来似乎是我们通知gcc把整个字符串传进去,但是字符串首地址也是字符a的地址,相当于把字符a传进去了,所以%1似乎代表的字符a。然后把它放到了%0也就是edx里。
如果你把"m"(*s)改为"m"(s),把指针传进去会怎样?我们修改代码为(例二):
#include<stdio.h>
void main()
{
//char c;
int tmp;
char *s="abcdefg";
printf("%d\n",s);
asm("movl %1,%0\n\t"
:"=d"(tmp)
:"m"(s));
printf("out:%d\n",tmp);
}
我们首先把指针s输出,然后再在内联汇编里传入s,不是传入*s!!我们把%1按照四字节传入edx,再把edx传入tmp整型变量,最后把tmp输出,其实就是我们费尽周折把%1给输出出来了。
结果是怎样呢?在我的电脑上,两个printf输出了同样的数字。这说明了什么,说明%1现在代表了指针s的数值。
之前我们传入字符a,它代表的是字符a,现在传入指针s,%1又代表了指针s的值。是不是有点乱。
再来看下一个例子(例三):
#include<stdio.h>
void main()
{
char c;
//int tmp;
char *s="abcdefg";
//printf("%d\n",s);
asm("movb %%ds:%1,%0\n\t"
:"=d"(c)
:"m"(*s));
printf("out:%c\n",c);
}
我们把第一段代码的mov指令语句改为了movb %%ds:%1,%0,加了个段前缀。运行结果依然输出out:a
我们把这三段代码分别gcc xxxx.c -S编译成汇编代码查看会发现,%1这个东西到底是什么根本不能确定,它不像%0所代表的edx一样,%1是随着你代码写法的改变由gcc自动选择合适的寻址方式。所以,第一和第三段代码的差距是ds段前缀,但结果不变,因为gcc自动调整了寻址方式。%1随着这种调整,它的意义也在不断发生变化。有时它是字符a(例一),有时它是字符a的偏移地址(例三),有时候它又是一个指针值(例二)。
引用国外一个网站的一句话:In addition to passing information in registers, gcc can understand references to raw memory. This will expand to some more complex addressing mode within the asm string.
就是这样,不必再纠结%1代表了什么,只需记住,把字符串*s整个传进去,相当于传递了它的第一个字符,%1(或者其他占位符)代表了这个字符。把字符串指针s传进去,%1就是这个指针s的值,不过没人这样干,没有意义。把整型变量var(int var=123;)传进去,那%1就代指这个整数。
可以在GDB里慢慢调试这三段代码。仔细看寄存器数值。
如果我说的不对请指正。
Ubuntu 12.04嵌入式交叉编译环境arm-linux-GCC搭建过程图解