最近在學習系統調用,一段用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
可以正确執行的原因就很明确了:
- 在
中,str存儲在棧空間中,而棧空間在系統的高位,隻取低32位位址,得到的是錯誤位址。test_asm_A.cpp
- 在
中,str存儲在堆空間中,而堆空間在系統的低位開始,在這樣一個小程式中,str位址的高32位為0,隻有低32位存在非零值,是以不會出現截斷錯誤。test_asm_B.cpp
可見,
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;
}