绝好!动态添加系统调用

大三了,选了一门操作系统实验课。

第一个实验就要求为linux添加系统调用。

于是乎,照着网上的教程一步步做。

奈何,编译内核这步实在太头疼了,虽然第一次就成功了,但是在性能还行的台式机上居然还花了两个半小时!

接着,感觉教程上的系统调用好弱智,我又写了个复杂一些的函数,想把它加进去试一试。

这次出问题了,我用笔记本编译了2个小时后,突然蹦出个错误,编译终止!

仔细对了一下教程,原来是少敲了”sys_“,在这一瞬间,我有种想砸电脑的冲动!

两个小时啊,我是用我那百般无辜的水汪汪的大眼睛盯着屏幕度过的,说得有点过。

反正就是不服气,为什么编译器偏偏就在这最后关头才发现这个错误,有错你早说嘛,我改还不行么,

可是你为什么2个小时后才告诉我,我的时间就不是时间吗?

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

 然后,我就萌生了这样一个想法,能不能动态地添加系统调用呢?

度娘告诉我,能!

 

于是乎,疯狂找教程,疯狂试代码,结果没一个可以用。

问题总是出在sys_call_table上,它是系统调用表的首址。

因为sys_call_table无法直接extern

所以只好来狠的,直接给它赋值!

可是,如何得到sys_call_table的地址呢,我看了不下5种方法,都太复杂,

只有一种深深地打动了我,那就是。。。

cat /proc/kallsyms | grep sys_call_table
当然,需要root权限。

这是我的结果

c1504160 R sys_call_table

可见,其地址为0xc1504160。

很激动,终于可以开工了。

首先,挑选系统调用号,我决定把rmdir这个命令对应的系统调用该掉!!!

vim /usr/src/linux/arch/x86/include/asm/unistd_32.h
发现它对应的号码是40

嘿嘿,模块的代码如下:

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/slab.h>
#include<linux/stat.h>
#include<linux/unistd.h>
#include<asm/elf.h>
#include<asm/unistd_32.h>
#include<asm/uaccess.h>
#include<linux/sched.h>
#define _NR_mycall 40
#define sys_call_table_adress 0xc1504160

unsigned long * sys_call_table = 0;
static int (*anything_saved)(void);

asmlinkage long sys_mycall(void)
{
    printk("Iammycall.current->pid=%d,current->comm = %s\n",current->pid,current->comm);    
    return current->pid;
}

static int __init init_addsyscall(void)
{
    printk("hello, kernel!\n");
    sys_call_table = (unsigned long *)sys_call_table_adress;
    anything_saved = (int(*)(void))(sys_call_table[_NR_mycall]);
    sys_call_table[_NR_mycall] = (unsigned long) &sys_mycall;
    return 0;
}

static void __exit exit_addsyscall(void)
{
        sys_call_table[_NR_mycall] = (unsigned long)anything_saved;
}

MODULE_LICENSE("GPL");
module_init(init_addsyscall);
module_exit(exit_addsyscall);
本程序是添加一个sys_mycall的系统调用,作用就是返回当前进程的pid。
在上述程序中不直接使用sys_call_table而是定义了sys_call_table_address这个宏定义,使其内容为sys_call_table的地址,是因为在内核中不允许使用sys_call_table,所以如果直接写sys_call_table编译时就会提示这个变量为定义unknow symbol。
上面的程序可以make通过,但是insmod时会导致死机
原因就是因为sys_call_table是只读的,在cr0寄存器的第16位来表示其权限,当该位为0表示有超级权限可以修改先sys_call_table[_NR_mycall]的服务例程地址,在改过之后要将该位再置位。
修改过的程序如下:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/slab.h>
#include<linux/stat.h>
#include<linux/unistd.h>
#include<asm/elf.h>
#include<asm/unistd_32.h>
#include<asm/uaccess.h>
#include<linux/sched.h>
#include<linux/time.h>

#define _NR_mycall 40
#define sys_call_table_adress 0xc1504160
unsigned int clear_and_return_cr0(void);
void setback_cr0(unsigned int val);
int orig_cr0;

unsigned long * sys_call_table = 0;
static int (*anything_saved)(void);

unsigned int clear_and_return_cr0(void)
{
    unsigned int cr0 = 0;
    unsigned int ret;

    asm volatile("movl %%cr0,%%eax"
        :"=a"(cr0)
      );                                    //存储cr0的值
    ret = cr0;

    /*将cr0的第16位清零,第16为0表示允许超级权限*/
    cr0 &= 0xfffeffff;
    asm volatile("movl %%eax,%%cr0"
            :
            :"a"(cr0)
          );
    return ret;
}
//恢复cr0寄存器的第16位
void setback_cr0(unsigned int val)
{
    asm volatile("movl %%eax,%%cr0"
        :
        :"a"(val)
      );    
}

