FreeBSD syscall module
System Call Modules
FreeBSDではシステムコールもモジュールで追加できる.
The System Call Function
システムコールの型は
typedef int sy_call_t(struct thread *, void *);
struct thread *
はそのまんま現在実行中のスレッドを指している.
void *
はシステムコールの引数を指す.
以下に文字列を受け取って出力するシステムコールの例を示す
struct sc_example_args {
char *str;
}
static int
sc_example(struct thread *td, void *syscall_args)
{
struct sc_example_args *uap;
uap = (struct sc_example_args *)syscall_args;
printf("%s\n", uap->str);
return (0);
}
システムコールの引数は,struct sc_example_args
のように構造体で定義し,システムコール内でvoid *
ポインタをキャストする事で適切に扱う.
The sysent Structure
システムコールテーブルはsysent[]
で,そのメンバはsysent
構造体
sysent
は<sys/sysent.h>
で定義されていて,
struct sysent { /* system call table */
int sy_narg; /* number of arguments */
sy_call_t *sy_call; /* implementing function */
au_event_t sy_auevent; /* audit event associated with syscall */
systrace_args_func_t sy_systrace_args_func;
/* optional argument conversion function. */
u_int32_t sy_entry; /* DTrace entry ID for systrace. */
u_int32_t sy_return; /* DTrace return ID for systrace. */
u_int32_t sy_flags; /* General flags for system calls. */
u_int32_t sy_thrcnt;
};
sc_example
では
static struct sysent sc_example_sysent = {
1, /* number of arguments */
sc_example /* implementing function */
};
The Offset Value
sysent[]
のオフセット(インデックス)がシステムコール番号で,0~550くらいまで(今の上限どれくらいだか調べてない)一意に割り当てられる.
モジュール内では,オフセットを明示的に宣言する必要があり,通常
static int offset NO_SYSCALL;
NO_SYSCALL
は,sysent[]
で使用可能な値,または開いている値に設定される.
自分で任意の番号を指定する事も出来るが,KLDは動的にロードされる為,追加するならよしなにしてもらった方が楽.
sys/kern/syscall.master
に使用済み/未使用の番号のリストがある.
The SYSCALL_MODULE Macro
DECLARE_MODULE
同様,システムコールモジュールの登録用にもマクロがある.
DECLARE_MODULE
でもいいけど,こっち使った方が便利.
<sys/sysent.h>
で定義されてる
#define SYSCALL_MODULE(name, offset, new_sysent, evh, arg) \
static struct syscall_module_data name##_syscall_mod = { \
evh, arg, offset, new_sysent, { 0, NULL, AUE_NULL } \
}; \
\
static moduledata_t name##_mod = { \
"sys/" #name, \
syscall_module_handler, \
&name##_syscall_mod \
}; \
DECLARE_MODULE(name, name##_mod, SI_SUB_SYSCALLS, SI_ORDER_MIDDLE)
で,syscall_module_data
は<sys/sysent.h>
で定義されてて,
struct syscall_module_data {
int (*chainevh)(struct module *, int, void *); /* next handler */
void *chainarg; /* arg for next event handler */
int *offset; /* offset into sysent */
struct sysent *new_sysent; /* new sysent */
struct sysent old_sysent; /* old sysent */
int flags; /* flags for syscall_register */
};
SYSCALL_MODULE
の各引数(name
, offset
, new_sysent
, evh
, arg
)
name
一般的なモジュール名と同じ.モジュールの名前
offset
offset
,さっきのシステムコール番号.
型はint *
なので参照
new_sysent
struct sysent *
.
evh
event handler.
一般的なモジュールと同じ,イベントハンドラ.
arg
イベントハンドラに渡される引数.
ここでは常にNULL
にする.
Example
試しに,文字列を受け取って出力するシステムコールを作る.
#include <sys/types.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysent.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysproto.h>
struct sc_example_args {
char *str;
};
static int
sc_example(struct thread *td, void *syscall_args)
{
struct sc_example_args *uap;
uap = (struct sc_example_args *)syscall_args;
printf("%s\n", uap->str);
return (0);
}
static struct sysent sc_example_sysent = {
1,
sc_example
};
static int offset = NO_SYSCALL;
static int
load(struct module *module, int cmd, void *arg)
{
int error = 0;
switch (cmd) {
case MOD_LOAD:
uprintf("System call loaded at offset %d\n", offset);
break;
case MOD_UNLAOD:
uprintf("System call unloaded from offset %d\n", offset);
break;
default:
error = EOPNOTSUPP;
break;
}
return (error);
}
SYSCALL_MODULE(sc_example, &offset, &sc_example_sysent, load, NULL);
KMOD = sc_example
SRCS = sc_example.c
SYSDIR = /usr/src/11/sys
.include <bsd.kmod.mk>
ビルドしてロードするとオフセットを教えてくれる.
$ make
machine -> /usr/src/11/sys/amd64/include
x86 -> /usr/src/11/sys/x86/include
Warning: Object directory not changed from original /home/vagrant/src/syscallmod
cc -O2 -pipe -Wall -fno-strict-aliasing -Werror -D_KERNEL -DKLD_MODULE -nostdinc -I. -I/usr/src/11/sys -fno-common -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -MD -MF.depend.sc_example.o -MTsc_example.o -mcmodel=kernel -mno-red-zone -mno-mmx -mno-sse -msoft-float -fno-asynchronous-unwind-tables -ffreestanding -fwrapv -fstack-protector -Wall -Wredundant-decls -Wnested-externs -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Winline -Wcast-qual -Wundef -Wno-pointer-sign -D__printf__=__freebsd_kprintf__ -Wmissing-include-dirs -fdiagnostics-show-option -Wno-unknown-pragmas -Wno-error-tautological-compare -Wno-error-empty-body -Wno-error-parentheses-equality -Wno-error-unused-function -Wno-error-pointer-sign -Wno-error-shift-negative-value -Wno-address-of-packed-member -mno-aes -mno-avx -std=iso9899:1999 -c sc_example.c -o sc_example.o
ld -m elf_x86_64_fbsd -d -warn-common -r -d -o sc_example.ko sc_example.o
:> export_syms
awk -f /usr/src/11/sys/conf/kmod_syms.awk sc_example.ko export_syms | xargs -J% objcopy % sc_example.ko
objcopy --strip-debug sc_example.ko
$ sudo kldload ./sc_example.ko
System call loaded at offset 210.
modfind
せっかくシステムコールを作ったので,呼び出したい.
自作システムコールがどの番号で呼び出せるのかを知るため,まずはmodfind(2)
を使ってモジュールを特定する.
modfind
はモジュール名を元にロードされているモジュールを探し,見つかった場合固有のID(Modid
)を返す.
#include <sys/param.h>
#include <sys/module.h>
int
modfind(const char *name);
注意が必要なのが,今回,SYSCALL_MODULE
を使って定義したシステムコールのモジュール名は,マクロを読めばわかるように,元の名前のprefixにsys/
を付けたものになっている.
modstat
modfind
で入手した固有のIDをもとに,モジュールの情報を入手する.
#include <sys/param.h>
#include <sys/module.h>
int
modstat(int modid, struct module_stat *stat);
module_stat
は<sys/module.h>
で定義されていて,こんな情報が含まれる
struct module_stat {
int version; /* set to sizeof(struct module_stat) */
char name[MAXMODNAME];
int refs;
int id;
modspecific_t data;
};
/* snip */
typedef union modspecific {
int intval;
u_int uintval;
long longval;
u_long ulongval;
} modspecific_t;
ここで,先程の自作システムコールのmodstat
を取るとdata.intval
がオフセット(システムコール番号)となる.
syscall
実際にユーザ空間から呼び出してみる.
#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/module.h>
int
main(int argc, char **argv)
{
int syscall_num;
struct module_stat stat;
if (argc != 2)
return printf("Usage: \n%s <string>\n", *argv), 1;
stat.version = sizeof(stat);
if (modstat(modfind("sys/sc_example"), &stat) < 0)
return perror("modstat"), 1;
syscall_num = stat.data.intval;
return (syscall(syscall_num, argv[1]));
}
コンパイルして実行すると,
$ ./syscall "Hello, kernel!"
$ dmesg |tail -n 1
Hello, kernel!
ちなperlだと
$ perl -e '$str = "Hello, kernel!";' -e 'syscall(210, $str);'
$ dmesg | tail -n 1
Hello, kernel!
Comments