众所周知,Linux系统调用是访问Linux内核的必经之路。作为上层软件开发着来说,一般无须考虑自己的程序是如何通过Linux kernel system call 层,因为这是libc的任务,程序员只需要知道libc提供的接口就可以了。但是,有时候为了定制或实现新的LibC一类的需要调用system call的软件包或这软件,了解如何写自己的可以直接调用Linux 系统调用的函数是必要的。在这篇文章中,给出了一个简单的调用write()系统调用的汇编代码和C中嵌入汇编两种方法实现的软件。
预备知识:
软件中断的产生。首先是通过“int 0x80”指令产生0x80号软件中断。Linux规定了0x80号中断是内核系统调用的中断门。当然,操作系统设计人员在设计系统时,可以更改该中断号。
系统调用号。每一个系统调用都分配了唯一的一个系统调用号,该调用号可以通过访问include/asm-i386/unistd.h文件
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_lchown 16
#define __NR_break 17
#define __NR_oldstat 18
#define __NR_lseek 19
#define __NR_getpid 20
#define __NR_mount 21
#define __NR_umount 22
#define __NR_setuid 23
#define __NR_getuid 24
#define __NR_stime 25
#define __NR_ptrace 26
#define __NR_alarm 27
#define __NR_oldfstat 28
..........
例如write()函数的系统调用号是4(#define __NR_write 4)
确定函数参数。在调用系统调用之前,要确定相应系统调用函数的个数,意义。通常,函数的参数分别通过不同寄存器传递。以X86为例,在linux- 2.6.17中,ebx, ecx, edx 分别代表第1,2,3个参数。由于Linux kenel 不同,其函数参数的传递通常有其对应的compiler确定。函数参数个数的去定可以通过搜索: sys_syscall_name() 函数。以write为例:
asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, size_t count);
现在你已经了解基本的概念和技术,现在看一下下面两个简单的程序,他们是将“hello world”输出到屏幕。当然是同多调用write 系统调用完成的。
/* write.S*/
.text
.globl _start, _exit, message
.align 4
_start:
/* Reseting EFLAGS */
pushl $0
popf
push %eax /* Save the register value*/
push %ebx
push %ecx
push %edx
mov $4, %eax /* system call number, write_sys() */
mov $1, %ebx /* file description */
mov $message, %ecx /* message address */
mov $14, %edx /* message size */
int $0x80 ;
pop %edx /* Restore the resigter value*/
pop %ecx
pop %ebx
pop %eax
_exit:
mov $1, %eax
int $0x80
message:
.asciz "hello world./n" ;
/* write.c*/
static char message[]={"hello world!/n"};
int main(int argc, char *argvs)
{
int size;
asm volatile( /
"int $0x80" /
: "=a"(size)
: "0"(4), "b"(1), "c"(message), "d"(13));
return 0;
}
有兴趣的可以用反汇编看一下二者经过编译后有什么区别。
预备知识:
软件中断的产生。首先是通过“int 0x80”指令产生0x80号软件中断。Linux规定了0x80号中断是内核系统调用的中断门。当然,操作系统设计人员在设计系统时,可以更改该中断号。
系统调用号。每一个系统调用都分配了唯一的一个系统调用号,该调用号可以通过访问include/asm-i386/unistd.h文件
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_lchown 16
#define __NR_break 17
#define __NR_oldstat 18
#define __NR_lseek 19
#define __NR_getpid 20
#define __NR_mount 21
#define __NR_umount 22
#define __NR_setuid 23
#define __NR_getuid 24
#define __NR_stime 25
#define __NR_ptrace 26
#define __NR_alarm 27
#define __NR_oldfstat 28
..........
例如write()函数的系统调用号是4(#define __NR_write 4)
确定函数参数。在调用系统调用之前,要确定相应系统调用函数的个数,意义。通常,函数的参数分别通过不同寄存器传递。以X86为例,在linux- 2.6.17中,ebx, ecx, edx 分别代表第1,2,3个参数。由于Linux kenel 不同,其函数参数的传递通常有其对应的compiler确定。函数参数个数的去定可以通过搜索: sys_syscall_name() 函数。以write为例:
asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, size_t count);
现在你已经了解基本的概念和技术,现在看一下下面两个简单的程序,他们是将“hello world”输出到屏幕。当然是同多调用write 系统调用完成的。
/* write.S*/
.text
.globl _start, _exit, message
.align 4
_start:
/* Reseting EFLAGS */
pushl $0
popf
push %eax /* Save the register value*/
push %ebx
push %ecx
push %edx
mov $4, %eax /* system call number, write_sys() */
mov $1, %ebx /* file description */
mov $message, %ecx /* message address */
mov $14, %edx /* message size */
int $0x80 ;
pop %edx /* Restore the resigter value*/
pop %ecx
pop %ebx
pop %eax
_exit:
mov $1, %eax
int $0x80
message:
.asciz "hello world./n" ;
/* write.c*/
static char message[]={"hello world!/n"};
int main(int argc, char *argvs)
{
int size;
asm volatile( /
"int $0x80" /
: "=a"(size)
: "0"(4), "b"(1), "c"(message), "d"(13));
return 0;
}
有兴趣的可以用反汇编看一下二者经过编译后有什么区别。