启用分页后的指令似乎无法执行

时间:2018-10-05 17:08:21

标签: assembly x86 bootloader gas osdev

当我必须设置EIP寄存器时,程序无法跳到正确的位置。我期望jmp *%ecx使用LEA跳到内存设置EIP的正确位置,大约为0xC0100000(标签:StartInHigherHalf)。我认为kmain并不是必需的,因为问题是在调用之前。我还是要发布它。

我尝试使用QEMU上的-d cpu标志对其进行调试,并且在跳转之前(被HLT阻止)说ECX未加载LEA函数。 LEA指令可能无法执行吗?为什么会发生这种情况?我该如何解决?

Boot.S:

.set ALIGN,    1<<0                                       
.set MEMINFO,  1<<1                               
.set FLAGS,    ALIGN | MEMINFO                         
.set MAGIC,    0x1BADB002                      
.set CHECKSUM, -(MAGIC + FLAGS)            

.set KERNEL_VIRTUAL_BASE, 0xC0000000                       
.set KERNEL_PAGE_NUMBER, (KERNEL_VIRTUAL_BASE >> 22)

.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM

.section .data
.align 0x1000
BootPageDirectory:

.quad 0x00000083

.rept KERNEL_PAGE_NUMBER - 1
.quad 0        
.endr

.quad 0x00000083
.rept 0x400 - KERNEL_PAGE_NUMBER - 1
.quad 0                    
.endr

.set STACKSIZE, 0x4000

.global __start__
.set __start__, (setup)
setup: 
    mov $(BootPageDirectory - KERNEL_VIRTUAL_BASE), %ecx
    mov %ecx, %cr3 

    mov %cr4, %ecx
    or $0x00000010, %ecx 
    mov %ecx, %cr4    

    mov %cr0, %ecx
    or $0x80000000, %ecx
    mov %ecx, %cr0

    lea StartInHigherHalf, %ecx
    jmp *%ecx      

StartInHigherHalf:
    movl $0, (BootPageDirectory)
    invlpg (0)

    mov $(stack + STACKSIZE), %esp                 
    push %eax                                        

    push %ebx

    call _init

    call kmain

    cli
1:  hlt
    jmp 1

.section .bss
.align 32

.lcomm stack, STACKSIZE    
.global gdtFlush            
.extern gp               

gdtFlush:
    lgdt (gp)

    mov $0x10, %eax
    mov %eax, %ds               
    mov %eax, %es
    mov %eax, %gs
    mov %eax, %fs
    mov %eax, %ss

    ljmp $0x08, $setcs                              

setcs:
    ret

linker.ld:

ENTRY(__start__)
OUTPUT_FORMAT(elf32-i386)

SECTIONS {
    . = 0xC0100000;

    .text  ALIGN(0x1000) : AT(ADDR(.text) - 0xC0000000) {
        *(.multiboot)
        *(.text)
    }
    .rodata ALIGN(0x1000) : AT(ADDR(.rodata) - 0xC0000000) {
        *(.rodata*)
    } 
    .data ALIGN(0x1000) : AT(ADDR(.data) - 0xC0000000) {
        *(.data)
    }
    .bss ALIGN(0x1000) : AT(ADDR(.bss) - 0xC0000000) {
        _sbss = .;
        *(COMMON)
        *(.bss)
        _ebss = .;
    }
}

kmain.c:

#include <kernel/tty.h>
#include <kernel/log.h>
#include <kernel/stack-protector.h>
#include <kernel/gdt.h>

__attribute__ ((noreturn));
    void kmain(void) {
    gdtInstall();

    initializeTerminal();
    char c;
    char *buffer = &c;
    char *start = buffer;

    char str[] = "Hello, kernel World!";

    atomicallyLog(1, 1, str, buffer);
    kprintf(start, 1);
}

1 个答案:

答案 0 :(得分:2)

如果我们暂时假设您的代码实际到达lea StartInHigherHalf, %ecx,并且不认为它实际执行了该指令-最明显的问题将是与分页相关。 LEA 指令恰好是在设置了分页位之后执行的第一条指令。如果分页错误,则下一条指令可能会出错。

在查看页面目录时,我注意到您使用.quad(8字节类型)而不是.int(4字节类型)来构建它。页面目录中的每个条目都是4个字节,而不是8个字节。这可能是导致问题的主要原因。您只需使用.fill指令即可避免使用.rept宏:

.fill repeat , size , value
     

结果,大小和值是绝对表达式。这会发出大小字节的重复副本。重复次数可以为零或更多。大小可能为零或更大,但如果大于8,则认为它的值8与其他人的汇编程序兼容。每个重复字节的内容取自一个8字节的数字。最高4字节为零。最低顺序的4个字节是按组装时在计算机上以整数的字节顺序呈现的值。重复中的每个大小字节均取自该数字的最低顺序大小字节。同样,这种奇怪的行为与其他人的汇编程序兼容。

     

大小和值是可选的。如果第二个逗号和值不存在,则将值假定为零。如果缺少第一个逗号和后面的标记,则假定大小为1。

您的引导页目录可以写为:

.align 0x1000
BootPageDirectory:    
.int 0x00000083    
.fill KERNEL_PAGE_NUMBER - 1, 4, 0    
.int 0x00000083
.fill 0x400 - KERNEL_PAGE_NUMBER - 1, 4, 0

.rept也可以工作:

