2

我正在尝试开发一个基本内核,我想让它执行任务切换到我从附加磁盘加载到内存中的一些代码。

我已经尝试按照英特尔手册中的第 7 章进行操作,但我的尝试似乎都导致了三重故障。

特别是,我认为我的分页设置有问题。我希望切换到的任务使用自己的任意页面映射运行,因此您将在下面的代码片段中看到,此任务中的虚拟地址 0x0 应该映射到物理地址 0x2000000,这也是我加载的位置编码。

我尝试设置分页定义了一个整数数组,就像在setup_page_directory_and_page_tables用户和内核页表和页目录的函数中一样。内核页面映射是身份映射,而用户不是。需要注意的一件有趣的事情是,当我更改分页设置,使用户的页面映射也是身份映射时,任务切换成功。

完整的代码可以在这里获取(注意:相关分支是 dev 分支)。

毫米.c

#include "mm.h"

#include "task.h"

// System memory map as told by BIOS.
extern unsigned int mem_map_buf_addr;
extern unsigned int mem_map_buf_entry_count;

// Kernel uninitialized data start and end.
extern int _bss_start;
extern int _bss_end;

// TSS for kernel and user tasks.
extern tss kernel_tss __attribute__((aligned(0x1000)));
extern tss user_tss __attribute__((aligned(0x1000)));

// Memory management structures for the kernel's use.
static uint32_t kernel_mem_bitmap;
static struct bios_mem_map* bmm;

#define NUM_GDT_ENTRIES 8
// Kernel structures for segmentation.
struct gdt_entry pm_gdt[NUM_GDT_ENTRIES];
struct gdt_info pm_gdt_info = {
    .len = sizeof(pm_gdt),
    .addr = (unsigned int) pm_gdt,
};

// Kernel structures for paging.
unsigned int kernel_page_directory[1024]__attribute__((aligned(0x1000)));
unsigned int kernel_page_tables[1024][1024];

unsigned int user_page_directory[USER_PAGE_DIR_SIZE]__attribute__((aligned(0x1000)));
unsigned int user_page_tables[USER_PAGE_DIR_SIZE][USER_PAGE_TABLE_SIZE];

extern void setup_and_enable_paging(void);

extern void *USER_PHY_ADDR;

void init_mm(void) {
    // These aren't used for anything.
    bmm = (struct bios_mem_map*) mem_map_buf_addr;
    kernel_mem_bitmap = ~((unsigned int) 0);

    setup_tss(); //defined in task.c
    setup_pm_gdt();
    setup_page_directory_and_page_tables();
    setup_and_enable_paging();
}

extern unsigned int kernel_entry;
void setup_page_directory_and_page_tables(void) {
    unsigned int addr = (unsigned int) &kernel_page_tables[0];
    unsigned int i, j, page_frame = 0;

    // Setup kernel paging.
    for (i = 0; i < 1024; i++) {
        // Set all PDEs.
        kernel_page_directory[i] = (addr & 0xfffff000) | 0x3;
        addr += 0x1000;
    }
    for (i = 0; i < 1024; i++) {
        for (j = 0; j < 1024; j++) {
            kernel_page_tables[i][j] = page_frame | 0x3;
            page_frame += 0x1000;
        }
    }

    // Setup user paging.
    addr = (unsigned int) &user_page_tables[0];
    for (i = 0; i < USER_PAGE_DIR_SIZE; i++) {
        // Set all PDEs.
        user_page_directory[i] = (addr & 0xfffff000) | 0x7;
        addr += 0x1000;
    }
    page_frame = USER_PHY_ADDR;
    for (i = 0; i < USER_PAGE_DIR_SIZE; i++) {
        for (j = 0; j < USER_PAGE_TABLE_SIZE; j++) {
            user_page_tables[i][j] = page_frame | 0x7;
            page_frame += 0x1000;

            if (page_frame >= 0xfffff000)
                goto exit;
        }
    }

exit:
    return;
}

