天天看點

使用linux系統調用ABI

本篇文章是基于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

繼續閱讀