Linux asm系统调用:32位和64位的区别

最近在学习系统调用,一段用asm内联汇编写的简单程序始终得不出正确的系统调用结果。经过提醒,我才了解到这是32位平台和64位平台的系统调用方法不同的原因。在此列出相关的程序和我的理解。

程序代码和问题

首先看如下一段简单的C程序(test.cpp):

#include 
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 
#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 -m32gcc test_asm_B.cpp -m32,可以发现两段代码都能正确输出。这说明,上述代码按32位编译,可以得到正确的结果。
如果没有-m32标志,则gcc默认按照64位方式编译。32位和64位程序在编译时有如下区别:

  • 32位和64位程序的地址空间范围不同。
  • 32位和64位程序的系统调用号不同,如本例中的write,在32位系统中调用号为4,在64位系统中则为1。
  • 对于32位程序,应调用int $0x80进入系统调用,将系统调用号传入eax,各个参数按照ebxecxedx的顺序传递到寄存器中,系统调用返回值储存到eax寄存器。
  • 对于64位程序,应调用syscall进入系统调用,将系统调用号传入rax,各个参数按照rdirsirdx的顺序传递到寄存器中,系统调用返回值储存到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;
}

你可能感兴趣的:(Linux,汇编)