首先是這麼一段代碼(例一):
#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搭建過程圖解 http://www.linuxidc.com/Linux/2013-06/85902.htm
Ubuntu 12.10安裝交叉編譯器arm-none-linux-gnueabi-GCC http://www.linuxidc.com/Linux/2013-03/82016.htm
Ubuntu下Vim+GCC+GDB安裝及使用 http://www.linuxidc.com/Linux/2013-01/78159.htm
Ubuntu下兩個GCC版本切換 http://www.linuxidc.com/Linux/2012-10/72284.htm
GCC 的詳細介紹:請點這裡
GCC 的下載地址:請點這裡