SlideShare una empresa de Scribd logo
1 de 35
Descargar para leer sin conexión
4 章 Linux カーネル – 割り込み・例外 5
・ IDT
・ IDT の初期化
mao
Web >https://www.pridact.com
Twitter >https://twitter.com/rivarten
Mail   >official@pridact.com
はじめに
割り込み・例外の話
視点
・概要
・ソースコード
ソースコードの情報が載っている場合は ,
実際にソースコードに目を通しながら資料を見ていっ
たほうがいいかもしれません。
今回はソースコードを読み進めていきます。
この資料について
・間違っていたらご指摘をお願い致します。
・ Linux Kernel のバージョンは 4.9.16 です。
・ページタイトルに * 印が付いているページは、
 少し踏み込んだ内容になっています。
 ざっと全体に目を通したければ、読み飛ばして
 もらっても構いません。
IDT (1)
・システム初期化時 , カーネルが割り込みを許可する前に ,
・ idtr レジスタに IDT の先頭アドレスを指定
・ IDT 内の全エントリを初期化
・ユーザーモードプロセスが int 命令を使用して 0~255 までの任意ベクタの
 割り込みを発行できる為 , 不正な割り込みや例外の発生を防ぐ為に ,IDT
 の初期化は慎重に行う .
Linux における初期化処理
a. 特定の割り込みやトラップのゲートディスクリプタの DPL を 0 に設定し ,
 不正な割り込み信号には一般保護例外を発生させるようにする .
b. ユーザーモードプロセスが特定の例外を直接生成できるようにする為に ,
 割り込みやトラップゲートのディスクリプタの DPL を 3 に設定し ,
 例外を発生させることを可能にする .
IDT (2)
・ Intel が提供している割り込みディスクリプタ
・割り込みゲートディスクリプタ
・トラップゲートディスクリプタ
・タスクゲートディスクリプタ
IDT (3)
・ Linux が提供している割り込みディスクリプタ
・割り込みゲート
Intel の割り込みゲート .DPL=0.
割り込みハンドラはすべて割り込みゲートで実装 .
ユーザーモードからアクセス不可 .
・システムトラップゲート ( 旧システムゲート )
Intel のトラップゲート .DPL=3.
ベクタ 4,5,128 の例外ハンドラはシステムトラップゲートで実装 .
into,bound,int $0x80 アセンブリ命令はユーザーモードでも使用可 .
・システム割り込みゲート
Intel の割り込みゲート .DPL=3.
ベクタ 3 の例外ハンドラはシステム割り込みゲートで実装 .
int3 アセンブリ命令はユーザーモードでも実行可 .
・トラップゲート
Intel のトラップゲート .DPL=0.
ほとんどの例外ハンドラはトラップゲートで実装 .
ユーザーモードからアクセス不可 .
・タスクゲート
Intel のタスクゲート .DPL=0. ダブルフォルト例外ハンドラの実装 .
IDT の初期化 (1)
・ IDT へのゲート登録
・ set_intr_gate(n,addr)
・ set_system_trap_gate(n,addr) ( 旧 set_system_gate)
・ set_system_intr_gate(n,addr)
・ set_trap_gate(n,addr)
・ set_task_gate(n,addr)
IDT の初期化 (2)
・ IDT へのゲート登録
・ set_intr_gate(n,addr)
IDT の n 番目のエントリに割り込みゲートを登録する .
ゲート内のセグメントセレクタには ,__KERNEL_CS が設定される .
オフセットフィールドには , 割り込みハンドラのアドレス addr を設定 .
DPL=0 で設定される .
※ すべての割り込みハンドラをこの関数で登録 .
[ver4.9.16 arch/x86/include/asm/desc.h:371]
#define set_intr_gate(n, addr)
do {
set_intr_gate_notrace(n, addr);
_trace_set_gate(n, GATE_INTERRUPT, (void*)trace_##addr,
0, 0, __KERNEL_CS);
} while (0)
[ver4.9.16 arch/x86/include/asm/desc.h:364]
#define set_intr_gate_notrace(n, addr)
do {
BUG_ON((unsigned)n > 0xFF);
_set_gate(n, GATE_INTERRUPT, (void *)addr, 0, 0,
__KERNEL_CS);
} while (0)
IDT の初期化 (3)
・ IDT へのゲート登録
・ set_system_trap_gate(n,addr)
IDT の n 番目のエントリにトラップゲートを登録する .
ゲート内のセグメントセレクタには ,__KERNEL_CS が設定される .
オフセットフィールドには , 例外ハンドラのアドレス addr を設定 .
DPL=3 で設定される .
ベクタ 4,5,128 がこのゲート .
[ver4.9.16 arch/x86/include/asm/desc.h:408]
static inline void set_system_trap_gate(unsigned int n, void *addr)
{
BUG_ON((unsigned)n > 0xFF);
_set_gate(n, GATE_TRAP, addr, 0x3, 0, __KERNEL_CS);
}
IDT の初期化 (4)
・ IDT へのゲート登録
・ set_system_intr_gate(n,addr)
IDT の n 番目のエントリに割り込みゲートを登録する .
ゲート内のセグメントセレクタには ,__KERNEL_CS が設定される .
オフセットフィールドには , 例外ハンドラのアドレス addr を設定 .
DPL=3 で設定される .
ベクタ 3 がこのゲート .
[ver4.9.16 arch/x86/include/asm/desc.h:402]
static inline void set_system_intr_gate(unsigned int n, void *addr)
{
BUG_ON((unsigned)n > 0xFF);
_set_gate(n, GATE_INTERRUPT, addr, 0x3, 0, __KERNEL_CS);
}
IDT の初期化 (5)
・ IDT へのゲート登録
・ set_trap_gate(n,addr)
set_system_trap_gate と同じだが ,DPL=0 で設定する .
殆どの例外ハンドラがこのゲート .
[ver4.9.16 arch/x86/include/asm/desc.h:414]
static inline void set_trap_gate(unsigned int n, void *addr)
{
BUG_ON((unsigned)n > 0xFF);
_set_gate(n, GATE_TRAP, addr, 0, 0, __KERNEL_CS);
}
IDT の初期化 (6)
・ IDT へのゲート登録
・ set_task_gate(n,addr)
IDT の n 番目のエントリにタスクゲートを登録する .
ゲート内のセグメントセレクタには ,
GDT 内の TSS セグメントのインデックスが設定される .
この TSS セグメントは , 実行する関数を含んでいるセグメント .
オフセットフィールドには ,0 を設定 .
DPL=0.
ダブルフォルト例外ハンドラがこのゲート .
[ver4.9.16 arch/x86/include/asm/desc.h:420]
static inline void set_task_gate(unsigned int n, void *addr)
{
BUG_ON((unsigned)n > 0xFF);
_set_gate(n, GATE_TASK, (void *)0, 0, 0, (gdt_entry<<3));
}
IDT の初期化 (7) *
・ IDT へのゲート登録
・全ゲート共通 (1)
[ver4.9.16 arch/x86/include/asm/desc.h:344]
static inline void _set_gate(int gate, unsigned type, void *addr,
unsigned dpl, unsigned ist, unsigned seg)
{
gate_desc s;
pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
write_idt_entry(idt_table, gate, &s);
write_trace_idt_entry(gate, &s);
}
[ver4.9.16 arch/x86/include/asm/desc.h:55] CONFIG_X86_64
static inline void pack_gate(gate_desc *gate, unsigned type, unsigned long func,
unsigned dpl, unsigned ist, unsigned seg)
{
gate->offset_low = PTR_LOW(func);
gate->segment = __KERNEL_CS;
gate->ist = ist;
gate->p = 1;
gate->dpl = dpl;
gate->zero0 = 0;
gate->zero1 = 0;
gate->type = type;
gate->offset_middle = PTR_MIDDLE(func);
gate->offset_high = PTR_HIGH(func);
}
IDT の初期化 (8) *
・ IDT へのゲート登録
・全ゲート共通 (2)
[ver4.9.16 arch/x86/include/asm/desc.h:71] #else (CONFIG_X86_32
static inline void pack_gate(gate_desc *gate, unsigned type, unsigned long func,
unsigned dpl, unsigned ist, unsigned seg)
{
gate->a = (seg << 16) | (base & 0xffff);
gate->b = (base & 0xffff0000) | (((0x80 | type | (dpl << 5)) & 0xff) << 8);
}
[ver4.9.16 arch/x86/include/asm/desc.h:324]
static inline void _trace_set_gate(int gate, unsigned type, void *addr,
unsigned dpl, unsigned ist, unsigned seg)
{
gate_desc s;
pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
write_trace_idt_entry(gate, &s);
}
[ver4.9.16 arch/x86/include/asm/desc.h:106]
#define write_idt_entry(dt, entry, g) native_write_idt_entry(dt, entry, g)
[ver4.9.16 arch/x86/include/asm/desc.h:119]
static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
{
memcpy(&idt[entry], gate, sizeof(*gate));
}
IDT の初期化 (9)
・コンピュータの起動直後 , まだリアルモードで動作している時にも ,
  BIOS は IDT を初期化して使用している .(1 度目の初期化 )
・ Linux が起動すると ,Linux は BIOS ルーチンを使用しない為 ,
  IDT を RAM の別の領域へ移動させ ,2 度目の初期化を行う .
