本篇文章是基于C和彙編的
首先采用調用0x80中斷的方式進行系統調用:
void prints(char *str){
int i = ;
while(str[i] != '\0'){
i++;
}
asm("movl $4, %%eax \n\t"
"movl $1, %%ebx \n\t"
"movl %0, %%ecx \n\t"
"movl %1, %%edx \n\t"
"int $0x80 \n\t"
::"m"(str), "m"(i)
);
return;
}
int main(){
char *str1 = "ab"; //字元串内容存儲在堆上
char str2[] = "ab"; //字元串内容存儲在棧上
prints(str1); //正常顯示
prints(str2); //不顯示
}
這裡的關鍵在于Linux系統0x80号中斷是32位系統調用。其接收4個參數,寄存器ecx中的參數為要顯示的内容的記憶體首位址,而在64位系統下用gcc編譯,生成的程式預設是64位的,64位程式的記憶體位址(也就是C語言中的指針)是64位的,我們把記憶體位址傳給ecx,ecx隻有32位,是以位址的高32位會丢失,系統按照錯誤的位址去尋址,肯定找不到内容。
那麼為何堆上的資料可以顯示,段上的不行呢,因為Linux系統在給程式配置設定記憶體空間的時候,堆的記憶體位置位于低記憶體位址,位址雖然是64位的,但是高32位全為0;而堆棧卻位于高記憶體位址,高32位不為零,是以隻能正确尋找到堆上的資料。
0x80中斷傳入不正确的位址會怎樣:并不會報錯,沒有任何提示,但是會傳回一個錯誤碼(負數),存在eax中,具體錯誤碼可以在頭檔案中檢視(頭檔案中是正數):
/usr/include/asm-generic/errno-base.h
/usr/include/asm-generic/errno.h
通過
echo $?
可以看到程式傳回242(0xF2),也就是-14(負數代表調用出錯,絕對值是錯誤碼;若正确調用會傳回正确輸出的字元數)。檢視errno-base.h中定義:
和上面分析的一樣:傳入了錯誤的記憶體位址。
實際上printf正确調用也會傳回寫入(标準輸出)的字元數(傳回值用eax寄存器傳遞),錯誤調用傳回負數,這點和系統調用的傳回值類似。
我們怎麼修改我們的程式呢?64位程式不再用
int $0x80
進行系統調用,可以考慮用
syscall
指令,這是64位系統的ABI調用方式,系統調用号和0x80不同,參數傳遞的寄存器約定也不一樣。
詳情可以檢視:http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
修改後的程式:
void prints(char *str){
long i = ; //這裡也可以用int,因為用int後面會編譯為`movl ..., %edx`,這個語句會把edx高32位清零。
while(str[i] != '\0'){
i++;
}
asm("syscall \n\t"::"a"(), "D"(), "S"(str), "d"(i));
}
int main(){
char *str = "ab\n";
char str1[] = "cd\n";
prints(str);
prints(str1);
}
syscall參數寄存器約定:
系統調用功能号:
rax
參數清單按順序分别是:
rdi、rsi、rdx、r10、r8、r9
普通函數參數寄存器約定:
參數清單按順序分别是:
rdi、rsi、rdx、rcx、r8、r9