我们知道调用“系统调用”有两种方式。
( 1) 将系统调用指令封装为 c库函数,通过库函数进行系统调用,操作简单。
(2)不依赖任何库函数,直接通过汇编指令 int与操作系统通信。
我们平常写的C语言用的就是第一种系统调用,通过函数调用write函数,我们下面用汇编语言来重写一下,利用我们定义的simu_write函数来更好的探究write函数的运作机理
syscall.S
section .data
str_c_lib: db "c library says: hello world",0xa
str_c_lib_len equ $-str_c_lib
section .text
global _start
_start:
;;;模拟c语言;;
push str_c_lib_len
push str_c_lib
push 1 ;是push到标准输出,也是一个参数
call simu_write
add esp,12
;;;退出程序,不然会出现段错误;;
mov eax,1
int 0x80
simu_write:
push ebp
mov ebp,esp
mov eax,4
mov ebx,[ebp+8]
mov ecx,[ebp+12]
mov edx,[ebp+16]
int 0x80
pop ebp
ret
我们简单的来解释一下这一种方法:其先通过函数调用约定,从右向左往栈中压入参数str_c_lib_len,str_c_lib,1,(write(1,str_c_lib,str_c_lib_len))然后调用simu_write函数
在我们的simu_write函数中先保存旧的ebp,然后调用第4号子功能:write系统调用,将其存放在eax中,然后分别放入参数。这里也就是我们所说的第二种系统调用了,不过我们先不在这里说,在下一个程序中说。
完成这些后,通过
mov eax,1
int 0x80
退出程序。其中第1号子功能是exit
int 0x80 发起中断,通知 Linux完成请求的功能,即完成退出请求
好,让我们来看第二种方法,绕过库函数,直接与OS通信
预备知识:
系统调用输入参数的传递方式:
当输入的参数小于等于 5 个时, Linux 用寄存器传递参数。当参数个数大于 5 个时,把参数按照顺序 放入连续的内存区域,并将该区域的首地址放到 ebx 寄存器。这里我们只演示参数小于等于 5 个的情况。 eax寄存器用来存储子功能号(寄存器 eip、 ebp、 esp是不能使用的)。 5个参数存放在以下寄存器中,
传送参数的顺序如下。
(1) ebx存储第 1个参数。
(2) ecx存储第 2个参数。
(3) edx存储第 3个参数。
(4) esi存储第 4个参数。
(5) edi存储第 5个参数。
程序如下:
section .data
str_syscall: db "syscall says: hello world!",0xa
str_syscall_len equ $-str_syscall
section .text
global _start
_start:
mov eax,4
mov ebx,1
mov ecx,str_syscall
mov edx,str_syscall_len
int 0x80
mov eax,1
int 0x80
是不是发现不用库函数,直接进行系统调用很简单呢~嘻嘻,我们今天就到此为止了