・ idt_table テーブル
IDT の 256 エントリのテーブル
[ver4.9.16 arch/x86/kernel/traps.c:82]
gate_desc idt_table[NR_VECTORS] __page_aligned_bss;
・ gate_desc/desc_struct 構造体
[ver4.9.16 arch/x86/include/asm/desc_defs.h:22]
struct desc_struct {
union {
struct {
unsigned int a;
unsigned int b; };
struct {
u16 limit0; #bit15-0(limit15-0)
u16 base0; #bit31-16(base15-0)
unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1; #bit39-32(base23-16)
#bit43-40(type)/bit44(S)
#bit46-45(DPL)/bit47(P)
unsigned limit:4, avl: 1, l: 1, d: 1, g: 1, base2: 8; }; #bit51-48(limit19-16)
#bit52(AVL)/bit53(L)/bit54(D)
#bit55(G)/bit63-56(base31-24)
}; } __attribute__((packed));
※ 参考 2 章 Linux カーネル – メモリ管理 1 アドレス変換機能  x86 におけるセグメンテーション
IDT の初期化 (10)
・ idt_descr 変数
IDT の大きさとアドレス .
システム初期化段階で ,
カーネルが lidt アセンブリ命令で idtr レジスタを設定する時に使用 .
[ver4.9.16 arch/x86/kernel/head_32.S:727]
.data
.globl boot_gdt_descr
.globl idt_desc
ALIGN
…
boot_gdt_descr:
…
idt_descr:
.word IDT_ENTRIES*8-1
.long idt_table
IDT の初期化 (11)
・ 1 度目の初期化 (1) - ゼロ埋め
ブートローダーから起動
--> X+0x200: カーネルセットアップコード (arch/x86/boot/header.S) : _start
--> カーネルセットアップコード ヘッダ (arch/x86/boot/compressed/head_32.S:63) : startup_32
--> カーネルセットアップコード メイン (init/main.c) : main()
[ver 4.9.16 arch/x86/boot/main.c:135]
void main(void){
…
go_to_protected_mode()
}
[ver 4.9.16 arch/x86/boot/pm.c:104]
void go_to_protected_mode(void)
{
readmode_switch_hook();
if (enable_a20()) {
die();
}
reset_coprocessor();
mask_all_interrupts();
setup_idt();
setup_gdt();
protected_mode_jump(boot_params.hdr.code32_start,
(u32)&boot_params + (ds() << 4)); //0x00100000 にジャンプ
}
[ver4.9.16 arch/x86/boot/pm.c:95]
static void setup_idt(void){
staic const struct gdt_ptr null_idt = {0, 0};
asm volatile(“lidtl %0” : : “m” (null_idt));
}
IDT の初期化 (12)
・ 1 度目の初期化 (2) - setup_once 関数呼出し前後
カーネルセットアップコードメイン
->init/main.c:main()
->init/main.c:protected_mode_jump(boot_params.hdr.code32_start,(u32)&boot_params + (ds() << 4)
->code32_start= 0x00100000(default for big kernel) = startup_32(arch/x86/kernel/head_32.S)
カーネル初期化時に , アセンブリ言語で記述された setup_once 関数を呼び出す .
idt_table の 256 エントリ全てを ignore_int() 割り込みハンドラを指すように初期化 .
[ver4.9.16 arch/x86/kernel/head_32.S:89]
__HEAD
ENTRY(startup_32) #BootStrap Processor エントリポイント
…
jmp default_entry
…
ENTRY(startup_32_smp) #Secondary Processor エントリポイント
…
default_entry:
…enable_paging へジャンプ
enable_paging:
…
movl setup_once_ref,%eax # 一度呼ばれたら ,setup_once_ref には 0 が代入されている
andl %eax,%eax
jz 1f #eax が 0 だったら前方の 1 にジャンプ -> set_once 関数を呼ばない
call *%eax
1:
…
jmp *(initial_code)
…
ENTRY(initial_code)
.long i386_start_kernel
ENTRY(setup_once_ref)
.long setup_once
IDT の初期化 (13)
・ 1 度目の初期化 (3) – setup_once 初期例外ハンドラ
カーネル初期化時に , アセンブリ言語で記述された setup_once 関数を呼び出す .
idt_table の 256 エントリ全てを ignore_int() 割り込みハンドラを指すように初期化 .
[ver4.9.16 arch/x86/kernel/head_32.S:611]
__INIT
setup_once:
…
# 初期例外ハンドラの設定
movl $idt_tale, %edi #idt_table の先頭ポインタを edi に格納
movl $early_idt_handler_array, %eax #early_idt_handler_array の先頭ポインタを eax に格納
movl $NUM_EXCEPTION_VECTORS, %ecx #NUM_EXCEPTION_VECTORS(=32) を ecx に格納
#loop 命令は ecx をループカウンタとして使用
1:
movl %eax, (%edi) #idt_table[ i ].a = early_idt_handler_array[ j ]
movl %eax,4(%edi) #idt_table[ i ].b = early_idt_handler_array[ j ]
movl $(0x8E000000 + __KERNEL_CS), 2(%edi) #0x8E000000 + __KERNEL_CS = 0x8E000060
#idt_table[ i ].base0 = 0x0060
#idt_table[ i ] .base1=0x00, .type:0xE, .s:0, .dpl:0, .p:1
add $EARLY_IDT_HANDLER_SIZE, %eax #i ++
addl $8, %edi #j ++
loop 1b #ecx = 0 ⇒ ループ終了
…
IDT の初期化 (14)
・ 1 度目の初期化 (4) - early_idt_handler_array
[ver4.9.16 arch/x86/kernel/head_32.S:610]
ENTRY(early_idt_handler_array)
i = 0
.rept NUM_EXCEPTION_VECTORS
.ifeq (EXCEPTION_ERRCODE_MASK >> i) & 1
pushl $0 # Dummy error code, to make stack frame uniform
.endif
pushl $i #20(%esp) Vector Number
jmp early_idt_handler_common
i = i + 1
.fill early_idt_handler_array + i*EARLY_IDT_HANDLER_SIZE - . , 1, 0xcc
.endr
< コンパイル時 >
・ NUM_EXCEPTION_VECTORS の回数分 ,[.rept] ~ [.endr] ディレクティブ内の処理を機械語として展開
・ .fill で ,EARLY_IDT_HANDLER_SIZE(=9)byte 領域の残りの部分を 0xCC で埋める
< 実行時 >
・ [arch/x86/include/asm/segment.h:211]
  #define EXCEPTION_ERRCODE_MASK 0x0027d00
 例外発生時に制御回路がハードウェアエラーコードを自動的にスタックに積まない例外ベクタの指定
  0000 0000 0000 0010 0111 1101 0000 0000 ⇒ ベクタ番号 8, 10, 11, 12, 13, 14, 17
 この場合は ,push $0 で 0 をスタックに積む
・ push $i で例外ベクタ番号をスタックに積む
・ early_idt_handler_common にジャンプして , 初期段階の共通割り込みハンドラへの処理へ移るようにする
・ early_idt_handler_common は early_fixup_exception() 関数 [ver4.9.16 arch/x86/mm/extable.c:129] を呼び出す
・ early_fixup_exception() 関数は ,fixup_exception() 関数を呼び出して修復可能ならば例外を修復する .
IDT の初期化 (15)
・ 1 度目の初期化 (5) – setup_once 初期割り込みハンドラ
…
# 初期割り込みハンドラの設定
movl $256 – NUM_EXCEPTION_VECTORS, %ecx #loop 命令のループカウンタとして使用
movl $ignore_int, %edx #edx に ignore_int のアドレスを格納
movl $(__KERNEL_CS << 16), %eax #eax に __KERNEL_CS(0x60) を
#16bit 左シフトして格納 (0x00600000)
movl %dx, %ax #16bit 版 ignore_int のオフセットを ax に格納
# これにアクセスするときは ,
#cs に selector=0x10=BOOT_CS を指定
#eax=0x0060[?][?][?][?]
movw $0x8E00, %dx #interrupt gate – dpl=0, present
#edx=[?][?][?][?]8E00
2:
movl %eax, (%edi) #idt_table[ i ].a = early_idt_handler_array[ j ]
movl %edx, 4(%edi) #idt_table[ i ].b = ignore_int
#idt_table[ i ] .base1=0x00, .type:0xE, .s:0, .dpl:0, .p:1
#idt_table[ i ] .limit:[?], avl: [?], l: [?], d: [?], g: [?], base2: [?];
add $8, %edi #j ++
loop 2b
#ifdef CONFIG_CC_STACKPROTECTOR
…
#endif
andl $0,setup_once_ref # 一回設定すると ,setup_once_ref を 0 にする
ret # 呼び出し元に戻る
IDT の初期化 (16)
・ 1 度目の初期化 (6) - ignore_int
アセンブリ言語で記述された何もしない割り込みハンドラ .
1. いくつかのレジスタをスタックに退避
2.printk() 関数を用いて , 「 Unknown interrupt or fault at : … 」
 というシステムメッセージ表示
3. スタックに退避しておいたレジスタの値を復帰 .
4.iret 命令を実行して , 割り込まれたプログラムを再開させる .
[ver4.9.16 arch/x86/kernel/head_32.S:610]
ALIGN
ignore_int:
cld
#ifdef CONFIG_PRINTK
…printk 関数を呼び出す
#endif
iret
IDT の初期化 (17)
・ 1 度目の初期化 (7) - 初期 IDT の有効化
startup_32/startup_32_smp -> default_entry -> enable_paging
[ver 4.9.16 arch/x86/kernel/head_32.S:414]
…
call *%eax #setup_once を呼び出す . 一度設定していたら呼び出さない .
1:
… i486 かどうかチェック . 最低でも i486 でないといけない
is486:
movl $0x50022, %ecx #AM, WP, NE, MP をセット
movl %cr0, %eax
andl $0x80000011, %eax #PG, PE, ET を保存
orl %ecx, %eax
movl %eax, %cr0 #cr0 に設定を反映
lgdt early_gdt_descr
lidt idt_descr
ljmp $(__KERNEL_CS), $1f
1: movl $(__KERNEL_DS), %eax # セグメントレジスタを再読み込み
movl %eax, %ss
・ ds,es を __USER_DS で初期化
・ fs を __KERNEL_PERCPU で初期化
・ gs を __KERNEL_STACK_CANARY で初期化
・ LDT をクリア
pushl $0 #fake return address for unwinder
jmp *(initial_code) #BootStrapProcessor -> i386_start_kernel へジャンプ
#ApplicationProcessor -> start_secondary へジャンプ
IDT の初期化 (18)
・ 2 度目の初期化 (1) - early_trap_init トラップゲートディスクリプタ初期化
startup_32 -> i386_start_kernel -> start_kernel
startup_64 -> x86_64_start_kernel -> start_kernel
-> local_irq_disable(), setup_arch()->early_trap_init(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable()
[ver 4.9.16 arch/x86/kernel/traps.c:897]
896 /* Set of traps needed for early debugging. */
897 void __init early_trap_init(void)
898 {
911 set_intr_gate_notrace(X86_TRAP_DB, debug);
912 /* int3 can be called from all */
913 set_system_intr_gate(X86_TRAP_BP, &int3);
914 #ifdef CONFIG_X86_32
915 set_intr_gate(X86_TRAP_PF, page_fault);
916 #endif
917 load_idt(&idt_descr);
918 }
[ver 4.9.16 arch/x86/include/asm/traps.h:129]
X86_TRAP_DB :1 Debug
X86_TRAP_BP :3 BreakPoint
X86_TRAP_PF :14 PageFault
[ver 4.9.16 arch/x86/include/asm/desc.h:93]
#define load_idt(dtr) native_load_idt(dtr)
[ver 4.9.16 arch/x86/include/asm/desc.h:221]
static inline void native_load_idt(const struct desc_ptr *dtr){
asm volatile(“lidt %0”::”m” (*dtr));
}
IDT の初期化 (19)
・ 2 度目の初期化 (1) - trap_init 割り込みハンドラ設定 (1)
startup_32 -> i386_start_kernel -> start_kernel
startup_64 -> x86_64_start_kernel -> start_kernel
-> local_irq_disable(), setup_arch()->early_trap_init(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable()
[ver 4.9.16 arch/x86/kernel/traps.c:927]
927 void __init trap_init(void)
928 {
929 int i;
939 set_intr_gate(X86_TRAP_DE, divide_error);
940 set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
941 /* int4 can be called from all */
942 set_system_intr_gate(X86_TRAP_OF, &overflow);
943 set_intr_gate(X86_TRAP_BR, bounds);
944 set_intr_gate(X86_TRAP_UD, invalid_op);
945 set_intr_gate(X86_TRAP_NM, device_not_available);
946 #ifdef CONFIG_X86_32
947 set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
948 #else
949 set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
950 #endif
951 set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun);
952 set_intr_gate(X86_TRAP_TS, invalid_TSS);
953 set_intr_gate(X86_TRAP_NP, segment_not_present);
954 set_intr_gate(X86_TRAP_SS, stack_segment);
955 set_intr_gate(X86_TRAP_GP, general_protection);
956 set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug);
957 set_intr_gate(X86_TRAP_MF, coprocessor_error);
958 set_intr_gate(X86_TRAP_AC, alignment_check);
IDT の初期化 (19)
・ 2 度目の初期化 (1) - trap_init 割り込みハンドラ設定 (2)
startup_32 -> i386_start_kernel -> start_kernel
startup_64 -> x86_64_start_kernel -> start_kernel
-> local_irq_disable(), setup_arch()->early_trap_init(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable()
[ver 4.9.16 arch/x86/kernel/traps.c:927]
959 #ifdef CONFIG_X86_MCE
960 set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);
961 #endif
962 set_intr_gate(X86_TRAP_XF, simd_coprocessor_error);
963
964 /* Reserve all the builtin and the syscall vector: */
965 for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
966 set_bit(i, used_vectors);
967
968 #ifdef CONFIG_IA32_EMULATION
969 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_compat);
970 set_bit(IA32_SYSCALL_VECTOR, used_vectors);
971 #endif
972
973 #ifdef CONFIG_X86_32
974 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32);
975 set_bit(IA32_SYSCALL_VECTOR, used_vectors);
976 #endif
977
978 /*
979 * Set the IDT descriptor to a fixed read-only location, so that the
980 * "sidt" instruction will not leak the location of the kernel, and
981 * to defend the IDT against arbitrary memory write vulnerabilities.
982 * It will be reloaded in cpu_init() */ #F00F バグ対策
983 __set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);
984 idt_descr.address = fix_to_virt(FIX_RO_IDT);
IDT の初期化 - F00F バグ *
・ Pentium の一部モデル ( 古いモデル ) に F00F バグが存在 .
 対策 : 本物の IDT を , 読み出し専用で
     固定マップリニアアドレス FIX_RO_IDT にマッピングし ,
     このリニアアドレスを指すように idtr レジスタを初期化 .
     この領域の物理メモリに直接アクセス出来る様にすることで ,
     当該モデルでハングアップがなくなる .
