使用linux系统调用ABI

本篇文章是基于C和汇编的

首先采用调用0x80中断的方式进行系统调用:

void prints(char *str){
    int i = 0;
    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中定义:

#define    EFAULT        14    /* Bad address */

和上面分析的一样:传入了错误的内存地址。

实际上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 = 0;  //这里也可以用int,因为用int后面会编译为`movl ..., %edx`,这个语句会把edx高32位清零。
    while(str[i] != '\0'){
        i++;
    }
    asm("syscall \n\t"::"a"(1), "D"(1), "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

猜你喜欢

转载自blog.csdn.net/xuejianbest/article/details/80607864