链接器错误设置使用内联汇编将GDT寄存器加载到LGDT指令

时间:2017-07-28 18:51:02

标签: gcc assembly x86 kernel osdev

我正在编译我的原型内核原型(听起来很奇怪,但它确实无关紧要)并且在安装中我需要将ASM文件链接到用gcc编译的C文件以获得可用作可执行文件的可执行文件核心。 问题是,在实现从实模式交换到保护模式后,我在链接kernel.c和loader.asm脚本时遇到此错误:

代码:

kernel.c:(.text+0x1e1): undefined reference to `gdtr'

我将解释所有安装过程是如何进行的,我将把代码放在下面。 安装步骤: 1:编译asm: 代码:

nasm -f elf32 loader.asm -o kasm.o

2:编译.c: 代码:

gcc -m32 -ffreestanding -c kernel.c -o kc.o

3:链接两者: 代码:

ld -m elf_i386 -T linker.ld -o kernel kasm.o kc.o

完整的错误输出是: 代码:

kc.o: In function `k_enter_protected_mode':
kernel.c:(.text+0x1e1): undefined reference to `gdtr'

代码如下: 代码:

/*
 *
 * kernel.c - version 0.0.1
 * This script is under the license of the distributed package, this license
 * can be found in the package itself
 * Script coded by Cristian Simón for the CKA Proyect
 * ----
 * License: GNU GPL v3
 * Coder: Cristian Simón
 * Proyect: CKA 
 * 
 */
/* Output defines */
#define BLACK_BGROUND 0X07 /* black background */
#define WHITE_TXT 0x07 /* light gray on black text */
#define GREEN_TXT 0x02 /* light green on black text */
#define RED_TXT 0x04 /* light red on black text*/
#define CYAN_TXT 0x03 /*light cyan on black text */
#include <stddef.h>
#include <stdint.h>
#include <cpuid.h>
void k_clear_screen();
void k_sleep_3sec();
unsigned int k_printf(char *message, unsigned int line, float color);
void k_malloc(size_t sz);
void k_free(void *mem);
/* k_clear_screen : to clear the entire text screen */
void k_clear_screen()
{
    char *vidmem = (char *) 0xC00B8000;
    unsigned int i=0;
    while(i < (80*25*2))
    {
        vidmem[i]=' ';
        i++;
        vidmem[i]=BLACK_BGROUND;
        i++;
    };
}

/* k_printf : the message and the line # */
unsigned int k_printf(char *message, unsigned int line, float color)
{
    char *vidmem = (char *) 0xC00B8000;
    unsigned int i=0;

    i=(line*80*2);

    while(*message!=0)
    {
        if(*message=='\n') /* check for a new line */
        {
            line++;
            i=(line*80*2);
            *message++;
        } else {
            vidmem[i]=*message;
            *message++;
            i++;
            vidmem[i]=color;
            i++;
        };
    };

    return(1);
}

/* 
* k_sleep_3sec : to make a simple delay of aprox 3 sec, since is a nasty sleep, 
* duration will vary
* from system to system
*/
void k_sleep_3sec()
{
    int c = 1, d = 1;
    for ( c = 1 ; c <= 20000 ; c++ )
    for ( d = 1 ; d <= 20000 ; d++ )
    {}
}
/* 
* Malloc and free functions for this kernel
* Maybe change in the future, sure
*/
static unsigned char our_memory[1024 * 1024]; /* reserve 1 MB for malloc */
static size_t next_index = 0;
int k_malloc_err;
void k_malloc(size_t sz)
{
    void *mem;
    if(sizeof our_memory - next_index < sz){
        return NULL;
        k_malloc_err = 1;
    }

    mem = &our_memory[next_index];
    next_index += sz;
    return mem;
}
void k_free(void *mem)
{
   /* we cheat, and don't free anything. */
}
/* Schreduler */
/*---*/
/*
* Our schreduler is a RTC (Run to Completion)
* In the future we will add more schredulers or change the type
* but for now this is what we got
*/
int proc_number_count = 0;
void k_schreduler(char *proc_name, unsigned int proc_prior)
{
    proc_number_count = proc_number_count + 1;
    int proc_number = proc_number_count;
}
void k_enter_protected_mode()
{
    __asm__ volatile ("cli;"          
        "lgdt (gdtr);"  
        "mov %eax, cr0;" 
        "or %al, 1;"    
        "mov cr0, %eax;" 
        "jmp 0x8,PModeMain;"
        "PModeMain:");
}
/*main function*/
void k_main() 
{
    k_clear_screen();
    k_printf(" Wellcome to", 0, WHITE_TXT);
    k_printf(" CKA!", 1, GREEN_TXT);
    k_printf("==============>", 2, WHITE_TXT);
    k_printf(" CKA stands for C Kernel with Assembly", 3, WHITE_TXT);
    k_printf(" Version 0.0.1, => based in the job of Debashis Barman", 4, WHITE_TXT);
    k_printf(" Contact => assemblyislaw@gmail.com / blueshell@mail2tor.com", 5, WHITE_TXT);
    k_printf("           or in the github repository page", 6, WHITE_TXT);
    k_sleep_3sec();
    k_clear_screen();
    /* here start the magic */
    k_printf(" !===> Starting Checkup <===!", 0, WHITE_TXT);
    k_printf(" =-=-=-=-=-=-=-=-=-=-=-=-=-=-", 1, WHITE_TXT);
    k_printf("[KernelInfo] Woah! No Kernel Panic for now! Well, lets fix that...", 2, CYAN_TXT);
    k_printf("[Proc1] Checking for k_malloc() and k_free() kernel functions", 3, WHITE_TXT);
    k_malloc(15);
    if (k_malloc_err == 1){
        k_printf("[F-ERROR] Unable to use k_malloc, do you have enough memory?", 4, RED_TXT);
        while(1){
            int error_stayer = 1;
        }

    } else{ 
        k_printf("[Proc1] k_malloc and k_free found, resuming boot...", 4, GREEN_TXT);
    }
    k_enter_protected_mode();
    k_printf("[KernelInfo] Switched to protected mode successfully", 5, CYAN_TXT);

}

这是kernel.c

代码:

ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)

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

   .text : AT(ADDR(.text) - 0xC0000000) {
       *(.text)
       *(.rodata*)
   }

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

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

这是linker.ld

代码:

global _loader                          ; Make entry point visible to linker.
extern k_main                           ; _main is defined elsewhere

; setting up the Multiboot header - see GRUB docs for details
MODULEALIGN equ  1<<0             ; align loaded modules on page boundaries
MEMINFO     equ  1<<1             ; provide memory map
FLAGS       equ  MODULEALIGN | MEMINFO  ; this is the Multiboot 'flag' field
MAGIC       equ    0x1BADB002     ; 'magic number' lets bootloader find the header
CHECKSUM    equ -(MAGIC + FLAGS)  ; checksum required

; This is the virtual base address of kernel space. It must be used to convert virtual
; addresses into physical addresses until paging is enabled. Note that this is not
; the virtual address where the kernel image itself is loaded -- just the amount that must
; be subtracted from a virtual address to get a physical address.
KERNEL_VIRTUAL_BASE equ 0xC0000000                  ; 3GB
KERNEL_PAGE_NUMBER equ (KERNEL_VIRTUAL_BASE >> 22)  ; Page directory index of kernel's 4MB PTE.


section .data
align 0x1000
BootPageDirectory:
    ; This page directory entry identity-maps the first 4MB of the 32-bit physical address space.
    ; All bits are clear except the following:
    ; bit 7: PS The kernel page is 4MB.
    ; bit 1: RW The kernel page is read/write.
    ; bit 0: P  The kernel page is present.
    ; This entry must be here -- otherwise the kernel will crash immediately after paging is
    ; enabled because it can't fetch the next instruction! It's ok to unmap this page later.
    dd 0x00000083
    times (KERNEL_PAGE_NUMBER - 1) dd 0                 ; Pages before kernel space.
    ; This page directory entry defines a 4MB page containing the kernel.
    dd 0x00000083
    times (1024 - KERNEL_PAGE_NUMBER - 1) dd 0  ; Pages after the kernel image.


section .text
align 4
MultiBootHeader:
    dd MAGIC
    dd FLAGS
    dd CHECKSUM

; reserve initial kernel stack space -- that's 16k.
STACKSIZE equ 0x4000

; setting up entry point for linker
loader equ (_loader - 0xC0000000)
global loader

_loader:
    ; NOTE: Until paging is set up, the code must be position-independent and use physical
    ; addresses, not virtual ones!
    mov ecx, (BootPageDirectory - KERNEL_VIRTUAL_BASE)
    mov cr3, ecx                                        ; Load Page Directory Base Register.

    mov ecx, cr4
    or ecx, 0x00000010                          ; Set PSE bit in CR4 to enable 4MB pages.
    mov cr4, ecx

    mov ecx, cr0
    or ecx, 0x80000000                          ; Set PG bit in CR0 to enable paging.
    mov cr0, ecx

    ; Start fetching instructions in kernel space.
    ; Since eip at this point holds the physical address of this command (approximately 0x00100000)
    ; we need to do a long jump to the correct virtual address of StartInHigherHalf which is
    ; approximately 0xC0100000.
    lea ecx, [StartInHigherHalf]
    jmp ecx                                                     ; NOTE: Must be absolute jump!

StartInHigherHalf:
    ; Unmap the identity-mapped first 4MB of physical address space. It should not be needed
    ; anymore.
    mov dword [BootPageDirectory], 0
    invlpg [0]

    ; NOTE: From now on, paging should be enabled. The first 4MB of physical address space is
    ; mapped starting at KERNEL_VIRTUAL_BASE. Everything is linked to this address, so no more
    ; position-independent code or funny business with virtual-to-physical address translation
    ; should be necessary. We now have a higher-half kernel.
    mov esp, stack+STACKSIZE           ; set up the stack
    push eax                           ; pass Multiboot magic number

    ; pass Multiboot info structure -- WARNING: This is a physical address and may not be
    ; in the first 4MB!
    push ebx

    call  k_main                 ; call kernel proper
    hlt                          ; halt machine should kernel return


section .bss
align 32
stack:
    resb STACKSIZE      ; reserve 16k stack on a uint64_t boundary

这是loader.asm

我试图在高级ASM块中解析这个转换ASM块并解析gdtr作为参数但是我不明白最后一个方法 我该如何解决错误?提前谢谢!

1 个答案:

答案 0 :(得分:1)

您的错误:

kc.o: In function `k_enter_protected_mode':
kernel.c:(.text+0x1e1): undefined reference to `gdtr'

由于这行汇编代码而生成:

"lgdt (gdtr);"

gdtr是内存操作数,表示可以找到GDT记录的内存地址的标签。您没有使用该名称定义此类结构。这导致未定义的引用。

您需要创建 GDT 记录,其中包含 GDT 表的大小和长度。此记录将通过 LGDT 指令加载到 GDT 寄存器中。您还没有创建 GDT 表。 gdtr应该是一个6字节结构,由 GDT 减1(存储在16位字)的长度和32位线性地址组成,其中 GDT <可以找到/ em>表。

我建议您只在call k_main之前的汇编代码中执行此操作,而不是在 C 中执行您想要的操作。

C 代码中完全删除k_enter_protected_mode功能。然后在汇编文件loader.asm中放置此代码,以便在StartInHigherHalf代码的开头加载新的GDT。所以它看起来像:

StartInHigherHalf:
    ; Set our own GDT, can't rely GDT register being valid after bootloader
    ; transfers control to our entry point
    lgdt [gdtr]         ; Load GDT Register with GDT record
    mov eax, DATA_SEG
    mov ds, eax         ; Reload all the data descriptors with Data selector (2nd argument)
    mov es, eax
    mov gs, eax
    mov fs, eax
    mov ss, eax

    jmp CODE_SEG:.setcs
                        ; Do the FAR JMP to next instruction to set CS with Code selector, and
                        ;    set the EIP (instruction pointer) to offset of setcs
.setcs:

唯一剩下的就是定义GDT表。通过将其更改为.data部分,可以在section .data align 0x1000 BootPageDirectory: ; This page directory entry identity-maps the first 4MB of the 32-bit physical address space. ; All bits are clear except the following: ; bit 7: PS The kernel page is 4MB. ; bit 1: RW The kernel page is read/write. ; bit 0: P The kernel page is present. ; This entry must be here -- otherwise the kernel will crash immediately after paging is ; enabled because it can't fetch the next instruction! It's ok to unmap this page later. dd 0x00000083 times (KERNEL_PAGE_NUMBER - 1) dd 0 ; Pages before kernel space. ; This page directory entry defines a 4MB page containing the kernel. dd 0x00000083 times (1024 - KERNEL_PAGE_NUMBER - 1) dd 0 ; Pages after the kernel image. ; 32-bit GDT to replace one created by multiboot loader ; Per the multiboot specification we Can't rely on GDTR ; being valid so we need our own if we ever intend to ; reload any of the segment registers (this may be an ; issue with protected mode interrupts). align 8 gdt_start: dd 0 ; null descriptor dd 0 gdt32_code: dw 0FFFFh ; limit low dw 0 ; base low db 0 ; base middle db 10011010b ; access db 11001111b ; 32-bit size, 4kb granularity, limit 0xfffff pages db 0 ; base high gdt32_data: dw 0FFFFh ; limit low (Same as code) dw 0 ; base low db 0 ; base middle db 10010010b ; access db 11001111b ; 32-bit size, 4kb granularity, limit 0xfffff pages db 0 ; base high end_of_gdt: gdtr: dw end_of_gdt - gdt_start - 1 ; limit (Size of GDT - 1) dd gdt_start ; base of GDT CODE_SEG equ gdt32_code - gdt_start DATA_SEG equ gdt32_data - gdt_start 部分中放置一个带有必需NULL描述符和平面32位代码和数据描述符的简单数据:

gdtr

我们现在添加了所需的GDT结构,并创建了一个名为loader.asm的记录,可以使用LGDT指令加载。

由于您使用OSDev作为资源,因此建议您查看GDT tutorial以获取有关创建GDT的信息。英特尔手册也是一个很好的信息来源。

其他观察

您的_loader设置了多重引导标头,因此您可以使用Multiboot compliant bootloader。当您使用符合Multiboot的引导加载程序时,您的CPU将被置于32位保护模式,然后开始运行从{{1}}开始的代码。您的问题表明您认为自己处于实模式,但实际上您已处于保护模式。使用Mulitboot加载程序时,无需将 CR0 位0设置为值1.保证已经为1(设置)。在上面的代码中,我在设置 GDT 后删除了它。