IDT の初期化 - F00F バグ *
・ Pentium の一部モデルでは F00F バグが存在 .
  Pentium,Pentium MMX,Pentium オーバードライブプロセッサの ,
 ある世代以前のモデルにある設計上の不具合 .
  Pentium Pro 以降はこのバグの影響はない .
・問題を起こす機械語バイト列「 F0 0F C7 C8 」の先頭 2byte に由来 .
・「ロックされた CMPXCHG8B インストラクションでの不正なオペランド」
 と Intel は呼んでいる
・「 F0 0F C7 C8 」のニーモニック
  lock cmpxchg8b eax
・ cmpxchg8b 命令
オペランドとして指定されたアドレスのメモリ上の 8byte 領域を得る .
edx:eax の値とメモリ上の 8byte 値を比較
一致 → Z フラグ =1,ecx:ebx の値を 8byte 領域にストア .
不一致 → Z フラグ =0,8byte データを edx:eax にロード
・ cmpxchg8b のオペランドにレジスタを指定すると「不正命令例外」
  → 例外ハンドラが呼ばれる .
・ lock プレフィクスをつけると以後のメモリアクセスが抑制される為 ,
  → 例外ハンドラを実行できず , この段階でハングアップし ,
 一切の命令を実行せず , 割り込みも受け付けなくなってしまう .
・ Linux ではカーネル 2.0.32/2.1.64 以降で対策が取られている .
https://ja.wikipedia.org/wiki/Pentium_F00F_バグ
IDT の初期化 (19)
・ 2 度目の初期化 (1) - trap_init 割り込みハンドラ設定 (3)
startup_32 -> i386_start_kernel -> start_kernel
startup_64 -> x86_64_start_kernel -> start_kernel
-> local_irq_disable(), setup_arch()->early_trap_init(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable()
[ver 4.9.16 arch/x86/kernel/traps.c:927]
985
986 /*
987 * Should be a barrier for any external CPU state:
988 */
989 cpu_init();
990
991 /*
992 * X86_TRAP_DB and X86_TRAP_BP have been set
993 * in early_trap_init(). However, ITS works only after
994 * cpu_init() loads TSS. See comments in early_trap_init().
995 */
996 set_intr_gate_ist(X86_TRAP_DB, &debug, DEBUG_STACK);
997 /* int3 can be called from all */
998 set_system_intr_gate_ist(X86_TRAP_BP, &int3, DEBUG_STACK);
999
1000 x86_init.irqs.trap_init(); #no operation. x86_init_noop()
1001
1002 #ifdef CONFIG_X86_64
1003 memcpy(&debug_idt_table, &idt_table, IDT_ENTRIES * 16);
1004 set_nmi_gate(X86_TRAP_DB, &debug);
1005 set_nmi_gate(X86_TRAP_BP, &int3);
1006 #endif
1007 }
IDT の初期化 (19)
・ 2 度目の初期化 (1) - early_irq_init  割り込みゲートディスクリプタ初期化
startup_32 -> i386_start_kernel -> start_kernel
startup_64 -> x86_64_start_kernel -> start_kernel
start_kernel -> local_irq_disable(), setup_arch(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable()
[ver 4.9.16 kernel/irq/irqdesc.c:504]
504 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
505 [0 ... NR_IRQS-1] = {
506 .handle_irq = handle_bad_irq,
507 .depth = 1,
508 .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
509 }
510 };
[ver 4.9.16 include/linux/irqdesc.h:51] 割り込みディスクリプタ struct irq_desc の定義
[ver 4.9.16 kernel/irq/irqdesc.c:512]
512 int __init early_irq_init(void)
513 {
514 int count, i, node = first_online_node;
515 struct irq_desc *desc;
517 init_irq_default_affinity();
519 printk(KERN_INFO "NR_IRQS:%dn", NR_IRQS);
521 desc = irq_desc;
522 count = ARRAY_SIZE(irq_desc);
524 for (i = 0; i < count; i++) {
525 desc[i].kstat_irqs = alloc_percpu(unsigned int);
526 alloc_masks(&desc[i], GFP_KERNEL, node);
527 raw_spin_lock_init(&desc[i].lock);
528 lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
529 desc_set_defaults(i, &desc[i], node, NULL, NULL);
530 }
531 return arch_early_irq_init();
532 }
IDT の初期化 (20)
・ 2 度目の初期化 (2) - init_IRQ BSP のベクタと irq_desc を紐付け
start_kernel -> local_irq_disable(), setup_arch(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable()
[ver 4.9.16 arch/x86/kernel/irqinit.c:84]
void __init init_IRQ(void)
int i;
for (i = 0; i < nr_legacy_irqs(); i++) #legacy pic(8259A) の IRQ 数分だけ設定
per_cpu(vector_irq, 0)[ISA_IRQ_VECTOR(i)] = irq_to_desc(i); #BSP のベクタと irq_desc を紐付け
x86_init.irqs.intr_init();
}
[ver 4.9.16 arch/x86/include/asm/hw_irq.h:180]
typedef struct irq_desc* vector_irq_t[NR_VECTORS];
DECLARE_PER_CPU(vector_irq_t, vector_irq);
[ver 4.9.16 arch/x86/kernel/irqinit.c:54]
DEFINE_PER_CPU(vector_irq_t, vector_irq) = {
[0 ... NR_VECTORS – 1] = VECTOR_UNUSED,
};
[ver 4.9.16 kernel/irq/irqdesc.c:534]
struct irq_desc *irq_to_desc(unsigned int irq)
{
return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}
EXPORT_SYMBOL(irq_to_desc);
IDT の初期化 (21)
・ 2 度目の初期化 (3) - x86_init.irqs.intr_init
start_kernel -> local_irq_disable(), setup_arch(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable()
init_IRQ -> x86_init.irqs.intr_init() = native_init_IRQ()
[ver 4.9.16 arch/x86/kernel/irqinit.c:167]
167 void __init native_init_IRQ(void)
168 {
169 int i;
172 x86_init.irqs.pre_vector_init(); # デバイス初期化
174 apic_intr_init(); #APIC のベクタ初期化
181 i = FIRST_EXTERNAL_VECTOR; #0x20(32)
182 #ifndef CONFIG_X86_LOCAL_APIC
183 #define first_system_vector NR_VECTORS
184 #endif
# この時点までで設定されていない未使用ベクタのハンドラを設定 ( 通常の割り込みハンドラ設定 )
185 for_each_clear_bit_from(i, used_vectors, first_system_vector) { #first_system_vector: 256 or 0xef
186 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
187 set_intr_gate(i, irq_entries_start +
188 8 * (i - FIRST_EXTERNAL_VECTOR));
189 }
190 #ifdef CONFIG_X86_LOCAL_APIC
191 for_each_clear_bit_from(i, used_vectors, NR_VECTORS)
192 set_intr_gate(i, spurious_interrupt); # スプリアス割り込みのハンドラを設定
193 #endif
195 if (!acpi_ioapic && !of_ioapic && nr_legacy_irqs())
196 setup_irq(2, &irq2); # IR2 のハンドラを設定
198 #ifdef CONFIG_X86_32
199 irq_ctx_init(smp_processor_id()); #hardirq と softirq を処理する為の CPU 毎のスタックを確保
200 #endif
201 }
IDT の初期化 (22)
・ 2 度目の初期化 (4) - irq_entries_start / common_interrupt
start_kernel -> local_irq_disable(), setup_arch(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable()
init_IRQ -> x86_init.irqs.intr_init() = native_init_IRQ()
[ver 4.9.16 arch/x86/entry/entry_32.S:603]
603 /*
604 * Build the entry stubs with some assembler magic.
605 * We pack 1 stub into every 8-byte block.
606 */
607 .align 8
608 ENTRY(irq_entries_start)
609 vector=FIRST_EXTERNAL_VECTOR #0x20
610 .rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR)
611 pushl $(~vector+0x80) /* Note: always in signed byte range */
612 vector=vector+1
613 jmp common_interrupt
614 .align 8
615 .endr
616 END(irq_entries_start)
617
618 /*
619 * the CPU automatically disables interrupts when executing an IRQ vector,
620 * so IRQ-flags tracing has to follow that:
621 */
622 .p2align CONFIG_X86_L1_CACHE_SHIFT
623 common_interrupt:
624 ASM_CLAC
625 addl $-0x80, (%esp) /* Adjust vector into the [-256, -1] range */
626 SAVE_ALL
627 TRACE_IRQS_OFF
628 movl %esp, %eax
629 call do_IRQ
630 jmp ret_from_intr
631 ENDPROC(common_interrupt)
IDT の初期化 (23)
・ 2 度目の初期化 (5) - IDT の有効化
start_kernel() [ver 4.9.16 init/main.c:479] BootStrap Processor のみ
-> rest_init() [ver 4.9.16 init/main.c:383]
-> kernel_init スレッド [ver 4.9.16 init/main.c:939]
-> kernel_init_freeable() [ver 4.9.16 init/main.c:986]
-> smp_init() [ver 4.9.16 kernel/smp.c:552] 各 CPU 専用の初期化処理 .
-> cpu_up(cpu) [ver 4.9.16 kernel/cpu.c:1093]
   BSP は boot_cpu_init() で CPUHP_ONLINE になる為 ,smp_init 内でのこの関数呼出しは ApplicationProcessor のみ .
-> do_cpu_up( 呼出し引数: cpu, CPUHP_ONLINE) [ver 4.9.16 kernel/cpu.c:1063]
-> _cpu_up( 呼出し引数: cpu, 0, target) [ver 4.9.16 kernel/cpu.c:1005]
-> cpuhp_up_callback( 呼出し引数: cpu, st, CPUHP_BRINGUP_CPU) [ver 4.9.16 kernel/cpu.c:485]
st->state が以下の順で変わっていく
CPUHP_OFFLINE->CPUHP_CREATE_THREADS->CPUHP_PREF_PREPARE
->CPUHP_WORKQUEUE_PREP->CPUHP_HRTIMERS_PREPARE->CPUHP_SMPCFD_PREPARE
->CPUHP_RELAY_PREPARE->CPUHP_SLAB_PREPARE->CPUHP_RCUTREE_PREP
->CPUHP_NOTIFY_PREPARE->CPUHP_BRINGUP_CPU -> …
-> cpuhp_invoke_callback( 呼出し引数: cpu, st->state, true, NULL) [ver 4.9.16 kernel/cpu.c:122]
各 state の startup.single を呼び出す .
cb( 呼出し引数: cpu) [ver 4.9.16 kernel/cpu.c:136]
= bringup_cpu( 呼出し引数: cpu) [ver 4.9.16 kernel/cpu.c:421]
以降は CPUHP_BRINGUP_CPU の場合
-> __cpu_up( 呼出し引数: cpu, tidle) [ver 4.9.16 arch/x86/include/asm/smp.h:93]
-> smp_ops.cpu_up( 呼出し引数: cpu, tidle) [ver 4.9.16 arch/x86/include/asm/smp.h:95]
= native_cpu_up( 呼出し引数: cpu, tidle) [ver 4.9.16 arch/x86/kernel/smpboot.c:1070]
-> do_boot_cpu( 呼出し引数: apicid, cpu, tidle) [ver 4.9.16 arch/x86/kernel/smpboot.c:949]
-> apic->wakeup_secondary_cpu( 呼出し引数: apicid, start_ip) APIC あり
wakeup_cpu_via_init_nmi( 呼出し引数: cpu, start_ip, apicid, &cpu0_nmi_registered) APIC なし
-> startup_32_smp -> start_secondary()   [ver 4.9.16 arch/x86/kernel/smpboot.c:213] Application Processor のみ
-> cpu_init() (CONFIG_X86_64) [ver 4.9.16 arch/x86/kernel/cpu/common.c:1470]
cpu_init() (CONFIG_X86_32) [ver 4.9.16 arch/x86/kernel/cpu/common.c:1573]
-> load_current_idt() [ver 4.9.16 arch/x86/include/asm/desc.h:495]
-> switch_to_new_gdt(cpu) [ver 4.9.16 arch/x86/kernel/cpu/common.c:450]
IDT の初期化 (24)
・ 2 度目の初期化 (5) - IDT の有効化
-> load_current_idt() [ver 4.9.16 arch/x86/include/asm/desc.h:495]
-> load_idt(&idt_descr) [ver 4.9.16 arch/x86/include/asm/desc.h:93]
-> switch_to_new_gdt(cpu) [ver 4.9.16 arch/x86/kernel/cpu/common.c:450]
-> load_gdt(&gdt_descr) [ver 4.9.16 arch/x86/include/asm/desc.h:92]
-> native_load_gdt(dtr) [ver 4.9.16 arch/x86/include/asm/desc.h:216]
-> asm volatile(“lgdt %0”::”m” (*dtr));[ver 4.9.16 arch/x86/include/asm/desc.h:218]

Más contenido relacionado

La actualidad más candente

V8 javascript engine for フロントエンドデベロッパー
V8 javascript engine for フロントエンドデベロッパーV8 javascript engine for フロントエンドデベロッパー
V8 javascript engine for フロントエンドデベロッパーTaketoshi 青野健利
 
Verilator勉強会 2021/05/29
Verilator勉強会 2021/05/29Verilator勉強会 2021/05/29
Verilator勉強会 2021/05/29ryuz88
 
GraalVM の概要と、Native Image 化によるSpring Boot 爆速化の夢
GraalVM の概要と、Native Image 化によるSpring Boot 爆速化の夢GraalVM の概要と、Native Image 化によるSpring Boot 爆速化の夢
GraalVM の概要と、Native Image 化によるSpring Boot 爆速化の夢apkiban
 
初心者向けCTFのWeb分野の強化法
初心者向けCTFのWeb分野の強化法初心者向けCTFのWeb分野の強化法
初心者向けCTFのWeb分野の強化法kazkiti
 
AAをつくろう!
AAをつくろう!AAをつくろう!
AAをつくろう!Takami Sato
 
関数型言語ElixirのIoTシステム開発への展開
関数型言語ElixirのIoTシステム開発への展開関数型言語ElixirのIoTシステム開発への展開
関数型言語ElixirのIoTシステム開発への展開Hideki Takase
 
Research modeで取得した深度(Depth)データを可視化する
Research modeで取得した深度(Depth)データを可視化するResearch modeで取得した深度(Depth)データを可視化する
Research modeで取得した深度(Depth)データを可視化するSoichiro Sugimoto
 
カルマンフィルタ入門
カルマンフィルタ入門カルマンフィルタ入門
カルマンフィルタ入門Yasunori Nihei
 
知っておきたいSpring Batch Tips
知っておきたいSpring Batch Tips知っておきたいSpring Batch Tips
知っておきたいSpring Batch Tipsikeyat
 
いまさら聞けないarmを使ったNEONの基礎と活用事例
いまさら聞けないarmを使ったNEONの基礎と活用事例いまさら聞けないarmを使ったNEONの基礎と活用事例
いまさら聞けないarmを使ったNEONの基礎と活用事例Fixstars Corporation
 
バグハンターの哀しみ
バグハンターの哀しみバグハンターの哀しみ
バグハンターの哀しみMasato Kinugawa
 
ネットワークOS野郎 ~ インフラ野郎Night 20160414
ネットワークOS野郎 ~ インフラ野郎Night 20160414ネットワークOS野郎 ~ インフラ野郎Night 20160414
ネットワークOS野郎 ~ インフラ野郎Night 20160414Kentaro Ebisawa
 
機械学習工学の進展と課題 2021
機械学習工学の進展と課題 2021機械学習工学の進展と課題 2021
機械学習工学の進展と課題 2021Fuyuki Ishikawa
 
#logstudy 01 rsyslog入門
#logstudy 01 rsyslog入門#logstudy 01 rsyslog入門
#logstudy 01 rsyslog入門Takashi Takizawa
 
Integrated Register Allocation introduction
Integrated Register Allocation introductionIntegrated Register Allocation introduction
Integrated Register Allocation introductionShiva Chen
 
組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門Norishige Fukushima
 
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)NTT DATA Technology & Innovation
 
OSS活動の活発さと評価の関係について
OSS活動の活発さと評価の関係についてOSS活動の活発さと評価の関係について
OSS活動の活発さと評価の関係についてTakuto Wada
 
仮想化技術によるマルウェア対策とその問題点
仮想化技術によるマルウェア対策とその問題点仮想化技術によるマルウェア対策とその問題点
仮想化技術によるマルウェア対策とその問題点Kuniyasu Suzaki
 

La actualidad más candente (20)

V8 javascript engine for フロントエンドデベロッパー
V8 javascript engine for フロントエンドデベロッパーV8 javascript engine for フロントエンドデベロッパー
V8 javascript engine for フロントエンドデベロッパー
 
Verilator勉強会 2021/05/29
Verilator勉強会 2021/05/29Verilator勉強会 2021/05/29
Verilator勉強会 2021/05/29
 
GraalVM の概要と、Native Image 化によるSpring Boot 爆速化の夢
GraalVM の概要と、Native Image 化によるSpring Boot 爆速化の夢GraalVM の概要と、Native Image 化によるSpring Boot 爆速化の夢
GraalVM の概要と、Native Image 化によるSpring Boot 爆速化の夢
 
初心者向けCTFのWeb分野の強化法
初心者向けCTFのWeb分野の強化法初心者向けCTFのWeb分野の強化法
初心者向けCTFのWeb分野の強化法
 
AAをつくろう!
AAをつくろう!AAをつくろう!
AAをつくろう!
 
関数型言語ElixirのIoTシステム開発への展開
関数型言語ElixirのIoTシステム開発への展開関数型言語ElixirのIoTシステム開発への展開
関数型言語ElixirのIoTシステム開発への展開
 
Research modeで取得した深度(Depth)データを可視化する
Research modeで取得した深度(Depth)データを可視化するResearch modeで取得した深度(Depth)データを可視化する
Research modeで取得した深度(Depth)データを可視化する
 
カルマンフィルタ入門
カルマンフィルタ入門カルマンフィルタ入門
カルマンフィルタ入門
 
知っておきたいSpring Batch Tips
知っておきたいSpring Batch Tips知っておきたいSpring Batch Tips
知っておきたいSpring Batch Tips
 
いまさら聞けないarmを使ったNEONの基礎と活用事例
いまさら聞けないarmを使ったNEONの基礎と活用事例いまさら聞けないarmを使ったNEONの基礎と活用事例
いまさら聞けないarmを使ったNEONの基礎と活用事例
 
バグハンターの哀しみ
バグハンターの哀しみバグハンターの哀しみ
バグハンターの哀しみ
 
ネットワークOS野郎 ~ インフラ野郎Night 20160414
ネットワークOS野郎 ~ インフラ野郎Night 20160414ネットワークOS野郎 ~ インフラ野郎Night 20160414
ネットワークOS野郎 ~ インフラ野郎Night 20160414
 
