35
4 章 Linux カーネル – 割り込み・例外 5 ・ IDT ・ IDT の初期化 mao Web >https://www.pridact.com Twitter >https://twitter.com/rivarten Mail >[email protected]

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

  • Upload
    mao999

  • View
    224

  • Download
    0

Embed Size (px)

Citation preview

4 章 Linux カーネル – 割り込み・例外 5・ IDT・ IDT の初期化

maoWeb >https://www.pridact.comTwitter >https://twitter.com/rivartenMail   >[email protected]

はじめに

割り込み・例外の話

視点・概要・ソースコード

ソースコードの情報が載っている場合は ,実際にソースコードに目を通しながら資料を見ていったほうがいいかもしれません。

今回はソースコードを読み進めていきます。

この資料について

・間違っていたらご指摘をお願い致します。・ 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_64static 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]__HEADENTRY(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,%eaxjz 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]__INITsetup_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) & 1pushl $0 # Dummy error code, to make stack frame uniform.endifpushl $i #20(%esp) Vector Numberjmp early_idt_handler_commoni = 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 関数を呼び出す

#endifiret

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, %eaxandl $0x80000011, %eax #PG, PE, ET を保存orl %ecx, %eaxmovl %eax, %cr0 #cr0 に設定を反映

lgdt early_gdt_descrlidt idt_descr

ljmp $(__KERNEL_CS), $1f1: 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 unwinderjmp *(initial_code) #BootStrapProcessor -> i386_start_kernel へジャンプ

#ApplicationProcessor -> start_secondary へジャンプ

IDT の初期化 (18)

・ 2度目の初期化 (1) - early_trap_init トラップゲートディスクリプタ初期化startup_32 -> i386_start_kernel -> start_kernelstartup_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 DebugX86_TRAP_BP :3 BreakPointX86_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_kernelstartup_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_32947 set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);948 #else949 set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);950 #endif951 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_kernelstartup_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_MCE960 set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);961 #endif962 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_EMULATION969 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_compat);970 set_bit(IA32_SYSCALL_VECTOR, used_vectors);971 #endif972 973 #ifdef CONFIG_X86_32974 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32);975 set_bit(IA32_SYSCALL_VECTOR, used_vectors);976 #endif977 978 /*979 * Set the IDT descriptor to a fixed read-only location, so that the980 * "sidt" instruction will not leak the location of the kernel, and981 * 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_kernelstartup_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 set993 * in early_trap_init(). However, ITS works only after994 * 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_641003 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 #endif1007 }

IDT の初期化 (19)

・ 2度目の初期化 (1) - early_irq_init  割り込みゲートディスクリプタ初期化startup_32 -> i386_start_kernel -> start_kernelstartup_64 -> x86_64_start_kernel -> start_kernelstart_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:%d\n", 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_initstart_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_interruptstart_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]