天天看點

Linux asm系統調用:32位和64位的差別程式代碼和問題原因分析64位系統的系統調用代碼

最近在學習系統調用,一段用asm内聯彙編寫的簡單程式始終得不出正确的系統調用結果。經過提醒,我才了解到這是32位平台和64位平台的系統調用方法不同的原因。在此列出相關的程式和我的了解。

程式代碼和問題

首先看如下一段簡單的C程式(

test.cpp

):

#include <unistd.h>
int main(){
    char str[] = "Hello\n";
    write(0, str, 6);
    return 0;
}           

這段程式調用了write函數,其接口為:

int write(int fd /*輸出位置句柄*/, const char* src /*輸出首位址*/, int len /*長度*/)           

fd為0則表示輸出到控制台。是以上述程式的執行結果為:向控制台輸出一個長度為6的字元串

"Hello\n"

在控制台調用

gcc test.cpp

,可以正确輸出。

為了更好地了解在彙編代碼下的系統調用過程,可把上述代碼改寫成内聯彙編的格式(具體文法可參考上一篇部落格:用asm内聯彙編實作系統調用):

//test_asm_A.cpp
int main(){
    char str[] = "Hello\n";
    asm volatile(
        "int $0x80\n\t"
        :
        :"a"(4), "b"(0), "c"(str), "d"(6)
        );
    return 0;
}           

其中,4是write函數的系統調用号,ebx/ecx/edx是系統調用的前三個參數。

然而,執行

gcc test_asm_A.cpp

編譯後,再運作程式,發現程式沒有任何輸出。一個很奇怪的問題是,如果采用如下

test_asm_B.cpp

的寫法,則程式可以正常地輸出:

//test_asm_B.cpp
#include <stdlib.h>
#include <
int main(){
    char *str = (char*)malloc(7 * sizeof(char));
    strcpy(str, "Hello\n");
    asm volatile(
        "int $0x80\n\t"
        :
        :"a"(4), "b"(0), "c"(str), "d"(6)
        );
    free(str);
    return 0;
}           

兩段代碼唯一的差別,是

test_asm_A.cpp

中的

str

存儲在棧空間,而

test_asm_B.cpp

中的

str

存儲在堆空間。

那麼,為什麼存儲位置的不同會造成完全不同的結果呢?

原因分析

經過提醒,将上述代碼用32位的方式編譯,即

gcc test_asm_A.cpp -m32

gcc test_asm_B.cpp -m32

,可以發現兩段代碼都能正确輸出。這說明,上述代碼按32位編譯,可以得到正确的結果。

如果沒有

-m32

标志,則gcc預設按照64位方式編譯。32位和64位程式在編譯時有如下差別:

  • 32位和64位程式的位址空間範圍不同。
  • 32位和64位程式的系統調用号不同,如本例中的write,在32位系統中調用号為4,在64位系統中則為1。
  • 對于32位程式,應調用

    int $0x80

    進入系統調用,将系統調用号傳入

    eax

    ,各個參數按照

    ebx

    ecx

    edx

    的順序傳遞到寄存器中,系統調用傳回值儲存到

    eax

    寄存器。
  • 對于64位程式,應調用

    syscall

    進入系統調用,将系統調用号傳入

    rax

    ,各個參數按照

    rdi

    rsi

    rdx

    的順序傳遞到寄存器中,系統調用傳回值儲存到

    rax

    寄存器。

再看上面兩段代碼,它們都是調用

int $0x80

進入系統調用,卻按照64位方式編譯,則會出現如下不正常情形:

  • 程式的位址空間是64位位址空間。
  • 0x80号中斷進入的是32位系統調用函數,是以仍按照32位的方式來解釋系統調用,即所有寄存器隻考慮低32位的值。

再看程式中傳入的各個參數,系統調用号(4),第1個和第3個參數(0和6)都是32位以内的,但是str的位址是64位位址,在0x80系統調用中隻有低32位會被考慮。

這樣,

test_asm_A.cpp

不能正确執行,而

test_asm_B.cpp

可以正确執行的原因就很明确了:

  • test_asm_A.cpp

    中,str存儲在棧空間中,而棧空間在系統的高位,隻取低32位位址,得到的是錯誤位址。
  • test_asm_B.cpp

    中,str存儲在堆空間中,而堆空間在系統的低位開始,在這樣一個小程式中,str位址的高32位為0,隻有低32位存在非零值,是以不會出現截斷錯誤。

可見,

test_asm_B.cpp

正确執行隻是一個假象。由于堆空間從低位開始,如果開辟空間過多,堆空間也進入高位的時候,這段代碼同樣可能出錯。

64位系統的系統調用代碼

最後,給出64位系統下可正确輸出的asm系統調用代碼:

//test_asm_C.cpp
int main(){
    char str[] = "Hello\n";
    //注意:64位系統調用中,write函數調用号為1
    asm volatile(
        "mov %2, %%rsi\n\t"
        "syscall"
        :
        :"a"(1), "D"(0), "b"(str), "d"(6)
        );
    return 0;
}           

繼續閱讀