asmlinkage long sys_mycall(void)
{
    printk("Iammycall.current->pid=%d,current->comm= %s\n",current->pid,current->comm);    
    return current->pid;
}

static int __init init_addsyscall(void)
{
    printk("hello, kernel!\n");
    sys_call_table = (unsigned long *)sys_call_table_adress;
//保存223系统调用号的服务例程
    anything_saved=(int(*)(void))(sys_call_table[_NR_mycall]);    printk("sys_call_table:%lu\n",sys_call_table[_NR_mycall]);
    orig_cr0 = clear_and_return_cr0(); //cr0寄存器的第16位清0
    //将自定义的系统调用函数的地址作为223的服务例程地址赋值给
//sys_call_table[223]
    sys_call_table[_NR_mycall] = (unsigned long) &sys_mycall;
    printk("changed sys_call_table:%lu\n",sys_call_table[_NR_mycall]);
    //恢复cr0寄存器的第16位    
setback_cr0(orig_cr0);
    return 0;
}

static void __exit exit_addsyscall(void)
{
        orig_cr0 = clear_and_return_cr0();
        //恢复223系统调用号对应的服务例程地址
        sys_call_table[_NR_mycall] = (unsigned long)anything_saved;
        setback_cr0(orig_cr0);
        printk("exit addsyscall sys_call_table:%lu\n",sys_call_table[_NR_mycall]);
}

MODULE_LICENSE("GPL");
module_init(init_addsyscall);
module_exit(exit_addsyscall);
make之后insmode的结果为:
[ 5448.767807] hello, kernel!
[ 5448.767812] sys_call_table:3238424928
[ 5448.767815] changed sys_call_table:4191797248
测试程序的代码如下:
#include<stdio.h>
#include<stdlib.h>

int main()
{
    unsigned long  pid = 0;

    pid = syscall(223);
    printf("current->pid=%ld\n",pid);
}
执行结果为:
current->pid=3942
dmesg结果为:
[ 5459.837582] I am mycall.current->pid=3942,current->comm = test_Mycall

上述代码中的汇编代码解释,以下面的语句为例:
asm volatile("movl %%cr0,%%eax"
        :"=a"(cr0)
      );    
基本内联汇编的格式是:
asm volatile("Instruction List");
asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。
volatile是可选的,你可以用它也可以不用它。如果你用了它,则是向GCC声明“不要动我所写的 Instruction List,我需要原封不动的保留每一条指令”,否则当你使用了优化选项(-O)进行编译时,GCC将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。
Instruction List是汇编指令序列,任意两个指令间要么被分号(;)分开,要么被放在两行;
放在两行的方法既可以从通过\n的方法来实现,也可以真正的放在两行;
可以使用1对或多对引号,每1对引号里可以放任一多条指令,所有的指令都要被放到引号中。
不过上述给出的例子是带有C/C++表达式的内联汇编
GCC允许你通过C/C++表达式指定内联汇编中"Instrcuction List"中指令的输入和输出,你甚至可以不关心到底使用哪个寄存器被使用,完全靠GCC来安排和指定。这一点可以让程序员避免去考虑有限的寄存器的使用,也可以提高目标代码的效率。
其格式为:
asm volatile("Instruction List" : Output : Input : Clobber/Modify);
1.Output
Output用来指定当前内联汇编语句的输出。
asm("movl %%cr0, %eax": "=a" (cr0));
这 个内联汇编语句的输出部分为"=r"(cr0),它是一个“操作表达式”,指定了一个输出操作。即cr0 = output_value,而output_value就是引号中的内容,而字母a是寄存器EAX / AX / AL的简写,说明cr0的值要从eax寄存器中获取,也就是说cr0 = eax。
所以上述语句所完成的操作为:先将cr0寄存器的值给eax寄存器,再将eax寄存器的值赋值给cr0变量。
2、Input
Input域的内容用来指定当前内联汇编语句的输入。以下面的语句为例:
asm volatile("movl %%eax,%%cr0"
        :
        :"a"(val)
      );    
第二行的:为: Output 中的,可以看出此处省略了output。
例中Input域的内容为一个变量val,被称作“输入表达式”,用来表示一个对当前内联汇编的输入。
像输出表达式一样,一个输入表达式也分为两部分:带括号的部分(val)和带引号的部分"a"。这两部分对于一个内联汇编输入表达式来说也是必不可少的。
括号中的表达式val是一个C/C++语言的表达式,它可以是一个变量,一个数字,还可以是一个复杂的表达式(比如a+b/c*d)。字母a表示当前输入变量val要通过寄存器eax输入到当前内联汇编中。所以上述语句所完成的操作应该是先将val的值输入到eax寄存器中,再将eax寄存器中的内容给cr0.
3.对于后面的Clobber/Modify不是很清楚。关于C/C++中的内联汇编可以查看下面的网页:
http://apps.hi.baidu.com/share/detail/48820703

嘿嘿 大功告成了,现在我的rmdir终于不能用了,哈哈!

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值