Linux カーネルのデバッグ (2): カーネル エラー処理プロセス

1- カーネル エラー処理

カーネルで致命的なエラーが発生した場合、CPU が正常に動作している限り、最も重要なことは、詳細なエラー情報をユーザーに出力し問題が発生したときのエラー シーンを保存することです。上記の致命的なエラーには、次の 2 つのタイプが含まれる可能性があります。

  • (1) 不正なメモリアクセス、不正な命令など、ハードウェアで検出できるエラーは、この時点で CPU が例外をトリガーし、例外処理プロセスに入ります例外処理プロセス中におっとまたはパニックがトリガーされます。

  • (2) カーネル コードが、一部のコードでは処理できない異常な分岐に入る. この時点でプログラムが実行され続けると、予期しない結果が発生する可能性があります. このとき、関連するコードは積極的に oops または panic に入ります.

その中で、パニックの意味はパニック、パニック、つまりカーネルが続行できなくなり、構成に従ってクラッシュメモリをダンプするかどうかを決定し、パニックイベントに関係するモジュールにノーティファイアー通知を送信し、パニックに関連するシステム情報を出力し、最後にシステムをハングアップするか再起動します

oops の重大度は panic よりも低いため、通常は関連するエラー メッセージを出力するだけで、カーネルを中断せずにプロセスを終了します。しかし、割り込みコンテキストで oops が発生した場合、またはカーネルが panic_on_oops オプションを設定した場合も、パニックに入ります。

2-arm64例外情報レジスタ

arm64 アーキテクチャでは、CPU がメモリアクセスエラーなどで例外に入った場合esr レジスタで例外の原因を取得しfar レジスタで異常なメモリのアドレス情報を取得できますesr レジスタは次のように定義されます。
ここに画像の説明を挿入
上図のEC は例外タイプを示し、いくつかの典型的な値は次のとおりです。

  • (1) b100000: ユーザーモードでの不正な命令など、低い例外レベルからの命令エラー

  • (2) b100001: 現在の例外レベルの命令が間違っています

  • (3) b100010: PCアライメントエラー

  • (4) b100100: ユーザーモードでのメモリ例外など、低い例外レベルからのデータアボート例外

  • (5) b100101: 現在の例外レベルのデータアボート例外

  • (6) b100110: スタックポインタspアライメントエラー

  • (7) b101111: 非同期例外である Serror 割り込みは、通常、メモリがバスにアクセスするときに発生するアボート例外など、外部アボートによって発生します。

ILは例外発生時の命令長を示し、その値は次のとおりです。

  • (1) 0: 16 ビットのサム命令長を示します。

  • (2) 1: 32 ビットアーム命令の長さを示します。

ISS は各タイプの具体的な原因を示し、その値は EC によって異なります.たとえば、EC がデータ アボートの場合、対応する ISS は次のように定義されます (具体的な意味については、armv8 trm を参照してください)。

ここに画像の説明を挿入
その中で、DFSC (Data Fault Status Code) は、データアボートに関する情報を与えるために使用され、その定義の一部として次のようなものがあります
ここに画像の説明を挿入
。例外であるため、armv8 アーキテクチャは far レジスタを介してこれを提供します。アドレス (仮想アドレス) の値、およびそれに対応するレジスタは次のように定義されます。

ここに画像の説明を挿入

3- 例外処理プロセス

カーネルで同期例外が発生すると、対応する例外処理エントリにジャンプします。

例外処理関数は、コンテキストの保存やスタック ポインターの切り替えなどの基本的なタスクを実行した後、特定の種類のハンドラーにジャンプします。例外が発生したときに CPU が arm64 モードであり、使用されているスタック ポインターが sp_el1 である場合、それはel1h_64_sync_handlerにジャンプします。

この関数は、esr_el1 レジスタの EC の値に従って、対応する例外タイプを取得し、特定の例外タイプに関連する処理関数を呼び出しますこの関数では、通常、esr_el1 レジスタの ISS の値から特定の異常原因を取得し、対応する処理を実行します。