void setup_pm_gdt(void) {
    pm_gdt_info.len = sizeof(pm_gdt);
    pm_gdt_info.addr = (long unsigned int) pm_gdt;

    make_gdt_entry(&pm_gdt[0], 0x0, 0x0, 0x0, 0x0);
    // KERNEL_CODE_SEGMENT
    make_gdt_entry(&pm_gdt[1], 0xfffff, 0x0, 0xa, 0xc9);
    // KERNEL_DATA_SEGMENT
    make_gdt_entry(&pm_gdt[2], 0xfffff, 0x0, 0x2, 0xc9);
    // USER_CODE_SEGMENT
    // make_gdt_entry(&pm_gdt[3], 0xfffff, 0x0, 0xa, 0xcf);
    // USER_DATA_SEGMENT
    // make_gdt_entry(&pm_gdt[4], 0xfffff, 0x0, 0x2, 0xcf);

    // Add entry for kernel task (TSS descriptor).
    make_gdt_entry(&pm_gdt[5], sizeof(kernel_tss), (unsigned int) &kernel_tss, 0x9, 0x18);
    // Add entry for user task (TSS descriptor).
    make_gdt_entry(&pm_gdt[6], sizeof(user_tss), (unsigned int) &user_tss, 0x9, 0x1e);
    // Add task gate for user TSS.
    make_gdt_entry(&pm_gdt[7], sizeof(user_tss), 0x30, 0x5, 0xe);

    load_pm_gdt();
}

extern void pm_jump(void);
void load_pm_gdt(void) {
    asm volatile("lgdt %0" : : "m"(pm_gdt_info));
    pm_jump(); // in kernel_entry.asm
}

void make_gdt_entry(struct gdt_entry* entry,
                    unsigned int limit,
                    unsigned int base,
                    char type,
                    /*flags format: S_DPL_P_AVL_L_DB_G*/
                    /*bits:         1_2___1_1___1_1__1*/
                    char flags) {
    // Set lower 16 bits of limit.
    entry->limit0_15 = limit & 0xffff;
    // Set lower 16 bits of base.
    entry->base0_15 = base & 0xffff;
    // Set bits 16 to 13 of base.
    entry->base16_23 = (base >> 16) & 0xff;
    // Set bits 24 to 31 of base.
    entry->base24_31 = (base >> 24) & 0xff;
    // Set upper 4 bits of 20 bit limit.
    entry->limit16_19_avl_l_db_g = (limit >> 16) & 0xf;
    // Set 4 bits of type.
    entry->type_s_dpl_p = type & 0xf;
    // Set S_DPL_P flags (lower 4 bits of 8 bit flags).
    entry->type_s_dpl_p |= (flags & 0xf) << 4;
    // Set AVL_L_DB_G flags (upper 4 bits of 8 bit flags).
    entry->limit16_19_avl_l_db_g |= flags & 0xf0;
}

任务.c

#include "task.h"

extern unsigned int user_page_directory[USER_PAGE_TABLE_SIZE]__attribute__((aligned(0x1000)));

tss user_tss;

void setup_tss(void) {
    tss *tss_ = &user_tss;

    tss_->CR3 = (unsigned int) user_page_directory | 0x3;
    tss_->EIP = 0x0;
    __asm__("   movw %%es, %0 \n" : "=m" (tss_->ES_l16b) : );
    __asm__("   movw %%cs, %0 \n" : "=m" (tss_->CS_l16b) : );
    __asm__("   movw %%ss, %0 \n" : "=m" (tss_->SS_l16b) : );
    __asm__("   movw %%ds, %0 \n" : "=m" (tss_->DS_l16b) : );
    __asm__("   movw %%fs, %0 \n" : "=m" (tss_->FS_l16b) : );
    __asm__("   movw %%gs, %0 \n" : "=m" (tss_->GS_l16b) : );
    __asm__("   movw %%esp, %0 \n" : "=m" (tss_->ESP) : );
    __asm__("   movw %%ebp, %0 \n" : "=m" (tss_->EBP) : );
}

void do_task_switch(void) {
    print("attempting to loading task register.\n");
    load_kernel_tr(); // in kernel_entry.asm
    print("successfully loaded task register.\n");

    print("attempting task switch.\n");
    switch_task(); // in kernel_entry.asm
    print("task switch successful.\n");
}

switch_task永远不会因为 CPU 三重故障而返回。我意识到在设置用户的 TSS 时,简单地复制当前段寄存器和堆栈指针可能不是我想要做的,但应用程序中的第一条指令是jmp $这样,因为我没有跨段跳转或使用堆栈我以为它仍然可以工作。

任务.h

#ifndef __TASK_H__

#include "system.h"

