7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FreeBSDカーネル開発シリーズ

Part1 ビルド Part2 モジュール Part3 ドライバ Part4 システムコール Part5 DTrace
✅ Done ✅ Done ✅ Done 👈 Now -

はじめに

システムコールはカーネルとユーザー空間の境界

open(), read(), write(), fork()...全部システムコール。

今回は自分だけのシステムコールを作ってみる。

システムコールの仕組み

┌─────────────────────────────────────────────────────────────┐
│                   User Space                                 │
│                                                              │
│  printf("hello")                                            │
│       ↓                                                      │
│  write(1, "hello", 5)  ← libc関数                           │
│       ↓                                                      │
│  syscall(SYS_write, 1, "hello", 5)  ← システムコール呼び出し │
├─────────────────────────────────────────────────────────────┤
│                   Kernel Space                               │
│                                                              │
│  sys_write(td, uap) ← カーネル内の実装                       │
│       ↓                                                      │
│  vn_write() → ファイルシステム → デバイスドライバ            │
└─────────────────────────────────────────────────────────────┘

x86_64ではsyscall命令でカーネルモードに移行する。

FreeBSDのシステムコール一覧

# システムコール番号を確認
cat /usr/src/sys/kern/syscalls.master | head -50
; ...
0	AUE_NULL	STD {
		int syscall(
		    int number,
		    ...
		);
	}
1	AUE_EXIT	STD {
		void sys_exit(
		    int rval
		);
	}
2	AUE_FORK	STD {
		int fork(void);
	}
3	AUE_READ	STD {
		ssize_t read(
		    int fd,
		    void *buf,
		    size_t nbyte
		);
	}
; ...

方法1: カーネルモジュールでシステムコールを追加

カーネルを再ビルドせずにシステムコールを追加できる。

ソースコード

mkdir -p ~/kmod/mysyscall
cd ~/kmod/mysyscall
vi mysyscall.c
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysent.h>
#include <sys/sysproto.h>
#include <sys/proc.h>

/* システムコールの引数 */
struct mysyscall_args {
    int value;
};

/* システムコールの実装 */
static int
mysyscall(struct thread *td, struct mysyscall_args *uap)
{
    int input = uap->value;
    int result;

    printf("mysyscall called with value=%d\n", input);

    /* 何か処理(ここでは2倍にするだけ) */
    result = input * 2;

    /* 戻り値を設定 */
    td->td_retval[0] = result;

    return (0);  /* エラーなし */
}

/* システムコール番号(未使用の番号を使う) */
/* 210番は nosys (未実装) なので使える */
#define MYSYSCALL_NUM   210

/* 元のシステムコールエントリ(復元用) */
static struct sysent old_sysent;
static int syscall_num = MYSYSCALL_NUM;

/* 新しいシステムコールエントリ */
static struct sysent mysyscall_sysent = {
    .sy_narg = 1,                    /* 引数の数 */
    .sy_call = (sy_call_t *)mysyscall,
    .sy_auevent = AUE_NULL,
    .sy_flags = SYF_CAPENABLED,
    .sy_thrcnt = SY_THR_STATIC,
};

static int
mysyscall_modevent(module_t mod, int event, void *arg)
{
    int error = 0;

    switch (event) {
    case MOD_LOAD:
        /* 元のエントリを保存 */
        old_sysent = sysent[syscall_num];

        /* 新しいエントリを設定 */
        sysent[syscall_num] = mysyscall_sysent;

        printf("mysyscall loaded at syscall number %d\n", syscall_num);
        break;

    case MOD_UNLOAD:
        /* 使用中かチェック */
        if (old_sysent.sy_call != (sy_call_t *)nosys) {
            /* 元が nosys じゃないなら復元 */
            printf("Warning: overwriting existing syscall\n");
        }

        /* 元に戻す */
        sysent[syscall_num] = old_sysent;

        printf("mysyscall unloaded\n");
        break;

    default:
        error = EOPNOTSUPP;
        break;
    }

    return (error);
}

static moduledata_t mysyscall_mod = {
    "mysyscall",
    mysyscall_modevent,
    NULL
};

