はじめに
SYSCALL_DEFINE はシステムコールを定義するためのマクロ。引数の数によって SYSCALL_DEFINE0 から SYSCALL_DEFINE6 まであり、例えば次のように利用される。変数の型と名前が引数に別々に取られて独特な形で面食らうが、関数定義とは異なるので仕方ない。
fs/open.c
SYSCALL_DEFINE1(chroot, const char __user *, filename) { return ksys_chroot(filename); }
カーネルのコードリーディングをする上で地味に重要(システムコール名の定義が文字列だから cscope などのシンボル検索にひっかからない。)。自分はシステムコールの中身を見ていくときは grep "SYSCALL_DEFINE" | grep <調べたいシステムコール名>
で検索することが多い。
今回は SYSCALL_DEFINE
が何しているのかを実装を読んで理解する。前提として以下を読んでいると理解しやすい。
まとめ
マクロは次のことを実現している。
- システムコール実行時に共通的に追加したい機能(ftrace や error_injection など)を設定する。
- システムコールの引数がレジスタ経由で渡されるため、これを関数が利用できるようにする。
検証環境
CentOS 8 を利用する。また CPU は x86_64 とする。
]# cat /etc/redhat-release CentOS Linux release 8.1.1911 (Core) ]# uname -a Linux localhost.localdomain 4.18.0-147.3.1.el8_1.x86_64 #1 SMP Fri Jan 3 23:55:26 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
実装を読む
SYSCALL_DEFINE1
から SYSCALL_DEFINE6
は include/linux/syscalls.h
で定義されている。
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__) ... #define SYSCALL_DEFINEx(x, sname, ...) \ SYSCALL_METADATA(sname, x, __VA_ARGS__) \ __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
SYSCALL_METADATA
マクロ
CONFIG_FTRACE_SYSCALLS=y
の場合、 tracepoint が利用できるようにメタデータを設定する。
__MAP(nb,__SC_STR_TDECL,__VA_ARGS__)
マクロによって、SYSCALL__DEFINE(...)
の型情報だけ抜き出す。__MAP(nb,__SC_STR_ADECL,__VA_ARGS__)
マクロによって、SYSCALL__DEFINE(...)
の変数名だけ抜き出す。SYSCALL_TRACE_ENTER_EVENT
マクロによって、システムコール実行前のフックポイントを設定する。SYSCALL_TRACE_EXIT_EVENT
マクロによって、システムコール実行後のフックポイントを設定する。
#define SYSCALL_METADATA(sname, nb, ...) \ static const char *types_##sname[] = { \ __MAP(nb,__SC_STR_TDECL,__VA_ARGS__) \ }; \ static const char *args_##sname[] = { \ __MAP(nb,__SC_STR_ADECL,__VA_ARGS__) \ }; \ SYSCALL_TRACE_ENTER_EVENT(sname); \ SYSCALL_TRACE_EXIT_EVENT(sname); \ static struct syscall_metadata __used \ __syscall_meta_##sname = { \ .name = "sys"#sname, \ .syscall_nr = -1, /* Filled in at boot */ \ .nb_args = nb, \ .types = nb ? types_##sname : NULL, \ .args = nb ? args_##sname : NULL, \ .enter_event = &event_enter_##sname, \ .exit_event = &event_exit_##sname, \ .enter_fields = LIST_HEAD_INIT(__syscall_meta_##sname.enter_fields), \ }; \ static struct syscall_metadata __used \ __attribute__((section("__syscalls_metadata"))) \ *__p_syscall_meta_##sname = &__syscall_meta_##sname; static inline int is_syscall_trace_event(struct trace_event_call *tp_event) { return tp_event->class == &event_class_syscall_enter || tp_event->class == &event_class_syscall_exit; }
システムコール実行の流れ
マクロの定義を見る前に、システムコールの実行の流れをおさらいする。
syscall_init()
で sysenter 命令が実行されたときに entry_SYSCALL_64 が実行されるように設定する。- ユーザ空間でレジスタを設定し、 sysenter 命令を実行する。
arch/x86/entry/entry_64.S#entry_SYSCALL_64
が実行される。- スタックの切り替え、レジスタに設定された引数から pt_regs を組み立てる。
arch/x86/entry/common.c#do_syscall_64
を実行する。sys_call_table[nr](regs);
を実行し、システムコールの本体を実行する。
sys_call_table
sys_call_table
はシステムコール番号と関数の対応表。arch/x86/entry/syscall_64.c
で定義されている。
asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { /* * Smells like a compiler bug -- it doesn't work * when the & below is removed. */ [0 ... __NR_syscall_max] = &sys_ni_syscall, #include <asm/syscalls_64.h> };
配列の定義部分で #include <asm/syscalls_64.h>
している。このヘッダーファイルはビルド時に arch/x86/entry/syscalls/Makefile
で arch/x86/entry/syscalls/syscalltbl.sh
が生成する。元の情報は ./arch/x86/entry/syscalls/syscall_64.tbl
であり、ここにシステムコール番号と関数の対応が定義されている。
# # 64-bit system call numbers and entry vectors # # The format is: # <number> <abi> <name> <entry point> # # The __x64_sys_*() stubs are created on-the-fly for sys_*() system calls # # The abi is "common", "64" or "x32" for this file. # 0 common read __x64_sys_read 1 common write __x64_sys_write 2 common open __x64_sys_open 3 common close __x64_sys_close 4 common stat __x64_sys_newstat
手動でもヘッダーファイルが生成できるので、どんなファイルが生成されるかを確認しておく。
]# bash ./arch/x86/entry/syscalls/syscalltbl.sh arch/x86/entry/syscalls/syscall_64.tbl syscalls_64.h ]# cat syscalls_64.h | head #ifdef CONFIG_X86 __SYSCALL_64(0, __x64_sys_read, ) #else /* CONFIG_UML */ __SYSCALL_64(0, sys_read, ) #endif #ifdef CONFIG_X86 __SYSCALL_64(1, __x64_sys_write, ) #else /* CONFIG_UML */ __SYSCALL_64(1, sys_write, ) #endif
__SYSCALL_DEFINEx
マクロ
これがシステムコールの本体。3つの関数(__x64_sys##name
、__se_sys##name
、__do_sys##name
)が定義される。
__x64_sys##name
- アセンブリから呼ばれるため、レジスタ経由で引数が渡せるように asmlinkage をつける。
- pt_regs からシステムコール関数で利用する値を取り出して
__se_sys##name
を呼び出す。
__se_sys##name
__do_sys##name
を呼び出す。- 呼び出しが適切に行われるように、tail call の最適化を防止する。
- 型情報のチェックをする。
__do_sys##name
SYSCALL_DEFINE
で定義した処理本体。
/* * Instead of the generic __SYSCALL_DEFINEx() definition, this macro takes * struct pt_regs *regs as the only argument of the syscall stub named * __x64_sys_*(). It decodes just the registers it needs and passes them on to * the __se_sys_*() wrapper performing sign extension and then to the * __do_sys_*() function doing the actual job. These wrappers and functions * are inlined (at least in very most cases), meaning that the assembly looks * as follows (slightly re-ordered for better readability): * * <__x64_sys_recv>: <-- syscall with 4 parameters * callq <__fentry__> * * mov 0x70(%rdi),%rdi <-- decode regs->di * mov 0x68(%rdi),%rsi <-- decode regs->si * mov 0x60(%rdi),%rdx <-- decode regs->dx * mov 0x38(%rdi),%rcx <-- decode regs->r10 * * xor %r9d,%r9d <-- clear %r9 * xor %r8d,%r8d <-- clear %r8 * * callq __sys_recvfrom <-- do the actual work in __sys_recvfrom() * which takes 6 arguments * * cltq <-- extend return value to 64-bit * retq <-- return * * This approach avoids leaking random user-provided register content down * the call chain. * * If IA32_EMULATION is enabled, this macro generates an additional wrapper * named __ia32_sys_*() which decodes the struct pt_regs *regs according * to the i386 calling convention (bx, cx, dx, si, di, bp). */ #define __SYSCALL_DEFINEx(x, name, ...) \ asmlinkage long __x64_sys##name(const struct pt_regs *regs); \ ALLOW_ERROR_INJECTION(__x64_sys##name, ERRNO); \ static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\ asmlinkage long __x64_sys##name(const struct pt_regs *regs) \ { \ return __se_sys##name(SC_X86_64_REGS_TO_ARGS(x,__VA_ARGS__));\ } \ __IA32_SYS_STUBx(x, name, __VA_ARGS__) \ static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \ { \ long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\ __MAP(x,__SC_TEST,__VA_ARGS__); \ __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \ return ret; \ } \ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
__PROTECT
は include/linux/linkage.h
に定義されている。これはコンパイラの最適化(tail call)を防ぐために必要らしい。pt_regs はレジスタ経由で渡されるので asmlinkage を付与するが、これが無視されないようにする。
参考 [PATCH 1/2] asmlinkage_protect replaces prevent_tail_call
/* * This is used by architectures to keep arguments on the stack * untouched by the compiler by keeping them live until the end. * The argument stack may be owned by the assembly-language * caller, not the callee, and gcc doesn't always understand * that. * * We have the return value, and a maximum of six arguments. * * This should always be followed by a "return ret" for the * protection to work (ie no more work that the compiler might * end up needing stack temporaries for). */ /* Assembly files may be compiled with -traditional .. */ #ifndef __ASSEMBLY__ #ifndef asmlinkage_protect # define asmlinkage_protect(n, ret, args...) do { } while (0) #endif #endif
ALLOW_ERROR_INJECTION
は、フォールトインジェクション機能(システムコール単位でエラーを返せるようにしてカーネルデバッグに役立てる機能)を設定する。ただし CentOS8 だと FAULT_INJECTION
や FAULT_INJECTION_DEBUG_FS
が設定されていないため、利用できない。
参考 Fault injection capabilities infrastructure
マクロを展開してみる
このマクロを <https://www.kimullaa.com/entry/2020/05/17/111833 に従って展開してみる。
# gcc -Wp,-MD,fs/.open.o.d -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/8/include -I./arch/x86/include -I./arch/x86/include/generated -I./include/drm-backport -I./include -I./arch/x86/include/uapi -I./arch/x86/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -include ./include/linux/compiler_types.h -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -fshort-wchar -Werror-implicit-function-declaration -Wno-format-security -std=gnu89 -fno-PIE -DCC_HAVE_ASM_GOTO -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -m64 -falign-jumps=1 -falign-loops=1 -mno-80387 -mno-fp-ret-in-387 -mpreferred-stack-boundary=3 -mskip-rax-setup -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_FXSAVEQ=1 -DCONFIG_AS_SSSE3=1 -DCONFIG_AS_CRC32=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -DCONFIG_AS_AVX512=1 -DCONFIG_AS_SHA1_NI=1 -DCONFIG_AS_SHA256_NI=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mindirect-branch=thunk-extern -mindirect-branch-register -fno-jump-tables -fno-delete-null-pointer-checks -Wno-frame-address -Wno-format-truncation -Wno-format-overflow -Wno-int-in-bool-context -O2 -Werror --param=allow-store-data-races=0 -Wframe-larger-than=2048 -fstack-protector-strong -Wno-unused-but-set-variable -Wno-unused-const-variable -fno-var-tracking-assignments -g -gdwarf-4 -pg -mrecord-mcount -mfentry -DCC_USING_FENTRY -fno-inline-functions-called-once -Wdeclaration-after-statement -Wno-pointer-sign -Wno-stringop-truncation -fno-strict-overflow -fno-merge-all-constants -fmerge-constants -fno-stack-check -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -Werror=incompatible-pointer-types -Werror=designated-init -fmacro-prefix-map=./= -Wno-packed-not-aligned -DKBUILD_BASENAME='"open"' -DKBUILD_MODNAME='"open"' -c -o fs/.tmp_open.i -E fs/open.c
これを表示すると、次のようになる。
static const char *types__chroot[] = { "const char *" }; static const char *args__chroot[] = { "filename" }; static struct syscall_metadata __syscall_meta__chroot; static struct trace_event_call __attribute__((__used__)) event_enter__chroot = { .class = &event_class_syscall_enter, { .name = "sys_enter""_chroot", }, .event.funcs = &enter_syscall_print_funcs, .data = (void *)&__syscall_meta__chroot, .flags = TRACE_EVENT_FL_CAP_ANY, }; static struct trace_event_call __attribute__((__used__)) __attribute__((section("_ftrace_events"))) *__event_enter__chroot = &event_enter__chroot;; static struct syscall_metadata __syscall_meta__chroot; static struct trace_event_call __attribute__((__used__)) event_exit__chroot = { .class = &event_class_syscall_exit, { .name = "sys_exit""_chroot", }, .event.funcs = &exit_syscall_print_funcs, .data = (void *)&__syscall_meta__chroot, .flags = TRACE_EVENT_FL_CAP_ANY, }; static struct trace_event_call __attribute__((__used__)) __attribute__((section("_ftrace_events"))) *__event_exit__chroot = &event_exit__chroot;; static struct syscall_metadata __attribute__((__used__)) __syscall_meta__chroot = { .name = "sys""_chroot", .syscall_nr = -1, .nb_args = 1, .types = 1 ? types__chroot : ((void *)0), .args = 1 ? args__chroot : ((void *)0), .enter_event = &event_enter__chroot, .exit_event = &event_exit__chroot, .enter_fields = { &(__syscall_meta__chroot.enter_fields), &(__syscall_meta__chroot.enter_fields) }, }; static struct syscall_metadata __attribute__((__used__)) __attribute__((section("__syscalls_metadata"))) *__p_syscall_meta__chroot = &__syscall_meta__chroot; long __x64_sys_chroot(const struct pt_regs *regs); static struct error_injection_entry __attribute__((__used__)) __attribute__((__section__("_error_injection_whitelist"))) _eil_addr___x64_sys_chroot = { .addr = (unsigned long)__x64_sys_chroot, .etype = EI_ETYPE_ERRNO, };; static long __se_sys_chroot(__typeof(__builtin_choose_expr((__builtin_types_compatible_p(typeof(( const char *)0), typeof(0LL)) || __builtin_types_compatible_p(typeof(( const char *)0), typeof(0ULL))), 0LL, 0L)) filename); static inline __attribute__((unused)) __attribute__((no_instrument_function)) long __do_sys_chroot(const char * filename); long __x64_sys_chroot(const struct pt_regs *regs) { return __se_sys_chroot(regs->di); } long __ia32_sys_chroot(const struct pt_regs *regs); static struct error_injection_entry __attribute__((__used__)) __attribute__((__section__("_error_injection_whitelist"))) _eil_addr___ia32_sys_chroot = { .addr = (unsigned long)__ia32_sys_chroot, .etype = EI_ETYPE_ERRNO, };; long __ia32_sys_chroot(const struct pt_regs *regs) { return __se_sys_chroot((unsigned int)regs->bx); } static long __se_sys_chroot(__typeof(__builtin_choose_expr((__builtin_types_compatible_p(typeof(( const char *)0), typeof(0LL)) || __builtin_types_compatible_p(typeof(( const char *)0), typeof(0ULL))), 0LL, 0L)) filename) { long ret = __do_sys_chroot(( const char *) filename); (void)(sizeof(struct { int:(-!!(!(__builtin_types_compatible_p(typeof(( const char *)0), typeof(0LL)) || __builtin_types_compatible_p(typeof(( const char *)0), typeof(0ULL))) && sizeof(const char *) > sizeof(long))); })); do { } while (0); return ret; } static inline __attribute__((unused)) __attribute__((no_instrument_function)) long __do_sys_chroot(const char * filename) { return ksys_chroot(filename); }
改行が省略されてるとさすがに見づらすぎるので indent -bap -gnu
で整形する。中身を見ると、だいたい上記に書いてあったことが書いてあるなという感じ。
static const char *types__chroot[] = { "const char *" }; static const char *args__chroot[] = { "filename" }; static struct syscall_metadata __syscall_meta__chroot; static struct trace_event_call __attribute__ ((__used__)) event_enter__chroot = { .class = &event_class_syscall_enter, { .name = "sys_enter" "_chroot",},.event.funcs = &enter_syscall_print_funcs,.data = (void *) &__syscall_meta__chroot,.flags = TRACE_EVENT_FL_CAP_ANY,}; static struct trace_event_call __attribute__ ((__used__)) __attribute__ ((section ("_ftrace_events"))) * __event_enter__chroot = &event_enter__chroot;; static struct syscall_metadata __syscall_meta__chroot; static struct trace_event_call __attribute__ ((__used__)) event_exit__chroot = { .class = &event_class_syscall_exit, { .name = "sys_exit" "_chroot",},.event.funcs = &exit_syscall_print_funcs,.data = (void *) &__syscall_meta__chroot,.flags = TRACE_EVENT_FL_CAP_ANY,}; static struct trace_event_call __attribute__ ((__used__)) __attribute__ ((section ("_ftrace_events"))) * __event_exit__chroot = &event_exit__chroot;; static struct syscall_metadata __attribute__ ((__used__)) __syscall_meta__chroot = { .name = "sys" "_chroot",.syscall_nr = -1,.nb_args = 1,.types = 1 ? types__chroot : ((void *) 0),.args = 1 ? args__chroot : ((void *) 0),.enter_event = &event_enter__chroot,.exit_event = &event_exit__chroot,.enter_fields = { &(__syscall_meta__chroot.enter_fields), &(__syscall_meta__chroot.enter_fields)},}; static struct syscall_metadata __attribute__ ((__used__)) __attribute__ ((section ("__syscalls_metadata"))) * __p_syscall_meta__chroot = &__syscall_meta__chroot; static long __se_sys_chroot (__typeof (__builtin_choose_expr ((__builtin_types_compatible_p (typeof ((const char *) 0), typeof (0LL)) || __builtin_types_compatible_p (typeof ((const char *) 0), typeof (0ULL))), 0LL, 0L)) filename); static inline __attribute__ ((unused)) __attribute__ ((no_instrument_function)) long __do_sys_chroot (const char *filename); long __x64_sys_chroot (const struct pt_regs *regs) { return __se_sys_chroot (regs->di); } long __ia32_sys_chroot (const struct pt_regs *regs); static struct error_injection_entry __attribute__ ((__used__)) __attribute__ ((__section__ ("_error_injection_whitelist"))) _eil_addr___ia32_sys_chroot = { .addr = (unsigned long) __ia32_sys_chroot,.etype = EI_ETYPE_ERRNO,};; long __ia32_sys_chroot (const struct pt_regs *regs) { return __se_sys_chroot ((unsigned int) regs->bx); } static long __se_sys_chroot (__typeof (__builtin_choose_expr ((__builtin_types_compatible_p (typeof ((const char *) 0), typeof (0LL)) || __builtin_types_compatible_p (typeof ((const char *) 0), typeof (0ULL))), 0LL, 0L)) filename) { long ret = __do_sys_chroot ((const char *) filename); (void) (sizeof (struct { int: (-! ! (!(__builtin_types_compatible_p (typeof ((const char *) 0), typeof (0LL)) || __builtin_types_compatible_p (typeof ((const char *) 0), typeof (0ULL))) && sizeof (const char *) > sizeof (long))); })); do { } while (0); return ret; } static inline __attribute__ ((unused)) __attribute__ ((no_instrument_function)) long __do_sys_chroot (const char *filename) { return ksys_chroot (filename); }