機械学習工学の進展と課題 2021
機械学習工学の進展と課題 2021機械学習工学の進展と課題 2021
機械学習工学の進展と課題 2021
 
Map
MapMap
Map
 
#logstudy 01 rsyslog入門
#logstudy 01 rsyslog入門#logstudy 01 rsyslog入門
#logstudy 01 rsyslog入門
 
Integrated Register Allocation introduction
Integrated Register Allocation introductionIntegrated Register Allocation introduction
Integrated Register Allocation introduction
 
組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門
 
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
 
OSS活動の活発さと評価の関係について
OSS活動の活発さと評価の関係についてOSS活動の活発さと評価の関係について
OSS活動の活発さと評価の関係について
 
仮想化技術によるマルウェア対策とその問題点
仮想化技術によるマルウェア対策とその問題点仮想化技術によるマルウェア対策とその問題点
仮想化技術によるマルウェア対策とその問題点
 

Similar a 4章 Linuxカーネル - 割り込み・例外 5

4章 Linuxカーネル - 割り込み・例外 4
 4章 Linuxカーネル - 割り込み・例外 4 4章 Linuxカーネル - 割り込み・例外 4
4章 Linuxカーネル - 割り込み・例外 4mao999
 
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。Satoshi Mimura
 
NetBSD/i386 割り込みベクタテーブル
NetBSD/i386 割り込みベクタテーブルNetBSD/i386 割り込みベクタテーブル
NetBSD/i386 割り込みベクタテーブルkusabanachi
 
スタート低レイヤー #0
スタート低レイヤー #0スタート低レイヤー #0
スタート低レイヤー #0Kiwamu Okabe
 
4章 Linuxカーネル - 割り込み・例外 2
4章 Linuxカーネル - 割り込み・例外 24章 Linuxカーネル - 割り込み・例外 2
4章 Linuxカーネル - 割り込み・例外 2mao999
 
core dumpでcode golf
core dumpでcode golfcore dumpでcode golf
core dumpでcode golfNomura Yusuke
 
○○大学の本当にあった怖い話
○○大学の本当にあった怖い話○○大学の本当にあった怖い話
○○大学の本当にあった怖い話idkqh7 Nishino
 
デバドラを書いてみよう!
デバドラを書いてみよう!デバドラを書いてみよう!
デバドラを書いてみよう!Masami Ichikawa
 
dofilewrite and vn_write
dofilewrite and vn_writedofilewrite and vn_write
dofilewrite and vn_writekusabanachi
 
2011.09.18 v7から始めるunix まとめ
2011.09.18 v7から始めるunix まとめ2011.09.18 v7から始めるunix まとめ
2011.09.18 v7から始めるunix まとめMakiko Konoshima
 
JIT のコードを読んでみた
JIT のコードを読んでみたJIT のコードを読んでみた
JIT のコードを読んでみたy-uti
 
PF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsolaPF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsolaandroid sola
 
DE0でラジコンカー作ってみた 関西de0 fpga勉強会20120519
DE0でラジコンカー作ってみた 関西de0 fpga勉強会20120519DE0でラジコンカー作ってみた 関西de0 fpga勉強会20120519
DE0でラジコンカー作ってみた 関西de0 fpga勉強会20120519Yasuhiro Ishii
 
2011.06.11 v7から始めるunix まとめ
2011.06.11 v7から始めるunix まとめ2011.06.11 v7から始めるunix まとめ
2011.06.11 v7から始めるunix まとめMakiko Konoshima
 
PHP AST 徹底解説
PHP AST 徹底解説PHP AST 徹底解説
PHP AST 徹底解説do_aki
 

Similar a 4章 Linuxカーネル - 割り込み・例外 5 (20)

4章 Linuxカーネル - 割り込み・例外 4
 4章 Linuxカーネル - 割り込み・例外 4 4章 Linuxカーネル - 割り込み・例外 4
4章 Linuxカーネル - 割り込み・例外 4
 
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。
 
NetBSD/i386 割り込みベクタテーブル
NetBSD/i386 割り込みベクタテーブルNetBSD/i386 割り込みベクタテーブル
NetBSD/i386 割り込みベクタテーブル
 
Ext4 filesystem(1)
Ext4 filesystem(1)Ext4 filesystem(1)
Ext4 filesystem(1)
 
スタート低レイヤー #0
スタート低レイヤー #0スタート低レイヤー #0
スタート低レイヤー #0
 
initramfsについて
initramfsについてinitramfsについて
initramfsについて
 
4章 Linuxカーネル - 割り込み・例外 2
4章 Linuxカーネル - 割り込み・例外 24章 Linuxカーネル - 割り込み・例外 2
4章 Linuxカーネル - 割り込み・例外 2
 
core dumpでcode golf
core dumpでcode golfcore dumpでcode golf
core dumpでcode golf
 
SystemV IPC
SystemV IPCSystemV IPC
SystemV IPC
 
Gingerbread
GingerbreadGingerbread
Gingerbread
 
○○大学の本当にあった怖い話
○○大学の本当にあった怖い話○○大学の本当にあった怖い話
○○大学の本当にあった怖い話
 
デバドラを書いてみよう!
デバドラを書いてみよう!デバドラを書いてみよう!
デバドラを書いてみよう!
 
dofilewrite and vn_write
dofilewrite and vn_writedofilewrite and vn_write
dofilewrite and vn_write
 
2011.09.18 v7から始めるunix まとめ
2011.09.18 v7から始めるunix まとめ2011.09.18 v7から始めるunix まとめ
2011.09.18 v7から始めるunix まとめ
 
JIT のコードを読んでみた
JIT のコードを読んでみたJIT のコードを読んでみた
JIT のコードを読んでみた
 
Interrupts on xv6
Interrupts on xv6Interrupts on xv6
Interrupts on xv6
 
PF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsolaPF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsola
 
DE0でラジコンカー作ってみた 関西de0 fpga勉強会20120519
DE0でラジコンカー作ってみた 関西de0 fpga勉強会20120519DE0でラジコンカー作ってみた 関西de0 fpga勉強会20120519
DE0でラジコンカー作ってみた 関西de0 fpga勉強会20120519
 
2011.06.11 v7から始めるunix まとめ
2011.06.11 v7から始めるunix まとめ2011.06.11 v7から始めるunix まとめ
2011.06.11 v7から始めるunix まとめ
 
PHP AST 徹底解説
PHP AST 徹底解説PHP AST 徹底解説
PHP AST 徹底解説
 

