目次: RISC-V
昨日の続きです。「64bit環境においてシステムコールの引数がレジスタ長(= 64bit)以下だった場合(例えばintが典型例)、どう扱うか?」コードの解説編です。
どうして符号拡張されるのか?逆アセンブルで説明したつもりになってはいけない、逃げてはダメだ!という熱心なあなたのために、マクロを展開しながら説明します。今回の解説は __MAP(), __SC_LONG(), __SC_CAST() マクロの展開結果です。
今まで説明してきた __MAP(), __SC_LONG(), __SC_CAST() マクロを総動員して全部展開すると、
__MAP(4,__SC_LONG,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)
↓
__SC_LONG(int, magic1),
__SC_LONG(int, magic2),
__SC_LONG(unsigned int, cmd),
__SC_LONG(void __user *, arg)
↓
long magic1,
long magic2,
long cmd,
long arg
__MAP(4,__SC_CAST,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)
↓
__SC_CAST(int, magic1),
__SC_CAST(int, magic2),
__SC_CAST(unsigned int, cmd),
__SC_CAST(void __user *, arg)
↓
(int)magic1,
(int)magic2,
(unsigned int)cmd,
(void __user *)arg
になります。他のマクロ __SC_TEST() と __SC_ARGS() と __PROTECT() は本筋ではないので詳細は省きますが、下記のような実装です。
// linux/include/linux/syscalls.h
#define __SC_TEST(t, a) (void)BUILD_BUG_ON_ZERO(!__TYPE_IS_LL(t) && sizeof(t) > sizeof(long))
#define __SC_ARGS(t, a) a
#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
// linux/include/linux/build_bug.h
/*
* Force a compilation error if condition is true, but also produce a
* result (of value 0 and type int), so the expression can be used
* e.g. in a structure initializer (or where-ever else comma expressions
* aren't permitted).
*/
#define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
// linux/include/linux/linkage.h
#ifndef __ASSEMBLY__
#ifndef asmlinkage_protect
# define asmlinkage_protect(n, ret, args...) do { } while (0)
#endif
#endif
展開するとこうなります。
__SC_TEST(AAA, BBB)
// AAAがlong longでもunsigned long longでもない型で、
// long型より大きい型(構造体とかが該当する)の場合に、
// コンパイルエラーにする。
__SC_ARG(AAA, BBB)
↓
BBB
__PROTECT(n, ret, args...)
↓
// m68kアーキテクチャでは引数をスタックに置かないようにする役目のコードになるらしい。
// 他のアーキテクチャでは下記のようになって何も意味はない。
do { } while (0)
これで不明な要素は全てなくなったはずです。続きはまた今度。
目次: RISC-V
昨日の続きです。「64bit環境においてシステムコールの引数がレジスタ長(= 64bit)以下だった場合(例えばintが典型例)、どう扱うか?」コードの解説編です。
どうして符号拡張されるのか?逆アセンブルで説明したつもりになってはいけない、逃げてはダメだ!という熱心なあなたのために、マクロを展開しながら説明します。今回の解説は __MAP(), __SC_LONG(), __SC_CAST() マクロの意味や実装です。
鍵となるのは __MAP(4, ...) の展開後に出てくる __MAP4(m, t, a, ...) というマクロです。
// linux/include/linux/syscalls.h
/*
* __MAP - apply a macro to syscall arguments
* __MAP(n, m, t1, a1, t2, a2, ..., tn, an) will expand to
* m(t1, a1), m(t2, a2), ..., m(tn, an)
* The first argument must be equal to the amount of type/name
* pairs given. Note that this list of pairs (i.e. the arguments
* of __MAP starting at the third one) is in the same format as
* for SYSCALL_DEFINE<n>/COMPAT_SYSCALL_DEFINE<n>
*/
#define __MAP0(m,...)
#define __MAP1(m,t,a,...) m(t,a)
#define __MAP2(m,t,a,...) m(t,a), __MAP1(m,__VA_ARGS__)
#define __MAP3(m,t,a,...) m(t,a), __MAP2(m,__VA_ARGS__)
#define __MAP4(m,t,a,...) m(t,a), __MAP3(m,__VA_ARGS__)
#define __MAP5(m,t,a,...) m(t,a), __MAP4(m,__VA_ARGS__)
#define __MAP6(m,t,a,...) m(t,a), __MAP5(m,__VA_ARGS__)
#define __MAP(n,...) __MAP##n(__VA_ARGS__)
マクロに渡された型tと引数aリストのペアを1つずつm(t, a) のようにmで変換を掛けるマクロですが、イメージしづらいと思うので展開結果を載せます。
__MAP(4,__SC_LONG,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)
↓
__MAP4(__SC_LONG,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)
↓
__SC_LONG(int, magic1),
__SC_LONG(int, magic2),
__SC_LONG(unsigned int, cmd),
__SC_LONG(void __user *, arg)
実装はぱっと見では良くわかりませんが、展開結果はわかりやすいですね。
先頭の展開結果 __SC_LONG(int, magic1) に着目します。
// linux/include/linux/syscalls.h
#define __SC_LONG(t, a) __typeof(__builtin_choose_expr(__TYPE_IS_LL(t), 0LL, 0L)) a
#define __TYPE_AS(t, v) __same_type((__force t)0, v)
#define __TYPE_IS_LL(t) (__TYPE_AS(t, 0LL) || __TYPE_AS(t, 0ULL))
// linux/include/linux/compiler_types.h
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
# define __force
ここまでの実装を踏まえて展開すると下記のようになります。
__SC_LONG(int, magic1),
↓ __TYPE_IS_LL() を展開
__typeof(
__builtin_choose_expr(
(__same_type((int)0, 0LL) ||
__same_type((int)0, 0ULL)), 0LL, 0L))
magic1,
↓ __same_typeを展開
__typeof(
__builtin_choose_expr(
(__builtin_types_compatible_p(typeof((int)0), typeof(0LL)) ||
__builtin_types_compatible_p(typeof((int)0), typeof(0ULL))), 0LL, 0L))
magic1,
最内側の__same_type() は引数に渡した値の型を比較するマクロです。マクロで使用する __builtin_types_compatible_p() は1つ目と2つ目の型が同じかどうか?をintで返す組み込み関数、typeof() は引数の型を返すキーワードです(関数ではないので値は返しません)。
従って__same_type((int)0, 0LL) はintと0LLの型、int型とlong long int型が等しいか比較しています。当然、等しくありません。ちなみにintは __SC_LONG(int, magic1) の最初の引数intから来たことを思い出していただけると嬉しいです。例えば __SC_LONG(long long, magic1) であれば比較結果は等しいと判断されるでしょう。
最内側の条件式 __same_type((int)0, 0LL) || __same_type((int)0, 0ULL)) の部分は型がlong longもしくはunsigned long longかどうか?を調べています。
判断結果を受け取る__builtin_choose_expr(const_exp, exp1, exp2) は、const_expが真なら、exp1を返し、偽ならexp2を返す組み込み関数です。もしconst_expがコンパイル時に判定できなければ、コンパイルエラーです。
以上をまとめると __SC_LONG(AAA, BBB) は、
という働きをします。また __SC_CAST() はその名の通りキャストを生成します。
// linux/include/linux/syscalls.h
#define __SC_CAST(t, a) (__force t) a
です。長いですね。続きはまた今度。
目次: RISC-V
昨日の続きです。「64bit環境においてシステムコールの引数がレジスタ長(= 64bit)以下だった場合(例えばintが典型例)、どう扱うか?」コードの解説編です。
どうして符号拡張されるのか?逆アセンブルで説明したつもりになってはいけない、逃げてはダメだ!という熱心なあなたのために、マクロを展開しながら説明します。今回の解説はSYSCALL_DEFINE4() たった1行です。
// linux/kernel/reboot.c
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
void __user *, arg)
{
...
// linux/include/linux/syscalls.h
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
下記のように展開されます。
SYSCALL_METADATA(_reboot, 4, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)
__SYSCALL_DEFINEx(4, _reboot, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)
SYSCALL_METADATA() の方は関数の実体と無関係なので今回は無視して、__SYSCALL_DEFINEx() を見ます。
// linux/include/linux/syscalls.h
/*
* The asmlinkage stub is aliased to a function named __se_sys_*() which
* sign-extends 32-bit ints to longs whenever needed. The actual work is
* done within __do_sys_*().
*/
#ifndef __SYSCALL_DEFINEx
#define __SYSCALL_DEFINEx(x, name, ...) \
__diag_push(); \
__diag_ignore(GCC, 8, "-Wattribute-alias", \
"Type aliasing is used to sanitize syscall arguments");\
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \
__attribute__((alias(__stringify(__se_sys##name)))); \
ALLOW_ERROR_INJECTION(sys##name, ERRNO); \
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
asmlinkage 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; \
} \
__diag_pop(); \
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
#endif /* __SYSCALL_DEFINEx */
長いマクロですね。rebootシステムコールの宣言部分 __SYSCALL_DEFINE4(reboot, ...) は下記のように展開されます。
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
void __user *, arg)
↓
__diag_push();
__diag_ignore(GCC, 8, "-Wattribute-alias", "Type aliasing is used to sanitize syscall arguments");
asmlinkage long sys_reboot(__MAP(4,__SC_DECL,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)) __attribute__((alias(__stringify(__se_sys_reboot))));
ALLOW_ERROR_INJECTION(sys_reboot, ERRNO);
static inline long __do_sys_reboot(__MAP(4,__SC_DECL,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg));
asmlinkage long __se_sys_reboot(__MAP(4,__SC_LONG,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg));
asmlinkage long __se_sys_reboot(__MAP(4,__SC_LONG,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg))
{
long ret = __do_sys_reboot(__MAP(4,__SC_CAST,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg));
__MAP(4,__SC_TEST,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg);
__PROTECT(4, ret,__MAP(4,__SC_ARGS,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg));
return ret;
}
__diag_pop();
static inline long __do_sys_reboot(__MAP(4,__SC_DECL,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg));
色々興味深い部分(__diag_push, __diag_ignore, などなど)はありますが、関数と関係ないので無視して、符号拡張を行う __se_sys_reboot() の実装部分を見ます。
asmlinkage long __se_sys_reboot(__MAP(4,__SC_LONG,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg))
{
long ret = __do_sys_reboot(__MAP(4,__SC_CAST,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg));
__MAP(4,__SC_TEST,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg);
__PROTECT(4, ret,__MAP(4,__SC_ARGS,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg));
return ret;
}
この関数本体の部分がどう展開されるかを見ます。続きはまた今度。
目次: RISC-V
昨日の続きです。64bit Linux環境のreboot() はmagicの符号拡張をしてもしなくても正常に動くという話をしました。
この話はrebootには限らないので、もっと一般化すると「64bit環境においてシステムコールの引数がレジスタ長(= 64bit)以下だった場合(例えばintが典型例)、どう扱うか?」となります。
動作を確認するにはデバッガで追えば早いでしょう。RISC-V 64bit向けに、
を用意して起動します。符号拡張を行わないmusl libc版のツールチェーンでbusyboxをビルドしてQEMU起動、GDBでアタッチします。
$ qemu-system-riscv64 \ -machine virt \ -net none \ -nographic \ -chardev stdio,id=con,mux=on \ -serial chardev:con -mon chardev=con,mode=readline \ -kernel linux/arch/riscv/boot/Image \ -initrd busybox/initramfs.cpio \ -s $ riscv64-unknown-linux-gnu-gdb linux/vmlinux (gdb) target remote :1234 ...
Linuxのシステムコールの実装は下記のようになります。SYSCALL_DEFINE() とはなんぞや?というところが気になると思いますが、今はひとまずシステムコール名の頭に __do_sys_ を付けた関数が定義されると理解しておけば大丈夫です。
//linux/kernel/reboot.c
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
void __user *, arg)
{
...
ブレークポイントを __do_sys_rebootに仕掛けてQEMU側のコンソールでbusybox poweroffコマンドを実行します。
(gdb) b __do_sys_reboot Breakpoint 1 at 0xffffffff8002d860: file kernel/reboot.c, line 702. (gdb) c Continuing. Breakpoint 1, __do_sys_reboot (magic1=-18751827, magic2=672274793, cmd=1126301404, arg=0x4321fedc) at kernel/reboot.c:702 702 { (gdb) p/x $a0 $1 = 0xfffffffffee1dead
システムコールの引数は負の数(つまり0xffffffff_fee1dead)となっています。この関数に辿り着く前に符号拡張されるようです。
(gdb) bt #0 __do_sys_reboot (magic1=-18751827, magic2=672274793, cmd=1126301404, arg=0x4321fedc) at kernel/reboot.c:702 #1 0xffffffff8002dab2 in __se_sys_reboot (magic1=<optimized out>, magic2=<optimized out>, cmd=<optimized out>, arg=<optimized out>) at kernel/reboot.c:700 #2 0xffffffff80002fe2 in handle_exception () at arch/riscv/kernel/entry.S:231 Backtrace stopped: frame did not save the PC
バックトレースを見ると__se_sys_reboot() という関数も呼ばれています。この関数を調べるためにブレークを掛けて再度実行します。
(gdb) b __se_sys_reboot Breakpoint 1 at 0xffffffff8002daa0: file kernel/reboot.c, line 700. (gdb) c Continuing. Breakpoint 1, __se_sys_reboot (magic1=4276215469, magic2=672274793, cmd=1126301404, arg=1126301404) at kernel/reboot.c:700 700 SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, (gdb) p/x $a0 $1 = 0xfee1dead
システムコール例外ハンドラに渡ってきた引数magicはレジスタa0に入っていて、値は0xfee1deadです。__do_sys_reboot() や __se_sys_reboot() 関数を定義するSYSCALL_DEFINE() はマクロのため、デバッガから見てもソースコードが表示されません。
不思議なSYSCALL_DEFINE() マクロの仕組みは後日調べるとして、とりあえず今は逆アセンブルします。
(gdb) disas Dump of assembler code for function __se_sys_reboot: => 0xffffffff8002daa0 <+0>: addi sp,sp,-16 0xffffffff8002daa2 <+2>: sd s0,0(sp) 0xffffffff8002daa4 <+4>: sd ra,8(sp) 0xffffffff8002daa6 <+6>: addi s0,sp,16 0xffffffff8002daa8 <+8>: sext.w a2,a2 ★ 0xffffffff8002daaa <+10>: sext.w a1,a1 ★ 0xffffffff8002daac <+12>: sext.w a0,a0 ★ 0xffffffff8002daae <+14>: jal ra,0xffffffff8002d860 <__do_sys_reboot> 0xffffffff8002dab2 <+18>: ld ra,8(sp) 0xffffffff8002dab4 <+20>: ld s0,0(sp) 0xffffffff8002dab6 <+22>: addi sp,sp,16 0xffffffff8002dab8 <+24>: ret
RISC-Vのアセンブラを見たことない方のために補足すると、sext.wという命令はRISC-Vの符号拡張命令です。また引数はa0, a1, a2, a3, a4, a5レジスタを経由して渡されます。すなわち __se_sys_reboot() 関数はa0, a1, a2レジスタ(引数magic1, magic2, cmdに相当)を符号拡張して __do_sys_reboot() 関数に渡しています。
ここで当初の疑問が解消しますね。疑問の内容は下記の通りで、
答えは途中で符号拡張しているのでどちらでも問題なかった、というわけです。なるほどね。
ツールチェーンのビルドはcrosstool-NGなどでもできますし、面倒な場合はGitHubにUbuntu 20.04用の *.debファイルを置いたのでお使いください。
GNU libcの場合は、
またmusl libcの場合は、
をお使いください。
目次: RISC-V
タイトルに反して実はRISC-Vはあまり関係ないですが……。GNU libcとmusl libcの実装を見ていたら、64bit環境でrebootシステムコールの引数magicに、
がいることに気づきました。
Cライブラリのrebootの関数宣言はint reboot(int cmd) となっています。musl libcの場合0xfee1deadをキャストせずlong型の引数に渡します。符号拡張は行われず0x00000000_fee1deadがシステムコールの引数に渡されます。
// musl/src/linux/reboot.c
int reboot(int type)
{
return syscall(SYS_reboot, 0xfee1dead, 672274793, type);
}
// musl/arch/riscv64/syscall_arch.h
static inline long __syscall3(long n, long a, long b, long c)
{
register long a7 __asm__("a7") = n;
register long a0 __asm__("a0") = a;
register long a1 __asm__("a1") = b;
register long a2 __asm__("a2") = c;
__asm_syscall("r"(a7), "0"(a0), "r"(a1), "r"(a2))
}
一方のGNU libcの場合は、0xfee1deadをintにキャストしてからシステムコールに渡します。この値は負の数なので符号拡張が行われて0xffffffff_fee1deadがシステムコールの引数に渡されます。
// glibc/sysdeps/unix/sysv/linux/reboot.c
/* Call kernel with additional two arguments the syscall requires. */
int
reboot (int howto)
{
return INLINE_SYSCALL (reboot, 3, (int) 0xfee1dead, 672274793, howto);
}
// glibc/sysdeps/unix/sysv/linux/riscv/sysdep.h
# define internal_syscall3(number, arg0, arg1, arg2) \
({ \
long int _sys_result; \
long int _arg0 = (long int) (arg0); \
long int _arg1 = (long int) (arg1); \
long int _arg2 = (long int) (arg2); \
\
{ \
register long int __a7 asm ("a7") = number; \
register long int __a0 asm ("a0") = _arg0; \
register long int __a1 asm ("a1") = _arg1; \
register long int __a2 asm ("a2") = _arg2; \
__asm__ volatile ( \
"scall\n\t" \
: "+r" (__a0) \
: "r" (__a7), "r" (__a1), "r" (__a2) \
: __SYSCALL_CLOBBERS); \
_sys_result = __a0; \
} \
_sys_result; \
})
最初に気づいたときはどちらかがバグっている……?と勘違いしましたが、当たり前ですがどちらも正常に動きます。すなわちLinux側で何か対応しているはずです。続きはまた今度。
目次: Linux
昨日(2023年1月26日の日記参照)の続きです。タイトルに「日本語 → 英語」の順の文字列を表示させると英語がズレてしまう問題についてです。
ルック&フィールの設定からフォントを変えて試したところ、源ノ角ゴシックの一部の太さ(Normalのみ)でこの問題が発生することが分かりました。実際に見てもらった方が分かりやすいと思います。
原因が良くわからないままですが、とりあえず源ノ角ゴシックを使うならNormal以外の太さを選択することで英語が上にズレてしまう問題を回避できそうです。
NormalだろうがRegularだろうが同じフォントだと思っていたんですけど、何か違うんでしょうか??フォントのレンダリングは良くわからないね……。
目次: Linux
タイトルのままなのですが、普段使いしているLinux環境(Debian Testing, LXDE)にて英語の位置がたまにズレていることがあります。どういうときにズレるのか気になったので、いくつかパターンを試しました。残念ながら原因や直し方までは調べていないので良くわかりません。
結論を先に書いておくと、日本語のあとに英語を書くとズレます。英語のあとに日本語を書いてもズレません。不思議ですね。
この画像をキャプチャしているときに気が付いたのですが、Leafpadのテキストボックスでも同じ現象が発生していました。
何なんでしょうね?GTK+ の仕様なんでしょうか?
NetFlixのような老舗に属するVODサービスって一時期に一気に増えたよね?という印象を持っていたのですが、調べたことがなかったので調べがてらメモしておきます。
ベンダー | 創業国 | 創業国サービスイン | 日本サービスイン |
---|---|---|---|
Amazon | アメリカ | 2006 | 2015/09 |
Hulu | アメリカ | 2007 | 2011/09 |
NetFlix | アメリカ | 2007 | 2015/09 |
U-NEXT | 日本 | 2007 | 同左 |
注意点としては創業 = サブスクリプション開始、ではないことです。今やVODサービスといえば定額制度(サブスクリプション制度、サブスク)が当たり前ですが、以前は1つの動画に1度支払う制度(PPV: Pay-Per-View制度)が普通でした。
したがって老舗VOD企業達も創業時にサブスクリプション制度を展開していたとは限りません。例えばHuluはPPVで参入、サブスク(Hulu Plus)の開始は2010年です。
といった細かい話をあえて無視すれば「老舗VOD企業は2007年に出揃った」と言ってしまって良いでしょう。
< | 2023 | > | ||||
<< | < | 02 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | 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 | - | - | - | - |
合計:
本日: