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,可别怪我没提醒你重新编译内核喔!
|