C言語でLinuxカーネルのデバッグ方法を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
本記事は以下の記事を理解していることを前提とします.
目次
【C言語】Linuxカーネルのデバッグ方法
C言語でLinuxカーネルのデバッグ方法を紹介します.
Linuxカーネルの開発サイクルは以下の繰り返しになります.
- コードを書く
- カーネルやモジュールをビルドする
- デプロイする
- テストやデバッグする
カーネルのデバッグの制約により,プロのカーネル開発者でもデバッグが真のボトルネックとなります.
なので,カーネルのデバッグ方法に慣れておくことは,時間と労力を節約するために重要です.
カーネルのデバッグ方法は以下になりますので,それぞれ解説していきます.
- printk関数でデバッグメッセージを表示
- BUG_ONマクロ,WARN_ONマクロでコードのアサーションとカーネルパニックのメッセージを解析
- QEMUとGDBを利用したデバッグ
printk関数
printk関数は,printf関数のカーネルレベル版です.
printk関数を利用すると,カーネルレベルで変数の値を表示できるので,デバッグしやすくなります.
printk関数のログレベルの定義
printk関数では,ログレベルを指定する必要があります(デフォルトのレベルはKERN_ERRまたはKERN_WARNING).
linux/include/linux/kern_levels.hにのログレベル0~7の定義があります.
※ログレベルの数値が小さいほど優先度が高くなります.
- ログレベル0:KERN_EMERG
- ログレベル1:KERN_ALERT
- ログレベル2:KERN_CRIT
- ログレベル3:KERN_ERR
- ログレベル4:KERN_WARNING
- ログレベル5:KERN_NOTICE
- ログレベル6:KERN_INFO
- ログレベル7:KERN_DEBUG
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#define KERN_SOH "\001" /* ASCII Start Of Header */ #define KERN_SOH_ASCII '\001' #define KERN_EMERG KERN_SOH "0" /* system is unusable */ #define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */ #define KERN_CRIT KERN_SOH "2" /* critical conditions */ #define KERN_ERR KERN_SOH "3" /* error conditions */ #define KERN_WARNING KERN_SOH "4" /* warning conditions */ #define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */ #define KERN_INFO KERN_SOH "6" /* informational */ #define KERN_DEBUG KERN_SOH "7" /* debug-level messages */ #define KERN_DEFAULT "" /* the default kernel loglevel */ |
また,linux/include/linux/printk.hに上記のエイリアスのマクロがあります.
- pr_emergマクロ:printk関数のログレベル0(KERN_EMERG)のマクロ
- pr_alertマクロ:printk関数のログレベル1(KERN_ALERT)のマクロ
- pr_critマクロ:printk関数のログレベル2(KERN_CRIT)のマクロ
- pr_errマクロ:printk関数のログレベル3(KERN_ERR)のマクロ
- pr_warnマクロ:printk関数のログレベル4(KERN_WARNING)のマクロ
- pr_noticeマクロ:printk関数のログレベル5(KERN_NOTICE)のマクロ
- pr_infoマクロ:printk関数のログレベル6(KERN_INFO)のマクロ
- pr_contマクロ:printk関数のログレベル7(KERN_CONT)のマクロ
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
/** * pr_emerg - Print an emergency-level message * @fmt: format string * @...: arguments for the format string * * This macro expands to a printk with KERN_EMERG loglevel. It uses pr_fmt() to * generate the format string. */ #define pr_emerg(fmt, ...) \ printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__) /** * pr_alert - Print an alert-level message * @fmt: format string * @...: arguments for the format string * * This macro expands to a printk with KERN_ALERT loglevel. It uses pr_fmt() to * generate the format string. */ #define pr_alert(fmt, ...) \ printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__) /** * pr_crit - Print a critical-level message * @fmt: format string * @...: arguments for the format string * * This macro expands to a printk with KERN_CRIT loglevel. It uses pr_fmt() to * generate the format string. */ #define pr_crit(fmt, ...) \ printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__) /** * pr_err - Print an error-level message * @fmt: format string * @...: arguments for the format string * * This macro expands to a printk with KERN_ERR loglevel. It uses pr_fmt() to * generate the format string. */ #define pr_err(fmt, ...) \ printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__) /** * pr_warn - Print a warning-level message * @fmt: format string * @...: arguments for the format string * * This macro expands to a printk with KERN_WARNING loglevel. It uses pr_fmt() * to generate the format string. */ #define pr_warn(fmt, ...) \ printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__) /** * pr_notice - Print a notice-level message * @fmt: format string * @...: arguments for the format string * * This macro expands to a printk with KERN_NOTICE loglevel. It uses pr_fmt() to * generate the format string. */ #define pr_notice(fmt, ...) \ printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__) /** * pr_info - Print an info-level message * @fmt: format string * @...: arguments for the format string * * This macro expands to a printk with KERN_INFO loglevel. It uses pr_fmt() to * generate the format string. */ #define pr_info(fmt, ...) \ printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__) /** * pr_cont - Continues a previous log message in the same line. * @fmt: format string * @...: arguments for the format string * * This macro expands to a printk with KERN_CONT loglevel. It should only be * used when continuing a log message with no newline ('\n') enclosed. Otherwise * it defaults back to KERN_DEFAULT loglevel. */ #define pr_cont(fmt, ...) \ printk(KERN_CONT fmt, ##__VA_ARGS__) |
printk関数のフォーマット指定子は「How to get printk format specifiers right」に記載してあります.
printk関数はprintf関数とは異なり,浮動小数点数の表示はサポートしていないことに注意して下さい.
printk関数は以下のように利用します.
1 |
printk(KERN_DEBUG "debug message from %s:%d\n", __func__, __LINE__); |
printk関数では,現在のログレベルより高い優先度(小さい数値)のログレベルのメッセージのみを出力します.
私のLinuxカーネルの.configでは,「CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4」と設定しています.
なので,ログレベル4のKERN_WARNINGがデフォルトになっていて,ログレベル3以下のprintk関数が表示されることがわかります.
1 |
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 |
printk関数のログレベルの取得と変更
printk関数のログレベルの情報は/proc/sys/kernel/printkにあります.
以下のコマンドでログレベルの情報を表示できます.
1 2 |
$ cat /proc/sys/kernel/printk 4 4 1 7 |
左から右の数値の意味は以下になります.
- 4:現在のログレベル
- 4:デフォルトのログレベル
- 1:最小値のログレベル(ログレベル0は除外)
- 7:ブート時のログレベル
現在のコンソールのログレベルを8に変更したい(ログレベル0~7を表示したい)場合はrootで以下のコマンドを実行します.
1 |
# echo 8 > /proc/sys/kernel/printk |
また,dmesgコマンドやsysctlコマンドでも同様に変更できます.
1 |
$ sudo dmesg -n 8 |
1 |
$ sudo sysctl -w kernel.printk=8 |
ログレベルを確認すると現在のログレベルが8に変更されていることがわかります.
1 2 |
$ cat /proc/sys/kernel/printk 8 4 1 7 |
上記の方法は一時的に変更するので,Linuxを再起動するとデフォルトの値に戻ってしまいます.
デフォルト値を変更するためには,Linuxカーネルを再ビルドすればできなくはないですが,時間がかかります.
なので,デフォルト値を変更したい場合は,/etc/default/grubのGRUB_CMDLINE_LINUXにログレベルの引数を設定します.
ログレベル8に設定する方法は以下になります.
1 |
GRUB_CMDLINE_LINUX="loglevel=8" |
変更後に,以下のコマンドでgrubを更新します.
1 |
$ sudo update-grub |
printk関数でログレベルを表示するコード
printk関数でログレベルを表示するコード一式をこちらからダウンロードして下さい.
以下のコマンドで解凍して下さい.
1 2 |
$ unzip printk_kernel_module.zip $ cd printk_kernel_module |
中身は以下になります.
- Makefile:カーネルモジュール用の構成ファイル
- printk_kernel_module.c:printk関数で全ログレベルを表示するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# # Makefile for Printk Kernel Module # # Hiroyuki Chishiro # obj-m += printk_kernel_module.o KDIR = /lib/modules/$(shell uname -r)/build RM = rm -f all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean $(RM) *~ *.orig |
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 29 30 31 32 33 34 35 |
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> static int __init printk_kernel_module_init(void) { printk(KERN_EMERG "Hello Worldl!: KERN_EMERG\n"); printk(KERN_ALERT "Hello Worldl!: KERN_ALERT\n"); printk(KERN_CRIT "Hello Worldl!: KERN_CRIT\n"); printk(KERN_ERR "Hello Worldl!: KERN_ERR\n"); printk(KERN_WARNING "Hello Worldl!: KERN_WARNING\n"); printk(KERN_NOTICE "Hello Worldl!: KERN_NOTICE\n"); printk(KERN_INFO "Hello Worldl!: KERN_INFO\n"); printk(KERN_DEBUG "Hello Worldl!: KERN_DEBUG\n"); return 0; } static void __exit printk_kernel_module_exit(void) { printk(KERN_EMERG "Goodbye Worldl!: KERN_EMERG\n"); printk(KERN_ALERT "Goodbye Worldl!: KERN_ALERT\n"); printk(KERN_CRIT "Goodbye Worldl!: KERN_CRIT\n"); printk(KERN_ERR "Goodbye Worldl!: KERN_ERR\n"); printk(KERN_WARNING "Goodbye Worldl!: KERN_WARNING\n"); printk(KERN_NOTICE "Goodbye Worldl!: KERN_NOTICE\n"); printk(KERN_INFO "Goodbye Worldl!: KERN_INFO\n"); printk(KERN_DEBUG "Goodbye Worldl!: KERN_DEBUG\n"); } module_init(printk_kernel_module_init); module_exit(printk_kernel_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Hiroyuki Chishiro"); MODULE_DESCRIPTION("Printk Kernel Module"); |
printk関数のコンソールの表示はCtrl + Alt + F1~F6の仮想コンソールでしか表示されないことに注意して下さい.
※GUIで起動する場合,仮想コンソールはCtrl + Alt + F3~F6になります.
GUIのコンソールやSSHで接続したネットワークのコンソールでは表示されません.
もしGUIのコンソールやSSHで接続したネットワークのコンソールで表示したい場合は,dmesgコマンドで以下のようにオプションをつけて実行します.
- -wオプション:新しいメッセージが出力されるのを待つ.
- -Hオプション:人間に読みやすい出力にする.
- -lオプション:ログレベルを指定する.(以下の例ではログレベル4~7を表示する.)
1 2 3 4 5 6 7 8 9 |
$ sudo dmesg -wH -l 4,5,6,7 & [Sep 1 21:22] Linux version 5.15.0-47-generic (buildd@lcy02-amd64-060) (gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #51-Ubuntu SMP Thu Aug 11 07:51:15 UTC 2022 (Ubuntu 5.15.0-47.51-generic 5.15.46) [ +0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-5.15.0-47-generic root=UUID=aeefa3fe-ae16-4f64-ab7d-760d249c56d3 ro quiet splash [ +0.000000] KERNEL supported cpus: [ +0.000000] Intel GenuineIntel [ +0.000000] AMD AuthenticAMD [ +0.000000] Hygon HygonGenuine [ +0.000000] Centaur CentaurHauls [ +0.000000] zhaoxin Shanghai |
※これまでのdmesgのログが表示されますが,その後にprintk関数を実行すると指定したログレベルで表示されます.
本記事ではこちらの方法でprintk関数の動作を確認します.
makeでビルドします.
1 2 3 4 5 6 7 8 9 10 |
$ make make -C /lib/modules/5.15.0-47-generic/build M=/home/chishiro/c-language/printk_kernel_module modules make[1]: Entering directory '/usr/src/linux-headers-5.15.0-47-generic' CC [M] /home/chishiro/c-language/printk_kernel_module/printk_kernel_module.o MODPOST /home/chishiro/c-language/printk_kernel_module/Module.symvers CC [M] /home/chishiro/c-language/printk_kernel_module/printk_kernel_module.mod.o LD [M] /home/chishiro/c-language/printk_kernel_module/printk_kernel_module.ko BTF [M] /home/chishiro/c-language/printk_kernel_module/printk_kernel_module.ko Skipping BTF generation for /home/chishiro/c-language/printk_kernel_module/printk_kernel_module.ko due to unavailability of vmlinux make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-47-generic' |
dmesgコマンドでログレベル4~7を表示する設定におけるカーネルモジュールのロードとアンロードの結果は以下になります.
ログレベル4~7のprintk関数の結果が表示されていることがわかります.
1 2 3 4 5 6 7 8 9 10 11 12 |
$ sudo insmod printk_kernel_module.ko [Sep 1 21:24] printk_kernel_module: loading out-of-tree module taints kernel. [ +0.000115] printk_kernel_module: module verification failed: signature and/or required key missing - tainting kernel [ +0.005949] Hello Worldl!: KERN_WARNING [ +0.000000] Hello Worldl!: KERN_NOTICE [ +0.000000] Hello Worldl!: KERN_INFO [ +0.000001] Hello Worldl!: KERN_DEBUG $ sudo rmmod printk_kernel_module.ko [Sep 1 21:25] Goodbye Worldl!: KERN_WARNING [ +0.000001] Goodbye Worldl!: KERN_NOTICE [ +0.000000] Goodbye Worldl!: KERN_INFO [ +0.000000] Goodbye Worldl!: KERN_DEBUG |
dmesgの表示を終了する場合は,pkillコマンドを利用します.
1 2 |
$ sudo pkill dmesg [2] terminated sudo -E dmesg -wH -l 4,5,6,7 |
次に,ログレベル0~7を表示してみましょう.
以下のコマンドで設定します.
1 |
$ sudo dmesg -wH -l 0,1,2,3,4,5,6,7 & |
dmesgコマンドでログレベル0~7を表示する設定におけるカーネルモジュールのロードとアンロードの結果は以下になります.
ログレベル0~7のprintk関数の結果が表示されていることがわかります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ sudo insmod printk_kernel_module.ko [ +33.809014] Hello Worldl!: KERN_EMERG [ +0.000021] Hello Worldl!: KERN_ALERT [ +0.000001] Hello Worldl!: KERN_CRIT [ +0.000000] Hello Worldl!: KERN_ERR [ +0.000001] Hello Worldl!: KERN_WARNING [ +0.000000] Hello Worldl!: KERN_NOTICE [ +0.000000] Hello Worldl!: KERN_INFO [ +0.000001] Hello Worldl!: KERN_DEBUG $ sudo rmmod printk_kernel_module.ko [ +17.332894] Goodbye Worldl!: KERN_EMERG [ +0.000004] Goodbye Worldl!: KERN_ALERT [ +0.000001] Goodbye Worldl!: KERN_CRIT [ +0.000000] Goodbye Worldl!: KERN_ERR [ +0.000001] Goodbye Worldl!: KERN_WARNING [ +0.000000] Goodbye Worldl!: KERN_NOTICE [ +0.000000] Goodbye Worldl!: KERN_INFO [ +0.000001] Goodbye Worldl!: KERN_DEBUG |
dmesgの表示を終了するために,pkillコマンドを利用します.
1 2 |
$ sudo pkill dmesg [2] terminated sudo -E dmesg -wH -l 0,1,2,3,4,5,6,7 |
BUG_ONマクロ,WARN_ONマクロ,カーネルパニック
BUG_ONマクロ,WARN_ONマクロはアサーションのカーネルレベル版です.
C言語でアサーションを知りたいあなたはこちらからどうぞ.
BUG_ONマクロは,引数cが真の場合,カーネルパニックを発生させ,コールスタックを表示した後に強制終了します.
これに対して,WARN_ONマクロは,引数cが真の場合,カーネルはコールスタックを表示し,実行を継続します.
BUG_ONマクロ,WARN_マクロの定義
linux/include/asm-generic/bug.hにBUG_ONマクロ,WARN_ONマクロが定義されています.
BUG_ONマクロとWARN_ONマクロはアーキテクチャ共通の定義とアーキテクチャ固有の定義の2種類があります.
x86-64とARM64はHAVE_ARCH_BUG_ONとHAVE_ARCH_WARN_ONは未定義なので,こちらを利用します.
1 2 3 4 5 6 7 8 9 10 |
#ifndef HAVE_ARCH_BUG_ON #define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0) #endif #ifndef HAVE_ARCH_WARN_ON #define WARN_ON(condition) ({ \ int __ret_warn_on = !!(condition); \ unlikely(__ret_warn_on); \ }) #endif |
BUG_ONマクロとWARN_ONマクロを利用するコード
BUG_ONマクロとWARN_ONマクロを利用するコード一式はこちらからダウンロードして下さい.
中身は以下になります.
- Makefile:カーネルモジュール用の構成ファイル
- bug_on_and_warn_on_kernel_module.c:BUG_ONマクロとWARN_ONマクロを利用するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# # Makefile for BUG_ON and WARN_ON Kernel Module # # Hiroyuki Chishiro # obj-m += bug_on_and_warn_on_kernel_module.o KDIR = /lib/modules/$(shell uname -r)/build RM = rm -f all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean $(RM) *~ *.orig |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> static int __init bug_on_and_warn_on_kernel_module_init(void) { WARN_ON(1); return 0; } static void __exit bug_on_and_warn_on_kernel_module_exit(void) { BUG_ON(1); } module_init(bug_on_and_warn_on_kernel_module_init); module_exit(bug_on_and_warn_on_kernel_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Hiroyuki Chishiro"); MODULE_DESCRIPTION("WARN_ON and BUG_ON Kernel Module"); |
ログレベル0~7を表示するために,以下のコマンドで設定します.
1 |
$ sudo dmesg -wH -l 0,1,2,3,4,5,6,7 & |
カーネルモジュールをロードすると,WARN_ONマクロによるコールスタックを表示します.
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 29 30 31 32 33 34 35 36 37 38 39 40 |
$ sudo insmod bug_on_and_warn_on_kernel_module.ko [Sep 1 21:27] ------------[ cut here ]------------ [ +0.000002] WARNING: CPU: 15 PID: 3375 at /home/chishiro/c-language/bug_on_and_warn_on_kernel_module/bug_on_and_warn_on_kernel_module.c:7 bug_on_and_warn_on_kernel_module_init+0x9/0x1000 [bug_on_and_warn_on_kernel_module] [ +0.000005] Modules linked in: bug_on_and_warn_on_kernel_module(OE+) rfcomm vsock_loopback bnep vmw_vsock_virtio_transport_common intel_rapl_msr intel_rapl_common vmw_vsock_vmci_transport crct10dif_pclmul vsock ghash_clmulni_intel aesni_intel crypto_simd cryptd rapl binfmt_misc vmw_balloon snd_ens1371 snd_ac97_codec gameport ac97_bus snd_pcm snd_seq_midi snd_seq_midi_event input_leds joydev snd_rawmidi serio_raw btusb btrtl nls_iso8859_1 btbcm snd_seq btintel snd_seq_device bluetooth ecdh_generic snd_timer ecc snd soundcore vmw_vmci mac_hid sch_fq_codel vmwgfx ttm drm_kms_helper cec rc_core fb_sys_fops syscopyarea sysfillrect sysimgblt ipmi_devintf ipmi_msghandler msr parport_pc ppdev lp ramoops parport reed_solomon pstore_blk drm pstore_zone mtd efi_pstore ip_tables x_tables autofs4 hid_generic usbhid hid crc32_pclmul psmouse mptspi mptscsih ahci mptbase e1000 libahci scsi_transport_spi i2c_piix4 pata_acpi [last unloaded: printk_kernel_module] [ +0.000029] CPU: 15 PID: 3375 Comm: insmod Tainted: G OE 5.15.0-47-generic #51-Ubuntu [ +0.000002] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 04/13/2018 [ +0.000001] RIP: 0010:bug_on_and_warn_on_kernel_module_init+0x9/0x1000 [bug_on_and_warn_on_kernel_module] [ +0.000003] Code: Unable to access opcode bytes at RIP 0xffffffffc07b0fdf. [ +0.000001] RSP: 0018:ffff9c31c5623cd8 EFLAGS: 00010246 [ +0.000001] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000001 [ +0.000001] RDX: 0000000000000000 RSI: ffffffff8c595408 RDI: ffffffffc07b1000 [ +0.000000] RBP: ffff9c31c5623cd8 R08: 0000000000000010 R09: 0000000000000000 [ +0.000001] R10: ffff90da06c83450 R11: 0000000000000000 R12: ffffffffc07b1000 [ +0.000001] R13: ffff90da06c83450 R14: 0000000000000000 R15: ffffffffc07ae040 [ +0.000001] FS: 00007f18eccb6c40(0000) GS:ffff90db361c0000(0000) knlGS:0000000000000000 [ +0.000001] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ +0.000001] CR2: ffffffffc07b0fdf CR3: 0000000149dd6004 CR4: 00000000003706e0 [ +0.000023] Call Trace: [ +0.000001] <TASK> [ +0.000002] do_one_initcall+0x46/0x1e0 [ +0.000003] ? kmem_cache_alloc_trace+0x19e/0x2e0 [ +0.000004] do_init_module+0x52/0x260 [ +0.000002] load_module+0xacd/0xbc0 [ +0.000001] __do_sys_finit_module+0xbf/0x120 [ +0.000002] __x64_sys_finit_module+0x18/0x20 [ +0.000001] do_syscall_64+0x59/0xc0 [ +0.000002] ? do_syscall_64+0x69/0xc0 [ +0.000001] ? syscall_exit_to_user_mode+0x27/0x50 [ +0.000002] ? do_syscall_64+0x69/0xc0 [ +0.000001] entry_SYSCALL_64_after_hwframe+0x61/0xcb [ +0.000002] RIP: 0033:0x7f18ecdd6a3d [ +0.000001] Code: 5b 41 5c c3 66 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d c3 a3 0f 00 f7 d8 64 89 01 48 [ +0.000001] RSP: 002b:00007ffd2d66a0e8 EFLAGS: 00000246 ORIG_RAX: 0000000000000139 [ +0.000002] RAX: ffffffffffffffda RBX: 000055aba7a457d0 RCX: 00007f18ecdd6a3d [ +0.000000] RDX: 0000000000000000 RSI: 000055aba6e5fcd2 RDI: 0000000000000003 [ +0.000001] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 [ +0.000001] R10: 0000000000000003 R11: 0000000000000246 R12: 000055aba6e5fcd2 [ +0.000000] R13: 000055aba7a45760 R14: 000055aba6e5e888 R15: 000055aba7a45900 [ +0.000002] </TASK> [ +0.000000] ---[ end trace c91054109cb096bb ]--- |
カーネルモジュールをアンロードすると,カーネルパニックを表示します.
以下の3行目で「kernel BUG at /home/chishiro/c-language/bug_on_and_warn_on_kernel_module/bug_on_and_warn_on_kernel_module.c:13!」と表示されています.
なので,bug_on_and_warn_on_kernel_module.cの13行目でバグが発生したことがわかります.
カーネルモジュールを再ロードできなくなったりフリーズしたりする等の不具合が発生するので,再起動しましょう!
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
$ sudo rmmod bug_on_and_warn_on_kernel_module.ko [Sep 1 21:28] ------------[ cut here ]------------ [ +0.000003] kernel BUG at /home/chishiro/c-language/bug_on_and_warn_on_kernel_module/bug_on_and_warn_on_kernel_module.c:13! [ +0.000007] invalid opcode: 0000 [#1] SMP NOPTI [ +0.000002] CPU: 14 PID: 3385 Comm: rmmod Tainted: G W OE 5.15.0-47-generic #51-Ubuntu [ +0.000002] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 04/13/2018 [ +0.000001] RIP: 0010:bug_on_and_warn_on_kernel_module_exit+0x0/0x1000 [bug_on_and_warn_on_kernel_module] [ +0.000003] Code: Unable to access opcode bytes at RIP 0xffffffffc07abfd6. [ +0.000001] RSP: 0018:ffff9c31c55abe40 EFLAGS: 00010286 [ +0.000002] RAX: ffffffffc07ac000 RBX: 0000000000000000 RCX: 0001020304050608 [ +0.000001] RDX: 0000000000000000 RSI: ffff9c31c55abe68 RDI: ffffffff8e3ce300 [ +0.000001] RBP: ffff9c31c55abea0 R08: fefefefefefefeff R09: 0000000000000000 [ +0.000001] R10: 0000000000000000 R11: 0000000000000000 R12: ffffffffc07ae040 [ +0.000001] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000 [ +0.000001] FS: 00007f026623dc40(0000) GS:ffff90db36180000(0000) knlGS:0000000000000000 [ +0.000001] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ +0.000001] CR2: ffffffffc07abfd6 CR3: 000000010bbbc004 CR4: 00000000003706e0 [ +0.000023] Call Trace: [ +0.000001] <TASK> [ +0.000001] ? __do_sys_delete_module.constprop.0+0x184/0x290 [ +0.000005] __x64_sys_delete_module+0x12/0x20 [ +0.000001] do_syscall_64+0x59/0xc0 [ +0.000002] ? syscall_exit_to_user_mode+0x27/0x50 [ +0.000002] ? __x64_sys_read+0x19/0x20 [ +0.000002] ? do_syscall_64+0x69/0xc0 [ +0.000001] ? do_syscall_64+0x69/0xc0 [ +0.000001] ? syscall_exit_to_user_mode+0x27/0x50 [ +0.000002] ? __x64_sys_close+0x11/0x50 [ +0.000002] ? do_syscall_64+0x69/0xc0 [ +0.000001] entry_SYSCALL_64_after_hwframe+0x61/0xcb [ +0.000003] RIP: 0033:0x7f0266365c9b [ +0.000001] Code: 73 01 c3 48 8b 0d 95 21 0f 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa b8 b0 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 65 21 0f 00 f7 d8 64 89 01 48 [ +0.000001] RSP: 002b:00007ffc31a91cc8 EFLAGS: 00000206 ORIG_RAX: 00000000000000b0 [ +0.000002] RAX: ffffffffffffffda RBX: 0000555cc11857d0 RCX: 00007f0266365c9b [ +0.000001] RDX: 000000000000000a RSI: 0000000000000800 RDI: 0000555cc1185838 [ +0.000001] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 [ +0.000001] R10: 00007f02663fdac0 R11: 0000000000000206 R12: 00007ffc31a91f20 [ +0.000001] R13: 0000555cc11852a0 R14: 00007ffc31a941ee R15: 0000555cc11857d0 [ +0.000001] </TASK> [ +0.000001] Modules linked in: bug_on_and_warn_on_kernel_module(OE-) rfcomm vsock_loopback bnep vmw_vsock_virtio_transport_common intel_rapl_msr intel_rapl_common vmw_vsock_vmci_transport crct10dif_pclmul vsock ghash_clmulni_intel aesni_intel crypto_simd cryptd rapl binfmt_misc vmw_balloon snd_ens1371 snd_ac97_codec gameport ac97_bus snd_pcm snd_seq_midi snd_seq_midi_event input_leds joydev snd_rawmidi serio_raw btusb btrtl nls_iso8859_1 btbcm snd_seq btintel snd_seq_device bluetooth ecdh_generic snd_timer ecc snd soundcore vmw_vmci mac_hid sch_fq_codel vmwgfx ttm drm_kms_helper cec rc_core fb_sys_fops syscopyarea sysfillrect sysimgblt ipmi_devintf ipmi_msghandler msr parport_pc ppdev lp ramoops parport reed_solomon pstore_blk drm pstore_zone mtd efi_pstore ip_tables x_tables autofs4 hid_generic usbhid hid crc32_pclmul psmouse mptspi mptscsih ahci mptbase e1000 libahci scsi_transport_spi i2c_piix4 pata_acpi [last unloaded: printk_kernel_module] [ +0.000038] ---[ end trace c91054109cb096bc ]--- [ +0.000001] RIP: 0010:bug_on_and_warn_on_kernel_module_exit+0x0/0x1000 [bug_on_and_warn_on_kernel_module] [ +0.000003] Code: Unable to access opcode bytes at RIP 0xffffffffc07abfd6. [ +0.000001] RSP: 0018:ffff9c31c55abe40 EFLAGS: 00010286 [ +0.000001] RAX: ffffffffc07ac000 RBX: 0000000000000000 RCX: 0001020304050608 [ +0.000001] RDX: 0000000000000000 RSI: ffff9c31c55abe68 RDI: ffffffff8e3ce300 [ +0.000001] RBP: ffff9c31c55abea0 R08: fefefefefefefeff R09: 0000000000000000 [ +0.000000] R10: 0000000000000000 R11: 0000000000000000 R12: ffffffffc07ae040 [ +0.000001] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000 [ +0.000001] FS: 00007f026623dc40(0000) GS:ffff90db36180000(0000) knlGS:0000000000000000 [ +0.000001] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ +0.000001] CR2: ffffffffc07abfd6 CR3: 000000010bbbc004 CR4: 00000000003706e0 zsh: segmentation fault (core dumped) sudo -E rmmod bug_on_and_warn_on_kernel_module.ko |
QEMUとGDBを利用したデバッグ方法
上記の簡単なコードでカーネルパニックの場所を見つけることは簡単ですが,複雑なコードでは難しいです.
なので,複雑なコードでカーネルパニックの場所を効率的に見つける方法を知りたいですよね.
そんなあなたにQEMUとGDBを利用したデバッグ方法を紹介します.
QEMUとGDB
QEMUは,仮想マシン全体をエミュレートすることができるフルシステムエミュレータです.
QEMUは,CPU,メモリ,デバイスのソフトウェアモデルを利用していますが,エミュレーションに時間がかかります.
また,QEMUはハードウェア仮想化支援機構(Hardware Virtualization Extensions)との併用により,高性能な仮想化を実現することも可能です.
参考までに,KVMは,インカーネルでの仮想化サポートとQEMUの支援機構を組み合わせています.
GDBはプログラムを効率的にデバッグするためのツールです.
まずはGDBを知りたいあなたはこちらからどうぞ.
GDBサーバは,元々はリモートマシン上で実行されているプログラムをデバッグするために利用します.
下図のように低性能な組込みシステムのようにリモートマシン上でGDBが利用できない場合にGDBクライアント・サーバとして通信することでGDBを実行できます.
GDBクライアント・サーバ間の通信には,TCP/IP,Serial(RS-232C等),JTAG等が利用されます.
QEMUとGDBを利用したカーネルのデバッグ方法
それでは,QEMUとGDBを利用したカーネルのデバッグ方法を紹介します.
上図のように,本記事ではGDBクライアント・サーバは同一のx86-64のホストPCであると仮定します.
※実機がARM64のボードの場合はアーキテクチャ依存やボード依存の設定に変更する必要があります.
Linuxカーネルは仮想マシン(QEMUまたはKVMでエミュレート)で実行されます.
ハードウェアデバイスはQEMUでエミュレートされます.
GDBサーバは,エミュレートされた仮想マシンであるQEMUで動作するため,QEMU上で動作するLinuxカーネルを完全に制御できます.
実機で動作する場合と比較して,QEMUを利用することでデバッグやコード探索が簡単にできます.
QEMUとGDBを利用したカーネルのデバッグは主に2つの方法がありますので,それぞれ解説していきます.
- 最小限のLinuxディストリビューションを実行する
- debootstrapディストリビューションを利用する
- ルートファイルシステムはホストシステムのディレクトリである
- ユーザ空間アプリケーションでの機能は限定的になる
- 完全なLinuxディストリビューションを実行する
- QEMUディスクイメージ(qcow2またはraw disk)を利用する
- ルートファイルシステムはディスクイメージ上にある
- ユーザ空間アプリケーションをフルに動作させることができる
以下のコマンドで実行環境をセットアップします.
1 |
$ sudo apt-get install qemu gdb debootstrap schroot ssh |
最小限のLinuxディストリビューション(debootstrap)を実行
QEMUとGDBによるデバッグ用カーネルの構築方法を紹介します.
GDBスクリプト,9p,virtioを有効にしてカーネルを再構築します.
.configにある以下の設定をしてLinuxカーネルをビルドして下さい.
- CONFIG_GDB_SCRIPTSを有効:「Provide GDB scripts for kernel debugging」をチェックする
- CONFIG_DEBUG_INFO_REDUCEDを無効:「Reduce debugging information」を未チェック状態にする
- CONFIG_FRAME_POINTERをサポートしている場合は有効の状態にする
CONFIG_GDB_SCRIPTSを有効,CONFIG_DEBUG_INFO_REDUCEDを無効にします.
1 2 3 4 5 6 7 |
$ make menuconfig # enable CONFIG_GDB_SCRIPTS -> Kernel hacking -> Compile-time checks and compiler options -> Compile the kernel with debug info (enable if disable) -> Provide GDB scripts for kernel debugging (enable CONFIG_GDB_SCRIPTS) -> Reduce debugging information (disable CONFIG_DEBUG_INFO_REDUCED) |
また,.configファイルで以下の設定になっているか確認して下さい.
もしそうでない場合は.configファイルを直接編集しましょう!
1 2 3 4 5 6 7 8 9 10 |
CONFIG_FRAME_POINTER=y CONFIG_DEBUG_INFO=y CONFIG_GDB_SCRIPTS=y CONFIG_E1000=y CONFIG_VIRTIO=y CONFIG_NET_9P=y CONFIG_NET_9P_VIRTIO=y CONFIG_9P_FS=y CONFIG_9P_FS_POSIX_ACL=y CONFIG_9P_FS_SECURITY=y |
後は,ビルドを成功するために,.configファイルを直接編集して以下の設定に変更しましょう.
※CONFIG_DEBUG_INFOは有効の状態にします.
「CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem"」を「CONFIG_SYSTEM_TRUSTED_KEYS=""」に変更
「CONFIG_SYSTEM_REVOCATION_KEYS="debian/canonical-revoked-certs.pem"」を「CONFIG_SYSTEM_REVOCATION_KEYS=""」に変更
「CONFIG_LOCALVERSION=""」を「CONFIG_LOCALVERSION="-mykernel"」のように好きな文字列に変更
https://hiroyukichishiro.com/build-linux-kernel-and-implement-new-system-call-in-c-language/
makeでビルドします.
1 2 |
$ make -j4 $ make -j4 modules |
デプロイを容易にするために必要な機能はビルトインされるので,「make modules_install」や「make install」は必要ないことに注意して下さい.
以下を作成します.
- linux-chroot:debootstrapでベーシックなDebianシステムのブートストラップファイルシステム
- .gdbinit:GDBの設定ファイル
1 2 |
$ sudo debootstrap --no-check-gpg --arch=amd64 --include=kmod,net-tools,iproute2,iputils-ping --variant=minbase bullseye linux-chroot $ echo "set auto-load safe-path" vmlinux-gdb.py > ~/.gdbinit |
QEMUでビルドしたLinuxカーネルを起動します.
QEMUの主なオプションは以下になります.
- -kernel:デバッグするLinuxカーネルのファイルを指定する.
- -s:GDBサーバを有効にし,ポート1234を開きます.
- -S:(オプション)最初のカーネル命令でGDBクライアント接続の継続を待って一時停止する.(以下の例では未使用)
rootの端末が起動することを確認しましょう.
1 2 3 4 5 6 7 8 9 10 |
$ sudo qemu-system-x86_64 -nographic -fsdev local,id=root,path=linux-chroot,security_model=passthrough -device virtio-9p-pci,fsdev=root,mount_tag=/dev/root -append 'nokaslr root=/dev/root rw rootfstype=9p rootflags=trans=virtio console=ttyS0 init=/bin/bash' -kernel arch/x86_64/boot/bzImage -smp cpus=4 -m 2G -s [ 0.000000] Linux version 5.15.0-mykernel (chishiro@aria) (gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #1 SMP Sat Sep 3 13:22:48 JST 2022 [ 0.000000] Command line: nokaslr root=/dev/root rw rootfstype=9p rootflags=trans=virtio console=ttyS0 init=/bin/bash [ 0.000000] KERNEL supported cpus: ... [ 3.423236] Run /bin/bash as init process [ 3.600855] random: fast init done bash: cannot set terminal process group (-1): Inappropriate ioctl for device bash: no job control in this shell root@(none):/# |
QEMUの実行を終了したい時は,「Ctrl-a + x」を入力します.
-Sオプションを利用する場合は,GDB側からLinuxカーネルを操作する必要があります.
QEMUを実行すると何も表示されません.
1 |
$ sudo qemu-system-x86_64 -nographic -fsdev local,id=root,path=linux-chroot,security_model=passthrough -device virtio-9p-pci,fsdev=root,mount_tag=/dev/root -append 'nokaslr root=/dev/root rw rootfstype=9p rootflags=trans=virtio console=ttyS0 init=/bin/bash' -kernel arch/x86_64/boot/bzImage -smp cpus=4 -m 2G -s -S |
他の端末でGDBを「sudo gdb vmlinux」で起動し,「target remote :1234」でLinuxカーネルに接続します.
そして,c(continue)を入力するとLinuxカーネルが起動するので確認しましょう.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$ sudo gdb vmlinux GNU gdb (Ubuntu 12.0.90-0ubuntu1) 12.0.90 Copyright (C) 2022 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from vmlinux... (gdb) target remote :1234 Remote debugging using :1234 0x000000000000fff0 in exception_stacks () (gdb) c Continuing. |
GDBを利用したLinuxカーネルのデバッグ方法の詳細を知りたいあなたは「Debugging kernel and modules via gdb」を読みましょう!
完全なLinuxディストリビューションを実行
完全なLinuxディストリビューションを実行する方法を紹介します.
.configにある以下の設定をしてLinuxカーネルをビルドして下さい.
- CONFIG_GDB_SCRIPTSを有効:「Provide GDB scripts for kernel debugging」をチェックする
- CONFIG_DEBUG_INFO_REDUCEDを無効:「Reduce debugging information」を未チェック状態にする
- CONFIG_FRAME_POINTERをサポートしている場合は有効の状態にする
CONFIG_GDB_SCRIPTSを有効,CONFIG_DEBUG_INFO_REDUCEDを無効にします.
1 2 3 4 5 6 7 |
$ make menuconfig # enable CONFIG_GDB_SCRIPTS -> Kernel hacking -> Compile-time checks and compiler options -> Compile the kernel with debug info (enable if disable) -> Provide GDB scripts for kernel debugging (enable CONFIG_GDB_SCRIPTS) -> Reduce debugging information (disable CONFIG_DEBUG_INFO_REDUCED) |
後は,ビルドを成功するために,.configファイルを直接編集して以下の設定に変更しましょう.
※CONFIG_DEBUG_INFOは有効の状態にします.
「CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem"」を「CONFIG_SYSTEM_TRUSTED_KEYS=""」に変更
「CONFIG_SYSTEM_REVOCATION_KEYS="debian/canonical-revoked-certs.pem"」を「CONFIG_SYSTEM_REVOCATION_KEYS=""」に変更
「CONFIG_LOCALVERSION=""」を「CONFIG_LOCALVERSION="-mykernel"」のように好きな文字列に変更
https://hiroyukichishiro.com/build-linux-kernel-and-implement-new-system-call-in-c-language/
makeでビルドします.
1 2 |
$ make -j4 $ make -j4 modules |
Ubuntuの公式ページからUbuntu 22.04 LTSのVMDKファイルをダウンロードします.
※自分で作成したVMDKファイルでもOKです.
1 |
$ wget https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.vmdk |
VMDKファイルをQCOW2ファイルに変換します.
1 |
$ qemu-img convert -O qcow2 ubuntu-22.04-server-cloudimg-amd64.vmdk ubuntu-22.04-server-cloudimg-amd64.qcow2 |
以下のコマンドで起動します.
debootstrapより遅いですが,Linuxカーネルが起動することを確認しましょう.
1 2 3 4 5 6 7 8 9 10 11 |
$ sudo qemu-system-x86_64 -s -nographic -hda ubuntu-22.04-server-cloudimg-amd64.qcow2 -kernel arch/x86_64/boot/bzImage -append "nokaslr root=/dev/sda1 console=ttyS0" -smp cpus=2 -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22 -m 2G ... [ OK ] Finished Wait for Network to be Configured. [ OK ] Started Network Time Synchronization. [ OK ] Reached target System Time Set. You are in emergency mode. After logging in, type "journalctl -xb" to view system logs, "systemctl reboot" to reboot, "systemctl default" or "exit" to boot into default mode. Press Enter for maintenance (or press Control-D to continue): root@ubuntu:~# |
まとめ
C言語でLinuxカーネルのデバッグ方法を紹介しました.
Linuxカーネルのデバッグ方法を習得して,開発を効率化しましょう!
Linuxカーネルに独自機能を追加する自作カーネルの開発手順をまとめると以下になります.
- カーネルの独自機能をテストするための簡単なユニットテストコードを書く.
- Linuxホストで開発する.
- 最小限のLinuxディストリビューション(debootstrap)のQEMU環境で,すべてのユニットテストケースがパスするまでテストとデバッグする.
- 完全なLinuxディストリビューションのQEMU環境でテストとデバッグする.
- 実機環境でテストとデバッグする.
Linuxカーネルのデバッグを深く理解したいあなたは,以下の記事を読みましょう!
- Debugging by printing
- KernelDebuggingTricks
- Kernel Debugging Tips
- Debugging kernel and modules via gdb
- Using kgdb, kdb and the kernel debugger internals
- gdb Cheatsheet
- Migrate a VirtualBox Disk Image (.vdi) to a QEMU Image (.img)
- The kernel’s command-line parameters
- An introduction to KProbes
- chroot,lxcコンテナ,QEMUエミュレータ
- QEMU を使った Linux のカーネルデバッグ
カーネルのデバッグ方法は以下の動画が参考になります.
GDBサーバの使い方は以下の動画がわかりやすいです.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!