先看看下面通过系统调用实现的hello world代码:
.section .data
msg:
.ascii "Hello world!\n"
.section .text
.globl _start
_start:
movl $4, %eax ; 此为系统调用号,通过系统调用号,我们可以确定这次系统调用是为了使用write函数
movl $1, %ebx ; write函数的第一个参数,是文件描述符
movl $msg, %ecx ; write函数的第第二个参数,是输出字符串的地址
movl $13, %edx ; write函数的第三个参数,是输出字符串的长度
int $0x80 ; 开始系统调用
movl $1, %eax
movl $0, %ebx
int $0x80
系统调用是通过int 0x80来实现的,eax寄存器中为调用的功能号,ebx、ecx、edx、esi等等寄存器则依次为参数,从 /usr/include/asm/unistd.h中可以看到exit的功能号_NR_exit为1,write(_NR_write)功能号为4,因此第一个int 0x80调用之前eax寄存器值为4,ebx为文件描述符,stdout的文件描述符为1,ecx则为buffer的内存地址,edx为buffer长度。第二个int0x80之前eax为1表示调用exit,ebx为0表示返回0。
系统调用和函数调用的参数传递方式具体可以参考:
系统调用和函数调用的参数传递方式
系统调用功能号
这部分可以参考:
- https://asm.sourceforge.net/syscall.html#2
- Linux System Call Table
- LINUX SYSTEM CALL TABLE FOR X86 64
调用功能号放在了/usr/include/asm/unistd.h之中,打开文件找到(我没找到)。
总共有383条,就不细细讲述了
总的来说,操作系统是根据系统调用功能号来判断系统调用的功能的,所以要想程序按照我们设想的方式运行,就要正确地在eax
中存放系统调用功能号,并且正确地填写系统调用的其他参数,才能实现功能。
execve函数详解
int execve(const char *filename, char *const argv[ ], char *const envp[ ]);
寄存器eax放execve的系统调用号11;
寄存器ebx放文件路径,即第一个参数;
寄存器ecx放第二个参数,是利用数组指针把内容传递给执行文件,并且需要以空指针(NULL)结束;
寄存器edx放最后一个参数,为传递给执行文件的新环境变量数组。
int 0x80:中断
执行系统调用函数execve()时,execve()通过int 0x80指令进入系统调用入口程序,并且把系统调用号11放入eax中,接着把参数放入ebx,ecx和edx中。
实例展示
#include<unistd.h>
int main(){
char *argv[]={"ls","-al","/etc/passwd",NULL};
char *envp[]={"PATH=/bin",NULL};
execve("/bin/ls",argv,envp);
return 0;
}
执行后如图: