本文将深入解析Intel int n instruction。

1. Theory

The int n instruction uses a vector as an argument, which allows a program to call any interrupt handler.

Dive into Intel iret instruction

2. Source code in KVM-Unit-Tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#define USERMODE_STACK_SIZE	0x2000
#define RET_TO_KERNEL_IRQ 0x20

uint64_t run_in_user(usermode_func func, unsigned int fault_vector,
uint64_t arg1, uint64_t arg2, uint64_t arg3,
uint64_t arg4, bool *raised_vector)
{
extern char ret_to_kernel;
uint64_t rax = 0;
static unsigned char user_stack[USERMODE_STACK_SIZE];

...
set_idt_entry(RET_TO_KERNEL_IRQ, &ret_to_kernel, 3);
...

asm volatile (
/* Prepare kernel SP for exception handlers */
"mov %%rsp, %[rsp0]\n\t"
/* Load user_ds to DS and ES */
"mov %[user_ds], %%ax\n\t"
"mov %%ax, %%ds\n\t"
"mov %%ax, %%es\n\t"
/* IRET into user mode */
"pushq %[user_ds]\n\t"
"pushq %[user_stack_top]\n\t"
"pushfq\n\t"
"pushq %[user_cs]\n\t"
"pushq $user_mode\n\t"
"iretq\n"

"user_mode:\n\t"
/* Back up registers before invoking func */
"push %%rbx\n\t"
"push %%rcx\n\t"
"push %%rdx\n\t"
"push %%r8\n\t"
"push %%r9\n\t"
"push %%r10\n\t"
"push %%r11\n\t"
"push %%rdi\n\t"
"push %%rsi\n\t"
/* Call user mode function */
"mov %[arg1], %%rdi\n\t"
"mov %[arg2], %%rsi\n\t"
"mov %[arg3], %%rdx\n\t"
"mov %[arg4], %%rcx\n\t"
"call *%[func]\n\t"
/* Restore registers */
"pop %%rsi\n\t"
"pop %%rdi\n\t"
"pop %%r11\n\t"
"pop %%r10\n\t"
"pop %%r9\n\t"
"pop %%r8\n\t"
"pop %%rdx\n\t"
"pop %%rcx\n\t"
"pop %%rbx\n\t"
/* Return to kernel via system call */
"int %[kernel_entry_vector]\n\t"
/* Kernel Mode */
"ret_to_kernel:\n\t"
"mov %[rsp0], %%rsp\n\t"
:
"+a"(rax),
[rsp0]"=m"(tss[0].rsp0)
:
[arg1]"m"(arg1),
[arg2]"m"(arg2),
[arg3]"m"(arg3),
[arg4]"m"(arg4),
[func]"m"(func),
[user_ds]"i"(USER_DS),
[user_cs]"i"(USER_CS),
[user_stack_top]"r"(user_stack +
sizeof(user_stack)),
[kernel_entry_vector]"i"(RET_TO_KERNEL_IRQ)
:
"rsi", "rdi", "rcx", "rdx");

return rax;
}

2.1 set_idt_entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set_idt_entry(RET_TO_KERNEL_IRQ, &ret_to_kernel, 3);

void set_idt_entry(int vec, void *addr, int dpl)
{
idt_entry_t *e = &boot_idt[vec];
memset(e, 0, sizeof *e);
e->offset0 = (unsigned long)addr;
e->selector = read_cs();
e->ist = 0;
e->type = 14;
e->dpl = dpl;
e->p = 1;
e->offset1 = (unsigned long)addr >> 16;
e->offset2 = (unsigned long)addr >> 32;
}

2.2 int n

1
2
3
4
5
6
7
8
/* Return to kernel via system call */
"int %[kernel_entry_vector]\n\t"
/* Kernel Mode */
"ret_to_kernel:\n\t"
"mov %[rsp0], %%rsp\n\t"
:
"+a"(rax),
[rsp0]"=m"(tss[0].rsp0)

Why handler is mov %[rsp0], %%rsp?

processor会操作内核栈,rsp会发生变化,而handler则将rsp恢复到正确的状态。

2.3 tss[0].rsp0

1
2
3
"mov %%rsp, %[rsp0]\n\t"
::
[rsp0]"=m"(tss[0].rsp0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct  __attribute__((packed)) {
u32 res1;
u64 rsp0;
u64 rsp1;
u64 rsp2;
u64 res2;
u64 ist1;
u64 ist2;
u64 ist3;
u64 ist4;
u64 ist5;
u64 ist6;
u64 ist7;
u64 res3;
u16 res4;
u16 iomap_base;
} tss64_t;

3. 总结

掌握住int n instruction的Operation即可!
详情当然是参考Intel SDM Vol2 INSTRUCTION SET REFERENCE!

要点:

  • 用户栈切换到内核栈
  • 设置好IDT entry
    • handler(rip)
    • dpl(is 3)
    • selector(kernel code segment)