処理フローでは、例外が実際に不正な操作によって引き起こされた場合 (ページ フォールト例外、ブレークポイント、シングル ステップ デバッグなどの例外は必ずしもエラーではなく、その他のデバッグ例外は通常のコード処理ロジックです)、呼び出します。 oops または panic to ユーザーがエラーを報告し、現在のプロセスを終了するか、システムをハングさせます。

カーネルには多くの種類の例外があり、その処理手順も類似しているため、arm64 モードでのカーネルの不正なアドレス アクセスを例に説明します。対応する処理の流れは次のとおりです。

ここに画像の説明を挿入

3.1 データアボート処理の流れ

el1h_64_sync_handler は、最初に esr_el1 register の値を読み取り、次にEC の内容を解析し、EC 値に従って対応する処理関数を呼び出します。たとえば、el1_abort はデータ アボートに対して呼び出されます。次のコードはそれを実装します:

asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
{
	unsigned long esr = read_sysreg(esr_el1);                  

	switch (ESR_ELx_EC(esr)) {                           
	case ESR_ELx_EC_DABT_CUR:
	case ESR_ELx_EC_IABT_CUR:
		el1_abort(regs, esr);
		break;
	case ESR_ELx_EC_PC_ALIGN:
		el1_pc(regs, esr);
		break;
	…
	default:
		__panic_unhandled(regs, "64-bit el1h sync", esr);
	}
}

el1_abort はdo_mem_abortを呼び出します, これは esr_el1 レジスタの DFSC の値に従って、対応する処理関数を呼び出します. これらの関数は、以下に示す fault_info 変数によって定義されます:

static const struct fault_info fault_info[] = {
	…
	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"level 0 translation fault"		},
	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"level 1 translation fault"		},
	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"level 2 translation fault"		},
	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"level 3 translation fault"		},
	{ do_bad,		SIGKILL, SI_KERNEL,	"unknown 8"			},
	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 1 access flag fault"	},
	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 2 access flag fault"	},
	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 3 access flag fault"	},
	…
}

以下は、do_mem_abort のコード フローです。

void do_mem_abort(unsigned long far, unsigned int esr, struct pt_regs *regs)
{
	const struct fault_info *inf = esr_to_fault_info(esr);          (1)
	unsigned long addr = untagged_addr(far);                        (2)

	if (!inf->fn(far, esr, regs))                                   (3)
		return;

	if (!user_mode(regs)) {                                         (4)
		pr_alert("Unhandled fault at 0x%016lx\n", addr);
		mem_abort_decode(esr);
		show_pte(addr);
	}

	arm64_notify_die(inf->name, regs, inf->sig, inf->code, addr, esr);
}
  • (1) DFSC の値に従って、fault_info 配列内の対応する処理関数ポインタを選択します。

  • (2) arm64 アーキテクチャは、仮想アドレスの空き上位ビットを使用してタグ情報を格納し、MTE 機能をサポートできるためです。したがって、実際の仮想アドレスを取得するときに、対応するタグ情報を最初に削除する必要があります。

  • (3) fault_info で取得したコールバック関数を呼び出す 不正アドレスアクセスエラーの場合、対応するコールバック関数は do_translation_fault

  • (4) 例外が不明な例外の場合は、次のプロセスで直接エラー処理を実行します。

do_translation_fault は、例外がユーザー モードまたはカーネル モードのどちらによってトリガーされたかに応じて、対応する処理関数を呼び出します。

static int __kprobes do_translation_fault(unsigned long far,
					  unsigned int esr,
					  struct pt_regs *regs)
{
	…
	if (is_ttbr0_addr(addr))
		return do_page_fault(far, esr, regs);               (1)

	do_bad_area(far, esr, regs);                                (2)
	return 0;
}
  • (1) ユーザーモード処理機能

  • (2) カーネルモード処理機能

カーネル モードの状況では、最終的に die_kernel_fault を呼び出して実際のエラー処理を実行します。そのコードは次のとおりです。