// Note: Fields ending in `_{l,u}16b` only use 16 bits.
// If such a field is defined with `unsigned int`, 
// it occupies the upper (u) or lower (l) 16 bits.
struct task_state_segment {
    unsigned int previous_task_link_l16b;
    unsigned int ESP0;
    unsigned int SS0_l16b;
    unsigned int ESP1;
    unsigned int SS1_l16b;
    unsigned int ESP2;
    unsigned int SS2_l16b;
    unsigned int CR3;
    unsigned int EIP;
    unsigned int EFLAGS;
    unsigned int EAX;
    unsigned int ECX;
    unsigned int EDX;
    unsigned int EBX;
    unsigned int ESP;
    unsigned int EBP;
    unsigned int ESI;
    unsigned int EDI;
    unsigned int ES_l16b;
    unsigned int CS_l16b;
    unsigned int SS_l16b;
    unsigned int DS_l16b;
    unsigned int FS_l16b;
    unsigned int GS_l16b;
    unsigned int LDT_u16b;
    unsigned int SSP;
}__attribute__((packed));

typedef struct task_state_segment tss;

void setup_tss();

void do_task_switch();

// #define USER_PAGE_DIR_SIZE 1024
// #define USER_PAGE_TABLE_SIZE 1024

#define USER_PAGE_DIR_SIZE 1024
#define USER_PAGE_TABLE_SIZE 1024

#endif // __TASK_H__

kernel_entry.asm

[bits 32]

global initialize_idt
global mem_map_buf_addr
global mem_map_buf_entry_count
global kernel_entry

extern main
extern idt_info_ptr

; kernel_entry expects the following information about the
; BIOS's memory map to be put on the stack:
;   the address of the buffer holding the memory map (top of stack)
;   the number of entries in the memory map.
kernel_entry:
  mov eax, [esp]
  mov [mem_map_buf_addr], eax
  mov eax, [esp+4]
  mov [mem_map_buf_entry_count], eax

  call main
jmp $

mem_map_buf_addr: dd 0x0
mem_map_buf_entry_count: dd 0x0

global pm_jump
pm_jump:
  jmp 0x8:pm_jmp_ret
pm_jmp_ret:
  mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
  ret

extern kernel_page_directory
global setup_and_enable_paging
setup_and_enable_paging:
  ; point CR3 to page directory
  mov eax, kernel_page_directory
  or eax, 0x3
  mov cr3, eax

  ; set CRO.PG to 1
  mov ebx, cr0  ; set left-most bit of CPU special control register.
    or ebx, 0x80000000
    mov cr0, ebx

  ret

USER_TASK_GATE_GDT_IDX equ 7
USER_TASK_GATE equ 8 * USER_TASK_GATE_GDT_IDX

global switch_task
switch_task:
  call USER_TASK_GATE: 0x0
  ret

KERNEL_TASK_SEG_IDX equ 5
KERNEL_TASK_SEG equ 8 * KERNEL_TASK_SEG_IDX
kernel_task_selector: dw KERNEL_TASK_SEG

; Function to load kernel task register.
global load_kernel_tr
load_kernel_tr:
  ltr [kernel_task_selector]
  ret

global dummy_branch
dummy_branch:
  mov eax, 0xfadefade
  iret

initialize_idt:
  lidt [idt_info_ptr]
  ret

global enable_interrupts
enable_interrupts:
  sti
  ret

global disable_interrupts
disable_interrupts:
  cli
  ret

extern fault_handler
extern irq_handler

global isr_common
global isr0
global isr1
global isr2
global isr3
global isr4
global isr5
global isr6
global isr7
global isr8
global isr9
global isr10
global isr11
global isr12
global isr13
global isr14
global isr15
global isr16
global isr17
global isr18
global isr19
global isr20
global isr21
global isr22
global isr23
global isr24
global isr25
global isr26
global isr27
global isr28
global isr29
global isr30
global isr31

global irq0
global irq1
global irq2
global irq3
global irq4
global irq5
global irq6
global irq7
global irq8
global irq9
global irq10
global irq11
global irq12
global irq13
global irq14
global irq15

isr0:
  cli
  push byte 0
  push byte 0
  jmp isr_common

isr1:
  cli
  push byte 0
  push byte 1
  jmp isr_common

isr2:
  cli
  push byte 0
  push byte 2
  jmp isr_common

isr3:
  cli
  push byte 0
  push byte 3
  jmp isr_common

isr4:
  cli
  push byte 0
  push byte 4
  jmp isr_common

isr5:
  cli
  push byte 0
  push byte 5
  jmp isr_common

isr6:
  cli
  push byte 0
  push byte 6
  jmp isr_common

isr7:
  cli
  push byte 0
  push byte 7
  jmp isr_common

isr8:
  cli
  push byte 8
  jmp isr_common

isr9:
  cli
  push byte 0
  push byte 9
  jmp isr_common

isr10:
  cli
  push byte 10
  jmp isr_common

isr11:
  cli
  push byte 11
  jmp isr_common

isr12:
  cli
  push byte 12
  jmp isr_common

isr13:
  cli
  push byte 13
  jmp isr_common

isr14:
  cli
  push byte 14
  jmp isr_common

isr15:
  cli
  push byte 0
  push byte 15
  jmp isr_common

isr16:
  cli
  push byte 0
  push byte 16
  jmp isr_common

isr17:
  cli
  push byte 0
  push byte 17
  jmp isr_common

isr18:
  cli
  push byte 0
  push byte 18
  jmp isr_common

isr19:
  cli
  push byte 0
  push byte 19
  jmp isr_common

isr20:
  cli
  push byte 0
  push byte 20
  jmp isr_common

isr21:
  cli
  push byte 0
  push byte 21
  jmp isr_common

isr22:
  cli
  push byte 0
  push byte 22
  jmp isr_common

isr23:
  cli
  push byte 0
  push byte 23
  jmp isr_common

isr24:
  cli
  push byte 0
  push byte 24
  jmp isr_common

isr25:
  cli
  push byte 0
  push byte 25
  jmp isr_common

isr26:
  cli
  push byte 0
  push byte 26
  jmp isr_common

isr27:
  cli
  push byte 0
  push byte 27
  jmp isr_common

isr28:
  cli
  push byte 0
  push byte 28
  jmp isr_common

isr29:
  cli
  push byte 0
  push byte 29
  jmp isr_common

isr30:
  cli
  push byte 0
  push byte 30
  jmp isr_common

isr31:
  cli
  push byte 0
  push byte 31
  jmp isr_common

isr_common:
  pusha
  push ds
  push es
  push fs
  push gs
  mov ax, 0x10
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  mov eax, esp
  push eax
  mov eax, fault_handler
  call eax
  pop eax
  pop gs
  pop fs
  pop es
  pop ds
  popa
  add esp, 8
  iret

irq0:
  cli
  push byte 0
  push byte 32
  jmp irq_common

irq1:
  cli
  push byte 0
  push byte 33
  jmp irq_common

irq2:
  cli
  push byte 0
  push byte 34
  jmp irq_common

irq3:
  cli
  push byte 0
  push byte 35
  jmp irq_common

irq4:
  cli
  push byte 0
  push byte 36
  jmp irq_common

irq5:
  cli
  push byte 0
  push byte 37
  jmp irq_common

irq6:
  cli
  push byte 0
  push byte 38
  jmp irq_common

irq7:
  cli
  push byte 0
  push byte 39
  jmp irq_common

irq8:
  cli
  push byte 0
  push byte 40
  jmp irq_common

irq9:
  cli
  push byte 0
  push byte 41
  jmp irq_common

irq10:
  cli
  push byte 0
  push byte 42
  jmp irq_common

irq11:
  cli
  push byte 0
  push byte 43
  jmp irq_common

irq12:
  cli
  push byte 0
  push byte 44
  jmp irq_common

irq13:
  cli
  push byte 0
  push byte 45
  jmp irq_common

irq14:
  cli
  push byte 0
  push byte 46
  jmp irq_common

irq15:
  cli
  push byte 0
  push byte 47
  jmp irq_common


irq_common:
  pusha
  push ds
  push es
  push fs
  push gs
  mov ax, 0x10
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  mov eax, esp
  push eax
  mov eax, irq_handler
  call eax
  pop eax
  pop gs
  pop fs
  pop es
  pop ds
  popa
  add esp, 8
  iret

内核编译为:

C_SOURCES = $(wildcard kernel/*.c kernel/**/*.c drivers/**/*.c fs/*.c)

C_FLAGS = -Wall -O0 -m32 -fno-pie -fno-stack-protector -ffreestanding -fno-hosted -nolibc -nostdlib -g
C_FLAGS += -I./

OBJ = $(patsubst %.c, %.o, ${C_SOURCES})
kernel.bin: kernel/kernel_entry.o ${OBJ}
    ld -o kernel.bin -m elf_i386 $^ --oformat binary -T kernel.ld
%.o: %.c
    gcc ${C_FLAGS} -c $< -o $@

%.o: %.asm
    nasm $< -f elf -g -o $@

内核.ld

SECTIONS
{
  . = 0x1000;
  .text : { *(.text) }
  .data : { *(.data) }
  .bss 0x100000 : {
   _bss_start = ( ADDR(.bss) ) ;
    *(.bss)
    *(.COMMON)
   _bss_end = ( ADDR(.bss ) + SIZEOF(.bss) ) ;
  }
}

ENTRY(kernel_entry)

加载的应用程序是 app.s (没关系)

    .globl main
    .intel_syntax noprefix

main:
    jmp $
    jmp 0xbaba
    iret
    add eax, ebx
loop:
    mov eax, 0x1
    cmp eax, 0x1
    jmp 0x8: 0x119f
exit:
    iret
    jmp 0x0:0x119d

该应用程序编译为:

gcc -o app.bin -m32 -fno-pic -fno-pie -flinker-output=exec -Ttext=0x400000 -Wl,-emain app.s

我通过使用 Qemu 命令手动检查 0x2000000 处的字节xp /16i 0x2000000并将它们与 hexdump -C app.bin -n 16. 有趣的是, 的输出objdump -d app.bin虽然包含类似于原始 app.s 的部分,但似乎没有匹配的字节。它以f3 0f 1e ..哪个 app.bin 开头7f 45 4c ...。我会把它贴在这里,剪掉一些部分。

objdump -d app.bin(截断):

app.bin:     file format elf32-i386


Disassembly of section .text:

00400000 <_start>:
  400000:   f3 0f 1e fb             endbr32 
  400004:   31 ed                   xor    %ebp,%ebp
  400006:   5e                      pop    %esi
  400007:   89 e1                   mov    %esp,%ecx
  400009:   83 e4 f0                and    $0xfffffff0,%esp
  40000c:   50                      push   %eax
  40000d:   54                      push   %esp
  40000e:   52                      push   %edx
  40000f:   e8 22 00 00 00          call   400036 <_start+0x36>
  400014:   81 c3 c8 2f 00 00       add    $0x2fc8,%ebx
  40001a:   8d 83 f4 d1 ff ff       lea    -0x2e0c(%ebx),%eax
  400020:   50                      push   %eax
  400021:   8d 83 84 d1 ff ff       lea    -0x2e7c(%ebx),%eax
  400027:   50                      push   %eax
  400028:   51                      push   %ecx
  400029:   56                      push   %esi
  40002a:   ff b3 1c 00 00 00       pushl  0x1c(%ebx)
  400030:   e8 0b 10 c0 ff          call   1040 <__libc_start_main@plt>
  400035:   f4                      hlt    
  400036:   8b 1c 24                mov    (%esp),%ebx
  400039:   c3                      ret    
  40003a:   66 90                   xchg   %ax,%ax
  40003c:   66 90                   xchg   %ax,%ax
  40003e:   66 90                   xchg   %ax,%ax

...


00400139 <__x86.get_pc_thunk.dx>:
  400139:   8b 14 24                mov    (%esp),%edx
  40013c:   c3                      ret    

0040013d <main>:
  40013d:   eb fe                   jmp    40013d <main>
  40013f:   e9 76 b9 c0 ff          jmp    baba <__cxa_finalize@plt+0xaa6a>
  400144:   cf                      iret   
  400145:   01 d8                   add    %ebx,%eax

00400147 <loop>:
  400147:   b8 01 00 00 00          mov    $0x1,%eax
  40014c:   83 f8 01                cmp    $0x1,%eax
  40014f:   ea 9f 11 00 00 08 00    ljmp   $0x8,$0x119f

00400156 <exit>:
  400156:   cf                      iret   
  400157:   ea 9d 11 00 00 00 00    ljmp   $0x0,$0x119d
  40015e:   66 90                   xchg   %ax,%ax
...

我观察到的另一件事:在我的调查中,objdump 输出 DID 匹配hexdump并且xp字节(我不确定我做了什么现在阻止这种情况发生:() 我观察到在将二进制文件加载到 0x2000000 后,0x2000000 + offset_of_main (在本例中为 0x13d)处的指令最终将是:

一世。jmp 0x2000000 + offset_of_main如果我使用jmp $(我希望它是jmp offset_of_main

ii. jmp 0x2000000 + arbitrary_constant如果我使用jmp arbitrary_constantegjmp 0x13djmp 0x200013d在我运行时出现xp /16 0x200013d(我本来希望它是jmp 0x13d

(我认为这是相关的,因为我假设 Qemu 通过 xp 报告的内容正是 CPU 看到指令的方式。)

4

0 回答 0