システムコールの追加
各システムコールは arch/i386/kernel/entry.S の system_callという関数から呼ばれる。
ユーザプログラムからレジスタを使って渡されたシステムコールへの引数はスタックに積まれ、システムコールを実現する関数の引数としてアクセスできる。
新しいシステムコールを追加するには以下のようにする。
- arch/i386/kernel/entry.Sに新しいエントリを追加する。
ENTRY(sys_call_table) .long SYMBOL_NAME(sys_ni_syscall) .long SYMBOL_NAME(sys_exit) : .long SYMBOL_NAME(sys_new_syscall) /* 新しいエントリ */ .rept NR_syscalls-191 /* '191'の値は追加したエントリ数だけ増やす */ .endr
- sys_new_syscallという関数をどこかに追加する。(kernel/sys.cの最後 に追加するとよい)
書式はasmlinkage int sys_new_syscall(...) { ... }
- include/asm/unistd.hで__NR_new_syscallというマクロを定義する。
#define __NR_exit 1 #define __NR_fork 2 : #define __NR_new_syscall 191 /* 追加 */
- include/bits/syscall.hでSYS_new_syscallというマクロを定義する。
#define SYS_new_syscall __NR_new_syscall
- 新しいシステムコールを使うには以下のようなユーザプログラムを書く。
#include <linux/unistd.h> _syscall1(int, new_syscall, long, x); main() { int ret; ret = new_syscall(1); }
_syscall1はシステムコールの引数が一つ(long x)という意味で、
int new_syscall(long x)
というシステムコールを呼ぶ関数を定義する。引数が2つあれば、
_syscall2(int, new_syscall, long, x, char *, y)
のようになる。
練習問題
問題:
コンソールに文字列を出力するシステムコールを作ってみよ。ヒント:
コンソールに文字列を表示するためのカーネル関数としてprintkが用意されている。使い方はprintfとほぼ同じである。(X Window Systemを立ち上げているとコンソールに出力された文字が見えないことがあるので注意すること。)LKMによるシステムコールのフック
LKM(loadable kernel module)は/sbin/insmodと/sbin/rmmodによって動的にカーネルに機能を追加したり削除したりすることを可能にする。
LKMは以下のようなプログラムになる。
/* lkm.c */
#define MODULE
#define KERNEL
#include <linux/module.h>
/* insmodの時に呼ばれる */
int init_module(void)
{
printk(“init\n”);
return 0; /* 成功 */
}
/* rmmodの時に呼ばれる */
void cleanup_module(void)
{
printk(“cleanup\n”);
}
テストは以下のように行う。
gcc -c lkm.c
でLKMをコンパイルし、lkm.oを作る。- rootになる。
/sbin/insmod lkm.oを実行すると"init"がコンソールに表示される。
/sbin/lsmodでlkmというLKMが組み込まれているのを確かめる。
/sbin/rmmod lkmを実行すると"cleanup"が表示される。 - 次にシステムコールのフック(横取り)を行えるようにする。
そのためにはシステムコールの追加の時に変更したsys_call_tableのエントリをLKMの init_moduleの中で変更してやればよい。
例えば、unameシステムコールをフックするLKMは以下のようになる。
#define MODULE
#define KERNEL
#include <linux/module.h>
#include <linux/utsname.h>
#include <sys/syscall.h>
extern void sys_call_table[];
int (orig_uname)(struct old_utsname buf);
/* 独自のunameシステムコール */
static int my_uname(struct old_utsname *buf)
{
/* オリジナルのunameシステムコールはorig_unameを使って呼べる */
return orig_uname(buf);
}
int init_module(void)
{
/* エントリを変更する */
orig_uname = sys_call_table[SYS_uname];
sys_call_table[SYS_uname] = my_uname;
return 0;
}
void cleanup_module(void)
{
/* エントリを元に戻す */
sys_call_table[SYS_uname] = orig_uname;
}
LKMをロードする前と後とで/bin/unameコマンドを実行結果を変えることができる。
練習問題
- 練習1:
前回作った文字列を表示するシステムコールをフックし、固定の文字列を前半部分に表示するようにしてみよ。
例:KERNEL: <表示すべき文字列>
- 練習2:
さらに、表示すべき文字列に変更を加えてみよ。(小文字を大文字にするなど)
ヒント:
システムコールを使ってカーネルに渡されたバッファの中身はカーネルから直接操作してはいけない。
一旦、copy_from_user(to, from, size)関数を使ってカーネル内のバッファにコピーすること。
コメント