.align 0x1000
BootPageDirectory:
.int 0x00000083    
.rept KERNEL_PAGE_NUMBER - 1
.int 0
.endr
.int 0x00000083
.rept 0x400 - KERNEL_PAGE_NUMBER - 1
.int 0
.endr

其他建议

您的代码似乎是OSDev Higher Half x86 Bare Bones内核的变体。主要的区别似乎是您从 NASM 转换为GNU汇编程序。我在OSDev Forum上写了关于此代码的主要缺陷。它的结构方式是,假设所有内容都是上半部分,则生成所有地址,而不是将上半部分的0x100000从下半部分的内容拆分为0x100000。这种设计导致以Multiboot规范未真正定义的方式使用Mulitboot。

您可以通过使用链接程序脚本将两者分开并将相应的部分放在boot.S中来解决此问题。链接描述文件(linker.ld)如下:

ENTRY(setup)
OUTPUT_FORMAT(elf32-i386)

KERNEL_VIRTUAL_BASE = 0xC0000000;

SECTIONS {
   /* The multiboot data and code will exist in low memory
      starting at 0x100000 */

   . = 0x00100000;

   .lowerhalf ALIGN(0x1000) : {
       *(.lowerhalf.data)
       *(.lowerhalf.text)
   }

   /* The kernel will live at 3GB + 1MB in the virtual
      address space, which will be mapped to 1MB in the
      physical address space. */

   . += KERNEL_VIRTUAL_BASE;
   .text ALIGN(0x1000) : AT(ADDR(.text) - KERNEL_VIRTUAL_BASE) {
       *(.text)
   }

   .data ALIGN (0x1000) : AT(ADDR(.data) - KERNEL_VIRTUAL_BASE) {
       *(.data)
       *(.rodata*)
   }

   .bss ALIGN (0x1000) : AT(ADDR(.bss) - KERNEL_VIRTUAL_BASE) {
       _sbss = .;
       *(COMMON)
       *(.bss)
       _ebss = .;
   }

   /DISCARD/ : {
       *(.eh_frame);
       *(.comment*);
   }
}

您的boot.S文件可以写为:

.set ALIGN,    1<<0
.set MEMINFO,  1<<1
.set FLAGS,    ALIGN | MEMINFO
.set MAGIC,    0x1BADB002
.set CHECKSUM, -(MAGIC + FLAGS)

.set KERNEL_VIRTUAL_BASE, 0xC0000000
.set KERNEL_PAGE_NUMBER, (KERNEL_VIRTUAL_BASE >> 22)

.section .lowerhalf.data,"aw",@progbits
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM

.align 0x1000
BootPageDirectory:
.int 0x00000083
.fill KERNEL_PAGE_NUMBER - 1, 4, 0    
.int 0x00000083
.fill 0x400 - KERNEL_PAGE_NUMBER - 1, 4, 0

.set STACKSIZE, 0x4000

.section .lowerhalf.text,"axw",@progbits
.global setup
setup:
    mov $BootPageDirectory, %ecx
    mov %ecx, %cr3

    mov %cr4, %ecx
    or $0x00000010, %ecx
    mov %ecx, %cr4

    mov %cr0, %ecx
    or $0x80000000, %ecx
    mov %ecx, %cr0

    jmp StartInHigherHalf

.section .text
StartInHigherHalf:
    movl $0, (BootPageDirectory)
    invlpg (0)

    mov $(stack + STACKSIZE), %esp
    push %eax

    push %ebx

    #call _init

    call kmain

    cli
1:  hlt
    jmp 1

/*
.global gdtFlush
.extern gp

gdtFlush:
    lgdt (gp)

    mov $0x10, %eax
    mov %eax, %ds
    mov %eax, %es
    mov %eax, %gs
    mov %eax, %fs
    mov %eax, %ss

    ljmp $0x08, $setcs

setcs:
    ret
*/

.section .bss
.align 32

.lcomm stack, STACKSIZE

无所事事的内核(kernel.c)看起来像:

volatile unsigned short int *const video_mem = (unsigned short int *)0xc00b8000;
void kmain(void) {
    /* print MDP in upper left of display using white on magenta */
    video_mem[0] = (0x57 << 8) | 'M';
    video_mem[1] = (0x57 << 8) | 'D';
    video_mem[2] = (0x57 << 8) | 'P';

    while (1) __asm__ ("hlt");
}

您可以使用以下命令生成ELF可执行文件:

i686-elf-gcc -c -g -m32 boot.S -o boot.o
i686-elf-gcc -c -g -m32 -O3 kernel.c -o kernel.o -ffreestanding -std=gnu99 \
    -mno-red-zone -fno-exceptions -Wall -Wextra

i686-elf-gcc -nostdlib -Wl,--build-id=none -T linker.ld boot.o kernel.o -lgcc -o kernel.elf

评论:

  • 尽管您也可以使用本机主机编译器,但我更喜欢使用GCC交叉编译器。
  • 文件kernel.elf可以由GRUB(在ISO文件中)加载,也可以直接由 QEMU -kernel选项加载。
  • 我通过直接跳转到标签StartInHigherHalf简化了JMP。您仍然可以使用LEA / JMP方法,但不会获得任何好处。
  • 您将gdtFlush错误地放在了.bss部分中。需要将其移至.text部分。我已经在代码中这样做了,但是我也对其进行了注释,以使该内核在分页中不起作用。您将需要重新添加它以使用您的代码库。
  • 我暂时将call _init注释掉,以使此功能不起作用。您需要删除注释才能将其集成到您的代码库中。