static void die_kernel_fault(const char *msg, unsigned long addr,
			     unsigned int esr, struct pt_regs *regs)
{
	…
	mem_abort_decode(esr);                             (1)

	show_pte(addr);                                    (2)
	die("Oops", regs, esr);                            (3)
	bust_spinlocks(0);
	do_exit(SIGKILL);                                  (4)
}

  • (1) esr_el1 レジスタの値を解析し、EC、IL、DFSC などの関連する内容を出力します。

  • (2) この関数は、pgd、p4d、pud、pmd、および pte などを含む、異常なアドレスに対応するページテーブル情報を出力します。

  • (3) 実際の金型操作を実行します。これについては、次のセクションで取り上げます。

  • (4) 現在のプロセスを強制終了する

3.2 金型加工の流れ

die 関数は主に oops 関連の処理を実行し、割り込み処理で例外がトリガーされたり、panic_on_oops オプションが設定されている場合、システムはパニックによってさらに中断されます。その主なプロセスは次のとおりです。

void die(const char *str, struct pt_regs *regs, int err)
{
	…
	ret = __die(str, err, regs);                                  (1)

	if (regs && kexec_should_crash(current))
		crash_kexec(regs);                                    (2)
	…
	if (in_interrupt())
		panic("%s: Fatal exception in interrupt", str);
	if (panic_on_oops)                                            (3)
		panic("%s: Fatal exception", str);
	…
}
  • (1) 金型関連の通知チェーンに対応する通知を呼び出して、金型関連の操作を実行させ、oops 関連の情報を出力します。

  • (2) クラッシュ システムが必要な場合は、この関数を使用して新しいクラッシュ カーネルを開始し、事後分析のために新しいカーネルを介してシステム メモリ情報をダンプします。たとえば、対応するクラッシュ カーネルは、kdump または ramdump によって構成できます。

  • (3) 例外が割り込みで発生した場合、または panic_on_oops が設定されている場合は、panic を呼び出してシステムをサスペンドします。

3.3 パニック処理の流れ

カーネルがパニック状態になると、それは実行を継続できないことを示しているため、システムがハングアップする前にいくつかの準備を行う必要があります。これには主に次の部分が含まれます。

  • (1) smp システムでは、1 つの CPU がパニックを処理しているときに、別の CPU もパニックを引き起こす可能性があります。ただし、このプロセスは主に一部のエラー情報の収集、メモリ ダンプなどに使用され、同時操作を必要とせず、サポートもしていません。したがって、後続のトリガーされた cpu に対してプロセスを実行する必要はありません。

  • (2) kgdb を使用してカーネルをデバッグしている場合、デバッガーが引き続きデバッグ作業を実行できることを期待することは明らかです。したがって、この時点でシステムがハングすることはありませんが、制御はデバッガーに移されます。

  • (3) カーネルに kdump などのメモリダンプ機能が設定されている場合、パニック発生時にダンプ関連のプロセスが起動されます。

  • (4) smp システムがハングアップする前に、システムを本当に停止させるために、他のすべての CPU の動作を停止する必要があります。

  • (5) 最後に、関連するシステム情報を出力した後、システムを再起動するか、無限ループに入ります。

対応するコードの実装は次のとおりです。

