システムコールの追加

とある課題でシステムコールをLinuxカーネルに追加することになりました
ググった限り一応方法は書いてあるのですが分かりづらいこともあったので備忘録として記録〜

カーネルのダウンロード

基本的にはsu環境で実行しています
必要なソフトウェアをインストールして今回はカーネルのバージョンを5.1で実行します
適当な場所(/usr/src以下)に解凍してディレクトリ内でmake olddefconfigを実行します
他にもoldconfigdefconfigがあるのですがoldだと現状のカーネルの設定を、defだと既定値を設定するようです(?)
実験PCは6コアに設定しているのでmake -j 6で6コア使ってコンパイルしていただきます
できたカーネルをインストールし、grub-mkconfigで実際に起動するカーネルの一覧に設定します

1
2
3
4
5
6
7
8
9
10
$ apt install flex bison libssl-dev libelf-dev -y
$ cd /usr/src
$ wget http://ftp.riken.jp/Linux/kernel/v5.x/linux-5.1.tar.xz
$ xz -dc linux-5.1.tar.xz | tar xfv -
$ cd linux-5.1
$ make olddefconfig
$ make -j 6
$ make modules_install && make install
$ grub-mkconfig
$ reboot

ただしこのままだと起動時にESCキー連打してGRUBのコンフィグから直接起動するしかできなかったので別途指定したカーネルで起動させます
(一部長いので省略しています)
/boot/grub/grub.cfgにgrubの設定があるのでmenuentryでgrepします

1
2
3
4
5
6
7
8
9
10
$ grep menuentry /boot/grub/grub.cfg
menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os ~~
submenu 'Advanced options for Ubuntu' $menuentry_id_option ~~
menuentry 'Ubuntu, with Linux 5.3.0-42-generic' ~~
menuentry 'Ubuntu, with Linux 5.3.0-42-generic (recovery mode)' ~~
menuentry 'Ubuntu, with Linux 5.3.0-40-generic' ~~
~~~
menuentry 'Ubuntu, with Linux 5.1.0' ~~
menuentry 'Ubuntu, with Linux 5.1.0 (recovery mode)'~~

自分が起動したいのはサブメニュー以下のUbuntu, with Linux 5.1.0なので/etc/default/grubにそのような設定を加えます

1
GRUB_DEFAULT=0

1
GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 5.1.0"

にすると指定したカーネルが設定されます

== 追記(2020/04/13) ==
update-grubを実行してGRUB_DEFAULTの設定を反映させる必要がありました
== ここまで ==

再起動後はuname -rで自分の設定したカーネルかどうか確認します

システムコールの追加

システムコールの登録

システムコールは/usr/src/linux-5.1/arch/x86/entry/syscalls/syscall_64.tblに番号とx64 or x86、関数名が登録されています
空いている番号を適当に選んで登録します
定数を返す、システムログに定数を記録する、システムログに任意の文字列を記録する(配列を渡す)、構造体を受け渡しするの4種類を今回は設定します
慣習らしくプレフィックスsys_をつけるらしいです
また引数を取るために少し工夫が必要らしく__x64を更に加えます

/usr/src/linux-5.1/arch/x86/entry/syscalls/syscall_64.tbl (追記)
1
2
3
4
428 common numacall_const sys_numacall_const
429 common numacall_syslog sys_numacall_syslog
431 common numacall_array __x64_sys_numacall_array
432 common numacall_struct __x64_sys_numacall_struct

システムコールの本体

システムコールの本体を記述しますがそのまんまって感じがします
引数を取る関数だけちょっと特殊でその設定を引き受けてくれるのがSYSCALL_DEFINEN(最後のNは1-6)です
普通のプログラムのような引数の取り方ではだめなようでそれを楽に記述できる方法(中身はマクロ?)らしい
取りたい引数の数をNに設定してカンマ区切りで関数名、引数1の型、引数1の引数名、…と書きます

/usr/src/linux-5.1/arch/x86/kernel/numacall.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <asm/current.h>

typedef struct {
char name[20];
int id;
int point;
} user;

//428
asmlinkage int sys_numacall_const(void){
return 256;
}

//429
asmlinkage void sys_numacall_syslog(void){
printk (KERN_INFO "Hello Numa Wrold!\n");
};

//431
SYSCALL_DEFINE1(numacall_array, char*, str){
char buf[512];
long copied = strncpy_from_user(buf, str, sizeof(buf));
if (copied < 0 || copied == sizeof(buf))
return -EFAULT;
printk (KERN_INFO "%s\n",buf);
return copied;
};

//432
SYSCALL_DEFINE1(numacall_struct, user*, u){
user a;
int oldpoint;
copy_from_user(&a,u,sizeof(user));
printk (KERN_INFO "User Struct name:%s id: %d point: %d\n",a.name,a.id,a.point);
oldpoint = a.point;
a.point *=2;
copy_to_user(u,&a,sizeof(user));
return oldpoint;
}

Makefile

このままではカーネルに取り込まれないのでMakefileに追加

/usr/src/linux-5.1/arch/x86/kernel/Makefile (追記)
1
obj-y += sys_numacall.o

ビルド!

一応これで準備は整ったのでビルドします

1
2
3
$ make -j 6
$ make modules_install && make install && grub-mkconfig
$ reboot

テスト!

テスト用のプログラム

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

#define numacall_const 428
#define numacall_syslog 429
#define numacall_array 431
#define numacall_struct 432

int main () {
typedef struct {
char name[20];
int id;
int point;
} user;
user u = {
"numa",
124,
5000
};

//const
printf("syscall(numacall_const) = %d\n",(int)syscall (numacall_const));

//syslog
printf("syscall(numacall_syslog)\n");
syscall(numacall_syslog);

//array
char str[512] = "Hello Test Syscall Now";
printf("syscall(numacall_array) len: %d\n", (int)syscall(numacall_array,str));

//struct
printf("syscall(numacall_struct) old point %d\n",(int)syscall(numacall_struct,&u));
printf("syscall after point %d\n",u.point);

}

実際に実行するとこうなります
ちゃんとシステムログに記録されています

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$sudo dmesg | tail -n 5
[ 20.009740] audit: type=1400 audit(1585545304.224:9): apparmor="STATUS" operation="profile_load" profile="unconfined" name="/usr/lib/NetworkManager/nm-dhcp-client.action" pid=611 comm="apparmor_parser"
[ 20.009744] audit: type=1400 audit(1585545304.224:10): apparmor="STATUS" operation="profile_load" profile="unconfined" name="/usr/lib/NetworkManager/nm-dhcp-helper" pid=611 comm="apparmor_parser"
[ 20.009748] audit: type=1400 audit(1585545304.224:11): apparmor="STATUS" operation="profile_load" profile="unconfined" name="/usr/lib/connman/scripts/dhclient-script" pid=611 comm="apparmor_parser"
[ 24.778661] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[ 66.927158] hv_balloon: Max. dynamic memory size: 4096 MB
$./a.out
syscall(numacall_const) = 1024
syscall(numacall_syslog)
syscall(numacall_array) len: 22
syscall(numacall_struct) old point 5000
syscall after point 10000
$sudo dmesg | tail -n 5
[ 24.778661] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[ 66.927158] hv_balloon: Max. dynamic memory size: 4096 MB
[ 107.795123] Hello Numa Wrold!
[ 107.795126] Hello Test Syscall Now
[ 107.795131] User Struct name:numa id: 124 point: 5000
$

参考

チュートリアル – システムコールの書き方
Linux4.0でシステムコールを追加する方法

Unable to add a custom system call on x86 ubuntu linux

関連記事