GCC 内联汇编约束字符 m 的用法

首先是这么一段代码(例一):

#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"这些约束符他们都特别指定eaxebxecx寄存器,我们使用它们时是把内存里的值传到寄存器,在输出时再把寄存器值传给内存变量。而"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%1代表的是字符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的偏移地址(例三),有时候它又是一个指针值(例二)。
​引用国外一个[网站的一句话​](http://locklessinc.com/articles/gcc_asm/ GCC Inline ASM):

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里慢慢调试这三段代码。仔细看寄存器数值。
​如果我说的不对请指正。

Show Comments