void panic(const char *fmt, ...)
{
	…
	this_cpu = raw_smp_processor_id();
	old_cpu  = atomic_cmpxchg(&panic_cpu, PANIC_CPU_INVALID, this_cpu);

	if (old_cpu != PANIC_CPU_INVALID && old_cpu != this_cpu)                       (1)
		panic_smp_self_stop();
	…
	pr_emerg("Kernel panic - not syncing: %s\n", buf);
	…
	kgdb_panic(buf);                                                               (2)

	if (!_crash_kexec_post_notifiers) {
		printk_safe_flush_on_panic();
		__crash_kexec(NULL);                                                   (3)

		smp_send_stop();                                                       (4)
	} else {
		crash_smp_send_stop();                                                 (5)
	}

	atomic_notifier_call_chain(&panic_notifier_list, 0, buf);                      (6)

	printk_safe_flush_on_panic();
	kmsg_dump(KMSG_DUMP_PANIC);                                                    (7)

	if (_crash_kexec_post_notifiers)
		__crash_kexec(NULL);                                                   (8)

	…
	panic_print_sys_info();                                                        (9)

	if (!panic_blink)
		panic_blink = no_blink;

	if (panic_timeout > 0) {
		pr_emerg("Rebooting in %d seconds..\n", panic_timeout);

		for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP) {
			touch_nmi_watchdog();
			if (i >= i_next) {
				i += panic_blink(state ^= 1);
				i_next = i + 3600 / PANIC_BLINK_SPD;
			}
			mdelay(PANIC_TIMER_STEP);                                      (10)
		}
	}
	if (panic_timeout != 0) {
		if (panic_reboot_mode != REBOOT_UNDEFINED)
			reboot_mode = panic_reboot_mode;
		emergency_restart();                                                   (11)
	}
	…
	pr_emerg("---[ end Kernel panic - not syncing: %s ]---\n", buf);

	suppress_printk = 1;
	local_irq_enable();
	for (i = 0; ; i += PANIC_TIMER_STEP) {
		touch_softlockup_watchdog();
		if (i >= i_next) {
			i += panic_blink(state ^= 1);
			i_next = i + 3600 / PANIC_BLINK_SPD;
		}
		mdelay(PANIC_TIMER_STEP);                                              (12)
	}
}
  • (1) CPU がすでにパニック プロセスを処理している場合、この CPU はプロセスを繰り返さず、現在の CPU を停止するだけです。

  • (2) パニック理由情報の出力

  • (3) パニック プロセスがメモリ ダンプを実行する場合、システム関連のすべての情報がダンプ ファイルに保存されるため、次の通知チェーンを呼び出す必要がないため、ダンプ操作を直接呼び出すことができます。ただし、ダンプ操作は 100% 安全ではないため、完全に信頼できない場合は、_crash_kexec_post_notifiers を設定します。これにより、最初に通知チェーンの呼び出しとログ ダンプ関連のプロセスが実行され、次にコア ダンプ操作が呼び出されます。
      カーネルをダンプして、実際にダンプ操作を実行するかどうかを判断するために、ダンプが実行されると、システムは kexec を介して新しい kdump カーネルに切り替わり、戻りませんダンプを実行しない場合、後続のプロセスが続行されます

  • (4 - 5) 現在の cpu 以外の他の cpu の動作を停止する

  • (6) パニックイベントに関連する関連モジュールによって登録された通知を呼び出す

  • (7) カーネルログバッファにログ情報をダンプする

  • (8) _crash_kexec_post_notifiers が設定されている場合、kexec カーネルが設定されているかどうかに応じて、メモリ ダンプ操作を実行するかどうかを決定します。

  • (9) メモリダンプが実行されていない場合は、システム関連の情報を出力します

  • (10) panic_timeout タイムアウト値が設定されている場合、タイムアウト待ち操作を行う

  • (11) panic_timeout タイムアウト値が設定されている場合、タイムアウト待ち完了後にシステムを再起動します。

  • (12) panic_timeout タイムアウト値が設定されていない場合、システムを無限ループ状態に設定し、システムをハングさせます。

4-おっと、パニックを手動でトリガーする方法

コーディング プロセスでは、予期しないコード ブランチが発生する場合があります。システムがこれらのブランチに入ると、問題または重大なエラーが発生したことを示します。問題の重大度に応じて、プログラムにいくつかの警告メッセージを出力させたり、システムを oops に設定したり、さらにはパニックにさせたりすることが必要になる場合があります。

この目的のために、カーネルは、上記の要件をサポートするためにいくつかの関連するマクロと関数を提供します. 以下は、一般的に使用される定義の一部です:

  • (1) WARN_ON(): 警告情報を出力し、スタックを呼び出しますが、oops または panic には入りません

  • (2) BUG_ON(): バグ関連の情報を出力し、oops プロセスに入る

  • (3) panic(): この関数は、パニック プロセスを直接開始し、システムをハング状態に設定します。

コーディングに加えて、ユーザーは sysrq マジック キーを介してパニック プロセスをトリガーすることもできます。次のコマンドは、proc メソッドを介して sysrq 関連のパニック プロセスをトリガーします。

  echo c > /proc/sysrq-trigger

元のリンク: https://zhuanlan.zhihu.com/p/545307249

おすすめ

転載: blog.csdn.net/weixin_45264425/article/details/129509648