动态添加系统调用

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

//输出值(即 printf 执行后的返回值) //eax=sys_num=4,sys_write 的系统调用号 //参数一:文件描述符(stdout) //参数二:要显示的字符串 //参数三:字符串长度
return value;
}
GCC=gcc OBJS=hello.o
CH3-5/Makefile
CH3-5/hello.c
#include <stdio.h>
#include <unistd.h>
int
main()
{
unsigned long sys_num = 259;
__asm__("int $0x80" ::"a"((long)(sys_num)));
//系统调用号
return 0;
}
修改好后,我们就可以编译并执行了。执行情况如下:
pushl %es; \
pushl %ds; \
pushl %eax; \
pushl %ebp; \
pushl %edi; \
pushl %esi; \
pushl %edx; \
pushl %ecx; \
pushl %ebx; \
movl $(__KERNEL_DS),%edx; \
movl %edx,%ds; \
从第 7 句向后的代码,便是系统调用执行完成后的善后处理工作。这里我们给出上面的 SAVE_ALL 宏的相反操作。即 RESTORE_ALL,代码如下:
#define RESTORE_ALL \ popl %ebx; \ popl %ecx; \ popl %edx; \ popl %esi; \ popl %edi; \ popl %ebp; \ popl %eax; \
我们更关心的是系统调用的实现机制。下面请跟我来看吧。
1. __asm__("int $0x80"
2. :"=a"(value)
//输出值(即 sys_write 执行后的返回值)
3. :"0"((long)(sys_num)),
//eax=sys_num=4,sys_write 的系统调用号
4. "b"(1),
是我们接着看 system_call 函数。
ENTRY(system_call)
1. pushl %eax
# save orig_eax
2. SAVE_ALL
3. ……
4. cmpl wenku.baidu.com(NR_syscalls),%eax 5. jae badsys 6. call *SYMBOL_NAME(sys_call_table)(,%eax,4)
movl %edx,%es;
从宏的代码中,可以得知它把 CPU 中的一些寄存器值入栈(为了从核心态返回时还能回到 进入前(用户态)的状态),同时还把核心态的数据段值写入 ds,es(这样的话,我们就可 以访问核心的态的数据段了,代码段在执行 int 指令时已经由 CPU 自动设置了)。
第 4,5 句代码是在测试我们传入的系统调用号是否超过了当前系统所支持的最大系统
调用数(对于 2.4 核心,支持的最大系统调用数是 260 个,当然你可以修改)。 第 6 句代码用传入的系统调用号,查表获得对应的系统调用函数地址并 call 之。该表定
义如下(定义在 Arch/i386/kernel/entry.S):
.data
ENTRY(sys_call_table)
.long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/
1: popl %ds; \
2: popl %es;\
addl $4,%esp; \
3: iret; \
.section .fixup,"ax";\
4: movl $0,(%esp); \
jmp 1b;
\
5: movl $0,(%esp); \
jmp 2b;
\
6: pushl %ss; \
popl %ds; \
7. ……
8. restore_all: 9. RESTORE_ALL 第 1 句代码首先把 eax 值入栈,我们可以知道该 eax 中保存的就是我们从用户态传入的 系统调用号(从代码的注释也可以看出 :))。 第 2 句代码是个宏定义,定义如下: #define SAVE_ALL \
cld; \
1. 修改好源代码后需要重新编译核心,这是个非常长和容易发生错误的过程。 2. 对于你修改及编译好后所得到的核心,你所做的添加(修改)是静态的,无
法在运行时动态改变(所以也就有了下面的动态方法)
A2-1-1 讨论 Linux 系统调用的体系
在 Linux 的核心中,0x80 号中断是所有系统调用的入口(当然你也可以修改,因为我 们有源代码吗 :),不过你改了之后只能自己玩玩,要不然人家 0x80 号中断的程序怎么执行 呢?)。但是还是有办法(可能还有其他办法)。办法是在你看了下面的“动态添加系统调用” 后就知道,这个就留给读者考虑了。
静态及动态添加系统调用
A2.系统调用的添加
――――――――摘之 “Linux1.0 核心游记”
A2-1 静态添加系统调用
所谓的静态静态添加系统调用,是指我们直接通过修改核心的源代码而达到的。只要我 们知道 Linux 下系统调用实现的框架,添加(当然也可以修改)系统调用将会是件非常简单 的事情。 该方法的缺点还是有的:
CH3-5/hello.c
!该程序中使用 sys_write 这个系统调用来输出要打印的字符。 !同时请注意在该程序中我们也用了 strlen 函数,它是 C 库中定义的标准函数 !不过,这里我们只需关注代码中的汇编代码即可。
#include <stdio.h>
#include <unistd.h>
到这为止,静态添加系统调用便完成了。
A2-2 动态添加系统调用
所谓动态添加系统调用,就是在 Linux 运行的时候把新的系统调用加入。从而避免了编 译核心的问题。
A2-2-1 动态添加系统调用的原理
动态添加系统可能会有很多种方法,这里我们只讨论一种方法。个人认为本书讨论的这 种方法是比较好的。同样你也要具有超级用户的权限。
对于本例,我们把实现体写在 fs/read_write.c 中。添加代码如下: asmlinkage void sys_helloworld (void) {
printk(“Hello world.\n”); } 第 4 步: 编译核心。 第 5 步: 核心编译成功后,我们便可以编写代码测试了。这里我们就修改上面的 CH3-5/hello.c 代码就可以的。Makefile 不变。 修改的代码如下:
从第 2 句到第 6 句代码,用于把系统调用号及参数一到到参数三设置到对应的寄存器中。
eax=sys_num(系统调用号)
ebx=1(参数一,标准输出)
ecx=lpBuffer 的值(参数二,要显示的字符串)
edx=iLen(参数三,字符串长度)
接下来执行 system_call 函数(所以我们也说该函数是所有系统调用的入口点函数)。于
//参数一:文件描述符(stdout)
5. "c"(lpBuffer),
//参数二:要显示的字符串
6. "d"(iLen));
//参数三:字符串长度
第 1 句代码用于执行 0x80 号中断。当程序执行到这句时,CPU 会从用户态切换进核心
态(也就是我们通常说的 ring0 级),并且同时会把 ss,esp,eflags,cs,eip 按顺序入栈。
pushl %ss; \
popl %es;\
pushl $11; \
call do_exit; \
.previous; \
.section __ex_table,"a";\
.align 4; \
.long 1b,4b; \
.long 2b,5b; \
.long 3b,6b; \
.previous 从宏的代码中,可以得知它把 CPU 中的一些寄存器值出栈。并且执行 iret 指令返回到用户 态。到这为止,系统调用便执行完成了,即实现了我们所需要的功能。最后我们用一张简单
.long SYMBOL_NAME(sys_exit)
.long SYMBOL_NAME(sys_fork)
.long SYMBOL_NAME(sys_read)
.long SYMBOL_NAME(sys_write)
.long SYMBOL_NAME(sys_open)
/* 5 */
……
我们给出的例子程序调用号是 4,所以根据上表我们可以知道对应的系统调用函数是 sys_write。这样我们就进入了真正的系统调用处理函数了。(关于 sys_write 实现这里不做介 绍)
用 0x80 中断号功能作为所有的系统调用入口点,是系统编写者定义的(也可以说是 Linus 定义的)。下面我们看一下其设置的代码(取之 2.4 核心,我们只看 x386) 定义于 Arch/i386/kernel/traps.c(很简单,就一个函数调用)
set_system_gate(SYSCALL_VECTOR,&system_call);!设置 0x80 号中断
SYSCALL_VECTOR 默认是 0x80(你可以修改) system_call 定义在 Arch\i386\kernel\entry.S set_system_gate 定义在 Arch/i386/kernel/traps.c,具体的代码分析这里就不做介绍了。大 致的功能是把 system_call 的地址(当然还有其他内容,比如类型值及特权级)设置到 IDT (中断描述符表)的第 0x80 项中(请注意每项是 8 个字节,在基础有所介绍)。 当用了 set_system_gate 设置好中断号,并且已经开中断。接下来我们就可以用编程的 方式来调用该中断号。调用中断的汇编指令是“int”。
.c.o: $(GCC) -c $<
all:$(OBJS) $(GCC) $(OBJS) -o hello
clean: rm -f *.o core
clobber:clean rm -f hello 这里的代码编译后,我们便可以执行了。其输出结果就如我们调用标准 C 库中的 printf
函数一样。请看下图
.long SYMBOL_NAME(sys_helloworld) 第 4 步:
在第 3 步我们只是添加了系统调用的声明,还要添加系统调用的实现体才行(关于系统 调用实现体的添加,有两个方法:第一个方法是写在系统核心的某个文件中,第二个方法是
在核心中新添加一个文件,不过用该方法你需要修改对应的 Makefile 文件。这里我们采用 第一个方法。)。
int
main()
{
int value = -1;
char *lpBuffer = "Hello everybody.\n";
unsigned long sys_num = 4;
int iLen = strlen(lpBuffer);
__asm__("int $0x80" :"=a"(value) :"0"((long)(sys_num)), "b"(1), "c"(lpBuffer), "d"(iLen));
的图来描述之。请看下图(该图为了描述方便,没有把情况都描述清楚,比如系统调用号超
过系统定义的最大系统调用数等等):
A2-1-2 修改代码来添加系统调用
通过上面的介绍,我们可以知道修改系统调用并不是件难事。那么我们就开始修改吧。 (假定你的核心在/usr/src/linux 下) 第 1 步:
我们打开 include/linux/sys.h 文件,修改 #define NR_syscalls 260 为 #define NR_syscalls 261(假设我们只要添加一个系统调用) 第 2 步: 个人认为这一步,也可以不做,因为作为系统调用的添加者,你当然是知道你加的系统 调用号的。不过我们还是不忽略它。请打开 include/asm-i386/unistd.h 添加如下代码 #define __NR_helloworld 259 这个名字,你可以自己决定用什么,只要不和系统冲突。 第 3 步: 打开 arch/i386/kernel/entry.S,在 .long SYMBOL_NAME(sys_set_tid_address)的后面加入 你要添加的系统调用函数名。假设我们要添加的函数名是 sys_helloworld,于是我们写成这 样:
相关文档
最新文档