论坛: UNIX系统 标题: FreeBSD下的系统调用分析 复制本贴地址    
作者: cimsxiyang [cimsxiyang]    版主   登录
cp from chinaunix
引用:

FreeBSD下的系统调用和Linux类似,都是通过int 80中断向内核发出的调用请求,让我们用一个简单的系统调用--getpid,从我们自己编写的应用程序开始,逐步深入到内核里的具体实现,来体验一下这个过程(以FreeBSD 4.5 mini为平台)。不过如果你对汇编、保护模式之类的概念是一塌糊涂,就不必往下看了。 
一:我们先编个小程序: 
test.c: 
#include <unistd.h> 
#include <stdio.h> 
main() 

printf("%d\n", getpid()); 
while (1) 
sleep(1); 

编译并运行它: 
[lemon@bsd]$ cc test.c 
[lemon@bsd]$ ./a.out & 
123 
验证一下: 
[lemon@bsd]$ ps -ax|grep a.out|grep -v "grep" 
123 p0 S 0:00.01 ./a.out 
好,这就是我们平时所能见到的东西了。 

二:从a.out到libc 
我们来更深入观察一下这个a.out: 
[lemon@bsd]$ ldd a.out 
a.out: 
libc.so.4 => /usr/lib/libc.so.4 (0x28065000) 
说明它调用了libc这个动态库(似乎很难找到哪个程序不是这么干的吧) 
让我们找到libc里的getpid这个函数: 
[lemon@bsd]$ cd /usr/src/lib/libc 
[lemon@bsd]$ grep -r "getpid" *|less 
经过排除,我们认为sys/Makefile.inc是个关键。 
[lemon@bsd]$ vi sys/Makefile.inc 
这是个Makefile,里面与系统调用有关的主要是这两句: 
.include "${.CURDIR}/../../sys/sys/syscall.mk" 
printf '#include "SYS.h"\nRSYSCALL(${.PREFIX})\n' > ${.TARGET} 
那它干了些什么呢?为什么我们没有见到哪个.c或.S文件里有这个getpid的实现呢?我是说,"实现"。 
不用着急,让我们编译一遍这个libc, 为简单起见,用一个最笨的方法: 
[lemon@bsd]$ cd /usr/src 
[lemon@bsd]$ make buildworld 
经过漫长的等待(读者自己就不必试了): 
[lemon@bsd]$ cd /usr/obj/lib/libc (是否这个目录,记不清了) 
[lemon@bsd]$ grep -r "getpid" * 
这下子露出了庐山真面目,出现了一个getpid.S文件,里面只有两行: 
#include "SYS.h" 
RSYSCALL(getpid) 
是不是有点眼熟?对,就是前面Makefile.inc里的: 
printf '#include "SYS.h"\nRSYSCALL(${.PREFIX})\n' > ${.TARGET} 
让我们再看看这个神秘的RSYSCALL宏: 
[lemon@bsd]$ vi /usr/src/lib/libc/i386/SYS.h 
#define SYSCALL(x) 2: PIC_PROLOGUE; jmp PIC_PLT(HIDENAME(cerror)); \ 
ENTRY(__CONCAT(_,x)); \ 
.weak CNAME(x); \ 
.set CNAME(x),CNAME(__CONCAT(_,x)); \ 
lea __CONCAT(SYS_,x),%eax; KERNCALL; jb 2b 

#define RSYSCALL(x) SYSCALL(x); ret 
#ifdef __ELF__ 
#define KERNCALL int $0x80 /* Faster */ 
#else 
#define KERNCALL LCALL(7,0) /* The old way */ 
#endif 
跟踪了半天,原来最后就到了这里: 
#define KERNCALL int $0x80 /* Faster */ 
每一个系统调用都要执行这条指令,而区分各个不同系统调用的东西是: 
lea __CONCAT(SYS_,x),%eax; 
也就是把系统调用的数字编号传给eax寄存器 
到目前为止,我们所见到的东西都是在a.out和libc.so文件里出现的,如果懂得一点i386保护模式汇编, 
就知道这里的int $0x80可不是随便就能执行的,它将引发一个CPU异常,使保护模式操作系统内核获得对CPU 
的控制,说白了,从这一刻起,我们需要看看内核的代码了。 

三:IDT 
i386 CPU有一个IDT表,相当于DOS下面的那个中断向量表。上面那个int $0x80一执行,CPU将自动根据IDT表的内容,找到相应的中断处理程序,在FreeBSD里,经过一些细节以后,将会执行到这里来: 
/usr/src/sys/i386/i386/trap.c里面的函数: 
void 
syscall2(frame) 
struct trapframe frame; 


.............. 

if (code >= p->p_sysent->sv_size) 
callp = &p->p_sysent->sv_table[0]; 
else 
callp = &p->p_sysent->sv_table[code]; 
............... 
error = (*callp->sy_call)(p, args); 
............... 

这里的一个数据结构: 
p->p_sysent->sv_table是FreeBSD所支持的所有系统调用的一个指针表,根据eax寄存器里的系统调用号,可以直接索引这个表,找到系统调用的实现代码,并在error = (*callp->sy_call)(p, args);里调用这段代码。 

