mirror of
https://github.com/0xAX/linux-insides.git
synced 2025-07-07 17:28:03 +00:00
Обновление linux-initialization-2.md
This commit is contained in:
parent
fdcd176d61
commit
6dc7698d6d
@ -194,7 +194,7 @@ static void set_intr_gate(unsigned int n, const void *addr)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Прежде всего он проверяет, что переданный номер прерывания не больше чем `255` с помощью макроса `BUG_ON`. Нам нужно сделать эту проверку, поскольку максимально возможное количество прерываний - `256`. Далее мы устаналиваем данные IDT заданными значениями. И уже после этого мы вызываем функцию `idt_setup_from_table`:
|
Прежде всего она проверяет, что переданный номер прерывания не больше чем `255` с помощью макроса `BUG_ON`. Нам нужно сделать эту проверку, поскольку максимально возможное количество прерываний - `256`. Далее мы устаналиваем данные IDT заданными значениями. И уже после этого мы вызываем функцию `idt_setup_from_table`:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
static void
|
static void
|
||||||
@ -249,21 +249,26 @@ asm volatile("lidt %0"::"m" (*dtr));
|
|||||||
Как говорилось ранее, мы заполнили `IDT` адресом `early_idt_handler_array`. Мы можем найти его в [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head_64.S):
|
Как говорилось ранее, мы заполнили `IDT` адресом `early_idt_handler_array`. Мы можем найти его в [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head_64.S):
|
||||||
|
|
||||||
```assembly
|
```assembly
|
||||||
.globl early_idt_handler_array
|
ENTRY(early_idt_handler_array)
|
||||||
early_idt_handlers:
|
|
||||||
i = 0
|
i = 0
|
||||||
.rept NUM_EXCEPTION_VECTORS
|
.rept NUM_EXCEPTION_VECTORS
|
||||||
.if (EXCEPTION_ERRCODE_MASK >> i) & 1
|
.if ((EXCEPTION_ERRCODE_MASK >> i) & 1) == 0
|
||||||
pushq $0
|
UNWIND_HINT_IRET_REGS
|
||||||
|
pushq $0 # Dummy error code, to make stack frame uniform
|
||||||
|
.else
|
||||||
|
UNWIND_HINT_IRET_REGS offset=8
|
||||||
.endif
|
.endif
|
||||||
pushq $i
|
pushq $i # 72(%rsp) Vector number
|
||||||
jmp early_idt_handler_common
|
jmp early_idt_handler_common
|
||||||
|
UNWIND_HINT_IRET_REGS
|
||||||
i = i + 1
|
i = i + 1
|
||||||
.fill early_idt_handler_array + i*EARLY_IDT_HANDLER_SIZE - ., 1, 0xcc
|
.fill early_idt_handler_array + i*EARLY_IDT_HANDLER_SIZE - ., 1, 0xcc
|
||||||
.endr
|
.endr
|
||||||
|
UNWIND_HINT_IRET_REGS offset=16
|
||||||
|
END(early_idt_handler_array)
|
||||||
```
|
```
|
||||||
|
|
||||||
Здесь мы видим создание обработчиков прерываний для первых `32` исключений. Мы проверяем, содержит ли исключение код ошибки, и ничего не делаем, если исключение не возвращает код ошибки, тогда мы помещаем в стек ноль. Мы делаем это для того чтобы стек был однородным. После этого мы помещаем номер исключения в стек и переходим на `early_idt_handler_array`, который является общим обработчиком прерываний на данный момент. Каждый девятый байт массива `early_idt_handler_array` состоит из необязательного кода ошибок, `номера вектора` и инструкции перехода. Мы можем видеть это в выводе утилиты `objdump`:
|
Функции, которые, как правило, не вызываются напрямую другими функциями, такие как `syscall` и обработчики прерываний, часто имеют необычные вещи вроде функции не C-типа с указателем стека. Такой код необходимо аннотировать с помощью макроса `UNWIND_HINT_IRET_REGS`, чтобы `objtool` смог его понять. Здесь мы видим создание обработчиков прерываний для первых `32` исключений. Мы проверяем, содержит ли исключение код ошибки, и ничего не делаем, если исключение не возвращает код ошибки, тогда мы помещаем в стек ноль. Мы делаем это для того чтобы стек был однородным. После этого мы помещаем номер исключения в стек и переходим на `early_idt_handler_array`, который является общим обработчиком прерываний на данный момент. Каждый девятый байт массива `early_idt_handler_array` состоит из необязательного кода ошибок, `номера вектора` и инструкции перехода. Мы можем видеть это в выводе утилиты `objdump`:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ objdump -D vmlinux
|
$ objdump -D vmlinux
|
||||||
@ -291,52 +296,42 @@ ffffffff81fe5014: 6a 02 pushq $0x2
|
|||||||
| %rflags |
|
| %rflags |
|
||||||
| %cs |
|
| %cs |
|
||||||
| %rip |
|
| %rip |
|
||||||
| rsp --> код ошибки |
|
| код ошибки | <-- %rsp
|
||||||
|--------------------|
|
|--------------------|
|
||||||
```
|
```
|
||||||
|
|
||||||
Давайте посмотрим на реализацию `early_idt_handler_common`. Он находится в том же ассемблерном файле [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head_64.S#L343) и первое что мы можем видеть это проверка [NMI](http://en.wikipedia.org/wiki/Non-maskable_interrupt). Нам не нужно обрабатывать их, поэтому просто игнорируем их в коде:
|
Давайте посмотрим на реализацию `early_idt_handler_common`. Он находится в том же ассемблерном файле [arch/x86/kernel/head_64.S](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/head_64.S) и прежде всего инкрементирует `early_recursion_flag`, чтобы предотвратить рекурсию в `early_idt_handler_common`:
|
||||||
|
|
||||||
```assembly
|
```assembly
|
||||||
cmpl $2,(%rsp)
|
incl early_recursion_flag(%rip)
|
||||||
je .Lis_nmi
|
|
||||||
```
|
```
|
||||||
|
|
||||||
где `is_nmi`:
|
Далее мы сохраняем регистры общего назначения в стек:
|
||||||
|
|
||||||
```assembly
|
```assembly
|
||||||
is_nmi:
|
|
||||||
addq $16,%rsp
|
|
||||||
INTERRUPT_RETURN
|
|
||||||
```
|
|
||||||
|
|
||||||
удаляет код ошибки и номер вектора из стека и вызывает макрос `INTERRUPT_RETURN`, который раскрывается до инструкции `iretq`. После проверки номера вектора (и это не `NMI`), мы проверяем `early_recursion_flag`, чтобы предотвратить рекурсию в `early_idt_handler_common`, и если он корректен, сохраняем регистры общего назначения в стек:
|
|
||||||
|
|
||||||
```assembly
|
|
||||||
pushq %rax
|
|
||||||
pushq %rcx
|
|
||||||
pushq %rdx
|
|
||||||
pushq %rsi
|
pushq %rsi
|
||||||
pushq %rdi
|
movq 8(%rsp), %rsi
|
||||||
|
movq %rdi, 8(%rsp)
|
||||||
|
pushq %rdx
|
||||||
|
pushq %rcx
|
||||||
|
pushq %rax
|
||||||
pushq %r8
|
pushq %r8
|
||||||
pushq %r9
|
pushq %r9
|
||||||
pushq %r10
|
pushq %r10
|
||||||
pushq %r11
|
pushq %r11
|
||||||
|
pushq %rbx
|
||||||
|
pushq %rbp
|
||||||
|
pushq %r12
|
||||||
|
pushq %r13
|
||||||
|
pushq %r14
|
||||||
|
pushq %r15
|
||||||
|
UNWIND_HINT_REGS
|
||||||
```
|
```
|
||||||
|
|
||||||
Мы должны сделать это, чтобы предотвратить появление неверных значений регистров при возврате из обработчика прерываний. После этого мы проверяем селектор сегмента в стеке:
|
Мы должны сделать это, чтобы предотвратить появление неверных значений регистров при возврате из обработчика прерываний. После этого мы проверяем номер вектора, и если он `#PF` или [ошибка страницы (Page Fault)](https://en.wikipedia.org/wiki/Page_fault), мы помещаем значение регистра `cr2` в регистр `rdi` и вызываем `early_make_pgtable` (мы скоро это увидим):
|
||||||
|
|
||||||
```assembly
|
```assembly
|
||||||
cmpl $__KERNEL_CS,96(%rsp)
|
cmpq $14,%rsi
|
||||||
jne 11f
|
|
||||||
```
|
|
||||||
|
|
||||||
который должен быть равен сегменту кода ядра, и если нет, мы переходим к метке `11`, которая печатает сообщение `PANIC` и выводит дамп стека.
|
|
||||||
|
|
||||||
После проверки сегмента кода мы проверяем номер вектора, и если это `#PF` или [ошибка страницы (Page Fault)](https://en.wikipedia.org/wiki/Page_fault), мы помещаем значение `cr2` в регистр `rdi` и вызываем `early_make_pgtable` (мы скоро это увидим):
|
|
||||||
|
|
||||||
```assembly
|
|
||||||
cmpl $14,72(%rsp)
|
|
||||||
jnz 10f
|
jnz 10f
|
||||||
GET_CR2_INTO(%rdi)
|
GET_CR2_INTO(%rdi)
|
||||||
call early_make_pgtable
|
call early_make_pgtable
|
||||||
@ -344,21 +339,23 @@ is_nmi:
|
|||||||
jz 20f
|
jz 20f
|
||||||
```
|
```
|
||||||
|
|
||||||
Если номер вектора не равен `#PF`, мы восстанавливаем регистры общего назначения из стека:
|
Если номер вектора не равен `#PF`, мы вызываем функцию `early_fixup_exception` с передачей указателя ядра (смотрите [соглашение о вызовах x86-64](https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions)):
|
||||||
|
|
||||||
```assembly
|
```assembly
|
||||||
popq %r11
|
10:
|
||||||
popq %r10
|
movq %rsp,%rdi
|
||||||
popq %r9
|
call early_fixup_exception
|
||||||
popq %r8
|
|
||||||
popq %rdi
|
|
||||||
popq %rsi
|
|
||||||
popq %rdx
|
|
||||||
popq %rcx
|
|
||||||
popq %rax
|
|
||||||
```
|
```
|
||||||
|
|
||||||
и выходим из обработчика с помощью `iret`.
|
Мы увидим реализацию функции `early_fixup_exception` позже.
|
||||||
|
|
||||||
|
```assembly
|
||||||
|
20:
|
||||||
|
decl early_recursion_flag(%rip)
|
||||||
|
jmp restore_regs_and_return_to_kernel
|
||||||
|
```
|
||||||
|
|
||||||
|
После того как мы уменьшим значение параметра `initial_recursion_flag`, мы восстанавливаем регистры из стека, которые мы сохранили ранее, и возвращаемся из обработчика с помощью `iretq`.
|
||||||
|
|
||||||
Это конец первого обработчика прерываний. Обратите внимание, что это очень ранний обработчик прерываний, поэтому он обрабатывает только ошибку страницы. Мы увидим обработчики и для других прерываний, но пока давайте посмотрим на обработчик ошибки страницы.
|
Это конец первого обработчика прерываний. Обратите внимание, что это очень ранний обработчик прерываний, поэтому он обрабатывает только ошибку страницы. Мы увидим обработчики и для других прерываний, но пока давайте посмотрим на обработчик ошибки страницы.
|
||||||
|
|
||||||
@ -367,16 +364,30 @@ is_nmi:
|
|||||||
|
|
||||||
В предыдущем разделе мы увидели первый начальный обработчик прерываний, который проверяет, что номер прерывания относится к ошибке страницы и вызывает `early_make_pgtable` для создания новых таблиц страниц. На данном этапе нам необходим обработчик `#PF`, поскольку планируется добавить способность загружать ядро выше `4G` и сделать структуру `boot_params` доступной над 4G.
|
В предыдущем разделе мы увидели первый начальный обработчик прерываний, который проверяет, что номер прерывания относится к ошибке страницы и вызывает `early_make_pgtable` для создания новых таблиц страниц. На данном этапе нам необходим обработчик `#PF`, поскольку планируется добавить способность загружать ядро выше `4G` и сделать структуру `boot_params` доступной над 4G.
|
||||||
|
|
||||||
Вы можете найти реализацию `early_make_pgtable` в [arch/x86/kernel/head64.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/head64.c) и он принимает только один параметр - адрес из регистра `cr2`, который вызывал ошибку страницы. Давайте посмотрим на неё более подробно:
|
Вы можете найти реализацию `early_make_pgtable` в [arch/x86/kernel/head64.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/head64.c) и он принимает только один параметр - адрес из регистра `cr2`, который вызывал ошибку страницы. Давайте посмотрим на неё более подробно:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
int __init early_make_pgtable(unsigned long address)
|
int __init early_make_pgtable(unsigned long address)
|
||||||
{
|
{
|
||||||
unsigned long physaddr = address - __PAGE_OFFSET;
|
unsigned long physaddr = address - __PAGE_OFFSET;
|
||||||
unsigned long i;
|
pmdval_t pmd;
|
||||||
|
|
||||||
|
pmd = (physaddr & PMD_MASK) + early_pmd_flags;
|
||||||
|
|
||||||
|
return __early_make_pgtable(address, pmd);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Затем мы вызываем функцию `__early_make_pgtable`, которая определена в том же файле, что и функция `early_make_pgtable`, как показано ниже:
|
||||||
|
|
||||||
|
```C
|
||||||
|
int __init __early_make_pgtable(unsigned long address, pmdval_t pmd)
|
||||||
|
{
|
||||||
|
unsigned long physaddr = address - __PAGE_OFFSET;
|
||||||
pgdval_t pgd, *pgd_p;
|
pgdval_t pgd, *pgd_p;
|
||||||
|
p4dval_t p4d, *p4d_p;
|
||||||
pudval_t pud, *pud_p;
|
pudval_t pud, *pud_p;
|
||||||
pmdval_t pmd, *pmd_p;
|
pmdval_t *pmd_p;
|
||||||
...
|
...
|
||||||
...
|
...
|
||||||
...
|
...
|
||||||
@ -389,7 +400,7 @@ int __init early_make_pgtable(unsigned long address)
|
|||||||
typedef unsigned long pgdval_t;
|
typedef unsigned long pgdval_t;
|
||||||
```
|
```
|
||||||
|
|
||||||
Также мы будем работать с типами `*_t`, например `pgd_t` и т.д. Все эти типы определены в [arch/x86/include/asm/pgtable_types.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/pgtable_types.h) и представляют собой структуры:
|
Также мы будем работать с типами `*_t`, например `pgd_t` и т.д. Все эти типы определены в [arch/x86/include/asm/pgtable_types.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/pgtable_types.h) и представляют собой структуры:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
typedef struct { pgdval_t pgd; } pgd_t;
|
typedef struct { pgdval_t pgd; } pgd_t;
|
||||||
@ -399,22 +410,38 @@ typedef struct { pgdval_t pgd; } pgd_t;
|
|||||||
Для примера,
|
Для примера,
|
||||||
|
|
||||||
```C
|
```C
|
||||||
extern pgd_t early_level4_pgt[PTRS_PER_PGD];
|
extern pgd_t early_top_pgt[PTRS_PER_PGD];
|
||||||
```
|
```
|
||||||
|
|
||||||
Здесь `early_level4_pgt` представляет начальный каталог таблиц страниц верхнего уровня, который состоит из массива типа `pgd_t` и `pgd` указывает на записи страниц нижнего уровня.
|
Здесь `early_top_pgt` представляет начальный каталог таблиц страниц верхнего уровня, который состоит из массива типа `pgd_t` и `pgd` указывает на записи страниц нижнего уровня.
|
||||||
|
|
||||||
После того как мы проверили, что у нас корректный адрес, мы получаем адрес записи глобального каталога страниц, который содержит адрес `#PF`, и присваиваем его значение переменной `pgd`:
|
После того как мы проверили, что у нас корректный адрес, мы получаем адрес записи глобального каталога страниц, который содержит адрес `#PF`, и присваиваем его значение переменной `pgd`:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
pgd_p = &early_level4_pgt[pgd_index(address)].pgd;
|
pgd_p = &early_top_pgt[pgd_index(address)].pgd;
|
||||||
pgd = *pgd_p;
|
pgd = *pgd_p;
|
||||||
```
|
```
|
||||||
|
|
||||||
На следующем шаге мы проверяем `pgd`, если он содержит верную запись в глобальном каталоге страниц, мы помещаем физический адрес записи в `pud_p`:
|
Затем мы проверяем, включена ли пятиуровневая страничная организация памяти:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
pud_p = (pudval_t *)((pgd & PTE_PFN_MASK) + __START_KERNEL_map - phys_base);
|
if (!pgtable_l5_enabled())
|
||||||
|
p4d_p = pgd_p;
|
||||||
|
```
|
||||||
|
|
||||||
|
В большинстве случаев пятиуровневая страничная организация не включена, поэтому `p4d_p` скорее всего равен `pgd_p`.
|
||||||
|
|
||||||
|
После этого мы исправляем адрес `p4d` с помощью:
|
||||||
|
|
||||||
|
```C
|
||||||
|
p4d_p += p4d_index(address);
|
||||||
|
p4d = *p4d_p;
|
||||||
|
```
|
||||||
|
|
||||||
|
На следующем шаге мы проверяем `p4d`, если он содержит верную запись p4d, мы помещаем физический адрес записи p4d в `pud_p`:
|
||||||
|
|
||||||
|
```C
|
||||||
|
pud_p = (pudval_t *)((p4d & PTE_PFN_MASK) + __START_KERNEL_map - phys_base);
|
||||||
```
|
```
|
||||||
|
|
||||||
где `PTE_PFN_MASK` является макросом:
|
где `PTE_PFN_MASK` является макросом:
|
||||||
@ -426,18 +453,18 @@ pud_p = (pudval_t *)((pgd & PTE_PFN_MASK) + __START_KERNEL_map - phys_base);
|
|||||||
который раскрывается до:
|
который раскрывается до:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
(~(PAGE_SIZE-1)) & ((1 << 46) - 1)
|
(signed long)(~(PAGE_SIZE-1)) & ((1 << 52) - 1)
|
||||||
```
|
```
|
||||||
|
|
||||||
или
|
Здесь используется [расширение знака](https://en.wikipedia.org/wiki/Sign_extension). Развёрнутая форма:
|
||||||
|
|
||||||
```
|
```
|
||||||
0b1111111111111111111111111111111111111111111111
|
0b1111111111111111111111111111111111111111111111111111
|
||||||
```
|
```
|
||||||
|
|
||||||
состоящий из 46 бит для маскирования страницы.
|
т.е макрос состоит из 52 бит для маскирования фрейма страниц.
|
||||||
|
|
||||||
Если `pgd` не содержит верный адрес, мы проверяем что `next_early_pgt` не больше чем `EARLY_DYNAMIC_PAGE_TABLES`, который равен `64` и представляет фиксированное количество буферов для настройки новых таблиц страниц по требованию. Если `next_early_pgt` больше, чем `EARLY_DYNAMIC_PAGE_TABLES` мы сбрасываем таблицы страниц и начинаем всё заново. Если `next_early_pgt` меньше, чем `EARLY_DYNAMIC_PAGE_TABLES`, мы создаём новый указатель верхнего каталога страниц, который указывает на текущую динамическую таблицу страниц и записываем его физический адрес с правами доступа `_KERPG_TABLE` в глобальный каталог страниц:
|
Если `p4d` не содержит верный адрес, мы проверяем что `next_early_pgt` не больше чем `EARLY_DYNAMIC_PAGE_TABLES`, который равен `64` и представляет фиксированное количество буферов для настройки новых таблиц страниц по требованию. Если `next_early_pgt` больше, чем `EARLY_DYNAMIC_PAGE_TABLES` мы сбрасываем таблицы страниц и начинаем всё заново. Если `next_early_pgt` меньше, чем `EARLY_DYNAMIC_PAGE_TABLES`, мы создаём новый указатель верхнего каталога страниц, который указывает на текущую динамическую таблицу страниц и записываем его физический адрес с правами доступа `_KERNPG_TABLE` в p4d:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
if (next_early_pgt >= EARLY_DYNAMIC_PAGE_TABLES) {
|
if (next_early_pgt >= EARLY_DYNAMIC_PAGE_TABLES) {
|
||||||
@ -448,10 +475,10 @@ if (next_early_pgt >= EARLY_DYNAMIC_PAGE_TABLES) {
|
|||||||
pud_p = (pudval_t *)early_dynamic_pgts[next_early_pgt++];
|
pud_p = (pudval_t *)early_dynamic_pgts[next_early_pgt++];
|
||||||
for (i = 0; i < PTRS_PER_PUD; i++)
|
for (i = 0; i < PTRS_PER_PUD; i++)
|
||||||
pud_p[i] = 0;
|
pud_p[i] = 0;
|
||||||
*pgd_p = (pgdval_t)pud_p - __START_KERNEL_map + phys_base + _KERNPG_TABLE;
|
*p4d_p = (p4dval_t)pud_p - __START_KERNEL_map + phys_base + _KERNPG_TABLE;
|
||||||
```
|
```
|
||||||
|
|
||||||
После этого мы исправляем адрес верхнего каталога страниц:
|
Как уже говорили ранее, мы исправляем адрес верхнего каталога страниц:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
pud_p += pud_index(address);
|
pud_p += pud_index(address);
|
||||||
@ -461,11 +488,96 @@ pud = *pud_p;
|
|||||||
На следующем шаге мы делаем те же действия что и ранее, но с промежуточным каталогом страниц. В конце мы исправляем адрес промежуточного каталога страниц, который содержит отображения текста ядра+виртуальные адреса данных:
|
На следующем шаге мы делаем те же действия что и ранее, но с промежуточным каталогом страниц. В конце мы исправляем адрес промежуточного каталога страниц, который содержит отображения текста ядра+виртуальные адреса данных:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
pmd = (physaddr & PMD_MASK) + early_pmd_flags;
|
|
||||||
pmd_p[pmd_index(address)] = pmd;
|
pmd_p[pmd_index(address)] = pmd;
|
||||||
```
|
```
|
||||||
|
|
||||||
После того как обработчик ошибки страницы завершён, `early_level4_pgt` содержит записи, которые указывают на корректные адреса.
|
После того как обработчик ошибки страницы завершён, `early_top_pgt` содержит записи, которые указывают на корректные адреса.
|
||||||
|
|
||||||
|
Другие обработчики исключений
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
На раннем этапе прерывания, исключения, кроме ошибки страницы, обрабатываются функцией `early_fixup_exception`, которая определена в [arch/x86/mm/extable.c](https://github.com/torvalds/linux/blob/master/arch/x86/mm/extable.c) и принимает два аргумента - указатель на стек ядра, состоящий из сохраненных регистров, и номер прерывания:
|
||||||
|
|
||||||
|
```C
|
||||||
|
void __init early_fixup_exception(struct pt_regs *regs, int trapnr)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
...
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Прежде всего нам нужно проверить некоторые условия:
|
||||||
|
|
||||||
|
```C
|
||||||
|
if (trapnr == X86_TRAP_NMI)
|
||||||
|
return;
|
||||||
|
if (early_recursion_flag > 2)
|
||||||
|
goto halt_loop;
|
||||||
|
if (!xen_pv_domain() && regs->cs != __KERNEL_CS)
|
||||||
|
goto fail;
|
||||||
|
```
|
||||||
|
|
||||||
|
Здесь мы просто игнорируем [NMI](https://en.wikipedia.org/wiki/Non-maskable_interrupt). Также мы убеждаемся, что мы не в рекурсивной ситуации.
|
||||||
|
|
||||||
|
Далее:
|
||||||
|
|
||||||
|
```C
|
||||||
|
if (fixup_exception(regs, trapnr))
|
||||||
|
return;
|
||||||
|
```
|
||||||
|
|
||||||
|
Функция `fixup_exception` определена в том же файле, что и `early_fixup_exception`:
|
||||||
|
|
||||||
|
```C
|
||||||
|
int fixup_exception(struct pt_regs *regs, int trapnr)
|
||||||
|
{
|
||||||
|
const struct exception_table_entry *e;
|
||||||
|
ex_handler_t handler;
|
||||||
|
e = search_exception_tables(regs->ip);
|
||||||
|
if (!e)
|
||||||
|
return 0;
|
||||||
|
handler = ex_fixup_handler(e);
|
||||||
|
return handler(e, regs, trapnr);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ex_handler_t` - тип указателя функции, определённый следующий образом:
|
||||||
|
|
||||||
|
```C
|
||||||
|
typedef bool (*ex_handler_t)(const struct exception_table_entry *,
|
||||||
|
struct pt_regs *, int)
|
||||||
|
```
|
||||||
|
|
||||||
|
Функция `search_exception_tables` ищет данный адрес в таблице исключений (т.е содержимое ELF-секции `__ex_table`). После этого мы получаем фактический адрес с помощью функции `ex_fixup_handler`. Наконец, мы вызываем фактический обработчик. Для получения дополнительной информации о таблице исключений смотрите [Documentation/x86/exception-tables.txt](https://github.com/torvalds/linux/blob/master/Documentation/x86/exception-tables.txt).
|
||||||
|
|
||||||
|
Вернёмся к функции `early_fixup_exception`. Следующий шаг:
|
||||||
|
|
||||||
|
```C
|
||||||
|
if (fixup_bug(regs, trapnr))
|
||||||
|
return;
|
||||||
|
```
|
||||||
|
|
||||||
|
Функция `fixup_bug` определена в [arch/x86/kernel/traps.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/traps.c). Давайте посмотрим на реализацию этой функции.
|
||||||
|
|
||||||
|
```C
|
||||||
|
int fixup_bug(struct pt_regs *regs, int trapnr)
|
||||||
|
{
|
||||||
|
if (trapnr != X86_TRAP_UD)
|
||||||
|
return 0;
|
||||||
|
switch (report_bug(regs->ip, regs)) {
|
||||||
|
case BUG_TRAP_TYPE_NONE:
|
||||||
|
case BUG_TRAP_TYPE_BUG:
|
||||||
|
break;
|
||||||
|
case BUG_TRAP_TYPE_WARN:
|
||||||
|
regs->ip += LEN_UD2;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Всё что делает эта функция - возвращает `1`, если генерируется исключение `#UD` (или [неверный опкод (Invalid Opcode)](https://wiki.osdev.org/Exceptions#Invalid_Opcode)) и функция `report_bug` вернула `BUG_TRAP_TYPE_WARN`, иначе возвращает `0`.
|
||||||
|
|
||||||
Заключение
|
Заключение
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
Loading…
Reference in New Issue
Block a user