DECLARE_MODULE(mysyscall, mysyscall_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

Makefile

KMOD=   mysyscall
SRCS=   mysyscall.c

.include <bsd.kmod.mk>

ビルド&ロード

make
kldload ./mysyscall.ko
mysyscall loaded at syscall number 210

ユーザー空間から呼び出す

vi test_mysyscall.c
#include <stdio.h>
#include <sys/syscall.h>
#include <unistd.h>

#define MYSYSCALL_NUM 210

int main(void)
{
    int input = 42;
    int result;

    printf("Calling mysyscall(%d)\n", input);

    /* システムコール呼び出し */
    result = syscall(MYSYSCALL_NUM, input);

    printf("Result: %d\n", result);

    return 0;
}
cc -o test_mysyscall test_mysyscall.c
./test_mysyscall
Calling mysyscall(42)
Result: 84

dmesgで確認:

dmesg | tail -1
# mysyscall called with value=42

方法2: カーネルソースに直接追加

本格的にシステムコールを追加するなら、カーネルソースを編集する。

syscalls.masterを編集

vi /usr/src/sys/kern/syscalls.master

ファイル末尾に追加:

; カスタムシステムコール
600	AUE_NULL	STD {
		int my_custom_syscall(
		    int value,
		    char *message
		);
	}

実装ファイルを作成

vi /usr/src/sys/kern/sys_mycustom.c
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sysproto.h>
#include <sys/kernel.h>
#include <sys/proc.h>

#ifndef _SYS_SYSPROTO_H_
struct my_custom_syscall_args {
    int value;
    char *message;
};
#endif

int
sys_my_custom_syscall(struct thread *td, struct my_custom_syscall_args *uap)
{
    char msg[256];
    int error;

    /* ユーザー空間からメッセージをコピー */
    error = copyinstr(uap->message, msg, sizeof(msg), NULL);
    if (error) {
        return (error);
    }

    printf("my_custom_syscall: value=%d, message='%s'\n",
           uap->value, msg);

    /* 処理結果を戻り値に設定 */
    td->td_retval[0] = uap->value * 3;

    return (0);
}

Makefileに追加

vi /usr/src/sys/conf/files

末尾に追加:

kern/sys_mycustom.c     standard

カーネルを再ビルド

cd /usr/src

# syscalls.masterから自動生成
make -C sys/kern sysent

# カーネルビルド
make buildkernel KERNCONF=GENERIC
make installkernel KERNCONF=GENERIC

reboot

呼び出しテスト

#include <stdio.h>
#include <sys/syscall.h>
#include <unistd.h>

#define MY_CUSTOM_SYSCALL 600

int main(void)
{
    int result;

    result = syscall(MY_CUSTOM_SYSCALL, 10, "Hello from userspace!");

    printf("Result: %d\n", result);

    return 0;
}

エラーハンドリング

システムコールでエラーを返す方法。

#include <sys/errno.h>

int
sys_my_syscall(struct thread *td, struct my_syscall_args *uap)
{
    /* 引数チェック */
    if (uap->value < 0) {
        return (EINVAL);  /* Invalid argument */
    }

    /* 権限チェック */
    if (priv_check(td, PRIV_ROOT)) {
        return (EPERM);   /* Operation not permitted */
    }

    /* メモリ不足 */
    void *ptr = malloc(1024, M_TEMP, M_NOWAIT);
    if (ptr == NULL) {
        return (ENOMEM);  /* Cannot allocate memory */
    }

    /* ... */

    return (0);  /* 成功 */
}

ユーザー空間とのデータ転送

copyin/copyout

/* ユーザー空間 → カーネル */
char kernel_buf[256];
error = copyin(uap->user_ptr, kernel_buf, len);
if (error) {
    return (error);
}

/* カーネル → ユーザー空間 */
error = copyout(kernel_buf, uap->user_ptr, len);
if (error) {
    return (error);
}

copyinstr(文字列)

char str[256];
size_t done;

error = copyinstr(uap->str, str, sizeof(str), &done);
if (error) {
    return (error);
}
printf("String: %s (len=%zu)\n", str, done);

fubyte/subyte(1バイト)

int byte = fubyte(uap->ptr);
if (byte == -1) {
    return (EFAULT);
}

error = subyte(uap->ptr, 0x42);
if (error) {
    return (EFAULT);
}

複数の戻り値

FreeBSDではtd_retval[0]td_retval[1]で2つの値を返せる。

int
sys_my_syscall(struct thread *td, struct my_syscall_args *uap)
{
    /* 2つの値を返す */
    td->td_retval[0] = 100;  /* 1つ目 */
    td->td_retval[1] = 200;  /* 2つ目 */

    return (0);
}

ユーザー空間では:

#include <sys/types.h>

/* pipe(2)のような2値を返すシステムコール */
int fds[2];
syscall(SYS_pipe, fds);
/* fds[0] = td_retval[0], fds[1] = td_retval[1] */

セキュリティ考慮事項

権限チェック

#include <sys/priv.h>

/* rootかチェック */
if (priv_check(td, PRIV_ROOT)) {
    return (EPERM);
}

/* 特定の権限をチェック */
if (priv_check(td, PRIV_NET_RAW)) {
    return (EPERM);
}

Capsicum対応

/* Capability Modeでも動作するか */
static struct sysent mysyscall_sysent = {
    .sy_flags = SYF_CAPENABLED,  /* Capabilityモードで許可 */
    /* ... */
};

デバッグ

ktrace/kdump

# システムコールをトレース
ktrace -t c ./test_mysyscall
kdump
  2345 test_mysyscall CALL  mysyscall(0x2a)
  2345 test_mysyscall RET   mysyscall 84

dtraceで追跡

dtrace -n 'syscall::mysyscall:entry { printf("pid=%d, arg=%d", pid, arg0); }'

まとめ

FreeBSDでカスタムシステムコールを作る方法:

  1. モジュール方式: 既存のsysentを上書き(開発・テスト向け)
  2. カーネル直接編集: syscalls.masterに追加(本番向け)

重要なポイント:

  • copyin/copyoutでユーザー空間とデータ転送
  • td->td_retvalで戻り値を設定
  • エラーはEINVAL等のerrnoを返す
  • 権限チェックを忘れずに

次回予告

Part5: DTraceでカーネルを丸裸にする

DTraceはSun(現Oracle)が開発した最強のトレースツール。カーネル内部で何が起きているか、リアルタイムで可視化できる。パフォーマンス分析やデバッグに必須のツールを使いこなそう。

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

7
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?