Linux Advent Calendar 2019でunameコマンドから始めるデバッグ&カーネルハック入門という記事を書きました。
このときはFedora 31を実験環境として選んで書いたんですが、環境をCentOS 8にして内容を加筆修正して電子書籍にしました。
目次はこんな感じです。
strace、gdb、bcc、bpftrace、カーネルハック、Livepatch、systemtapでunameコマンドと戯れる内容です。
BOOTH様便利ですね👍
機種依存ですが、fedoraで5.4系のカーネルで起動時にカーネルパニックするようになり、自分もこのバグに当たったので修正してパッチをlkmlに投稿しました。 このパッチはLinux5.5.に取り込まれました🎉
ついでなのでどんな感じでデバッグしたかの記録です。10日前くらいの話だし、デバッグ時の思考の時系列は多少違う気もしますがだいたいこんな感じということで。
こんな感じです。
自分がこのバグに当たったときにはfedoraのbugzillaにすでにバグ登録されてました
5.4系のカーネルだと起動時にカーネルパニックする☠という内容です。実際こんな感じでした。
ただ、自分が普段使ってるデスクトップPCでも同じカーネルを使ってますがこちらでは同様の現象は起きてませんでした。ということで機種依存っぽい感じがします。そして、bugzillaを見てると共通点が見えてきました。 それは次の二点です。
上に貼り付けたoopsはカーネルのコマンドラインオプションにquietが付いているので表示されていないですが、quietを外すと↓のprintk()の部分が表示されました。
/* sched_clock_stable() is determined in late_initcall */ if (!trace_boot_clock && !sched_clock_stable()) { printk(KERN_WARNING "Unstable clock detected, switching default tracing clock to \"global\"\n" "If you want to keep using the local clock, then add:\n" " \"trace_clock=local\"\n" "on the kernel command line\n"); tracing_set_clock(&global_trace, "global"); }
ってことで、これを試すと起動成功したのでbugzillaにtrace_clock=locakを付けたら大丈夫だったよとコメントしました。また、他の人はSecure bootを無効にすることで対応したりもしてました。
ring_buffer_set_clock()はこんな関数です。
void ring_buffer_set_clock(struct ring_buffer *buffer, u64 (*clock)(void)) { buffer->clock = clock; }
どこでエラーになったか一目瞭然ですね。
この関数に至る流れはtracing_set_default_clock()から始まり、 tracing_set_clock() -> ring_buffer_set_clock()となります。
初期化されていない状態でring_buffer_set_clock()が呼ばれてる感じですね。
ちなみにここで変更しようとしているclockは/sys/kernel/debug/tracing/trace_clockとして見えます。
masami@moon:~$ sudo cat /sys/kernel/debug/tracing/trace_clock [local] global counter uptime perf mono mono_raw boot x86-tsc
5.3系では問題なかったので5.4系からのバグではあるのですが、まず以下の部分でUnstable clock detectedとなるのは5.3ではどうだったのか?というのを調べます。trace_boot_clockはtrace_clockオプションなので普段は設定してないので気にしません。
if (!trace_boot_clock && !sched_clock_stable()) {
調べるといっても、5.3系カーネルの起動時にquietオプションを外すだけですが。そして結果はどうだったかと言うと5.3系でも5.4.7でも同じパスを通っていました。このPCはUnstable clock detectedと判断されるようですね。デスクトップPC(cpuはi7-9700K)のほうはこのパスは通ってませんでした。
とくに同じようなエラーが出てる感じはありませんでした。
以下の条件に当てはまる人がバグに当たっている感じです。
回避策はtrace_clock=localを設定するかSecure Bootを無効にする。
Secure Bootを無効にしちゃったら再現しないので自前でビルドしたカーネルへの署名方法を調べます。調べた結果は↓にまとめました。
まずはバージョン同じものを使って、カーネルのコンフィグはfedoraの5.4.7のコンフィグを使います。ビルドしてこのカーネルから起動してみると無事に起動しました。ということで、次の手順に進みます。
調べると言うかパッチを一旦外せるだけ外してから足していくとかそんな感じですね。fedoraのカーネルパッケージのソース一式はgitリポジトリにあるのでクローンしてきて、f31ブランチからブランチを切手作業していきます。この時点でf31ブランチは5.4.12カーネルになっていました。
手順的にはspecファイルでパッチをコメントアウトして、fedpkgでsrpmを作ります。そして、必要最小限のパッケージだけ作りたかったので出来たsrpmをインストールしてrpmbuildでビルドするという方法を取りました。fedpkgで同じようなことができるんでしょうか? それはともかくrpmbuildはこんなオプションでやりました。
$ rpmbuild -bb --with baseonly --without debuginfo --target=$(uname -m) kernel.spec
arm64-Add-option-of-13-for-FORCE_MAX_ZONEORDER.patchのように外すと他に直さなきゃいけないところが出てくるパッチもあったりするのですが、まあこの辺は今回のバグに関係ないだろってことでこういうパッチは外さずに進めました。そして、efi-secureboot.patchを外せば問題ないというところまで判明しました。
efi-secureboot.patchはLinux 5.4から入ったlockdownに関係するパッチです。
lockdownについてはこちらを参照してもらうとして。。
このパッチの内容は大きく分けると
最後のCONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOTの値に応じて処理してる部分は以下のifdefのところです。
diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index 77ea96b794bd..a119e1bc9623 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c @@ -73,6 +73,7 @@ #include <linux/jiffies.h> #include <linux/mem_encrypt.h> #include <linux/sizes.h> +#include <linux/security.h> #include <linux/usb/xhci-dbgp.h> #include <video/edid.h> @@ -1027,6 +1028,13 @@ void __init setup_arch(char **cmdline_p) if (efi_enabled(EFI_BOOT)) efi_init(); + efi_set_secure_boot(boot_params.secure_boot); + +#ifdef CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT + if (efi_enabled(EFI_SECURE_BOOT)) + security_lock_kernel_down("EFI Secure Boot mode", LOCKDOWN_CONFIDENTIALITY_MAX); +#endif + dmi_setup(); /*
上記のコードはカーネルがuefiのsecure boot環境で起動されてたらlockdownしてます。
ここまでくるとCONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOTが疑わしい感じしてきます。そこでこのオプションは選択せずにカーネルをビルドします。オプションはkernel-x86_64-fedora.configにてこのように行われています。
CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT=y # CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY is not set # CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY is not set CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=y
オプションの設定はkernel-x86_64-fedora.configでCONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOTをコメントアウトしてsrpmを作ってからビルドしました。そしたら予想通り起動できたので、さらなるデバッグに入ります。
5.4.12のコードを見てたらこんなコードが入っていたんですね。これはregister_tracer()のコードです。
if (security_locked_down(LOCKDOWN_TRACEFS)) { pr_warning("Can not register tracer %s due to lockdown\n", type->name); return -EPERM; }
他にも同じようなチェックがいくつかありました。それでlockdownされているとtracerの初期化しないんだなということかってわかり、初期化されていないのにtrace clockを変更しようとしたからヌルポになるということなんだなと理解しました。
このときは5.4.7ではなくてfedoraのカーネルパッケージのgitでは5.4.12だったのでstable treeの5.4.12を使いました。原因はほぼ掴めたというところなんですが、upstreamで再現するかどうか再度検証します。
まずlockdown機能に関するコンフィグを調べます。lockdownではCONFIG_SECURITY_LOCKDOWN_LSMが最重要の設定でY/Nの2択です。これでYを選べばlockdownの機能が使えます。Yを選択すると更にどのレベルでlockするかという選択があります。選択肢は3個でデフォルトはNoneです。fedoraのカーネルもCONFIG_LOCK_DOWN_KERNEL_FORCE_NONEを選択していました。
fedoraの場合はCONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOTが4個目の選択肢ってところでしょうか。
オプションの内容ですがKconfigによるとCONFIG_LOCK_DOWN_KERNEL_FORCE_NONEは何もしません。CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITYは「Features that allow the kernel to be modified at runtime are disabled.」とあり、CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY「Features that allow the kernel to be modified at runtime or that permit userland code to read confidential material held inside the kernel are disabled」で一番制限が厳しくなっています。
最初にfedoraのコンフィグでmainlineカーネルをビルドしたときはCONFIG_LOCK_DOWN_KERNEL_FORCE_NONEが選択されてたので残りの2パターンを試したところCONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITYを設定したときに再現しましたキタ━━━━(゚∀゚)━━━━!!
lockdownが有効になっているときはtrace_clockを変更できないようにするのが良いだろうと思い、このようなパッチを書きました。
@@ -9420,6 +9420,11 @@ __init static int tracing_set_default_clock(void) { /* sched_clock_stable() is determined in late_initcall */ if (!trace_boot_clock && !sched_clock_stable()) { + if (security_locked_down(LOCKDOWN_TRACEFS)) { + pr_warn("Can not set tracing clock due to lockdown\n"); + return -EPERM; + } + printk(KERN_WARNING "Unstable clock detected, switching default tracing clock to \"global\"\n" "If you want to keep using the local clock, then add:\n"
upstreamのカーネルでも再現できたのでupstreamにパッチを送ります。lkmlをみてたらtracing系はlkmlにccで良さげだったのでccにはlkml、toはscripts/get_maintainer.plで出てきたメンテナの人にしてgit format-patchでパッチを作りgit send-emailで送りました。
パッチを送ってしばらくしたらメンテナの人からmainlineへのpull rquestに自分のパッチが入っていたのでacceptされということがわかり、良かったって感じでしたね。5.5-rc8が出るのかと思ってたけどrc8は出ずに5.5が2020/01/27にでて、これに修正が含まれました。
mainlineに修正が取り込まれたのでそのうち5.4のstable treeにも修正が取り込まれるんじゃないかと思います。
今回のバグは機種依存・カーネルの設定依存な面もありましたが、バグの原因はわかってしまえば簡単だったのと修正も単純な方法で済ますことができて良かったです😊 あとfedora向けの単純なsecure boot用の署名方法がわかったのは良かったかも。
5.5系は5.5から、5.4系は5.4.16からパッチが入った。
5.5リリースメール
5.4.16リリースメール
fedoraなら多分一番手軽だと思う署名方法です。他のディストリビューションはわかりません😭
ディストリビューションは(もちろん) fedoraです。バージョンは31です。実機ではなくてqemuでテストしました。
まずpesignとnss-toolsパッケージをインストールします。pesignパッケージをインストールするとpesignコマンドなどのバイナリの他に/etc/pki/pesign/と/etc/pki/pesign-rh-testにデータベースがインストールされます。今回は/etc/pki/pesign-rh-testにあるものを使います。
まずcertificateファイルを取り出します。
$ sudo certutil -d /etc/pki/pesign-rh-test -L -n "Red Hat Test CA" -r > rhca.cer
次に取り出したcertファイルを登録します。このときにパスワードを聞かれるのでお好きなパスワードを入れてください。
$ sudo mokutil --import ./rhca.cer input password: input password again:
インポートに成功すると次のように確認できます。
$ sudo mokutil --list-new | head -n 10 [key 1] SHA1 Fingerprint: 65:37:ee:ed:f1:ea:fe:d0:a4:cd:d8:91:eb:f2:2b:45:45:41:cd:44 Certificate: Data:
そうしたらここで一旦rebootします。
再起動するとgrubなどは起動せずにuefiのアプリが起動します。
なにか適当にキーを押すとメニューが出るのでEnroll MOKを選択してエンターキーを押します。
View Key 0を選択してエンターキーを押すと鍵の内容が確認できます。
Continueを選ぶと鍵を登録するか聞かれるのでYesを選択してエンターキーを押しましょう。
パスワードを聞かれるので先程入力したパスワードを打ち込みます。
パスワードが正しければメニュー画面に戻ります。Rebootを選択すればシステムが再起動して通常のブートフローになります。
参考文献の手順だとpesignのオプションでエラーになったんですが、fedoraのカーネルパッケージをビルドしたときのログを見てたらコマンドラインオプションがわかりました。
↓がログです。
+ '[' -f arch/x86_64/boot/zImage.stub ']' + _pesign_nssdir=/etc/pki/pesign + '[' 'Red Hat Test Certificate' = 'Red Hat Test Certificate' ']' + _pesign_nssdir=/etc/pki/pesign-rh-test + '[' -x /usr/bin/pesign ']' + '[' x86_64 == x86_64 -o x86_64 == aarch64 ']' + '[' 0 -ge 7 -a -f /usr/bin/rpm-sign ']' ++ id -un ++ uname -m + '[' '%{vendor}' == 'Fedora Project' -a mockbuild == mockbuild -a x86_64 == x86_64 ']' + '[' -S /var/run/pesign/socket ']' + /usr/bin/pesign -c 'Red Hat Test Certificate' --certdir /etc/pki/pesign-rh-test -i arch/x86/boot/bzImage -o vmlinuz.signed -s + '[' '!' -s -o vmlinuz.signed ']' + '[' '!' -s vmlinuz.signed ']'
コマンドはこんな感じになります。-iオプションにbzImageのパスを渡してください。
$ pesign -c 'Red Hat Test Certificate' --certdir /etc/pki/pesign-rh-test -i ./bzImage -o vmlinuz.signed -s
自分の場合はカーネルのmake installまで済ませていて、カーネルはvmlinuz-5.4.8-testとして置いておいたのでvminuz.signedを/boot/vmlinuz-5.4.8-testとしてコピーして完了でした。 あとは再起動してこのカーネルで起動します。
起動してカーネルのバージョン、secure bootが有効なことを確認してすべてOKって感じです🎉
$ uname -a Linux secureboot-test 5.4.8-test #1 SMP Thu Jan 9 00:04:00 JST 2020 x86_64 x86_64 x86_64 GNU/Linux $ dmesg | grep Secure [ 0.008666] Secure boot enabled
deleteオプションで鍵を消せます。再起動すると登録したときと同じようにShimのアプリが起動するのでそこで鍵を削除できます。
$ sudo mokutil --delete ./rhca.cer input password: input password again:
鍵を消すとその鍵で署名したカーネルでは起動できなくなります。
自作モジュールもロードできます。
$ lsmod | grep test_mod test_mod 16384 0
dmesgでも
[ 104.658715] test_mod: loading out-of-tree module taints kernel. [ 104.658741] test_mod: module verification failed: signature and/or required key missing - tainting kernel [ 104.658958] test_mod: install test_mod : insmod
次のブログを参考にしました。
(´-`).。oO(killコマンドを提供するプロジェクト多いなって
とりあえず気付いた範囲で4つ。
上のリストはmanページへのリンクになっているのでオプションの違いとかはそちらを参照してください。busyboxのkillは最小構成って感じなのでオプションは1つしかないですね。-sオプションはbusybox以外は実装してますね。で、-lオプションは全てに存在。まあ、これはシグナル名を表示するので無いと困りますね。
じゃあ、ディストリビューションはどのkillコマンドを採用してるのか?ということで手元にあるディストリビューションを確認したところこんなふうになってました。
ディストリビューション | killコマンドを提供するパッケージ |
---|---|
fedora 31 | util-linux |
centos 8 | util-linux |
arch linux(docker image) | util-linux |
ubuntu 18.04 | procps-ng |
alpine linux(docker image) | busybox |
ソースを見てみます。調べるときに実際に動作させてなくてコードを読んだだけなのでもしかしたら間違ってる可能性もありますが。
kill_verbose()がkill(2)でsignalをプロセスに投げているところのようです。
static int kill_verbose(const struct kill_control *ctl) { int rc = 0; if (ctl->verbose) printf(_("sending signal %d to pid %d\n"), ctl->numsig, ctl->pid); if (ctl->do_pid) { printf("%ld\n", (long) ctl->pid); return 0; } #ifdef HAVE_SIGQUEUE if (ctl->use_sigval) rc = sigqueue(ctl->pid, ctl->numsig, ctl->sigdata); else #endif rc = kill(ctl->pid, ctl->numsig); if (rc < 0) warn(_("sending signal to %s failed"), ctl->arg); return rc; }
if文でctl->do_pidをチェックしているところは-pオプションの処理です。util-linuxのkillはsigqueue(3)が利用できる場合はkill(2)ではなくてsigqueue(3)を使うんですね。sigqueue(3)の場合はデータを付加できるのでできるならこっちを使おうってことですね。これはmanにも書かれていますが-qオプションを使うとプロセスに送るデータを設定できます。
procps-ngの場合はkill_main()がkillを実現する関数っぽいです。 この関数の主要なところはここです。
for (i = 0; i < argc; i++) { pid = strtol_or_err(argv[i], _("failed to parse argument")); if (!kill((pid_t) pid, signo)) continue; error(0, errno, "(%ld)", pid); exitvalue = EXIT_FAILURE; continue; }
こちらはこれといって特殊なことはしてないですね。
coreutisの場合はsend_signals()という関数がkill(2)を呼んでいます。
static int send_signals (int signum, char *const *argv) { int status = EXIT_SUCCESS; char const *arg = *argv; do { char *endp; intmax_t n = (errno = 0, strtoimax (arg, &endp, 10)); pid_t pid = n; if (errno == ERANGE || pid != n || arg == endp || *endp) { error (0, 0, _("%s: invalid process id"), quote (arg)); status = EXIT_FAILURE; } else if (kill (pid, signum) != 0) { error (0, errno, "%s", quote (arg)); status = EXIT_FAILURE; } } while ((arg = *++argv)); return status; }
これも短い関数ですね。こちらもprocps-ng同様にkill(2)を呼ぶだけです。
busyboxの場合はkill_main()という関数でkillの処理を行っています。余談ですがbusyboxのkill.cはprocps/にあるし、procps-ngと同じ感じにしてるんですかね?
#if ENABLE_KILL /* Looks like they want to do a kill. Do that */ while (arg) { # if SH_KILL /* * We need to support shell's "hack formats" of * " -PRGP_ID" (yes, with a leading space) * and " PID1 PID2 PID3" (with degenerate case "") */ while (*arg != '\0') { char *end; if (*arg == ' ') arg++; pid = bb_strtoi(arg, &end, 10); if (errno && (errno != EINVAL || *end != ' ')) { bb_error_msg("invalid number '%s'", arg); errors++; break; } if (kill(pid, signo) != 0) { bb_perror_msg("can't kill pid %d", (int)pid); errors++; } arg = end; /* can only point to ' ' or '\0' now */ } # else /* ENABLE_KILL but !SH_KILL */ pid = bb_strtoi(arg, NULL, 10); if (errno) { bb_error_msg("invalid number '%s'", arg); errors++; } else if (kill(pid, signo) != 0) { bb_perror_msg("can't kill pid %d", (int)pid); errors++; } # endif arg = *++argv; } return errors; #endif
コードにはSH_KILLは通常定義されてるのかどうかわかりませんが、定義の有無にかかわらずkill(2)を使うだけですね。
util-linuxはsigqueue(3)が利用可能ならそれを使い、利用できなければkill(2)を呼ぶという実装になっていますが、それ以外はkill(2)を呼ぶ形でした。
Linuxでは基本的とも言えるkillコマンドですが、このコマンドを提供するパッケージは複数あり、ディストリビューションによってどのパッケージのkillコマンドを使うか選択していました。 また、実装でも微妙な違いがあるということがわかりました( ´ー`)フゥー...
Raspberry Piで学ぶコンピュータアーキテクチャ (Make:PROJECTS)
この記事はLinux Advent Calendar 2019の1日目の記事です。
本記事ではLinuxサーバのホスト名、Linuxカーネルのバージョン、cpuアーキテクチャなどのシステム情報を表示するuname(1)を利用してLinux環境でのデバッグとカーネルハックについて説明していきます。本記事ではコマンドやツールの使い方の説明ではなくて、それらを使ってどのようにデバッグするのかというところを説明します。
ディストリビューションにはFedora 31(x86_64)を利用します。動作環境はQEMUやlibvirt、Oracle VM VirtualBoxなどの仮想環境でも良いですし、物理マシンにインストールしても構いませんが仮想環境を利用したほうが手軽に環境構築できるのでおすすめです。ユーザーはsudoを使えるようにしておいてください。インストール時もしくはインストール後に以下のグループをインストールすると後で楽かもしれません。
パッケージグループのインストールは以下のコマンドで行えます。
[masami@unamebook ~]$ sudo dnf group install -y "C Development Tools and Libraries" "Development Tools" "RPM Development Tools" "System tools" "Guest Agents"
ファイルの編集をするのにエディタが必要です。これは好きなエディタを使ってください。
本記事で行うツールの利用方法などはディストリビューションに依存しないことが多いと思いますが、コマンドの出力結果やソースコードについてはディストリビューション固有のpatchが当たっている場合もあり、fedora 31以外のディストリビューション(場合によってはfedora 31でもパッケージのバージョン違い)と違うことがあります。
uname(1)*1を実行したことのある方は多いと思いますが、まずは普通にuname(1)を実行しましょう。このコマンドはcoreutilsパッケージに含まれています。
[masami@unamebook ~]$ uname -a Linux unamebook 5.3.11-300.fc31.x86_64 #1 SMP Tue Nov 12 19:08:07 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
unameコマンドに渡した-aオプションはすべての情報を表示させるオプションです。上記の出力結果を分解したものが以下の「表1 unameの出力内容」です。
出力内容 | 該当するunameコマンドのオプション | 意味 |
---|---|---|
Linux | -s | カーネル名 |
unamebook | -n | ホスト名 |
5.3.11-300.fc31.x86_64 | -r | カーネルリリース |
#1 SMP Tue Nov 12 19:08:07 UTC 2019 | -v | カーネルバージョン*2 |
x86_64 | -m | マシンのハードウェア名 |
x86_64 | -p | プロセッサ名 |
x86_64 | -i | ハードウェアプラットフォーム |
GNU/Linux | -o | オペレーティングシステム名 |
表1 unameの出力内容
uname(1)を実行してシステム情報を見ることができましたがこの情報はどこから取得したのでしょうか?この情報はLinuxカーネルが持っていて、uname(1)はシステムコールを通じてカーネルに問い合わせを行い、カーネルから受け取った結果を表示しています。このときに利用するシステムコールはuname(2)*3です。uname(2)のプロトタイプは次のようになっています。カーネルは引数で渡されたbuf変数にシステム情報をセットします。
int uname(struct utsname *buf)
uname(2)のインターフェース
[masami@unamebook ~]$ sudo dnf install -y man-pages
strace(1)は、uname(1)の実行時にuname(2)が呼ばれていることを確認しましょう。ここではデバッガは使わずにstrace(1)を利用します。strace(1)は簡単に言うと指定したコマンド・プロセスによるシステムコール呼び出しをトレースするコマンドです。まずはstraceパッケージをインストールします。
[masami@unamebook ~]$ sudo dnf install -y strace
straceパッケージをインストールしたら実行してみましょう。オプションの細かい内容は説明しませんが、ここではuname(2)に絞って表示させるように実行しました。
[masami@unamebook ~]$ strace -v -s 1024 -e trace=uname -C uname -a uname({sysname="Linux", nodename="unamebook", release="5.3.11-300.fc31.x86_64", version="#1 SMP Tue Nov 12 19:08:07 UTC 2019", machine="x86_64", domainname="(none)"}) = 0 uname({sysname="Linux", nodename="unamebook", release="5.3.11-300.fc31.x86_64", version="#1 SMP Tue Nov 12 19:08:07 UTC 2019", machine="x86_64", domainname="(none)"}) = 0 uname({sysname="Linux", nodename="unamebook", release="5.3.11-300.fc31.x86_64", version="#1 SMP Tue Nov 12 19:08:07 UTC 2019", machine="x86_64", domainname="(none)"}) = 0 Linux unamebook 5.3.11-300.fc31.x86_64 #1 SMP Tue Nov 12 19:08:07 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux +++ exited with 0 +++ % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000007 2 3 uname ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000007 3 total
uname(2)の呼び出しが3回ありますね。なぜ3回uname(2)を実行しているのでしょうか?これはソースコードを確認しないとわかりません。unameコマンドを提供しているのはcoreutilsパッケージなのでcoreutilsパッケージのソースコードをダウンロードしましょう。利用しているのはfedoraにインストールされているcoreutilsパッケージなのでupstreamではなくfedoraのソースコードを取得します。
[masami@unamebook ~]$ dnf download --source coreutils
ソースパッケージ(.src.rpm)をダウンロードしたらインストールします。
まず展開先のディレクトリをセットアップします。
[masami@unamebook ~]$ rpmdev-setuptree
そしてrpmコマンドでインストールします。
[masami@unamebook ~]$ rpm -i coreutils-8.31-6.fc31.src.rpm
インストール時に次のようなwarningが出ますが無視して大丈夫です。
warning: user mockbuild does not exist - using root warning: user mockbuild does not exist - using root
これで~/rpmbuildにソースパッケージがインストールされましたが、ソースコードはまだ展開されていないでソースコードの展開&patchを当てましょう。と、その前にいくつか必要なパッケージをインストールします。
[masami@unamebook ~]$ sudo dnf builddep -y coreutils
これで大丈夫です。ではソースコードの準備をしましょう。
[masami@unamebook ~]$ cd rpmbuild/SPECS/ [masami@unamebook SPECS]$ rpmbuild -bp coreutils.spec
ソースコードを展開しpatchが適用されたソースコードは~/rpmbuild/BUILDにあります。
[masami@unamebook SPECS]$ cd ~/rpmbuild/BUILD/coreutils-8.31/ [masami@unamebook coreutils-8.31]$ ls ABOUT-NLS autom4te.cache build-aux configure DIR_COLORS dist-check.mk GNUmakefile lib Makefile.am NEWS src thanks-gen THANKStt.in aclocal.m4 bootstrap cfg.mk configure.ac DIR_COLORS.256color doc init.cfg m4 Makefile.in po tests THANKS.in TODO AUTHORS bootstrap.conf ChangeLog COPYING DIR_COLORS.lightbgcolor gnulib-tests INSTALL maint.mk man README THANKS THANKS-to-translators
uname(1)のソースコードはsrc/uname.cです。grepでuname(2)の呼び出し箇所を調べると3箇所あることがわかります。
[masami@unamebook coreutils-8.31]$ grep -n "uname *(" src/uname.c 286: if (uname (&name) == -1) 313: uname(&u); 364: uname(&u);
286行目付近を見るとこのようになっています。toprint変数はuname(1)の引数に応じてビットが立ちます。オプションに-aを渡した場合はこのif文は真になるので286行目のuname(2)が実行されます。
280 if (toprint 281 & (PRINT_KERNEL_NAME | PRINT_NODENAME | PRINT_KERNEL_RELEASE 282 | PRINT_KERNEL_VERSION | PRINT_MACHINE)) 283 { 284 struct utsname name; 285 286 if (uname (&name) == -1) 287 die (EXIT_FAILURE, errno, _("cannot get system name"));
313行目はどうかというと、ここは-pオプションの処理部分です。マクロのelseブロックが実行されるようです。
304 #if HAVE_SYSINFO && defined SI_ARCHITECTURE 305 { 306 static char processor[257]; 307 if (0 <= sysinfo (SI_ARCHITECTURE, processor, sizeof processor)) 308 element = processor; 309 } 310 #else 311 { 312 static struct utsname u; 313 uname(&u); 314 element = u.machine; 315 }
残りの364行目も確認しましょう。こちらは-iオプションのハードウェアプラットフォーム取得部分です。こちらもマクロのelseブロックが呼ばれているようです。
354 #if HAVE_SYSINFO && defined SI_PLATFORM 355 { 356 static char hardware_platform[257]; 357 if (0 <= sysinfo (SI_PLATFORM, 358 hardware_platform, sizeof hardware_platform)) 359 element = hardware_platform; 360 } 361 #else 362 { 363 static struct utsname u; 364 uname(&u); 365 element = u.machine; 366 if(strlen(element)==4 && element[0]=='i' && element[2]=='8' && element[3]=='6') 367 element[1]='3'; 368 } 369 #endif
この3箇所のuname(2)の呼び出しですが、286行目はupstreamのコードにもあります。しかし、残りの2箇所はupstreamのコードには存在しません。この2箇所はfedoraのcoreutilsパッケージが独自当てているpatchです。このpatchは~/rpmbuild/SOURCES/coreutils-8.2-uname-processortype.patchです。興味のある方は確認してみてください。
ソースコードを読んでuname(2)の呼び出し箇所が3箇所あることはわかったのですが、一応実際の動作を見てみましょう。ここではgdbを利用します。gdbを使ってデバッグをする場合はデバッグ情報があると便利です。通常のパッケージに含まれるバイナリファイルはデバッグ情報が存在しないため、デバッグが不便です。fedoraの場合はデバッグ情報はdebuginfoパッケージとして存在しているのでこれをインストールします。glibcのdebuginfoパッケージも合わせてインストールします。
と、その前にdebuginfo-installコマンドが必要なのでパッケージをインストールしましょう。
[masami@unamebook ~]$ sudo dnf install -y dnf-utils
dnf-utilsパッケージをインストールしたらdebuginfo-installコマンドが利用できますので、これでdebuginfoのパッケージをインストールします。
[masami@unamebook ~]$ sudo debuginfo-install -y coreutils glibc
debuginfoパッケージをインストールしたらgdbを起動しましょう。gdbは自動的にデバッグ情報を読み込んでくれます。
[masami@unamebook ~]$ gdb -q /usr/bin/uname Reading symbols from /usr/bin/uname... Reading symbols from /usr/lib/debug/usr/bin/uname-8.31-6.fc31.x86_64.debug... (gdb)
gdbが立ち上がったらとりあえず280行目のif文あたりにブレークポイントを張りましょう。listコマンドで280行目付近を表示します。
(gdb) list 280 275 toprint = decode_switches (argc, argv); 276 277 if (toprint == 0) 278 toprint = PRINT_KERNEL_NAME; 279 280 if (toprint 281 & (PRINT_KERNEL_NAME | PRINT_NODENAME | PRINT_KERNEL_RELEASE 282 | PRINT_KERNEL_VERSION | PRINT_MACHINE)) 283 { 284 struct utsname name;
breakコマンド(略性はb)で280行目にブレークポイントをセットします。
(gdb) b 280 Breakpoint 1 at 0x27b4: /usr/src/debug/coreutils-8.31-6.fc31.x86_64/separate/../src/uname.c:280. (2 locations)
runコマンド(略称はr)でunameコマンドを実行します。uname(1)のオプションには-aを渡しています。unameコマンドを実行すると先程設定したブレークポイントで止まるので、あとはnでステップ実行していけばuname(2)の呼び出しが3回行われるのがわかります。
(gdb) r -a Starting program: /usr/bin/uname -a Breakpoint 1, main (argc=2, argv=0x7fffffffe448) at ../src/uname.c:280 280 if (toprint (gdb) n 286 if (uname (&name) == -1) (gdb) n ~略~ 301 if (toprint & PRINT_PROCESSOR) (gdb) 313 uname(&u); (gdb) n ~略~ (gdb) n 364 uname(&u); (gdb) c Continuing. Linux unamebook 5.3.11-300.fc31.x86_64 #1 SMP Tue Nov 12 19:08:07 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux [Inferior 1 (process 6522) exited normally]
今回はプログラムが単純で短いのでステップ実行で済ませています。 ここまででstrace(1)、ソースコードリーディング、gdb(1)を使いuname -aの実行時にuname(2)が3回呼ばれる謎を調べることに成功しました。
bpftraceはLinuxのeBPFを使用したトレースツールです。eBFPとはextended Berkeley Packet Filterの略で元来はパケットフィルタの機能ですがLinuxではこのパケットフィルタ機能が進化し、パケットフィルタだけでは収まらなくなった機能です。Linuxではbpf(2)があり、c言語でeBPFの機能を使ったプログラムを作成できますが、これは大変なので普通はBCCを使うことが多いのではないでしょうか。BCCはBPF Compiler Collectionの略でPythonとc言語を使ってeBFPを利用するプログラムを作ることが出来ます。そしてbpftraceですがBCCをバックエンドとして使った高レベルのトレース用言語と説明されています。bpftraceではカーネルレベルのトレースを取ることが出来ます。Fedoraにはパッケージがあるのでインストールしましょう。
[masami@unamebook ~]$ sudo dnf install -y bpftrace
インストールが完了したらuname(2)のトレースを行ってみましょう。まずはシステムコールの呼び出しをトレースします。 この他に、カーネルのソースも読みたいのでこちらもインストールしましょう。今回はdebuginfo-installコマンドを使用してデバッグ情報とソースをインストールします。coreutilsのときは単にソースを取得したいだけだったのでsrpmパッケージをインストールしましたが、debuginfoパッケージの場合はデバッグ情報とソースコードをインストールできます。gdb(1)の章でcoreutilsとglibcのdebuginfoパッケージをインストールしましたが、このときにソースコードもインストールされいます。じゃあ、この2つの使い分けは?という疑問があると思いますが、ソースを変更して再ビルドする用途にはsrpmパッケージを、デバッグ情報のみが必要な場合はdebuginfoパッケージを使用すれば良いと思います。では、カーネルのdebuginfoパッケージをインストールします。
[masami@unamebook ~]$ sudo debuginfo-install -y kernel-debuginfo-common-x86_64
debuginfoパッケージに含まれるソースコードは/usr/src/debug以下にインストールされます。
[masami@unamebook ~]$ ls /usr/src/debug/ coreutils-8.31-6.fc31.x86_64 glibc-2.30-13-g919af705ee kernel-5.3.fc31
インストールできたらカーネル側のuname(2)を処理する関数を探します。システムコールはinclude/linux/syscalls.hで探すことが出来ます。カーネルのソースコードは以下の場所にあります。
[masami@unamebook ~]$ cd /usr/src/debug/kernel-5.3.fc31/linux-5.3.11-300.fc31.x86_64/
[masami@unamebook linux-5.3.11-300.fc31.x86_64]$ grep uname include/linux/syscalls.h asmlinkage long sys_newuname(struct new_utsname __user *name); asmlinkage long sys_memfd_create(const char __user *uname_ptr, unsigned int flags); asmlinkage long sys_uname(struct old_utsname __user *); asmlinkage long sys_olduname(struct oldold_utsname __user *);
いくつか見つかりますね。sys_のprefixはシステムコールを表しています。名前からsys_newuname、sys_unameのどちらかだろうと想像できますね。ファイルを実際に読んで確認しましょう。まずはそのままな名前のsys_unameを見てみます。
/* obsolete: kernel/sys.c */ asmlinkage long sys_gethostname(char __user *name, int len); asmlinkage long sys_uname(struct old_utsname __user *); asmlinkage long sys_olduname(struct oldold_utsname __user *);
obsoleteってコメントがありますね。こちらではなさそうです。ではsys_newunameはどうでしょうか。こちらは特にコメントもありません。uname(2)の実行時はsys_newuname()が実行されるようですね。
asmlinkage long sys_setpriority(int which, int who, int niceval); ~略~ asmlinkage long sys_newuname(struct new_utsname __user *name);
コメントにkernel/sys.cとあるので実装はこのファイルにあるようです。では、このファイルも見てみましょう。SYSCALL_DEFINE1というのはシステムコールの関数に使われるマクロです。SYSCALL_DEFINE1の1は引数を一つ受け取るという意味です。実装は比較的単純ですね。
SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name) { struct new_utsname tmp; down_read(&uts_sem); memcpy(&tmp, utsname(), sizeof(tmp)); up_read(&uts_sem); if (copy_to_user(name, &tmp, sizeof(tmp))) return -EFAULT; if (override_release(name->release, sizeof(name->release))) return -EFAULT; if (override_architecture(name)) return -EFAULT; return 0; }
ここまででユーザー空間でuname(2)を実行するとカーネル空間のsys_newuname()が実行されることがわかりました。前置きが長くなりましたが実際にbfptrace(1)を使って見ましょう。まずは単純にsys_newutsname()の実行をトレースします。この関数が実行されたときに呼び出し元のコマンド名を表示させるのが以下のコマンドラインです。
[masami@unamebook ~]$ sudo bpftrace -e 'tracepoint:syscalls:sys_enter_newuname { printf("%s\n", comm); }'
コマンドを入力してちょっとすると次のようなメッセージが表示されます。終了したい場合はCtrl-Cで終了できます。
Attaching 1 probe...
メッセージが表示されたら別の端末からuname -aを実行すると以下のようにunameが3回表示されます。uname -aを実行するとuname(2)が3回実行されるというのはstrace(1)の章で調べましたね。
[masami@unamebook ~]$ sudo bpftrace -e 'tracepoint:syscalls:sys_enter_newuname { printf("%s\n", comm); }' Attaching 1 probe... uname uname uname ^C
systemtapはbpftrace同様にカーネルのトレーシングを行うことができるツールです。systemtapを応用するとトレース以上のこともできます。まずはインストールしましょう。
[masami@unamebook ~]$ sudo dnf install -y systemtap
systemtapはstap(1)がコマンドラインのプログラムとなります。systemtapは専用のスクリプト言語があり、c言語のコードを埋め込むこともできます。本章ではsystemtapを使ってsys_newutsname()の挙動を変更してみましょう。uname(1)で表示するノード名などはutsname()から取得しています。utsname()の戻り値をtmp変数に一旦保存し、それをcopy_to_user()でユーザー空間のプログラムから渡されたnameにコピーします。
SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name) { struct new_utsname tmp; down_read(&uts_sem); memcpy(&tmp, utsname(), sizeof(tmp)); up_read(&uts_sem); if (copy_to_user(name, &tmp, sizeof(tmp))) return -EFAULT; if (override_release(name->release, sizeof(name->release))) return -EFAULT; if (override_architecture(name)) return -EFAULT; return 0; }
では、systemtapを使ってプログラムにnodenameを書き換えてみましょう。systemtapを利用したライブパッチ機能の実装という感じです。systemtapのプログラムは次のようになります。
#!/usr/bin/env stap %{ #include <linux/string.h> #include <linux/uaccess.h> #include <uapi/linux/utsname.h> %} function set_dummy_uname:long(name:long) %{ struct new_utsname u = { 0 }; if (copy_from_user(&u, (struct new_utsname *) STAP_ARG_name, sizeof(u))) STAP_RETURN(-EFAULT); strcpy(u.nodename, "livepatched"); if (copy_to_user((struct new_utsname *) STAP_ARG_name, &u, sizeof(u))) STAP_RETURN(-EFAULT); STAP_RETURN(0); %} probe begin { printf("start livepatch code. Ctrl-C to stop\n") } probe kernel.function("__do_sys_newuname").return { // Only set dummy nodename to test program if (execname() == "uname-test") { // Use @entry() to get name parameter in return probe. set_dummy_uname(@entry($name)) } } probe end { printf("Done\n") }
このプログラムではシステムコールのreturn時に処理を入れています。まずシステムコールを呼び出したプログラム名を調べ、テストプログラムでなければ特に変更は行わないで終了します。nodenameを設定しているのはset_dummy_uname()で呼び出し時にシステムコールのパラメータとして渡されたname変数をset_dummy_uname()の引数として渡しています。システムコールを呼び出したのがテストプログラムならnodenameをlivepatchedに変更します。systamtapスクリプトの特徴として、自分で実装した関数の引数にアクセスする場合はSTAP_ARG_変数名でアクセスします。このスクリプトではnameという変数名で引数を受け取っているためSTAP_ARG_nameと書いています。
ここで使用するテストプログラムですが以下のような簡単なものです。
#include <stdio.h> #include <sys/utsname.h> int main(int argc, char **argv) { struct utsname name = { 0 }; if (uname(&name)) { perror("uname"); return -1; } printf("nodename: %s\n", name.nodename); return 0; }
このcコードをコンパイルしましょう。
[masami@unamebook ~]$ gcc uname-test.c -o uname-test
では、このsystemtapのスクリプトを実行しましょう。ここでも端末を2つ使います。1つはuname-testプログラムを実行します。もう1つは次のようにsystemtapのスクリプトを実行します。start livepatch ~と出たら準備完了です。
[masami@unamebook ~]$ sudo stap -g uname_livepatch.stp start livepatch code. Ctrl-C to stop
別の端末でテストします。
[masami@unamebook ~]$ uname -n unamebook [masami@unamebook ~]$ ./uname-test nodename: livepatched [masami@unamebook ~]$ uname -n unamebook [masami@unamebook ~]$
次にsystemtapのプログラムをCtrl-Cで止めてuname-testを実行してみます。systemtapのスクリプトを終了したことでsys_newuname()の挙動がもとに戻ったことでuname(2)の戻り値がもとに戻りましたね。
[masami@unamebook ~]$ ./uname-test nodename: unamebook
前章のsystemtapではsystemtapを利用してカーネルの挙動を変えてみました。本章ではカーネルのソースコードを変更し、前章と同様の挙動に変えてみます。カーネルのソースパッケージにパッチを当てるにはgit-am(1)で当てることができるパッチを作る必要があります。パッチの作り方としてメインラインのカーネルを変更してパッチをつくるかFedoraのカーネルソースを変更するの2パターンがあります。FedoraのカーネルにはFedoraプロジェクトが適用したパッチも含まれているため、メインラインのカーネルを変更した場合、変更内容によってはFedoraのカーネルソースとコンフリクトが発生する可能性もあります。そこで本章ではFedoraのカーネルソースを変更してパッチを作ります。まずはカーネルのソースパッケージをダウンロードしてインストールします。以前coreutilsのソースパッケージをインストールしているので綺麗なrpmbuildディレクトリを作りたいところです。よって、既存のrpmbuildディレクトリはリネームして置いておき、新たにrpmbuildディレクトリを作成します。そしてソースパッケージをダウンロードしてインストールします。
[masami@unamebook ~]$ dnf download --source kernel [masami@unamebook ~]$ rpm -i kernel-5.3.11-300.fc31.src.rpm
カーネルパッケージのビルドに必要な依存パッケージのインストールを行います。
[masami@unamebook ~]$ sudo dnf builddep -y kernel [masami@unamebook ~]$ sudo dnf install -y pesign
これでカーネルソースパッケージをビルドする準備は整ったのでですが、specファイルにあるbuildidを設定してFedoraのrpmと区別できるようにします。お好みのエディタでkernel.specを開き# define buildid .localの下に%define buildid .unametestを追加します。
# define buildid .local %define buildid .unametest
この段階ではまだ~/rpmbuild/SOURCESにカーネルのコードやパッチが置かれているだけなのでソースコードを展開します。
[masami@unamebook SPECS]$ rpmbuild -bp kernel.spec
展開に成功すると~/rpmbuild/BUILD/kernel-5.3.fc31/linux-5.3.11-300.unametest.fc31.x86_64/にパッチが当てられたカーネルのコードが置かれます。また通常のカーネルソースツリーとは違いconfigsディレクトリが存在します。このディレクトリにはfedoraの各アーキテクチャ向けのconfigファイルが存在します。rpmbuildではなく、ローカル環境でビルドを試す際はこのディレクトリにあるconfigを使用してmake oldconfigを行うか、使用しているカーネルのコンフィグファイルが/bootにあるのでそれをコピーして使いましょう。 では、ここからはカーネルのソースコードを弄っていきます。まずは~/rpmbuild/BUILD/kernel-5.3.fc31に移動し、 linux-5.3.11-300.unametest.fc31.x86_64をコピーして作業ディレクトリを作ります。コピー先の名前は任意です。ここでは linux-5.3.11-300.unametest.fc31.x86_64.hackとしました。
[masami@unamebook kernel-5.3.fc31]$ cp -a linux-5.3.11-300.unametest.fc31.x86_64 linux-5.3.11-300.unametest.fc31.x86_64.hack
一旦ソースをいじっていない状態でビルドができることを確認しましょう。この段階でビルドに成功することを確認しておけば、ソースの変更後にビルドが通らなくなった場合の問題の切り分けに役立ちます。この段階でgitのブランチも作っておきます。 linux-5.3.11-300.unametest.fc31.x86_64には.gitディレクトリがあり、gitがすでに利用できるようになっています。
[masami@unamebook kernel-5.3.fc31]$ cd linux-5.3.11-300.unametest.fc31.x86_64.hack/ [masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ git checkout -b uname-hack [masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ cp configs/kernel-5.3.11-x86_64.config .config [masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ make oldconfig scripts/kconfig/conf --oldconfig Kconfig # # configuration written to .config #
make oldconfigは特に問題なく完了すると思います。では、カーネルとカーネルモジュールをビルドします。bzImageで圧縮されたカーネルのイメージをビルドし、modulesでカーネルモジュールをビルドしています。-j$(nproc)は並列ビルドのオプションです。ログインしている環境で利用可能なcpu数をもとにビルドを並列実行します。カーネルのビルドは最後にarch/x86/boot/bzImage is readyというメッセージが出れば成功です。rpmのソースパッケージを使ってるのでrpmbuildでビルドしても良いのですが、それだと時間がかかるのでまずは手軽にmakeでビルドしています。
[masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ make -j$(nproc) bzImage ~略~ BUILD arch/x86/boot/bzImage Setup is 17692 bytes (padded to 17920 bytes). System is 9085 kB CRC dde74ef3 Kernel: arch/x86/boot/bzImage is ready (#1)
モジュールのビルドも実行します。モジュールのビルドは成功時にこれといったログはありません。次のようにしれっとプロンプトが表示されていたら成功しています。
[masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ make -j$(nproc) modules ~略~ LD [M] sound/x86/snd-hdmi-lpe-audio.ko LD [M] virt/lib/irqbypass.ko
さて、カーネルとモジュールのビルドに成功したら準備は完了です。実際にカーネルのソースに手を加えましょう。変更するのはkernel/sys.cです。カレントプロセス名を調べてプロセス名がuname-testならnodenameを変更します。やっていることはsystemtapの場合と同様です。変更内容は次のようになります。
diff --git a/kernel/sys.c b/kernel/sys.c index 2969304..908ce7f 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -1243,6 +1243,10 @@ SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name) down_read(&uts_sem); memcpy(&tmp, utsname(), sizeof(tmp)); up_read(&uts_sem); + + if (!strcmp(current->comm, "uname-test")) + strcpy(tmp.nodename, "livepatched"); + if (copy_to_user(name, &tmp, sizeof(tmp))) return -EFAULT;
systemtapの場合は関数の途中にコードを入れていないので関数から返るところに処理を入れ込みましたが、今回は直接カーネルのコードを弄っているので素直なコードの変更になってますね。変更したらカーネルがビルドできるか確認しましょう。ビルド方法は先ほどと同じです。
[masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ make -j$(nproc) bzImage ~略~ BUILD arch/x86/boot/bzImage Setup is 17692 bytes (padded to 17920 bytes). System is 9089 kB CRC 27223b7c Kernel: arch/x86/boot/bzImage is ready (#2)
今回も成功したらarch/x86/boot/bzImage is readyというメッセージが出ます。このメッセージの最後にある#2というのはこのカーネルをビルドしたのが2回目という意味です。ビルドに成功したのでパッチを作成しましょう。まず最初にgitのメールアドレスと名前を変更します。これはFedoraのカーネルの場合、デフォルトではFedoraのカーネルチームのメールアドレスが使われるので上書きして自分のメールアドレスと名前に変更します。そうしたら変更をコミットし、git-format-patch(1)でパッチを作ります。出来たパッチはカレントディレクトリに置かれます。
[masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ git config --local --add user.email <your email> [masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ git config --local --add user.name "your name" [masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ git add kernel/sys.c [masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ git commit -m "uname: add test code" [uname-hack 9ea2b59] uname: add test code 1 file changed, 4 insertions(+) [masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ git format-patch -s master 0001-uname-add-test-code.patch [masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$
ここで出来たパッチは次のようになります。コミットメッセージ、変更内容、メール送信用のサブジェクトなどが設定されています。最後の2.23.0というのはgitのバージョンです。
From 9ea2b595c1c246a7ff94e6c4c721896ffcf9f5a6 Mon Sep 17 00:00:00 2001 From: Your Name <your email> Date: Mon, 25 Nov 2019 23:26:46 +0900 Subject: [PATCH] uname: add test code Signed-off-by:Your Name <your email> --- kernel/sys.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/sys.c b/kernel/sys.c index 2969304..908ce7f 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -1243,6 +1243,10 @@ SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name) down_read(&uts_sem); memcpy(&tmp, utsname(), sizeof(tmp)); up_read(&uts_sem); + + if (!strcmp(current->comm, "uname-test")) + strcpy(tmp.nodename, "livepatched"); + if (copy_to_user(name, &tmp, sizeof(tmp))) return -EFAULT; -- 2.23.0
では、このパッチをFedoraのカーネルソースパッケージに追加してrpmbuild(8)でビルドしましょう。パッチを~/rpmbuild/SOURCESディレクトリにコピーします。
[masami@unamebook linux-5.3.11-300.unametest.fc31.x86_64.hack]$ cp 0001-uname-add-test-code.patch ../../../SOURCES/.
そして~/rpmbuild/SPECSに移動します。kernel.specファイルをエディタで開き、# END OF PATCH DEFINITIONSの行まで移動します。PatchXXX(Xは数字)の行がありますが、この数字は任意です。ここでは999としました。次のように# END OF PATCH DEFINITIONSの上に2行を追加します。
# uname hack patch Patch999: 0001-uname-add-test-code.patch # END OF PATCH DEFINITIONS
そうしたらビルドしましょう。ここでは必要最小限のカーネルパッケージだけ作成します。次のようなコマンドライン引数でビルドを行います。
[masami@unamebook SPECS]$ rpmbuild -bb --with baseonly --without debuginfo --without debug --target=$(uname -m) kernel.spec ~略~ Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.0BA8HX + umask 022 + cd /home/masami/rpmbuild/BUILD + cd kernel-5.3.fc31 + /usr/bin/rm -rf /home/masami/rpmbuild/BUILDROOT/kernel-5.3.11-300.unametest.fc31.x86_64 + RPM_EC=0 ++ jobs -p + exit 0
出来上がったrpmファイルは~ rpmbuild/RPMS/x86_64/に置かれます。
[masami@unamebook x86_64]$ ls kernel-5.3.11-300.unametest.fc31.x86_64.rpm kernel-devel-5.3.11-300.unametest.fc31.x86_64.rpm kernel-modules-extra-5.3.11-300.unametest.fc31.x86_64.rpm kernel-core-5.3.11-300.unametest.fc31.x86_64.rpm kernel-modules-5.3.11-300.unametest.fc31.x86_64.rpm
出来上がったrpmをインストールします。
[masami@unamebook x86_64]$ sudo dnf localinstall -y ./*.rpm
インストールしたらカーネルの優先順を確認しましょう。通常はインストールしたカーネルで起動できると思いますが、出来なかった場合はここで調べたindexを使ってgrub2-rebootコマンドで起動するカーネルを設定してリブートします。
[masami@unamebook ~]$ sudo grubby --info=ALL index=0 kernel="/boot/vmlinuz-5.3.11-300.unametest.fc31.x86_64" args="ro resume=UUID=c98315dc-7144-427b-a6a0-2aad53d8ebc5 rhgb quiet" root="UUID=0b584087-de7b-42f8-8dea-d3b679d22613" initrd="/boot/initramfs-5.3.11-300.unametest.fc31.x86_64.img" title="Fedora (5.3.11-300.unametest.fc31.x86_64) 31 (Thirty One)" id="24b7d4741c7c4982889ac4d4acb27ae1-5.3.11-300.unametest.fc31.x86_64"
再起動したらuname(1)でカーネルを確認しましょう。unametestが入っているのでビルドしたカーネルで正しく起動したかわかりますね。
[masami@unamebook ~]$ uname -a Linux unamebook 5.3.11-300.unametest.fc31.x86_64 #1 SMP Mon Nov 25 23:32:51 JST 2019 x86_64 x86_64 x86_64 GNU/Linux
そして、systemtapのときにも使ったuname-testを実行してみましょう。
[masami@unamebook ~]$ ./uname-test nodename: livepatched
nodenameが変わりましたね。ここでuname-testをuname-test2としてコピーして実行すると、オリジナルのnodenameがでましたね。ということでカーネルに手を入れてuname(2)の挙動を変更することにも成功しました🎉
[masami@unamebook ~]$ cp uname-test uname-test2 [masami@unamebook ~]$ ./uname-test2 nodename: unamebook
systemtapの章ではsystemtapを利用してカーネルの挙動を変えてみましたが、LinuxカーネルにはLivepatchという機能があり、実行中のカーネルの挙動を再起動なしに変更する(patchを当てる)ことができる機能があります。本章ではこの機能でunameシステムコールの挙動を変更してみます。ただしFedoraのカーネルではLivepatch機能は有効になっていないためカーネルのソースパッケージよりLivepatchを有効にしたカーネルを作る必要があります。カーネルのソースは前章でダウンロードしてきてあるのでこれに対してコードを弄っていきましょう。ここで本当はunameの挙動を変更するのがベターなところですが、unameの実装をLivepatchするのはちょっと手間なので単純に弄ることができるgetpid(2)を使います。
まず~/rpmbuild/SOURCESに移動し、Livepatchの機能を有効にする設定を追加します。
[masami@unamebook ~]$ cd rpmbuild/SOURCES/ [masami@unamebook SOURCES]$ echo "CONFIG_LIVEPATCH=y" >> kernel-local [masami@unamebook SOURCES]$ echo "CONFIG_TEST_LIVEPATCH=m" >> kernel-local
kernel-localファイルはこのようになります。
[masami@unamebook SOURCES]$ cat kernel-local # This file is intentionally left empty in the stock kernel. Its a nicety # added for those wanting to do custom rebuilds with altered config opts. CONFIG_LIVEPATCH=y CONFIG_TEST_LIVEPATCH=m
Fedoraではkernel-localファイルにカーネルのコンフィギュレーションを設定することでベースとなる設定ファイル(x86_64ならkernel-x86_64.config)に設定を追加できます。 設定を追加したら~/rpmbuild/SPECに移動し依存パッケージのインストールを行います。kernel-localファイルを作ったらあとはビルドするだけです。
[masami@unamebook SOURCES]$ cd ~/rpmbuild/SPECS/
先程はunametestという名称をカーネルパッケージに追加しましたが、今度はgetpidtestという名称を付けましょう。kernel.specを以下のように変更します。
# define buildid .local %define buildid .getpidtest
specファイルを変更したらカーネルをビルドします。
[masami@unamebook SPECS]$ rpmbuild -bb --with baseonly --without debuginfo --without debug --target=$(uname -m) kernel.spec ~略~ + cd kernel-5.3.fc31 + /usr/bin/rm -rf /home/masami/rpmbuild/BUILDROOT/kernel-5.3.11-300.getpidtest.fc31.x86_64 + RPM_EC=0 ++ jobs -p + exit 0
ビルドが終了したら~/rpmbuild/RPMS/x86_64に移動してlsするとgetpidtestと名前の付いたrpmが出来ているのが確認できます。
[masami@unamebook x86_64]$ ls kernel-5.3.11-300.getpidtest.fc31.x86_64.rpm kernel-core-5.3.11-300.unametest.fc31.x86_64.rpm kernel-modules-5.3.11-300.getpidtest.fc31.x86_64.rpm kernel-modules-extra-5.3.11-300.unametest.fc31.x86_64.rpm kernel-5.3.11-300.unametest.fc31.x86_64.rpm kernel-devel-5.3.11-300.getpidtest.fc31.x86_64.rpm kernel-modules-5.3.11-300.unametest.fc31.x86_64.rpm kernel-core-5.3.11-300.getpidtest.fc31.x86_64.rpm kernel-devel-5.3.11-300.unametest.fc31.x86_64.rpm kernel-modules-extra-5.3.11-300.getpidtest.fc31.x86_64.rpm
新しくビルドしたrpmをインストールします。
[masami@unamebook x86_64]$ sudo dnf localinstall -y kernel-*.getpidtest.*
次にgrubのエントリを確認します。
[masami@unamebook x86_64]$ sudo grubby --info=ALL | grep index -A1 index=0 kernel="/boot/vmlinuz-5.3.11-300.unametest.fc31.x86_64" -- index=1 kernel="/boot/vmlinuz-5.3.11-300.getpidtest.fc31.x86_64" -- index=2 kernel="/boot/vmlinuz-5.3.11-300.fc31.x86_64" -- index=3 kernel="/vmlinuz-0-rescue-24b7d4741c7c4982889ac4d4acb27ae1"
getpidtestのカーネルはindexが1となっているのでこのカーネルで起動するようにします。
[masami@unamebook x86_64]$ sudo grub2-reboot 1
これで準備完了なので再起動します。再起動したらもちろんuname(1)で正しいカーネルで起動したか確認します😄
[masami@unamebook ~]$ uname -a Linux unamebook 5.3.11-300.getpidtest.fc31.x86_64 #1 SMP Tue Nov 26 23:10:42 JST 2019 x86_64 x86_64 x86_64 GNU/Linux
カーネルビルド時にライブパッチ機能が有効になっていたか確認します。fedoraは/bootにカーネルのコンフィグが置かれるのでこれで確認できます。
[masami@unamebook ~]$ grep CONFIG_LIVEPATCH /boot/config-5.3.11-300.getpidtest.fc31.x86_64 CONFIG_LIVEPATCH=y
ではライブパッチを作りましょう。カーネルのライブパッチはカーネルモジュールとして作成します。まず作業ディレクトリを作ります。
[masami@unamebook ~]$ mkdir getpid-livepatch-module
そして、getpid-livepatch.cを以下の内容で作成します。your nameのところは適当に変えてください。
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> #include <linux/kernel.h> #include <linux/livepatch.h> #include <linux/string.h> #include <linux/sched.h> MODULE_DESCRIPTION("getpid livepatch test module"); MODULE_AUTHOR("your name"); MODULE_LICENSE("GPL"); MODULE_INFO(livepatch, "Y"); asmlinkage long livepatch_getpid(void) { pr_info("%s\n", __func__); if (unlikely(!strcmp(current->comm, "getpid-test"))) return 1; return task_tgid_vnr(current); } static struct klp_func funcs[] = { { .old_name = "__x64_sys_getpid", .new_func = livepatch_getpid, }, {} }; static struct klp_object objs[] = { { .funcs = funcs, }, {} }; static struct klp_patch patch = { .mod = THIS_MODULE, .objs = objs, }; static int getpid_livepatch_init(void) { int ret = 0; ret = klp_enable_patch(&patch); if (ret) { pr_info("failed to enable getpid live patch\n"); return ret; } pr_info("getpid livepatch test module is enabled\n"); return ret; } static void getpid_livepatch_cleanup(void) { } module_init(getpid_livepatch_init); module_exit(getpid_livepatch_cleanup);
このライブパッチではgetpid(2)を実行したプログラム名がgetpid-testの場合に1を返し、それ以外の場合は普通にpidを返しています。このファイルをビルドするのにMakefileが必要ですのでこれも作成します。Makefileのインデントはtabなんで気をつけてください。
KERNDIR := /lib/modules/`uname -r`/build BUILD_DIR := $(shell pwd) VERBOSE = 0 obj-m := getpid-livepatch.o smallmod-objs := getpid-livepatch.o all: make -C $(KERNDIR) M=$(BUILD_DIR) KBUILD_VERBOSE=$(VERBOSE) modules clean: rm -f *.o rm -f *.ko rm -f *.mod.c rm -f *~
ビルドします。ビルドに成功するとgetpid-livepatch.koというファイルができます。
[masami@unamebook getpid-livepatch-module]$ make make -C /lib/modules/`uname -r`/build M=/home/masami/getpid-livepatch-module KBUILD_VERBOSE=0 modules make[1]: Entering directory '/usr/src/kernels/5.3.11-300.getpidtest.fc31.x86_64' CC [M] /home/masami/getpid-livepatch-module/getpid-livepatch.o Building modules, stage 2. MODPOST 1 modules CC /home/masami/getpid-livepatch-module/getpid-livepatch.mod.o LD [M] /home/masami/getpid-livepatch-module/getpid-livepatch.ko make[1]: Leaving directory '/usr/src/kernels/5.3.11-300.getpidtest.fc31.x86_64'
次にテストプログラムを用意しましょう。getpid-test.cという名前でファイルを作り、以下の内容で保存します。
#include <sys/types.h> #include <unistd.h> #include <stdio.h> int main(int argc, char **argv) { printf("pid is %d\n", getpid()); return 0; }
ビルドします。
[masami@unamebook getpid-livepatch-module]$ gcc getpid-test.c -o getpid-test
実行すると以下のようにpidが表示されます。
[masami@unamebook getpid-livepatch-module]$ ./getpid-test pid is 1435
では、ライブパッチのモジュールをロードします。
[masami@unamebook getpid-livepatch-module]$ sudo insmod ./getpid-livepatch.ko
dmesgコマンドでカーネルのログを見ると以下のような行があるのが確認できます。
[ 959.659727] getpid_livepatch: loading out-of-tree module taints kernel. [ 959.659729] getpid_livepatch: tainting kernel with TAINT_LIVEPATCH [ 959.659787] getpid_livepatch: module verification failed: signature and/or required key missing - tainting kernel [ 959.668116] livepatch: enabling patch 'getpid_livepatch' [ 959.670655] livepatch: 'getpid_livepatch': starting patching transition [ 959.670802] getpid_livepatch: getpid livepatch test module is enabled
ただ、livepatch_getpid()の最初にpr_info()で関数名を表示させているので以下のようなログが大量に出てると思いますが。
[ 1042.927756] getpid_livepatch: livepatch_getpid
それはさておきgetpid-testを再度実行するとpidとして1が返ってきます。ということでカーネルのライブパッチ機能を使ったライブパッチも成功です🎉
[masami@unamebook getpid-livepatch-module]$ ./getpid-test pid is 1
プログラム名が違えばもちろんpidは普通に返ってきます。
[masami@unamebook getpid-livepatch-module]$ cp getpid-test getpid-test2 [masami@unamebook getpid-livepatch-module]$ ./getpid-test2 pid is 1455
FedoraをupstreamとしているRHELとCentOSのuname(1)の挙動も紹介します。
バージョンは8.1です。これでuname(1)を実行するとuname(2)が3回呼ばれているのがわかりますね。
[masami@rhel8 ~]$ cat /etc/os-release | grep VERSION_ID VERSION_ID="8.1" [masami@rhel8 ~]$ strace -v -s 1024 -e trace=uname -C uname -a uname({sysname="Linux", nodename="rhel8", release="4.18.0-147.0.3.el8_1.x86_64", version="#1 SMP Mon Nov 11 12:58:36 UTC 2019", machine="x86_64", domainname="(none)"}) = 0 uname({sysname="Linux", nodename="rhel8", release="4.18.0-147.0.3.el8_1.x86_64", version="#1 SMP Mon Nov 11 12:58:36 UTC 2019", machine="x86_64", domainname="(none)"}) = 0 uname({sysname="Linux", nodename="rhel8", release="4.18.0-147.0.3.el8_1.x86_64", version="#1 SMP Mon Nov 11 12:58:36 UTC 2019", machine="x86_64", domainname="(none)"}) = 0 Linux rhel8 4.18.0-147.0.3.el8_1.x86_64 #1 SMP Mon Nov 11 12:58:36 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux +++ exited with 0 +++ % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000068 22 3 uname ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000068
coreutilsのバージョンは8.30です。
[masami@rhel8 ~]$ rpm -qi coreutils | grep -e Version -e Release Version : 8.30 Release : 6.el8
CentOSのバージョンは8です。こちらもuname(2)が3回呼ばれてますね。
masami@centos8 ~]$ cat /etc/os-release | grep VERSION_ID VERSION_ID="8" [masami@centos8 ~]$ strace -v -s 1024 -e trace=uname -C uname -a uname({sysname="Linux", nodename="centos8", release="4.18.0-80.11.2.el8_0.x86_64", version="#1 SMP Tue Sep 24 11:32:19 UTC 2019", machine="x86_64", domainname="(none)"}) = 0 uname({sysname="Linux", nodename="centos8", release="4.18.0-80.11.2.el8_0.x86_64", version="#1 SMP Tue Sep 24 11:32:19 UTC 2019", machine="x86_64", domainname="(none)"}) = 0 uname({sysname="Linux", nodename="centos8", release="4.18.0-80.11.2.el8_0.x86_64", version="#1 SMP Tue Sep 24 11:32:19 UTC 2019", machine="x86_64", domainname="(none)"}) = 0 Linux centos8 4.18.0-80.11.2.el8_0.x86_64 #1 SMP Tue Sep 24 11:32:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux +++ exited with 0 +++ % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000013 4 3 uname ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000013 3 total
coreutilsのバージョンはこちらも8.30です。
[masami@centos8 ~]$ rpm -qi coreutils | grep -e Version -e Release Version : 8.30 Release : 6.el8
最後はgetpid(2)を使いましたが、主にuname(1)とuname(2)を使ってユーザーランドでのデバッグからカーネルを弄るところまでを行いました(_´Д`)ノ~~オツカレー
Raspberry Piで学ぶコンピュータアーキテクチャ (Make:PROJECTS)
FedoraではkernelのテストイベントとかGnome 3.34 Test Day、I18N Test Day などのテストイベントがちょくちょくあります。 今回はKernel 5.3 Test Weekの備忘録です。ちなみに、この手のイベントはfedora MAGAZINEで紹介されることがあります。
今回の参加結果の最終成果はこちらです。
今回のtest weekは開催時期が9月末から10月の最初の週でちょうどfedora 31のリリースが近かったころです。当時のfedora 29と30はカーネルは5.2系が使われていて、fedora 31が5.3系って感じでした。メインラインは5.4のrc1が出てた辺りです。それでfedora 31もそろそろ出るし、ベータとかrcのテストも参加してなかったし、メインラインのカーネルも最近使ってないなーなんてことでテストに参加しとくかーって感じで参加したわけです。
テストに使うカーネルのrpmをデスクトップ環境のfedora 30マシンにインストールして再起動すると、本来GDMのログイン画面がでるところでスクリーンがブリンクし続ける状態でした。一応メインラインのカーネル(5.4-rc1)も試してみたけど結果は同じでした。ここまでで5.3〜で症状が出るらしいってことは確定。というわけでここまでの内容で一旦bugzillaにバグレポートしました。
1758831 – GDM login screen doesn't show up on kernel 5.3.2
その後ですが、じゃあ、fedora 31ならどうよ?と思ってテスト用にビルドされたライブデスクトップのisoイメージを使ってみたところこちらも同様の現象。ただブートメニューからTroubleshooting" -> "Start kernel in basic graphic mode"を選ぶと問題なし。間違いなくGPU関連のバグですねって感じです。この情報もbugzillaに追記しました。
一応ここまででバグを見つけた人として一応のことはしたのですが、自分のPCで新しいカーネルが使えないのは最高に困るのでここからはbisectして原因を探りました。バグに当たっている人がテスト時点では自分くらいしかいなかったし、他のディストリビューションを見てもArch Linuxで同じような現象の人が1人くらいいたかなーって感じでした。そうするとバグは機種依存っぽいし自分でデバッグしたほうが良さげだったんですね。
ということでbisectを開始したわけですがバグが実機じゃないと再現できないのでダラダラやりつつ土曜日の朝から夕方まで使った気がします。そして、first bad commitは36a0f92020dc8794d3aa69b7fb4c5d2bf99b0099というのがわかりました。ちなみにこのpatchは一連のシリーズの中の一つなのと、このコミット以降でリファクタリングされていてファイルのパスやヘルパー関数が追加されています。さて、差分ですがpatchだけ見るとわかりにくいので自分の環境で動かなくなった原因のsanitize_aux_ch()より主要部分のbefore/afterを示すとこうなります。
before
const struct ddi_vbt_port_info *info = &dev_priv->vbt.ddi_port_info[port]; for (p = PORT_A; p < I915_MAX_PORTS; p++) { struct ddi_vbt_port_info *i = &dev_priv->vbt.ddi_port_info[p]; if (p == port || !i->present || info->alternate_aux_channel != i->alternate_aux_channel) continue; i->supports_dp = false; i->alternate_aux_channel = 0; }
after
struct ddi_vbt_port_info *info = &dev_priv->vbt.ddi_port_info[port]; for (p = PORT_A; p < I915_MAX_PORTS; p++) { struct ddi_vbt_port_info *i = &dev_priv->vbt.ddi_port_info[p]; if (p == port || !i->present || info->alternate_aux_channel != i->alternate_aux_channel) continue; info->supports_dp = false; info->alternate_aux_channel = 0; }
大きな違いはfor文中のif文でcontinueしなかった場合に使用している変数です。変更前は配列のp番目の要素をiにセットしてその変数の構造体のデータに値を設定していますが、変更後の方は最初にinfoに設定した値をそのまま使っていてp番目の要素に対する変更は行っていません。そんなわけで、変更前と同様の挙動にしたらどう?と思って作ったのが以下の修正です。
diff --git a/drivers/gpu/drm/i915/display/intel_bios.c b/drivers/gpu/drm/i915/display/intel_bios.c index efb39f350b19..c886dae82aa7 100644 --- a/drivers/gpu/drm/i915/display/intel_bios.c +++ b/drivers/gpu/drm/i915/display/intel_bios.c @@ -1313,6 +1313,8 @@ static void sanitize_aux_ch(struct drm_i915_private *dev_priv, p = get_port_by_aux_ch(dev_priv, info->alternate_aux_channel); if (p != PORT_NONE) { + info = &dev_priv->vbt.ddi_port_info[p]; + DRM_DEBUG_KMS("port %c trying to use the same AUX CH (0x%x) as port %c, " "disabling port %c DP support\n", port_name(port), info->alternate_aux_channel, @@ -1330,6 +1332,7 @@ static void sanitize_aux_ch(struct drm_i915_private *dev_priv, info->supports_dp = false; info->alternate_aux_channel = 0; } + } static const u8 cnp_ddc_pin_map[] = {
このpatchをstableの5.3.2に当てたところちゃんと動くようになりましたワーイヽ(゚∀゚)メ(゚∀゚)メ(゚∀゚)ノワーイ これでとりあえず自分の環境では新しいカーネルでもなんとか動くようになったわけですが、ローカルパッチを当て続けるとかあり得ないですね。なので次はupstreamへの報告となります。
36a0f92020dc8794d3aa69b7fb4c5d2bf99b0099を見るとsanitize_ddc_pin()も同じような変更が入ってるのですが、こちらは自分の環境では影響出てなかったです。GPUわからん😨
upstreamにバグ報告と言っても報告先として考えられるのはlkml、サブシステムのメーリングリスト、kernelのbugzilla、またはそれ以外とあります。GPU(i915)のバグ報告先をさがしたところ↓が見つかりました。ここにレポートの仕方が書かれていたのでまずはそれに従います。drm-tipツリーをcloneしてきてこのツリーの最新コードを試したり。。
バグのレポートはfreedesktop.orgのbugzillaを使うということだったのでアカウントを作りました。そしていざレポートってところで色々書いてsubmitボタンを押すと空白ページになるだけでバグが登録できません😨 たまたまバグったのかな?と思ってもう一度試してもダメ、じゃあ時間を置いてと思って次の日に試してもダメ。。。バグ報告したいのにバグ報告できないというバグにハマったわけです\(^o^)/オワタ こんなのわからんよと思ってbugzillaについて聞けるところはないかな?と探したところircで聞けるというのがわかったのでircクライアントをインストールし、freenodeにある#freedesktopに入って状況説明したところ中の人がログを確認したりデバッグを手伝ってくれました。このときのバグレポートはfedoraと同じ感じで書いていたのでタイトルにloginという単語が入っていたのですが、これがバグの原因というのがわかりました。だいたい2時間位色々と試したんじゃないかな。。 ちなみにLoginだと大丈夫で小文字のloginという文字列がバグレポートの新規作成時に入っているとダメという状況でした。既存のバグレポートの編集時だと大丈夫という謎。 それはともかく、手伝ってくれた方には感謝しかありません👃
なんだかんだで無事に報告できました。タイトルについてるbisectedというのはbisect結果を貼ったあとに変更されました。ちゃんと見てくれてますね( ̄ー ̄)bグッ!
111966 – [bisected] GDM Login screen doesn't show up on kernel 5.3 or later
まず普通にバグの報告したあとでbisectの結果やquick hackなパッチをレポートしました。その後、同じような現象の人が現れ、この方も自分が作ったパッチでバグが直ったという報告をしてくれました👃 その後のちゃんとしたパッチの作成までは結構速く進み、コミット36a0f92020dc8794d3aa69b7fb4c5d2bf99b0099の設定方法だとダメな機種があるということでコメント等適切に直したパッチが作成されました。自分ともう一人の方はこのパッチをテストして動くのを確認しました。
修正方法は自分がやった方法と同じなので自分のパッチも暫定修正とはいえ正しい修正方法にたどり着けててエライって感じです😃
修正パッチがサブシステムのメーリングリストに投げられたのであとは次の3個の待ちとなります。とくに自分がすることはありません。
3のときに Patch "drm/i915: Favor last VBT child device with conflicting AUX ch/DDC pin" has been added to the 5.3-stable tree
ってメールが届いて5.3.8(5.3.7はすでにリリースされてたので次は5.3.8のときだった)にパッチが入るんだなーってのがわかりました。
freedesktop.orgのbugzillaは1か2のタイミングでcloseされたと思います。
新しいstable releaseが出れば fedoraのカーネルが最新のstable releaseに追従したときにfedoraのカーネルでもバグが直ります。なのでこちらも待ちです(-_-)zzz fedoraのパッケージは最初updates-testingというリポジトリにアップロードされます。そこで人間がテストするか一定期間経てばupdatesリポジトリにパッケージが入ります。で、fedoraにカーネル5.3.8のパッケージがアップロードされたので早速テストして結果を書きました。
https://bodhi.fedoraproject.org/updates/FEDORA-2019-0e85bbd15b
あと、fedoraのbugzillaのほうにも5.3.8のカーネルパッケージで直ったという報告を書いておきました。で、こちらもクローズとなりました。
というわけでkernel test weekへの参加とバグ発見、bisectとローカルパッチの作成、upstreamへの報告とupstreamへの協力、fedoraでの修正確認ということを最近しました( ´ー`)フゥー... 今回一番難しかったのはbugzillaでバグ報告できない問題でしたね。自分の環境で動かせられないし/(^o^)\
超例解Linuxカーネルプログラミング~最先端Linuxカーネルの修正コードから学ぶソフトウェア品質~
このブロクで使ってるカテゴリは↓のようになっていて、こいつLinuxとかカーネル好きだなって感じなんですが、前まではLinux自体は使うけどそれくらいでガッツリとLinuxに絡んだりとかはしてませんでした。
前職はこんな感じのところでnodejsなんかのコード書いてわけですね。
web系っぽい記事もたまに書いてましたよw
とまあ、前職はweb系だったわけですが今はLinuxがっつりな仕事になってます。転職したのは去年の12月ですけどね。Linuxが好きでいろいろやってきて、そこでやったことをblogに書いたりしたわけですが、それも今の仕事で役立つことがあったりとまあ何が役立つかわからないものですね。メモリ管理に興味を持ったり自作OSに興味を持ったり、CTFだったりと低レイヤ周辺をウロウロしてたけど基本的な部分はブレなかったのが良かった気がします。 継続は力なりなんて格言もあるし、続けるってのは大事っすね。とゆーか、ものすごく運が良かったとは思ってます。すごい人達とも知り合うことができたし。
もともとソフトウェアエンジニアになったのも趣味を仕事にしたら一石二鳥じゃね?ってところから仕事にした感じですね。今流行りの未経験からエンジニアになった系ですw もうかなり前だけどw で、そんなことはどうでもよくて、趣味&独学でやってきたので自分の好きな分野はまあわかるけど情報理論とか理論系よくわからん😭という状況だったりというのもありました。じゃあ、ちゃんと勉強しちゃうか?ってことで2019年4月から放送大学の情報コースに全科履修生で入学したりしました。まあ、この辺の決断はかなり軽くて昼に町に出てどのごはん屋さんに行くかを決めるよりは軽い感じで決めてます。会社員兼学生という感じなので履修ペースは遅いけどそこはしょうがない。。。今のところ1学期に4科目くらいのペースですね。
2019度第1学期にとったこれは自分のわかる分野だったので単位認定試験で一番良い評価とれました😃
だがしかし、こっちはギリギリ単位取れた。。。数学苦手😨
ちなみに、コンピュータの動作と管理を見てたらこんなことが😄
\177ELF on Twitter: "@uchan_nos @d_kami 放送大学の情報コースの教科書に見慣れたお名前を発見😇… "
@uchan_nos @d_kami 放送大学の情報コースの教科書に見慣れたお名前を発見😇 pic.twitter.com/tW7zYocCRE
— \177ELF (@masami256) May 9, 2019
さて、2019度第2学期も無事に単位を取ることができるのか💀