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"這些約束符他們都特別指定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裏慢慢調試這三段代碼。仔細看寄存器數值。

    ​如果我說的不對請指正。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章