システムコールの追加

システムコールの追加

各システムコールは arch/i386/kernel/entry.S の system_callという関数から呼ばれる。
ユーザプログラムからレジスタを使って渡されたシステムコールへの引数はスタックに積まれ、システムコールを実現する関数の引数としてアクセスできる。

新しいシステムコールを追加するには以下のようにする。

  1. 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
  2. sys_new_syscallという関数をどこかに追加する。(kernel/sys.cの最後 に追加するとよい)
    書式は

    asmlinkage int sys_new_syscall(...)
    {
    ...
    }
  3. include/asm/unistd.hで__NR_new_syscallというマクロを定義する。
    #define __NR_exit 1
    #define __NR_fork 2
    :
    #define __NR_new_syscall 191 /* 追加 */
  4. include/bits/syscall.hでSYS_new_syscallというマクロを定義する。
    #define SYS_new_syscall __NR_new_syscall
  5. 新しいシステムコールを使うには以下のようなユーザプログラムを書く。
    #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”);
}

テストは以下のように行う。

  1. gcc -c lkm.cでLKMをコンパイルし、lkm.oを作る。
  2. rootになる。
    /sbin/insmod lkm.oを実行すると"init"がコンソールに表示される。
    /sbin/lsmodでlkmというLKMが組み込まれているのを確かめる。
    /sbin/rmmod lkmを実行すると"cleanup"が表示される。
  3. 次にシステムコールのフック(横取り)を行えるようにする。
    そのためにはシステムコールの追加の時に変更した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)関数を使ってカーネル内のバッファにコピーすること。

コメント

タイトルとURLをコピーしました