4章 Linuxカーネル - 割り込み・例外 5

  • 1. 4 章 Linux カーネル – 割り込み・例外 5 ・ IDT ・ IDT の初期化 mao Web >https://www.pridact.com Twitter >https://twitter.com/rivarten Mail   >official@pridact.com
  • 3. この資料について ・間違っていたらご指摘をお願い致します。 ・ Linux Kernel のバージョンは 4.9.16 です。 ・ページタイトルに * 印が付いているページは、  少し踏み込んだ内容になっています。  ざっと全体に目を通したければ、読み飛ばして  もらっても構いません。
  • 4. IDT (1) ・システム初期化時 , カーネルが割り込みを許可する前に , ・ idtr レジスタに IDT の先頭アドレスを指定 ・ IDT 内の全エントリを初期化 ・ユーザーモードプロセスが int 命令を使用して 0~255 までの任意ベクタの  割り込みを発行できる為 , 不正な割り込みや例外の発生を防ぐ為に ,IDT  の初期化は慎重に行う . Linux における初期化処理 a. 特定の割り込みやトラップのゲートディスクリプタの DPL を 0 に設定し ,  不正な割り込み信号には一般保護例外を発生させるようにする . b. ユーザーモードプロセスが特定の例外を直接生成できるようにする為に ,  割り込みやトラップゲートのディスクリプタの DPL を 3 に設定し ,  例外を発生させることを可能にする .
  • 5. IDT (2) ・ Intel が提供している割り込みディスクリプタ ・割り込みゲートディスクリプタ ・トラップゲートディスクリプタ ・タスクゲートディスクリプタ
  • 6. IDT (3) ・ Linux が提供している割り込みディスクリプタ ・割り込みゲート Intel の割り込みゲート .DPL=0. 割り込みハンドラはすべて割り込みゲートで実装 . ユーザーモードからアクセス不可 . ・システムトラップゲート ( 旧システムゲート ) Intel のトラップゲート .DPL=3. ベクタ 4,5,128 の例外ハンドラはシステムトラップゲートで実装 . into,bound,int $0x80 アセンブリ命令はユーザーモードでも使用可 . ・システム割り込みゲート Intel の割り込みゲート .DPL=3. ベクタ 3 の例外ハンドラはシステム割り込みゲートで実装 . int3 アセンブリ命令はユーザーモードでも実行可 . ・トラップゲート Intel のトラップゲート .DPL=0. ほとんどの例外ハンドラはトラップゲートで実装 . ユーザーモードからアクセス不可 . ・タスクゲート Intel のタスクゲート .DPL=0. ダブルフォルト例外ハンドラの実装 .
  • 7. IDT の初期化 (1) ・ IDT へのゲート登録 ・ set_intr_gate(n,addr) ・ set_system_trap_gate(n,addr) ( 旧 set_system_gate) ・ set_system_intr_gate(n,addr) ・ set_trap_gate(n,addr) ・ set_task_gate(n,addr)
  • 8. IDT の初期化 (2) ・ IDT へのゲート登録 ・ set_intr_gate(n,addr) IDT の n 番目のエントリに割り込みゲートを登録する . ゲート内のセグメントセレクタには ,__KERNEL_CS が設定される . オフセットフィールドには , 割り込みハンドラのアドレス addr を設定 . DPL=0 で設定される . ※ すべての割り込みハンドラをこの関数で登録 . [ver4.9.16 arch/x86/include/asm/desc.h:371] #define set_intr_gate(n, addr) do { set_intr_gate_notrace(n, addr); _trace_set_gate(n, GATE_INTERRUPT, (void*)trace_##addr, 0, 0, __KERNEL_CS); } while (0) [ver4.9.16 arch/x86/include/asm/desc.h:364] #define set_intr_gate_notrace(n, addr) do { BUG_ON((unsigned)n > 0xFF); _set_gate(n, GATE_INTERRUPT, (void *)addr, 0, 0, __KERNEL_CS); } while (0)
  • 9. IDT の初期化 (3) ・ IDT へのゲート登録 ・ set_system_trap_gate(n,addr) IDT の n 番目のエントリにトラップゲートを登録する . ゲート内のセグメントセレクタには ,__KERNEL_CS が設定される . オフセットフィールドには , 例外ハンドラのアドレス addr を設定 . DPL=3 で設定される . ベクタ 4,5,128 がこのゲート . [ver4.9.16 arch/x86/include/asm/desc.h:408] static inline void set_system_trap_gate(unsigned int n, void *addr) { BUG_ON((unsigned)n > 0xFF); _set_gate(n, GATE_TRAP, addr, 0x3, 0, __KERNEL_CS); }
  • 10. IDT の初期化 (4) ・ IDT へのゲート登録 ・ set_system_intr_gate(n,addr) IDT の n 番目のエントリに割り込みゲートを登録する . ゲート内のセグメントセレクタには ,__KERNEL_CS が設定される . オフセットフィールドには , 例外ハンドラのアドレス addr を設定 . DPL=3 で設定される . ベクタ 3 がこのゲート . [ver4.9.16 arch/x86/include/asm/desc.h:402] static inline void set_system_intr_gate(unsigned int n, void *addr) { BUG_ON((unsigned)n > 0xFF); _set_gate(n, GATE_INTERRUPT, addr, 0x3, 0, __KERNEL_CS); }
  • 11. IDT の初期化 (5) ・ IDT へのゲート登録 ・ set_trap_gate(n,addr) set_system_trap_gate と同じだが ,DPL=0 で設定する . 殆どの例外ハンドラがこのゲート . [ver4.9.16 arch/x86/include/asm/desc.h:414] static inline void set_trap_gate(unsigned int n, void *addr) { BUG_ON((unsigned)n > 0xFF); _set_gate(n, GATE_TRAP, addr, 0, 0, __KERNEL_CS); }
  • 12. IDT の初期化 (6) ・ IDT へのゲート登録 ・ set_task_gate(n,addr) IDT の n 番目のエントリにタスクゲートを登録する . ゲート内のセグメントセレクタには , GDT 内の TSS セグメントのインデックスが設定される . この TSS セグメントは , 実行する関数を含んでいるセグメント . オフセットフィールドには ,0 を設定 . DPL=0. ダブルフォルト例外ハンドラがこのゲート . [ver4.9.16 arch/x86/include/asm/desc.h:420] static inline void set_task_gate(unsigned int n, void *addr) { BUG_ON((unsigned)n > 0xFF); _set_gate(n, GATE_TASK, (void *)0, 0, 0, (gdt_entry<<3)); }
  • 13. IDT の初期化 (7) * ・ IDT へのゲート登録 ・全ゲート共通 (1) [ver4.9.16 arch/x86/include/asm/desc.h:344] static inline void _set_gate(int gate, unsigned type, void *addr, unsigned dpl, unsigned ist, unsigned seg) { gate_desc s; pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg); write_idt_entry(idt_table, gate, &s); write_trace_idt_entry(gate, &s); } [ver4.9.16 arch/x86/include/asm/desc.h:55] CONFIG_X86_64 static inline void pack_gate(gate_desc *gate, unsigned type, unsigned long func, unsigned dpl, unsigned ist, unsigned seg) { gate->offset_low = PTR_LOW(func); gate->segment = __KERNEL_CS; gate->ist = ist; gate->p = 1; gate->dpl = dpl; gate->zero0 = 0; gate->zero1 = 0; gate->type = type; gate->offset_middle = PTR_MIDDLE(func); gate->offset_high = PTR_HIGH(func); }
  • 14. IDT の初期化 (8) * ・ IDT へのゲート登録 ・全ゲート共通 (2) [ver4.9.16 arch/x86/include/asm/desc.h:71] #else (CONFIG_X86_32 static inline void pack_gate(gate_desc *gate, unsigned type, unsigned long func, unsigned dpl, unsigned ist, unsigned seg) { gate->a = (seg << 16) | (base & 0xffff); gate->b = (base & 0xffff0000) | (((0x80 | type | (dpl << 5)) & 0xff) << 8); } [ver4.9.16 arch/x86/include/asm/desc.h:324] static inline void _trace_set_gate(int gate, unsigned type, void *addr, unsigned dpl, unsigned ist, unsigned seg) { gate_desc s; pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg); write_trace_idt_entry(gate, &s); } [ver4.9.16 arch/x86/include/asm/desc.h:106] #define write_idt_entry(dt, entry, g) native_write_idt_entry(dt, entry, g) [ver4.9.16 arch/x86/include/asm/desc.h:119] static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate) { memcpy(&idt[entry], gate, sizeof(*gate)); }
  • 15. IDT の初期化 (9) ・コンピュータの起動直後 , まだリアルモードで動作している時にも ,   BIOS は IDT を初期化して使用している .(1 度目の初期化 ) ・ Linux が起動すると ,Linux は BIOS ルーチンを使用しない為 ,   IDT を RAM の別の領域へ移動させ ,2 度目の初期化を行う . ・ idt_table テーブル IDT の 256 エントリのテーブル [ver4.9.16 arch/x86/kernel/traps.c:82] gate_desc idt_table[NR_VECTORS] __page_aligned_bss; ・ gate_desc/desc_struct 構造体 [ver4.9.16 arch/x86/include/asm/desc_defs.h:22] struct desc_struct { union { struct { unsigned int a; unsigned int b; }; struct { u16 limit0; #bit15-0(limit15-0) u16 base0; #bit31-16(base15-0) unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1; #bit39-32(base23-16) #bit43-40(type)/bit44(S) #bit46-45(DPL)/bit47(P) unsigned limit:4, avl: 1, l: 1, d: 1, g: 1, base2: 8; }; #bit51-48(limit19-16) #bit52(AVL)/bit53(L)/bit54(D) #bit55(G)/bit63-56(base31-24) }; } __attribute__((packed)); ※ 参考 2 章 Linux カーネル – メモリ管理 1 アドレス変換機能  x86 におけるセグメンテーション
  • 16. IDT の初期化 (10) ・ idt_descr 変数 IDT の大きさとアドレス . システム初期化段階で , カーネルが lidt アセンブリ命令で idtr レジスタを設定する時に使用 . [ver4.9.16 arch/x86/kernel/head_32.S:727] .data .globl boot_gdt_descr .globl idt_desc ALIGN … boot_gdt_descr: … idt_descr: .word IDT_ENTRIES*8-1 .long idt_table
  • 17. IDT の初期化 (11) ・ 1 度目の初期化 (1) - ゼロ埋め ブートローダーから起動 --> X+0x200: カーネルセットアップコード (arch/x86/boot/header.S) : _start --> カーネルセットアップコード ヘッダ (arch/x86/boot/compressed/head_32.S:63) : startup_32 --> カーネルセットアップコード メイン (init/main.c) : main() [ver 4.9.16 arch/x86/boot/main.c:135] void main(void){ … go_to_protected_mode() } [ver 4.9.16 arch/x86/boot/pm.c:104] void go_to_protected_mode(void) { readmode_switch_hook(); if (enable_a20()) { die(); } reset_coprocessor(); mask_all_interrupts(); setup_idt(); setup_gdt(); protected_mode_jump(boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4)); //0x00100000 にジャンプ } [ver4.9.16 arch/x86/boot/pm.c:95] static void setup_idt(void){ staic const struct gdt_ptr null_idt = {0, 0}; asm volatile(“lidtl %0” : : “m” (null_idt)); }
  • 18. IDT の初期化 (12) ・ 1 度目の初期化 (2) - setup_once 関数呼出し前後 カーネルセットアップコードメイン ->init/main.c:main() ->init/main.c:protected_mode_jump(boot_params.hdr.code32_start,(u32)&boot_params + (ds() << 4) ->code32_start= 0x00100000(default for big kernel) = startup_32(arch/x86/kernel/head_32.S) カーネル初期化時に , アセンブリ言語で記述された setup_once 関数を呼び出す . idt_table の 256 エントリ全てを ignore_int() 割り込みハンドラを指すように初期化 . [ver4.9.16 arch/x86/kernel/head_32.S:89] __HEAD ENTRY(startup_32) #BootStrap Processor エントリポイント … jmp default_entry … ENTRY(startup_32_smp) #Secondary Processor エントリポイント … default_entry: …enable_paging へジャンプ enable_paging: … movl setup_once_ref,%eax # 一度呼ばれたら ,setup_once_ref には 0 が代入されている andl %eax,%eax jz 1f #eax が 0 だったら前方の 1 にジャンプ -> set_once 関数を呼ばない call *%eax 1: … jmp *(initial_code) … ENTRY(initial_code) .long i386_start_kernel ENTRY(setup_once_ref) .long setup_once
  • 19. IDT の初期化 (13) ・ 1 度目の初期化 (3) – setup_once 初期例外ハンドラ カーネル初期化時に , アセンブリ言語で記述された setup_once 関数を呼び出す . idt_table の 256 エントリ全てを ignore_int() 割り込みハンドラを指すように初期化 . [ver4.9.16 arch/x86/kernel/head_32.S:611] __INIT setup_once: … # 初期例外ハンドラの設定 movl $idt_tale, %edi #idt_table の先頭ポインタを edi に格納 movl $early_idt_handler_array, %eax #early_idt_handler_array の先頭ポインタを eax に格納 movl $NUM_EXCEPTION_VECTORS, %ecx #NUM_EXCEPTION_VECTORS(=32) を ecx に格納 #loop 命令は ecx をループカウンタとして使用 1: movl %eax, (%edi) #idt_table[ i ].a = early_idt_handler_array[ j ] movl %eax,4(%edi) #idt_table[ i ].b = early_idt_handler_array[ j ] movl $(0x8E000000 + __KERNEL_CS), 2(%edi) #0x8E000000 + __KERNEL_CS = 0x8E000060 #idt_table[ i ].base0 = 0x0060 #idt_table[ i ] .base1=0x00, .type:0xE, .s:0, .dpl:0, .p:1 add $EARLY_IDT_HANDLER_SIZE, %eax #i ++ addl $8, %edi #j ++ loop 1b #ecx = 0 ⇒ ループ終了 …
  • 20. IDT の初期化 (14) ・ 1 度目の初期化 (4) - early_idt_handler_array [ver4.9.16 arch/x86/kernel/head_32.S:610] ENTRY(early_idt_handler_array) i = 0 .rept NUM_EXCEPTION_VECTORS .ifeq (EXCEPTION_ERRCODE_MASK >> i) & 1 pushl $0 # Dummy error code, to make stack frame uniform .endif pushl $i #20(%esp) Vector Number jmp early_idt_handler_common i = i + 1 .fill early_idt_handler_array + i*EARLY_IDT_HANDLER_SIZE - . , 1, 0xcc .endr < コンパイル時 > ・ NUM_EXCEPTION_VECTORS の回数分 ,[.rept] ~ [.endr] ディレクティブ内の処理を機械語として展開 ・ .fill で ,EARLY_IDT_HANDLER_SIZE(=9)byte 領域の残りの部分を 0xCC で埋める < 実行時 > ・ [arch/x86/include/asm/segment.h:211]   #define EXCEPTION_ERRCODE_MASK 0x0027d00  例外発生時に制御回路がハードウェアエラーコードを自動的にスタックに積まない例外ベクタの指定   0000 0000 0000 0010 0111 1101 0000 0000 ⇒ ベクタ番号 8, 10, 11, 12, 13, 14, 17  この場合は ,push $0 で 0 をスタックに積む ・ push $i で例外ベクタ番号をスタックに積む ・ early_idt_handler_common にジャンプして , 初期段階の共通割り込みハンドラへの処理へ移るようにする ・ early_idt_handler_common は early_fixup_exception() 関数 [ver4.9.16 arch/x86/mm/extable.c:129] を呼び出す ・ early_fixup_exception() 関数は ,fixup_exception() 関数を呼び出して修復可能ならば例外を修復する .
  • 21. IDT の初期化 (15) ・ 1 度目の初期化 (5) – setup_once 初期割り込みハンドラ … # 初期割り込みハンドラの設定 movl $256 – NUM_EXCEPTION_VECTORS, %ecx #loop 命令のループカウンタとして使用 movl $ignore_int, %edx #edx に ignore_int のアドレスを格納 movl $(__KERNEL_CS << 16), %eax #eax に __KERNEL_CS(0x60) を #16bit 左シフトして格納 (0x00600000) movl %dx, %ax #16bit 版 ignore_int のオフセットを ax に格納 # これにアクセスするときは , #cs に selector=0x10=BOOT_CS を指定 #eax=0x0060[?][?][?][?] movw $0x8E00, %dx #interrupt gate – dpl=0, present #edx=[?][?][?][?]8E00 2: movl %eax, (%edi) #idt_table[ i ].a = early_idt_handler_array[ j ] movl %edx, 4(%edi) #idt_table[ i ].b = ignore_int #idt_table[ i ] .base1=0x00, .type:0xE, .s:0, .dpl:0, .p:1 #idt_table[ i ] .limit:[?], avl: [?], l: [?], d: [?], g: [?], base2: [?]; add $8, %edi #j ++ loop 2b #ifdef CONFIG_CC_STACKPROTECTOR … #endif andl $0,setup_once_ref # 一回設定すると ,setup_once_ref を 0 にする ret # 呼び出し元に戻る
  • 22. IDT の初期化 (16) ・ 1 度目の初期化 (6) - ignore_int アセンブリ言語で記述された何もしない割り込みハンドラ . 1. いくつかのレジスタをスタックに退避 2.printk() 関数を用いて , 「 Unknown interrupt or fault at : … 」  というシステムメッセージ表示 3. スタックに退避しておいたレジスタの値を復帰 . 4.iret 命令を実行して , 割り込まれたプログラムを再開させる . [ver4.9.16 arch/x86/kernel/head_32.S:610] ALIGN ignore_int: cld #ifdef CONFIG_PRINTK …printk 関数を呼び出す #endif iret
  • 23. IDT の初期化 (17) ・ 1 度目の初期化 (7) - 初期 IDT の有効化 startup_32/startup_32_smp -> default_entry -> enable_paging [ver 4.9.16 arch/x86/kernel/head_32.S:414] … call *%eax #setup_once を呼び出す . 一度設定していたら呼び出さない . 1: … i486 かどうかチェック . 最低でも i486 でないといけない is486: movl $0x50022, %ecx #AM, WP, NE, MP をセット movl %cr0, %eax andl $0x80000011, %eax #PG, PE, ET を保存 orl %ecx, %eax movl %eax, %cr0 #cr0 に設定を反映 lgdt early_gdt_descr lidt idt_descr ljmp $(__KERNEL_CS), $1f 1: movl $(__KERNEL_DS), %eax # セグメントレジスタを再読み込み movl %eax, %ss ・ ds,es を __USER_DS で初期化 ・ fs を __KERNEL_PERCPU で初期化 ・ gs を __KERNEL_STACK_CANARY で初期化 ・ LDT をクリア pushl $0 #fake return address for unwinder jmp *(initial_code) #BootStrapProcessor -> i386_start_kernel へジャンプ #ApplicationProcessor -> start_secondary へジャンプ
  • 24. IDT の初期化 (18) ・ 2 度目の初期化 (1) - early_trap_init トラップゲートディスクリプタ初期化 startup_32 -> i386_start_kernel -> start_kernel startup_64 -> x86_64_start_kernel -> start_kernel -> local_irq_disable(), setup_arch()->early_trap_init(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable() [ver 4.9.16 arch/x86/kernel/traps.c:897] 896 /* Set of traps needed for early debugging. */ 897 void __init early_trap_init(void) 898 { 911 set_intr_gate_notrace(X86_TRAP_DB, debug); 912 /* int3 can be called from all */ 913 set_system_intr_gate(X86_TRAP_BP, &int3); 914 #ifdef CONFIG_X86_32 915 set_intr_gate(X86_TRAP_PF, page_fault); 916 #endif 917 load_idt(&idt_descr); 918 } [ver 4.9.16 arch/x86/include/asm/traps.h:129] X86_TRAP_DB :1 Debug X86_TRAP_BP :3 BreakPoint X86_TRAP_PF :14 PageFault [ver 4.9.16 arch/x86/include/asm/desc.h:93] #define load_idt(dtr) native_load_idt(dtr) [ver 4.9.16 arch/x86/include/asm/desc.h:221] static inline void native_load_idt(const struct desc_ptr *dtr){ asm volatile(“lidt %0”::”m” (*dtr)); }
  • 25. IDT の初期化 (19) ・ 2 度目の初期化 (1) - trap_init 割り込みハンドラ設定 (1) startup_32 -> i386_start_kernel -> start_kernel startup_64 -> x86_64_start_kernel -> start_kernel -> local_irq_disable(), setup_arch()->early_trap_init(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable() [ver 4.9.16 arch/x86/kernel/traps.c:927] 927 void __init trap_init(void) 928 { 929 int i; 939 set_intr_gate(X86_TRAP_DE, divide_error); 940 set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK); 941 /* int4 can be called from all */ 942 set_system_intr_gate(X86_TRAP_OF, &overflow); 943 set_intr_gate(X86_TRAP_BR, bounds); 944 set_intr_gate(X86_TRAP_UD, invalid_op); 945 set_intr_gate(X86_TRAP_NM, device_not_available); 946 #ifdef CONFIG_X86_32 947 set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS); 948 #else 949 set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK); 950 #endif 951 set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun); 952 set_intr_gate(X86_TRAP_TS, invalid_TSS); 953 set_intr_gate(X86_TRAP_NP, segment_not_present); 954 set_intr_gate(X86_TRAP_SS, stack_segment); 955 set_intr_gate(X86_TRAP_GP, general_protection); 956 set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug); 957 set_intr_gate(X86_TRAP_MF, coprocessor_error); 958 set_intr_gate(X86_TRAP_AC, alignment_check);
  • 26. IDT の初期化 (19) ・ 2 度目の初期化 (1) - trap_init 割り込みハンドラ設定 (2) startup_32 -> i386_start_kernel -> start_kernel startup_64 -> x86_64_start_kernel -> start_kernel -> local_irq_disable(), setup_arch()->early_trap_init(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable() [ver 4.9.16 arch/x86/kernel/traps.c:927] 959 #ifdef CONFIG_X86_MCE 960 set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK); 961 #endif 962 set_intr_gate(X86_TRAP_XF, simd_coprocessor_error); 963 964 /* Reserve all the builtin and the syscall vector: */ 965 for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++) 966 set_bit(i, used_vectors); 967 968 #ifdef CONFIG_IA32_EMULATION 969 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_compat); 970 set_bit(IA32_SYSCALL_VECTOR, used_vectors); 971 #endif 972 973 #ifdef CONFIG_X86_32 974 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32); 975 set_bit(IA32_SYSCALL_VECTOR, used_vectors); 976 #endif 977 978 /* 979 * Set the IDT descriptor to a fixed read-only location, so that the 980 * "sidt" instruction will not leak the location of the kernel, and 981 * to defend the IDT against arbitrary memory write vulnerabilities. 982 * It will be reloaded in cpu_init() */ #F00F バグ対策 983 __set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO); 984 idt_descr.address = fix_to_virt(FIX_RO_IDT);
  • 27. IDT の初期化 - F00F バグ * ・ Pentium の一部モデル ( 古いモデル ) に F00F バグが存在 .  対策 : 本物の IDT を , 読み出し専用で      固定マップリニアアドレス FIX_RO_IDT にマッピングし ,      このリニアアドレスを指すように idtr レジスタを初期化 .      この領域の物理メモリに直接アクセス出来る様にすることで ,      当該モデルでハングアップがなくなる .
  • 28. IDT の初期化 - F00F バグ * ・ Pentium の一部モデルでは F00F バグが存在 .   Pentium,Pentium MMX,Pentium オーバードライブプロセッサの ,  ある世代以前のモデルにある設計上の不具合 .   Pentium Pro 以降はこのバグの影響はない . ・問題を起こす機械語バイト列「 F0 0F C7 C8 」の先頭 2byte に由来 . ・「ロックされた CMPXCHG8B インストラクションでの不正なオペランド」  と Intel は呼んでいる ・「 F0 0F C7 C8 」のニーモニック   lock cmpxchg8b eax ・ cmpxchg8b 命令 オペランドとして指定されたアドレスのメモリ上の 8byte 領域を得る . edx:eax の値とメモリ上の 8byte 値を比較 一致 → Z フラグ =1,ecx:ebx の値を 8byte 領域にストア . 不一致 → Z フラグ =0,8byte データを edx:eax にロード ・ cmpxchg8b のオペランドにレジスタを指定すると「不正命令例外」   → 例外ハンドラが呼ばれる . ・ lock プレフィクスをつけると以後のメモリアクセスが抑制される為 ,   → 例外ハンドラを実行できず , この段階でハングアップし ,  一切の命令を実行せず , 割り込みも受け付けなくなってしまう . ・ Linux ではカーネル 2.0.32/2.1.64 以降で対策が取られている . https://ja.wikipedia.org/wiki/Pentium_F00F_バグ
  • 29. IDT の初期化 (19) ・ 2 度目の初期化 (1) - trap_init 割り込みハンドラ設定 (3) startup_32 -> i386_start_kernel -> start_kernel startup_64 -> x86_64_start_kernel -> start_kernel -> local_irq_disable(), setup_arch()->early_trap_init(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable() [ver 4.9.16 arch/x86/kernel/traps.c:927] 985 986 /* 987 * Should be a barrier for any external CPU state: 988 */ 989 cpu_init(); 990 991 /* 992 * X86_TRAP_DB and X86_TRAP_BP have been set 993 * in early_trap_init(). However, ITS works only after 994 * cpu_init() loads TSS. See comments in early_trap_init(). 995 */ 996 set_intr_gate_ist(X86_TRAP_DB, &debug, DEBUG_STACK); 997 /* int3 can be called from all */ 998 set_system_intr_gate_ist(X86_TRAP_BP, &int3, DEBUG_STACK); 999 1000 x86_init.irqs.trap_init(); #no operation. x86_init_noop() 1001 1002 #ifdef CONFIG_X86_64 1003 memcpy(&debug_idt_table, &idt_table, IDT_ENTRIES * 16); 1004 set_nmi_gate(X86_TRAP_DB, &debug); 1005 set_nmi_gate(X86_TRAP_BP, &int3); 1006 #endif 1007 }
  • 30. IDT の初期化 (19) ・ 2 度目の初期化 (1) - early_irq_init  割り込みゲートディスクリプタ初期化 startup_32 -> i386_start_kernel -> start_kernel startup_64 -> x86_64_start_kernel -> start_kernel start_kernel -> local_irq_disable(), setup_arch(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable() [ver 4.9.16 kernel/irq/irqdesc.c:504] 504 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = { 505 [0 ... NR_IRQS-1] = { 506 .handle_irq = handle_bad_irq, 507 .depth = 1, 508 .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock), 509 } 510 }; [ver 4.9.16 include/linux/irqdesc.h:51] 割り込みディスクリプタ struct irq_desc の定義 [ver 4.9.16 kernel/irq/irqdesc.c:512] 512 int __init early_irq_init(void) 513 { 514 int count, i, node = first_online_node; 515 struct irq_desc *desc; 517 init_irq_default_affinity(); 519 printk(KERN_INFO "NR_IRQS:%dn", NR_IRQS); 521 desc = irq_desc; 522 count = ARRAY_SIZE(irq_desc); 524 for (i = 0; i < count; i++) { 525 desc[i].kstat_irqs = alloc_percpu(unsigned int); 526 alloc_masks(&desc[i], GFP_KERNEL, node); 527 raw_spin_lock_init(&desc[i].lock); 528 lockdep_set_class(&desc[i].lock, &irq_desc_lock_class); 529 desc_set_defaults(i, &desc[i], node, NULL, NULL); 530 } 531 return arch_early_irq_init(); 532 }
  • 31. IDT の初期化 (20) ・ 2 度目の初期化 (2) - init_IRQ BSP のベクタと irq_desc を紐付け start_kernel -> local_irq_disable(), setup_arch(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable() [ver 4.9.16 arch/x86/kernel/irqinit.c:84] void __init init_IRQ(void) int i; for (i = 0; i < nr_legacy_irqs(); i++) #legacy pic(8259A) の IRQ 数分だけ設定 per_cpu(vector_irq, 0)[ISA_IRQ_VECTOR(i)] = irq_to_desc(i); #BSP のベクタと irq_desc を紐付け x86_init.irqs.intr_init(); } [ver 4.9.16 arch/x86/include/asm/hw_irq.h:180] typedef struct irq_desc* vector_irq_t[NR_VECTORS]; DECLARE_PER_CPU(vector_irq_t, vector_irq); [ver 4.9.16 arch/x86/kernel/irqinit.c:54] DEFINE_PER_CPU(vector_irq_t, vector_irq) = { [0 ... NR_VECTORS – 1] = VECTOR_UNUSED, }; [ver 4.9.16 kernel/irq/irqdesc.c:534] struct irq_desc *irq_to_desc(unsigned int irq) { return (irq < NR_IRQS) ? irq_desc + irq : NULL; } EXPORT_SYMBOL(irq_to_desc);
  • 32. IDT の初期化 (21) ・ 2 度目の初期化 (3) - x86_init.irqs.intr_init start_kernel -> local_irq_disable(), setup_arch(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable() init_IRQ -> x86_init.irqs.intr_init() = native_init_IRQ() [ver 4.9.16 arch/x86/kernel/irqinit.c:167] 167 void __init native_init_IRQ(void) 168 { 169 int i; 172 x86_init.irqs.pre_vector_init(); # デバイス初期化 174 apic_intr_init(); #APIC のベクタ初期化 181 i = FIRST_EXTERNAL_VECTOR; #0x20(32) 182 #ifndef CONFIG_X86_LOCAL_APIC 183 #define first_system_vector NR_VECTORS 184 #endif # この時点までで設定されていない未使用ベクタのハンドラを設定 ( 通常の割り込みハンドラ設定 ) 185 for_each_clear_bit_from(i, used_vectors, first_system_vector) { #first_system_vector: 256 or 0xef 186 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */ 187 set_intr_gate(i, irq_entries_start + 188 8 * (i - FIRST_EXTERNAL_VECTOR)); 189 } 190 #ifdef CONFIG_X86_LOCAL_APIC 191 for_each_clear_bit_from(i, used_vectors, NR_VECTORS) 192 set_intr_gate(i, spurious_interrupt); # スプリアス割り込みのハンドラを設定 193 #endif 195 if (!acpi_ioapic && !of_ioapic && nr_legacy_irqs()) 196 setup_irq(2, &irq2); # IR2 のハンドラを設定 198 #ifdef CONFIG_X86_32 199 irq_ctx_init(smp_processor_id()); #hardirq と softirq を処理する為の CPU 毎のスタックを確保 200 #endif 201 }
  • 33. IDT の初期化 (22) ・ 2 度目の初期化 (4) - irq_entries_start / common_interrupt start_kernel -> local_irq_disable(), setup_arch(), trap_init(), early_irq_init(), init_IRQ(), local_irq_enable() init_IRQ -> x86_init.irqs.intr_init() = native_init_IRQ() [ver 4.9.16 arch/x86/entry/entry_32.S:603] 603 /* 604 * Build the entry stubs with some assembler magic. 605 * We pack 1 stub into every 8-byte block. 606 */ 607 .align 8 608 ENTRY(irq_entries_start) 609 vector=FIRST_EXTERNAL_VECTOR #0x20 610 .rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR) 611 pushl $(~vector+0x80) /* Note: always in signed byte range */ 612 vector=vector+1 613 jmp common_interrupt 614 .align 8 615 .endr 616 END(irq_entries_start) 617 618 /* 619 * the CPU automatically disables interrupts when executing an IRQ vector, 620 * so IRQ-flags tracing has to follow that: 621 */ 622 .p2align CONFIG_X86_L1_CACHE_SHIFT 623 common_interrupt: 624 ASM_CLAC 625 addl $-0x80, (%esp) /* Adjust vector into the [-256, -1] range */ 626 SAVE_ALL 627 TRACE_IRQS_OFF 628 movl %esp, %eax 629 call do_IRQ 630 jmp ret_from_intr 631 ENDPROC(common_interrupt)
  • 34. IDT の初期化 (23) ・ 2 度目の初期化 (5) - IDT の有効化 start_kernel() [ver 4.9.16 init/main.c:479] BootStrap Processor のみ -> rest_init() [ver 4.9.16 init/main.c:383] -> kernel_init スレッド [ver 4.9.16 init/main.c:939] -> kernel_init_freeable() [ver 4.9.16 init/main.c:986] -> smp_init() [ver 4.9.16 kernel/smp.c:552] 各 CPU 専用の初期化処理 . -> cpu_up(cpu) [ver 4.9.16 kernel/cpu.c:1093]    BSP は boot_cpu_init() で CPUHP_ONLINE になる為 ,smp_init 内でのこの関数呼出しは ApplicationProcessor のみ . -> do_cpu_up( 呼出し引数: cpu, CPUHP_ONLINE) [ver 4.9.16 kernel/cpu.c:1063] -> _cpu_up( 呼出し引数: cpu, 0, target) [ver 4.9.16 kernel/cpu.c:1005] -> cpuhp_up_callback( 呼出し引数: cpu, st, CPUHP_BRINGUP_CPU) [ver 4.9.16 kernel/cpu.c:485] st->state が以下の順で変わっていく CPUHP_OFFLINE->CPUHP_CREATE_THREADS->CPUHP_PREF_PREPARE ->CPUHP_WORKQUEUE_PREP->CPUHP_HRTIMERS_PREPARE->CPUHP_SMPCFD_PREPARE ->CPUHP_RELAY_PREPARE->CPUHP_SLAB_PREPARE->CPUHP_RCUTREE_PREP ->CPUHP_NOTIFY_PREPARE->CPUHP_BRINGUP_CPU -> … -> cpuhp_invoke_callback( 呼出し引数: cpu, st->state, true, NULL) [ver 4.9.16 kernel/cpu.c:122] 各 state の startup.single を呼び出す . cb( 呼出し引数: cpu) [ver 4.9.16 kernel/cpu.c:136] = bringup_cpu( 呼出し引数: cpu) [ver 4.9.16 kernel/cpu.c:421] 以降は CPUHP_BRINGUP_CPU の場合 -> __cpu_up( 呼出し引数: cpu, tidle) [ver 4.9.16 arch/x86/include/asm/smp.h:93] -> smp_ops.cpu_up( 呼出し引数: cpu, tidle) [ver 4.9.16 arch/x86/include/asm/smp.h:95] = native_cpu_up( 呼出し引数: cpu, tidle) [ver 4.9.16 arch/x86/kernel/smpboot.c:1070] -> do_boot_cpu( 呼出し引数: apicid, cpu, tidle) [ver 4.9.16 arch/x86/kernel/smpboot.c:949] -> apic->wakeup_secondary_cpu( 呼出し引数: apicid, start_ip) APIC あり wakeup_cpu_via_init_nmi( 呼出し引数: cpu, start_ip, apicid, &cpu0_nmi_registered) APIC なし -> startup_32_smp -> start_secondary()   [ver 4.9.16 arch/x86/kernel/smpboot.c:213] Application Processor のみ -> cpu_init() (CONFIG_X86_64) [ver 4.9.16 arch/x86/kernel/cpu/common.c:1470] cpu_init() (CONFIG_X86_32) [ver 4.9.16 arch/x86/kernel/cpu/common.c:1573] -> load_current_idt() [ver 4.9.16 arch/x86/include/asm/desc.h:495] -> switch_to_new_gdt(cpu) [ver 4.9.16 arch/x86/kernel/cpu/common.c:450]
  • 35. IDT の初期化 (24) ・ 2 度目の初期化 (5) - IDT の有効化 -> load_current_idt() [ver 4.9.16 arch/x86/include/asm/desc.h:495] -> load_idt(&idt_descr) [ver 4.9.16 arch/x86/include/asm/desc.h:93] -> switch_to_new_gdt(cpu) [ver 4.9.16 arch/x86/kernel/cpu/common.c:450] -> load_gdt(&gdt_descr) [ver 4.9.16 arch/x86/include/asm/desc.h:92] -> native_load_gdt(dtr) [ver 4.9.16 arch/x86/include/asm/desc.h:216] -> asm volatile(“lgdt %0”::”m” (*dtr));[ver 4.9.16 arch/x86/include/asm/desc.h:218]