asmlinkage_protectマクロ
システムコールの引数はレジスタで渡たされ、従ってその関数にはasmlinkageの宣言が必要です。asmlinkageを宣言すると、改めてレジスタをスタックにセットし、あたかもスタック渡しでコールされたようにいたします。
asmlinkage_protectマクロは、openシステムコールのように、その引数をそのまま他の関数の引数となる関数をコールするだけの関数内で使われ、tail callと呼ばれる最適化に起因する問題を回避します。
この回避として、引数のfilename/flags/modeを参照する命令を記述しておけばいいわけですが、その為ダミーの変数を定義し、それに代入すると、メモリtoメモリで無駄が発生します。また最適化でスキップされるかもしれない。asmlinkage_protectは直接レジスタに代入する事で実現します。
asmlinkage_protectマクロは、openシステムコールのように、その引数をそのまま他の関数の引数となる関数をコールするだけの関数内で使われ、tail callと呼ばれる最適化に起因する問題を回避します。
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) { long ret; if (force_o_largefile()) flags |= O_LARGEFILE; ret = do_sys_open(AT_FDCWD, filename, flags, mode); /* avoid REGPARM breakage on x86: */ asmlinkage_protect(3, ret, filename, flags, mode); return ret; }まずtail callについてです。下記サンプルはgcc -O2でコンパイルした内容です。test1()からtest2()をコールする場合、callでなくjmpしています。
#include "stdio.h" void test1(int *a); void test2(int *a); int dummy; void main() { int val; test1(&val); } void test1(int *val) { dummy = 1; <-これを入れないとmainからtest1をスキップして直接test2をコールしてしいました。 test2(val); } void test2(int *val) { printf("%d\n", *val); }
08048310 <main>: 8048310: 55 push %ebp 8048311: 89 e5 mov %esp,%ebp 8048313: 83 e4 f0 and $0xfffffff0,%esp 8048316: 83 ec 20 sub $0x20,%esp 8048319: 8d 44 24 1c lea 0x1c(%esp),%eax 804831d: 89 04 24 mov %eax,(%esp) 8048320: e8 db 00 00 00 call 8048400 <test1> 8048325: c9 leave 8048326: c3 ret 8048327: 90 nop 080483e0 <test2>: 80483e0: 83 ec 1c sub $0x1c,%esp 80483e3: 8b 44 24 20 mov 0x20(%esp),%eax 80483e7: 8b 00 mov (%eax),%eax 80483e9: c7 04 24 e4 84 04 08 movl $0x80484e4,(%esp) 80483f0: 89 44 24 04 mov %eax,0x4(%esp) 80483f4: e8 e7 fe ff ff call 80482e0 <printf@plt> 80483f9: 83 c4 1c add $0x1c,%esp 80483fc: c3 ret 80483fd: 8d 76 00 lea 0x0(%esi),%esi 08048400 <test1>: 8048400: c7 05 20 97 04 08 01 movl $0x1,0x8049720 8048407: 00 00 00 804840a: e9 d1 ff ff ff jmp 80483e0 <test2> 804840f: 90 nopこの最適化は、先のopenシステムコール関数では問題です。コンパイラの処理系によっては、asmlinkageの処理をすることなく、open()がdo_sys_open()をjmpで呼び出す可能性があるからです。たぶん。do_sys_open()はasmlinkage宣言されていません。しかもこの引数はスタック上にありません。
この回避として、引数のfilename/flags/modeを参照する命令を記述しておけばいいわけですが、その為ダミーの変数を定義し、それに代入すると、メモリtoメモリで無駄が発生します。また最適化でスキップされるかもしれない。asmlinkage_protectは直接レジスタに代入する事で実現します。
#define asmlinkage_protect(n, ret, args...) \ __asmlinkage_protect##n(ret, ##args) #define __asmlinkage_protect_n(ret, args...) \ __asm__ __volatile__ ("" : "=r" (ret) : "0" (ret), ##args) #define __asmlinkage_protect0(ret) \ __asmlinkage_protect_n(ret) #define __asmlinkage_protect1(ret, arg1) \ __asmlinkage_protect_n(ret, "g" (arg1)) #define __asmlinkage_protect2(ret, arg1, arg2) \ __asmlinkage_protect_n(ret, "g" (arg1), "g" (arg2)) #define __asmlinkage_protect3(ret, arg1, arg2, arg3) \ __asmlinkage_protect_n(ret, "g" (arg1), "g" (arg2), "g" (arg3)) #define __asmlinkage_protect4(ret, arg1, arg2, arg3, arg4) \ __asmlinkage_protect_n(ret, "g" (arg1), "g" (arg2), "g" (arg3), \ "g" (arg4)) #define __asmlinkage_protect5(ret, arg1, arg2, arg3, arg4, arg5) \ __asmlinkage_protect_n(ret, "g" (arg1), "g" (arg2), "g" (arg3), \ "g" (arg4), "g" (arg5)) #define __asmlinkage_protect6(ret, arg1, arg2, arg3, arg4, arg5, arg6) \ __asmlinkage_protect_n(ret, "g" (arg1), "g" (arg2), "g" (arg3), \ "g" (arg4), "g" (arg5), "g" (arg6)) #define __asmlinkage_protect3(ret, arg1, arg2, arg3) \ __asmlinkage_protect_n(ret, "g" (arg1), "g" (arg2), "g" (arg3))opneシステムコールの場合、__asmlinkage_protect3マクロで以下の様な拡張インラインアセンブラに展開されます。asmlinkage故、各引数はレジスタに設定するため参照されます。従っていったんスタックに設定することが保障されます。そこで、do_sys_open()をjmpで呼び出されても問題ないわけです。
__asm__ __volatile__ ("" : "=r" (ret) : "0" (ret), "g" (filename), "g" (flags), "g" (mode))filename, flags, modeを参照する。と言う実だけが必要なので、任意の汎用レジスタに代入すればいいわけですが、retは返り値となるため、セットしたレジスタでretに代入しなければなりません。