unameはどこから情報を取得しているのか?
unameを実行するとカーネル情報を表示してくれますが、どこから情報を取得しているのでしょうか?
$ uname -r
6.0.0-rc7
init/version.cにあるグローバル変数init_uts_nsから取得しています。
struct uts_namespace init_uts_ns = {
.ns.count = REFCOUNT_INIT(2),
.name = {
.sysname = UTS_SYSNAME,
.nodename = UTS_NODENAME,
.release = UTS_RELEASE,
.version = UTS_VERSION,
.machine = UTS_MACHINE,
.domainname = UTS_DOMAINNAME,
},
.user_ns = &init_user_ns,
.ns.inum = PROC_UTS_INIT_INO,
};
各プロセスはtask_struct構造体の中にinit_uts_nsのアドレスを持っていて、
init_uts_ns.name.releaseのように情報を取得しています。
init_uts_nsのアドレスはsystem.Mapにあります。
ffffffff8281a750 D loops_per_jiffy
ffffffff8281a760 D envp_init
ffffffff8281a880 d argv_init
ffffffff8281a990 d ramdisk_execute_command
ffffffff8281a9a0 D init_uts_ns
ffffffff8281aba0 D rootfs_fs_type
ffffffff8281abe8 D root_mountflags
ffffffff8281ac00 d kern_do_mounts_initrd_table
ffffffff8281adc0 d initramfs_domain
init_uts_nsのアドレスはffffffff8281a9a0であることがわかります。
Dはグローバル変数の意味です。
gdbでデバッグしましょう。
(gdb) p init_uts_ns
$1 = {name = {sysname = "Linux", '\000' <repeats 59 times>,
nodename = "(none)", '\000' <repeats 58 times>,
release = "6.0.0-rc7", '\000' <repeats 55 times>,
version = "#2 SMP PREEMPT_DYNAMIC Tue Oct 4 01:53:58 JST 2022", '\000' <repeats 14 times>, machine = "x86_64", '\000' <repeats 58 times>,
domainname = "(none)", '\000' <repeats 58 times>},
user_ns = 0xffffffff82878e60 <init_user_ns>, ucounts = 0x0 <fixed_percpu_data>, ns = {
stashed = {counter = 0}, ops = 0xffffffff82228dc0 <utsns_operations>,
inum = 4026531838, count = {refs = {counter = 3}}}}
(gdb) p init_uts_ns.name.release
$2 = "6.0.0-rc7", '\000' <repeats 55 times>
(gdb) p init_uts_ns.name.sysname
$3 = "Linux", '\000' <repeats 59 times>
(gdb) p &init_uts_ns
$5 = (struct uts_namespace *) 0xffffffff8281a9a0 <init_uts_ns>
(gdb) x 0xffffffff8281a9a0
0xffffffff8281a9a0 <init_uts_ns>: 0x756e694c
(gdb) x/s 0xffffffff8281a9a0
0xffffffff8281a9a0 <init_uts_ns>: "Linux"
(gdb) p &init_uts_ns.name.release
$7 = (char (*)[65]) 0xffffffff8281aa22 <init_uts_ns+130>
(gdb) x/s 0xffffffff8281aa22
0xffffffff8281aa22 <init_uts_ns+130>: "6.0.0-rc7"
構造体を表示したり、変数からアドレスを取得、アドレス指定で表示をしています。
変数指定する場合にはp, アドレス指定する場合にはxで表示します。
system.Mapの情報を合致していますね。
次はシステムコール内の処理を見ていきましょう。
SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name)
{
struct new_utsname tmp;
memcpy(&tmp, utsname(), sizeof(tmp));
return 0;
}
SYSCALL_DEFINE1は
__x64_sys_newuname __se_sys_newuname __do_sys_newuname
3つの関数へマクロ展開されます。
utsname()をたどると、this_cpu_read_stable()にたどり着きます。
static __always_inline struct task_struct *get_current(void)
{
return this_cpu_read_stable(current_task);
}
this_cpu_read_stableはマクロなので展開してみます。
(gdb) p this_cpu_read_stable(current_task)
asm("mov" "q " "%%""gs"":" "%" "P[var]" ", " "%[val]" : [val] "=" "r" (pfo_val__) : [var] "p" (&(current_task)));
拡張インラインアセンブラなのですが、人間用にわかりやすく変換します。
asm("movq %%gs:%P[var], %[val]"
:[val]=r(pfo_val__)
:[var]p(&(current_task)));
X86 PerCPUの可変ベースアドレス(gsレジスタ)の原理。
X86では、mov %gs:varという簡単なアセンブリ命令で、現在のCPUのpercpu変数の値を非常に効果的に取り出しています。
この一文で(task_struct *)current_taskのアドレスを取り出しています。
このcurrent_taskの中にunameの情報が入っているわけですね。
★ unameシステムコールで%gsレジスタ取得
アドレス表示
そもそもcurrent_taskのアドレスは変化しているのか?
そのままpfo_val__ = current_taskではダメなのか?
ここでper_cpuでcurrent_taskを取得するとどうなるのか?
per_cpu変数をそのまま取得するとper_cpu(current_task, get_cpu())と同じ
意味なのか?
Discussion