目次: 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の場合は、
をお使いください。
< | 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 | - | - | - | - |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.15.
using GD bundled (2.1.0 compatible)(png support.)