四:getpid的老窝 
[lemon@bsd]$ cd /usr/src/sys/kern 
[lemon@bsd]$ vi kern_prot.c 
#ifndef _SYS_SYSPROTO_H_ 
struct getpid_args { 
int dummy; 
}; 
#endif 

/* 
* NOT MP SAFE due to p_pptr access 
*/ 
/* ARGSUSED */ 
int 
getpid(p, uap) 
struct proc *p; 
struct getpid_args *uap; 


p->p_retval[0] = p->p_pid; 
#if defined(COMPAT_43) || defined(COMPAT_SUNOS) 
p->p_retval[1] = p->p_pptr->p_pid; 
#endif 
return (0); 

简单吧!本来也只是个很简单的系统调用。 

五:踏上归途 
上面的getpid函数执行以后又会回到 
/usr/src/sys/i386/i386/trap.c 
还是在那个syscall2函数里: 
刚才那个error = (*callp->sy_call)(p, args); 
调用的就是上面这个getpid函数,接下来就是对其返回值的判断: 
switch (error) { 
case 0: //函数正常结束,将返回值保存到eax, edx 
/* 
* Reinitialize proc pointer `p' as it may be different 
* if this is a child returning from fork syscall. 
*/ 
p = curproc; 

frame.tf_eax = p->p_retval[0]; 
frame.tf_edx = p->p_retval[1]; 
frame.tf_eflags &= ~PSL_C; 
break; 

case ERESTART: //自动重启被中断的系统调用 
/* 
* Reconstruct pc, assuming lcall $X,y is 7 bytes, 
* int 0x80 is 2 bytes. We saved this in tf_err. 
*/ 
frame.tf_eip -= frame.tf_err; 
break; 

case EJUSTRETURN: //直接返回 
break; 

default: //函数返回一个错误值 
bad: 
if (p->p_sysent->sv_errsize) { 
if (error >= p->p_sysent->sv_errsize) 
error = -1; /* XXX */ 
else 
error = p->p_sysent->sv_errtbl[error]; 

frame.tf_eax = error; 
frame.tf_eflags |= PSL_C; 
break; 

接下来准备返回到user模式下的libc里,在这之前调用userret重算进程优先级,以及其它一些工作: 
have_mplock = userret(p, &frame, sticks, have_mplock); 
然后就是返回到libc,再返回到我们的那个a.out里。 

这就是getpid的一切了! 

六:让我们玩个游戏 
到现在为止,我们已经大概知道了系统调用的整个过程,现在我们来玩点刺激的,编一个我们自己的系统调用,搞搞小破坏。就拿BSD系统里特有的securelevel开刀,本人对它恨之入骨,同时又宠爱有加。要知道在正规的系统里这个值只能通过sysctl接口改变,并且是不允许降下来的,我们现在就偏要把它降下来。 
要增加一个系统调用,需要编辑这个文件: 
[lemon@bsd]$ vi /usr/src/sys/kern/syscalls.master 
这个文件列出了所有的系统调用(通过module动态注册的除外,这方面的东西这次就不分析了) 
找到一个空的条目: 
241 UNIMPL NOHIDE nosys 
这是在4.5版里的,新的版本下这个条目可能就被占用了,可以随意换一个。 
我们不太清楚这个文件的格式,就还是参照一下getpid的定义,反正今天缠上它了。 
20 STD POSIX { pid_t getpid(void); } 
依葫芦画个瓢还是很简单的: 
241 STD POSIX { pid_t lemon(void); } 
就用我的大名lemon做函数名了!接下来要为它找个栖身之处。 
继续纠缠可怜的getpid: 
[lemon@bsd]$ vi /usr/src/sys/kern/kern_prot.c 
copy一下getpid的所有东西,相关的名字改一下: 
#ifndef _SYS_SYSPROTO_H_ 
struct lemon_args { 
int dummy; 
}; 
#endif 

int 
lemon(p, uap) 
struct proc *p; 
struct lemon_args *uap; 

/*在这里干坏事没人敢管*/ 
securelevel = -1; 
return (0); 

接下来要更新一些相关的文件: 
[lemon@bsd]$ /bin/sh /usr/src/sys/kern/makesyscalls.sh 
至此,内核里的手脚就做完了,现在编个小程序: 
#include <unistd.h> 
#include <sys/syscall.h> 
#include <stdio.h> 

main() 

if (syscall(241) == 0) 
printf("Haha! give you some color see see!\n"); 
else 
printf("Shit! Wait! I'll back soon!\n"); 

试试吧!如果这个程序core down,可别怪我没提醒你重新编译内核喔! 




地主 发表时间: 06/15 11:12

回复: group [group]   论坛用户   登录
引用:

不过如果你对汇编、保护模式之类的概念是一塌糊涂,就不必往下看了。


我不能往下看了

B1层 发表时间: 06/19 12:42

论坛: UNIX系统

20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon

粤ICP备05087286号