早期 trap gate 初始化

start_kernel()line 23调用了setup_arch()来完成很多架构相关的初始化工作

setup_arch 函数中与中断相关的第一个函数是 idt_setup_early_traps函数,其对IDT进行了中断服务函数入口的填充。

idt_setup_early_traps函数分析

idt_setup_early_traps函数于/source/arch/x86/kernel/idt.c#L253处实现

/**
 * idt_setup_early_traps - Initialize the idt table with early traps
 *
 * On X8664 these traps do not use interrupt stacks as they can't work
 * before cpu_init() is invoked and sets up TSS. The IST variants are
 * installed after that.
 */
void __init idt_setup_early_traps(void)
{
    idt_setup_from_table(idt_table, early_idts, ARRAY_SIZE(early_idts), true);
    load_idt(&idt_descr);
}

// In /source/arch/x86/kernel/idt.c#L58

/*
 * Early traps running on the DEFAULT_STACK because the other interrupt
 * stacks work only after cpu_init().
 */
static const __initconst struct idt_data early_idts[] = {
    INTG(X86_TRAP_DB,       debug),
    SYSG(X86_TRAP_BP,       int3),
#ifdef CONFIG_X86_32
    INTG(X86_TRAP_PF,       page_fault),
#endif
};

// In /source/arch/x86/kernel/idt.c#L218

static void idt_setup_from_table(gate_desc *idt, const struct idt_data *t, int size, bool sys)
{
    gate_desc desc;

    for (; size > 0; t++, size--) {
        // 初始化 desc 的各个成员变量
        idt_init_desc(&desc, t);
        // 将 desc 填入 idt
        write_idt_entry(idt, t->vector, &desc);
        if (sys)
            set_bit(t->vector, system_vectors);
    }
}

// In /source/arch/x86/kernel/idt.c#L203

static inline void idt_init_desc(gate_desc *gate, const struct idt_data *d)
{
    unsigned long addr = (unsigned long) d->addr;

    gate->offset_low    = (u16) addr;
    gate->segment       = (u16) d->segment;
    gate->bits      = d->bits;
    gate->offset_middle = (u16) (addr >> 16);
#ifdef CONFIG_X86_64
    gate->offset_high   = (u32) (addr >> 32);
    gate->reserved      = 0;
#endif
}

idt_setup_from_table中,首先调用了idt_init_desc初始化了一个表示 IDT 入口项的 gate_desc 类型的结构体。

然后把这个中断门通过 write_idt_entry 宏填入了 IDT 中。这个宏展开后是 native_write_idt_entry ,其将中断门信息通过索引拷贝到了 idt_table 之中

// In /source/arch/x86/include/asm/desc.h#L128

#define write_idt_entry(dt, entry, g)      native_write_idt_entry(dt, entry, g)

// In /source/arch/x86/include/asm/desc.h#L141

static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
{
    memcpy(&idt[entry], gate, sizeof(*gate));
}

关于 gate_desc 结构体

gate_desc 结构体是一个在 x86 中被称为门的 16 字节数组。它拥有下面的结构:

gate_desc/source/arch/x86/include/asm/desc_defs.h#L88中定义

typedef struct gate_struct gate_desc;

gate_struct/source/arch/x86/include/asm/desc_defs.h#L77中定义

struct gate_struct {
    u16     offset_low;
    u16     segment;
    struct idt_bits bits;
    u16     offset_middle;
#ifdef CONFIG_X86_64
    u32     offset_high;
    u32     reserved;
#endif
} __attribute__((packed));

struct idt_bits {
    u16     ist : 3,
            zero    : 5,
            type    : 5,
            dpl : 2,
            p   : 1;
} __attribute__((packed));

为了能从中断号得到对应的IDT,处理器把异常和中断向量分为 16 个级别。处理器处理异常和中断的发生就像它看到 call 指令时处理一个程序调用一样。处理器使用中断或异常的唯一的识别码(即中断号)作为索引来寻找对应的 IDT 的条目。

IDT中的 IDT 条目由下面的域组成:

  • 0-15 bits – 段选择器偏移,处理器用它作为中断处理程序的入口指针基址。
  • 16-31 bits – 段选择器基址,包含中断处理程序入口指针。
  • IST – 在 x86_64 上的一个新的机制。
  • Type – 描述了 IDT 条目的类型。(即:中断门、任务门、陷阱门)
  • DPL – 描述符特权等级。
  • P – 段存在标志。
  • 48-63 bits – 中断处理程序基址的第二部分。
  • 64-95 bits – 中断处理程序基址的第三部分。
  • 96-127 bits – CPU 保留位。

0x03 异常处理前处理

我们在之前讨论了IDT的初始化过程,现在我们来详细的看一看异常处理究竟是如何执行的。

首先我们注意到给idt_setup_from_table传入的参数有一项为early_idts数组,其中定义了DEBUGINT3(、page_fault)两种异常(32位架构时,额外定义page_fault异常)。也就是说,在cpu_init()执行前,内核就已经能够处理这两种异常,那么我们就以这两种异常为例进行分析。

调试异常和断点异常

第一个异常 —— debug异常(助记符为#DB),通常在在调试事件发生异常时报告。

例如:尝试更改调试寄存器的内容。(调试寄存器是x86英特尔80386处理器开始出现在处理器中的特殊寄存器,从此它的名称可以确定这些寄存器的主要用途是调试)这些寄存器允许在代码上设置断点,并读取或写入数据以对其进行跟踪。调试寄存器只能在特权模式下访问,以任何其他特权级别执行时尝试读取或写入调试寄存器都会导致一般保护错误异常(General_protection_fault)因此使用set_intr_gate_ist初始化#DB异常,而不是set_system_intr_gate_ist

#DB异常的Verctor编号为1(也称为X86_TRAP_DB),并且正如我们在规范中看到的那样,该异常没有错误代码

Verctor 编号 异常助记符 异常描述 异常类型 错误代码
1 #DB Reserved F/T NO

第二个异常 —— breakpoint异常(助记符为#BP),当处理器执行int 3指令时发生异常。与DB异常不同,该#BP异常可能发生在用户空间中。我们可以将其添加到代码中的任何位置,例如,让我们看一下简单的程序:

// breakpoint.c
#include <stdio.h>

int main() {
    int i;
    while (i < 6){
        printf("i equal to: %d\n", i);
        __asm__("int3");
        ++i;
    }
}

如果我们编译并运行该程序,我们将看到以下输出:

$ gcc breakpoint.c -o breakpoint
i equal to: 0
Trace/breakpoint trap

但是,如果将其与gdb一起运行,我们将看到断点并可以继续执行程序:

$ gdb breakpoint
...
...
...
(gdb) run
Starting program: /home/alex/breakpoints 
i equal to: 0

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000400585 in main ()
=> 0x0000000000400585 <main+31>:    83 45 fc 01    add    DWORD PTR [rbp-0x4],0x1
(gdb) c
Continuing.
i equal to: 1

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000400585 in main ()
=> 0x0000000000400585 <main+31>:    83 45 fc 01    add    DWORD PTR [rbp-0x4],0x1
(gdb) c
Continuing.
i equal to: 2

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000400585 in main ()
=> 0x0000000000400585 <main+31>:    83 45 fc 01    add    DWORD PTR [rbp-0x4],0x1
...
...
...
分类: CTF

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注