さらに、PageFaultの例では、例外処理プロセスのいくつかの機能と、x86アーキテクチャ用のLinuxカーネルモジュールを開発する際にこの機能を使用できる方法の説明が考慮されます。
コア例外
カーネルで例外が使用される場所と方法の例として、カーネル空間とユーザー空間の間でデータをコピーすることを検討してください。 通常、関数copy_from_userとcopy_to_userがこれを担当します。memcpyとは異なる特徴は、異なるアドレススペース間のデータ転送中に発生する例外を正しく処理することです。
実際、データがカーネルからユーザーにコピーされる状況(
copy_to_user
関数)を考慮すると、書き込みが試行されるユーザープロセスのページがスワップ内にあるか、プロセスにまったくアクセスできない場合に状況が発生する可能性があります。 そして、最初のケースで問題の正しい解決策がこのページをロードしてコピーを継続することである場合、2番目のケースでは操作を中断してエラーコードをユーザーに返す必要があります(たとえば
-EINVAL
)。
明らかに、欠落しているページに対応するアドレスをアドレス指定するコマンドを実行すると、例外、つまりページ障害の例外 、またはページ
#PF
(
#PF
)が発生します。 この時点で、カーネルは現在のタスクのコンテキストを保存し、対応するハンドラーのコードdo_page_faultを実行します。 何らかの方法で問題を修正し、カーネルは中断されたタスクのコンテキストを復元します。 ただし、例外の処理結果によっては、戻りアドレスが例外の原因となった命令のアドレスと異なる場合があります。 つまり、カーネルで提供されるメカニズムのおかげで、潜在的に「危険な」命令に対して、実行時に例外がスローされた場合に作業を続行するアドレスを指定することができます。
例外処理インターフェース
示されたメカニズムの実装方法を理解するには、カーネルからユーザーに4バイトをコピーするプリミティブの実装( __put_user_4関数)を検討する価値があります。
62 ENTRY(__put_user_4) 63 ENTER 64 mov TI_addr_limit(%_ASM_BX),%_ASM_BX 65 sub $3,%_ASM_BX 66 cmp %_ASM_BX,%_ASM_CX 67 jae bad_put_user 68 ASM_STAC 69 3: movl %eax,(%_ASM_CX) <- 70 xor %eax,%eax 71 EXIT 72 ENDPROC(__put_user_4) ... 89 bad_put_user: 90 CFI_STARTPROC 91 movl $-EFAULT,%eax 92 EXIT ... 98 _ASM_EXTABLE(3b,bad_put_user)
ご覧のとおり、アドレス範囲のチェックに加えて、この関数はデータを直接転送します(69行目の
movl
命令)。 これは、例外が予想される場所です。なぜなら、 ターゲットアドレスが実際にユーザー空間アドレスの範囲に属しているという事実に加えて、それに関する情報はこれ以上ありません。 次に、次の_ASM_EXTABLEマクロに注意する必要があります。
43 # define _ASM_EXTABLE(from,to) \ 44 .pushsection "__ex_table","a" ; \ 45 .balign 8 ; \ 46 .long (from) - . ; \ 47 .long (to) - . ; \ 48 .popsection
このマクロのアクションは、
from
および
to
2つの値を特別な
__ex_table
セクションに追加することです。これは、
__ex_table
とおり、69行目の「疑わしい」命令のアドレスと、例外が処理された後に実行を続ける命令に対応します。すなわち
bad_put_user
。
__ex_table
エントリを追加すると、障害点を管理しやすくなります。 このテーブルは、例外を処理するときにカーネルによって使用されます。
例外テーブルとその処理
そのため、前述したように、例外テーブルはこれらの命令に関する情報が保存される中心的な場所であり、その間のエラーは個別に処理する必要があります。 今後、カーネル自体のテーブルに加えて、モジュールごとに個別のテーブルも提供されることに注意してください。 ただし、 exception_table_entry構造によって記述される要素の構造を考慮する価値があります。
97 struct exception_table_entry { 98 int insn, fixup; 99 };
ご覧のとおり、テーブル要素の形式は、マクロ
_ASM_EXTABLE
調べたときに明らかになったものに対応しています。 最初の要素は命令を記述し、2番目の要素は例外の場合に制御が転送されるコードを記述します。 ページ障害例外が発生するたびに、Linuxカーネルは、とりわけ、この例外の原因となったコマンドのアドレスがカーネル
__ex_table
テーブルに
__ex_table
いるか、ロードされたモジュールのテーブルに
__ex_table
いるかを
__ex_table
ます。 そのようなレコードが見つかった場合、対応するアクションが実行されます。 そうでない場合、カーネルはいくつかの標準ロジックを実行して例外処理を完了します。
カーネルモジュールの個々の例外テーブルに関しては、これらのテーブルの要素の形式は標準であり、カーネルの形式に対応しています。 各モジュールのこのようなテーブルへのリンクは、
THIS_MODULE->extable
で利用できますが、テーブル要素の数は
THIS_MODULE->num_exentries
含まれてい
THIS_MODULE->num_exentries
。 THIS_MODULEマクロ自体は、モジュール記述子構造へのリンクを提供します。
223 struct module 224 { ... 276 /* Exception table */ 277 unsigned int num_exentries; 278 struct exception_table_entry *extable; ... 378 };
以下は、例外の原因となった命令に一致するハンドラーを検索する主要なカーネル関数です。 彼女のコードは次のとおりです 。
50 /* Given an address, look for it in the exception tables. */ 51 const struct exception_table_entry *search_exception_tables(unsigned long addr) 52 { 53 const struct exception_table_entry *e; 54 55 e = search_extable(__start___ex_table, __stop___ex_table-1, addr); 56 if (!e) 57 e = search_module_extables(addr); 58 return e; 59 }
ご覧のとおり、まず最初に、
__ex_table
カーネルのベーステーブルで検索が実行され、その後、結果がない場合にのみ、モジュール例外テーブル間で検索が続行されます。 命令アドレスに一致するハンドラーがない場合、この関数を実行するカーネルの結果は
NULL
になり
NULL
。 それ以外の場合、結果は例外テーブルの対応する要素へのポインタになります。
カーネルモジュールでの例外処理
したがって、例外を処理するための一般的な手順が明確であれば、トレーニングのために、例外を作成して処理することを目的とするモジュールを作成できます。 すでに書いたコードはgithubで入手できます。 次に、コードの簡単な説明とコメントを提供します。
したがって、通常のNULLポインターの逆参照を行う関数でPageFault例外の生成を処理します。
static void raise_page_fault(void) { debug(" %s enter\n", __func__); ((int *)0)[0] = 0xdeadbeef; debug(" %s leave\n", __func__); }
明らかに、nullポインターに書き込もうとすると、クラッシュが発生します。 そして、これはまさにあなたが必要とするものです。 適切に対応するには、次のことを行う必要があります。
- 例外の原因となっている命令のアドレスを決定する
- 有効な要素
exception_table_entry
作成します - 作成した要素をモジュールの
extable
テーブルに追加します
以下は、 udis86を使用した逆アセンブリを使用して上記のアクションを実行する関数です。
static int fixup_page_fault(struct exception_table_entry * entry) { ud_t ud; ud_initialize(&ud, BITS_PER_LONG, \ UD_VENDOR_ANY, (void *)raise_page_fault, 128); while (ud_disassemble(&ud) && ud.mnemonic != UD_Iret) { if (ud.mnemonic == UD_Imov && \ ud.operand[0].type == UD_OP_MEM && ud.operand[1].type == UD_OP_IMM) { unsigned long address = \ (unsigned long)raise_page_fault + ud_insn_off(&ud); extable_make_insn(entry, address); extable_make_fixup(entry, address + ud_insn_len(&ud)); return 0; } } return -EINVAL; }
ご覧のとおり、最初のステップは逆
raise_page_fault
を構成することです(分析の始まりは
raise_page_fault
)。 次に、指定された検索深度で、コマンドを繰り返し処理します。 目的のコマンド(操作
((int *)0)[0] = 0xdeadbeef;
)は、タイプ
UD_OP_MEM
第1オペランドとタイプ
UD_OP_MEM
の第2オペランドを持つ通常の
movl $0xdeadbeef, 0
UD_OP_IMM
。 コマンドアドレスが見つかるとすぐに、テーブル要素が形成されます。 同時に、機能が実行されます。
static void extable_make_insn(struct exception_table_entry * entry, unsigned long addr) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0) entry->insn = (unsigned int)((addr - (unsigned long)&entry->insn)); #else entry->insn = addr; #endif } static void extable_make_fixup(struct exception_table_entry * entry, unsigned long addr) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0) entry->fixup = (unsigned int)((addr - (unsigned long)&entry->fixup)); #else entry->fixup = addr; #endif }
それらの最初は、構造内の命令のアドレスを形成します。 2番目はfixapアドレス、つまり 制御が転送されるコマンド。 カーネル3.5から、
exception_table_entry
構造に小さな変更が発生したことに注意することが重要です。つまり、フィールドの次元が縮小されました-64ビットアーキテクチャの
insn
および
fixup
です。 これにより、アドレスの保存に必要なメモリを削減できましたが、計算ロジックはわずかに変更されました。 したがって、カーネル3.5以降、
insn
および
fixup
、これらの要素に関連するアドレスオフセットに対応する32ビット値が格納されます。 706276543b699d80f546e45f8b12574e7b18d952をすべて台無しにするコミットをもたらすことに興味がある人のために。
おわりに
この例は、カーネルモジュールを使用してLinuxカーネルの例外処理を管理する機能を示しています。 このテスト例では、事前に準備された環境、つまり構成されたモジュール
exables
テーブルで例外(PageFault)が呼び出されました。 後者の状況により、異常終了を解消し、緊急指示に続くコマンドでプログラムの実行を継続することができました。
さらに、準備されたテストケースにより、 除算エラー (#DE)や未定義オペコード (#UD)などの他の例外を処理する可能性を評価できます。
struct { const char * name; int (* fixup)(struct exception_table_entry *); void (* raise)(void); } exceptions[] = { { .name = "0x00 - div0 error (#DE)", .fixup = fixup_div0_error, .raise = raise_div0_error, }, { .name = "0x06 - undefined opcode (#UD)", .fixup = fixup_undefined_opcode, .raise = raise_undefined_opcode, }, { .name = "0x14 - page fault (#PF)", .fixup = fixup_page_fault, .raise = raise_page_fault, }, };