assembly - 将16位实模式代码链接到兼容Multiboot的ELF可执行文件时出现LD错误

标签 assembly x86 nasm ld osdev

我正在编写一个包含32位内核的Multiboot兼容ELF可执行文件。我的主要问题是在生成可执行文件时收到一系列链接器错误:


重定位被截断以适合:R_386_16针对.text


下面的链接描述文件,代码和构建脚本

我决定尝试在我的OS中实现VESA VBE图形。我在OSDev forum中找到了现有的VESA驱动程序,并尝试将其集成到我自己的OS中。我尝试将其添加到我的源目录中,并与NASM组装在一起,并使用LD将其链接到最终可执行文件中。我收到的具体错误是:

vesa.asm:(.text+0x64): relocation truncated to fit: R_386_16 against `.text'
obj/vesa.o: In function `svga_mode':
vesa.asm:(.text+0x9d): relocation truncated to fit: R_386_16 against `.text'
vesa.asm:(.text+0xb5): relocation truncated to fit: R_386_16 against `.text'
obj/vesa.o: In function `done':
vesa.asm:(.text+0xc7): relocation truncated to fit: R_386_16 against `.text'


导致错误的行(依次)如下:

mov ax,[vid_mode]
mov cx,[vid_mode]
mov bx,[vid_mode]
jmp 0x8:pm1


我也用“链接器错误”注释了这些行

这是文件(vesa.asm):

BITS    32

global do_vbe

save_idt: dd 0
          dw 0
save_esp: dd 0
vid_mode: dw 0

do_vbe:
cli
mov word [vid_mode],ax
mov [save_esp],esp
sidt [save_idt]
lidt [0x9000] ;; saved on bootup see loader.asm

jmp 0x18:pmode
pmode:
mov ax,0x20
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0
dec eax
mov cr0,eax
jmp 0:realmode1

[bits 16]
realmode1:
xor ax,ax
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov sp,0xf000
sti

;; first zero out the 256 byte memory for the return function from getmodeinfo
cld
;; ax is already zero! I just saved myself a few bytes!!
mov cx,129
mov di,0x5000
rep stosw

mov ax,[vid_mode] ; Linker error 
xor ax,0x13
jnz svga_mode

;; Ok, just a regular mode13
mov ax,0x13
int 0x10
;; we didnt actually get a Vidmode structure in 0x5000, so we 
;; fake it with the stuff the kernel actually uses
mov word [0x5001],0xDD     ; mode attribs, and my favorite cup size
mov word [0x5013],320      ; width
mov word [0x5015],200      ; height
mov byte [0x501a],8        ; bpp
mov byte [0x501c],1        ; memory model type = CGA
mov dword [0x5029],0xa0000 ; screen memory
jmp done

svga_mode:

mov ax,0x4f01 ; Get mode info function
mov cx,[vid_mode] ; Linker error 
or cx,0x4000 ; always try to use linear buffer
mov di,0x5001
int 0x10
mov [0x5000],ah
or ah,ah
jnz done

mov ax,0x4f02 ; Now actually set the mode
mov bx,[vid_mode] ; ; Linker error 
or bx,0x4000
int 0x10

done:
cli
mov eax,cr0
inc eax
mov cr0,eax
jmp 0x8:pm1 ; Linker error

[bits 32]
pm1:
mov eax,0x10
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov dword esp,[save_esp]
lidt [save_idt]
ret


主条目文件(entry.asm):

extern kmain
extern do_vbe

; Multiboot Header
MBALIGN     equ 1<<0
MEMINFO     equ 1<<1
;VIDINFO        equ 1<<2
FLAGS       equ MBALIGN | MEMINFO; | VIDINFO
MAGIC       equ 0x1BADB002
CHECKSUM    equ -(MAGIC + FLAGS)

section .text
align 4

dd MAGIC
dd FLAGS
dd CHECKSUM
;dd 0
;dd 0
;dd 0
;dd 0
;dd 0
;dd 0
;dd 800
;dd 600
;dd 32

STACKSIZE equ 0x4000

global entry

entry:


    mov esp, stack+STACKSIZE
    push eax

    push ebx

    call do_vbe

    cli
    call kmain

    cli
    hlt
hang:
    jmp hang

section .bss
align 32
stack:
    resb STACKSIZE


我的链接描述文件:

OUTPUT_FORMAT(elf32-i386)
ENTRY(entry)
SECTIONS
 {
   . = 100000;
   .text : { *(.text) }
   .data : { *(.data) }
   .bss  : { *(.bss)  }
 }


我的构建脚本(请注意,我正在使用Cygwin):

cd src

for i in *.asm
do
    echo Assembling $i
    nasm -f elf32 -o "../obj/${i%.asm}.o" "$i"
done

for i in *.cpp
do
    echo Compiling $i
    i686-elf-g++ -c "$i" -o "../obj/${i%.cpp}.o" -I ../include --freestanding -fno-exceptions -fno-rtti -std=c++14 -Wno-write-strings
done

for i in *.S
do
    echo Compiling $i
    i686-elf-as -c "$i" -o "../obj/${i%.S}.o"
done

for i in *.c
do
    echo Compiling $i
    i686-elf-gcc -c "$i" -o "../obj/${i%.cpp}.o" -I ../include --freestanding
done

cd ..

i686-elf-ld -m elf_i386 -T linkscript.ld -o bin/kernel.sys obj/*.o 


如果有帮助,这里是目录结构:

/src Source Files
/include Include files
/obj Object files
/bin Kernel Executable

最佳答案

链接器错误的原因

您收到的此错误:


重定位被截断以适合:R_386_16针对.text


有效地告诉您,当链接器尝试解决.text部分中的这些重定位时,由于连接器计算出的虚拟内存地址(VMA)不能容纳在16位指针(_16)中而无法执行此操作。

如果在与NASM组装时使用-g -Fdwarf,则可以使用i686-elf-objdump -SDr -Mi8086 vesa.o之类的命令从OBJDUMP产生更多可用的输出。


-S输出源
-D用于拆卸,
-r显示重定位信息。


以下是我得到的输出(虽然略有不同,但此处介绍的思想仍然适用):

0000004a <realmode1>:

[bits 16]
realmode1:
...
mov ax,[vid_mode] ; Linker error
  63:   a1 0a 00                mov    ax,ds:0xa
                        64: R_386_16    .text
...
mov cx,[vid_mode] ; Linker error
  9a:   8b 0e 0a 00             mov    cx,WORD PTR ds:0xa
                        9c: R_386_16    .text
...
mov bx,[vid_mode] ; ; Linker error
  b2:   8b 1e 0a 00             mov    bx,WORD PTR ds:0xa
                        b4: R_386_16    .text
...
jmp 0x8:pm1 ; Linker error
  c5:   ea ca 00 08 00          jmp    0x8:0xca
                        c6: R_386_16    .text


我已经删除了简短的信息,并在输出中将其替换为...。有一个[bits 16]伪指令强制所有内存地址为16位,除非被覆盖。例如,c6: R_386_16 .text表示在偏移(0xc6)处有一个重定位,该重定位是出现在.text节中的16位指针。请记住这一点。现在查看链接描述文件:

. = 100000;
.text : { *(.text) }
.data : { *(.data) }
.bss  : { *(.bss)  }


VMA(来源)为0x100000。在这种情况下,这实际上是所有代码和数据的起点。最终可执行文件中生成的所有地址都将超过0xFFFF,这是可以容纳16位指针的最大值。这就是链接器抱怨的原因。

您可以通过在括号[]之间的标签名称之前指定DWORD来覆盖默认地址和操作数大小。可以通过在操作数之前指定DWORD来编码绝对的32位FAR JMP。这些行:

mov ax,[vid_mode]
mov cx,[vid_mode]
mov bx,[vid_mode]
jmp 0x8:pm1


会成为:

mov ax,[dword vid_mode]
mov cx,[dword vid_mode]
mov bx,[dword vid_mode]
jmp dword 0x8:pm1


如果您汇编修改后的代码并按照上述方法使用OBJDUMP,则会得到以下输出(为简便起见,请剪切以下内容):

mov ax,[dword vid_mode] ; Linker error
  63:   67 a1 0a 00 00 00       addr32 mov ax,ds:0xa
                        65: R_386_32    .text
...
mov cx,[dword vid_mode] ; Linker error
  9d:   67 8b 0d 0a 00 00 00    addr32 mov cx,WORD PTR ds:0xa
                        a0: R_386_32    .text
...
mov bx,[dword vid_mode] ; ; Linker error
  b8:   67 8b 1d 0a 00 00 00    addr32 mov bx,WORD PTR ds:0xa
                        bb: R_386_32    .text
...
jmp dword 0x8:pm1 ; Linker error
  ce:   66 ea d6 00 00 00 08    jmp    0x8:0xd6
  d5:   00
                        d0: R_386_32    .text


现在,指令中添加了0x660x67前缀,指令中的地址占4个字节。每个重定位的类型均为R_386_32,它告诉链接器要重定位的地址为32位宽。



尽管上一节中的更改将消除链接期间的警告,但是在运行时,某些操作可能无法按预期进行(包括崩溃)。在80386+上,您可以生成使用32位地址存储数据的16位实模式代码,但必须将CPU置于允许此类访问的模式下。允许通过DS段访问值大于0xFFFF的32位指针的模式称为Unreal ModeOSDev Wiki具有一些可用作此类支持基础的代码。假设尚未重新映射PIC,并且PIC处于其初始配置,那么按需实现虚幻模式的通常方法是用执行以下操作的方法替换0x0d中断处理程序:


查询PIC1 OCW3以查看是否正在服务IRQ5或是否存在常规保护错误。在没有PIC重新映射#GP故障和IRQ5的情况下,它们指向同一中断向量,因此必须区分它们。
如果设置了IRQ5 ISR,则调用先前保存的中断处理程序(链接)。至此,我们都完成了。
如果未设置IRQ5 ISR,则由于常规保护故障而调用了0x0d中断。假设故障是由于无效的数据访问。
切换到保护模式,并使用包含16位数据描述符的GDT,该描述符的基数为0,限制为0xffffffff。使用相应的选择器设置ES和DS。
离开保护模式
从中断处理程序返回。


如果已重新映射PIC1,以免与x86异常处理中断(int 0x08至int 0x0f)冲突,则不再执行步骤1,2,3。 Remapping the PICs避免这种冲突在x86 OS设计中很常见。问题中的代码不执行任何PIC重新映射。

如果您想在VM8086任务中使用代码而不是进入实模式,则此机制将不起作用。

DOS的HIMEM.SYS在1980年代做了类似的事情,如果您对此感兴趣,可以在article中找到有关此问题的讨论。

注意:尽管我给出了使用虚幻模式的一般描述,但是我不推荐这种方法。它需要对实模式,保护模式和中断处理有更广泛的了解。



更优选的解决方案

与其使用大于0xFFFF的32位数据指针,而且不确保处理器处于虚幻模式,不如使用一种更容易理解的解决方案。一种这样的解决方案是将实模式代码和数据从Multiboot加载程序物理加载到0x100000以上的RAM中复制到实模式中断向量表(IVT)上方的前64KB内存中。这使您可以继续使用16位指针,因为前64KB内存可通过16位指针(0x0000至0xFFFF)进行寻址。如果需要,该32位代码仍将能够访问实模式数据。

为此,您将必须创建一个更复杂的GNU LD linker scriptlink.ld),它在较低的内存中使用虚拟内存地址(原点)。地址0x01000是一个不错的选择。 Multiboot标头仍然必须存在于ELF可执行文件的开头。

必须克服的一个问题是,Multiboot加载程序会将代码和数据读入0x100000以上的内存中。必须先手动将16位实模式代码和数据复制到地址0x01000,然后才能使用实模式代码。链接描述文件可以帮助生成符号来计算此类副本的开始和结束地址。

请参阅上一节中的代码,以了解执行此操作的链接脚本link.ld和用于执行复制的kernel.c文件。

使用正确调整的VESA代码,您应该尝试执行的操作。



您发现的VESA代码存在问题


VESA code依赖于硬编码的地址
考虑到内核会在特定位置手动加载到内存(<64KB)中,并且某些地址在调用之前已经包含特定数据,因此并未考虑使用Multiboot。
该代码未遵循CDECL调用约定,因此无法直接从C代码进行调用。
有一个错误将32位代码放在[bits 16]指令下。
该代码未显示所需的GDT表,但可以从该代码中推断出,GDT中必须以特定顺序至少包含5个描述符。


空描述符
32位代码段(基数为0,限制为0xffffffff)。选择器0x08
32位数据段(以0为基数,限制为0xffffffff)。选择器0x10
16位代码段(基数为0,限制至少为0xffff)。选择器0x18
16位数据段(基数为0,限制至少为0xffff)。选择器0x20



作者有以下评论:


在启动时,使用sidt指令将实模式IDT保存在已知位置(我的位置是0x9000),并且不要覆盖内存中的地址0-0x500。它还假定您使用8和16作为PMode中代码和数据的段寄存器。它将函数4f01的结果存储在0x5000,并自动将第13位设置(使用帧缓冲区)




一个完整的例子

以下代码是上述建议的完整实现。使用链接描述文件并生成实模式代码和数据,并将其放置在0x1000开始。该代码使用C设置具有32和16位代码和数据段的适当GDT,并将实模式代码从0x100000以上复制到0x1000。它还解决了以前在VESA驱动程序代码中标识的其他问题。要进行测试,它会切换到视频模式0x13(320x200x256),并将VGA调色板的一部分一次绘制到显示器32位。

link.ld

OUTPUT_FORMAT("elf32-i386");
ENTRY(mbentry);

/* Multiboot spec uses 0x00100000 as a base */
PHYS_BASE = 0x00100000;
REAL_BASE = 0x00001000;

SECTIONS
{
    . = PHYS_BASE;

    /* Place the multiboot record first */
    .multiboot : {
        *(.multiboot);
    }

    /* This is the tricky part. The LMA (load memory address) is the
     * memory location the code/data is read into memory by the
     * multiboot loader. The LMA is after the colon. We want to tell
     * the linker that the code/data in this section was loaded into
     * RAM in the memory area above 0x100000. On the other hand the
     * VMA (virtual memory address) specified before the colon acts
     * like an ORG directive. The VMA tells the linker to resolve all
     * subsequent code starting relative to the specified VMA. The
     * VMA in this case is REAL_BASE which we defined as 0x1000.
     * 0x1000 is 4KB page aligned (useful if you ever use paging) and
     * resides above the end of the interrupt table and the
     * BIOS Data Area (BDA)
     */

    __physreal_diff = . - REAL_BASE;
    .realmode REAL_BASE : AT(ADDR(.realmode) + __physreal_diff) {
        /* The __realmode* values can be used by code to copy
         * the code/data from where it was placed in RAM by the
         * multiboot loader into lower memory at REAL_BASE
         *
         * . (period) is the current VMA */
        __realmode_vma_start = .;

        /* LOADADDR is the LMA of the specified section */
        __realmode_lma_start = LOADADDR(.realmode);
        *(.text.realmode);
        *(.data.realmode);
    }
    . = ALIGN(4);
    __realmode_vma_end = .;
    __realmode_secsize   = ((__realmode_vma_end)-(__realmode_vma_start));
    __realmode_secsize_l = __realmode_secsize>>2;
    __realmode_lma_end   = __realmode_vma_start + __physreal_diff + __realmode_secsize;

    /* . (period) is the current VMA. We set it to the value that would
     * have been generated had we not changed the VMA in the previous
     * section. The .text section also specified the LMA = VMA with
     * AT(ADDR(.text))
     */
    . += __physreal_diff;
    .text ALIGN(4K): AT(ADDR(.text)) {
        *(.text);
    }

    /* From this point the linker script is typical */
    .data ALIGN(4K) : {
        *(.data);
    }

    .data ALIGN(4K) : {
        *(.rodata);
    }

    /* We want to avoid this section being placed in low memory */
    .eh_frame : {
        *(.eh_frame*);
    }

    .bss ALIGN(4K): {
        *(COMMON);
        *(.bss)
    }

    /* The .note.gnu.build-id section will usually be placed at the beginning
     * of the ELF object. We discard it (if it is present) so that the
     * multiboot header is placed as early as possible in the file. The
     * multiboot header must appear in the first 8K and be on a 4 byte
     * aligned offset per the multiboot spec.
     */
    /DISCARD/ : {
        *(.note.gnu.build-id);
        *(.comment);
    }
}


gdt.inc

CODE32SEL equ 0x08
DATA32SEL equ 0x10
CODE16SEL equ 0x18
DATA16SEL equ 0x20


vesadrv.asm

; Video driver code - switches the CPU back into real mode
; Then executes an int 0x10 instruction

%include "gdt.inc"

global do_vbe

bits 16
section .data.realmode
save_idt: dw 0
          dd 0
save_esp: dd 0
vid_mode: dw 0
real_ivt: dw (256 * 4) - 1      ; Realmode IVT has 256 CS:IP pairs
          dd 0                  ; Realmode IVT physical address at address 0x00000

align 4
mode_info:TIMES 129 dw 0        ; Buffer to store mode info from Int 10h/ax=4f01h
                                ; Plus additional bytes for the return status byte
                                ; at beginning of buffer

bits 32
section .text
do_vbe:
    mov ax, [esp+4]             ; Retrieve videomode passed on stack
    pushad                      ; Save all the registers
    pushfd                      ; Save the flags (including Interrupt flag)
    cli
    mov word [vid_mode],ax
    mov [save_esp],esp
    sidt [save_idt]
    lidt [real_ivt]             ; We use a real ivt that points to the
                                ; physical address 0x00000 at the bottom of
                                ; memory. The IVT in real mode is 256*4 bytes
                                ; and runs from physical address 0x00000 to
                                ; 0x00400
    jmp CODE16SEL:pmode16

bits 16
section .text.realmode
pmode16:
    mov ax,DATA16SEL
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax
    mov eax,cr0
    dec eax
    mov cr0,eax
    jmp 0:realmode1

realmode1:
    ; Sets real mode stack to grow down from 0x1000:0xFFFF
    mov ax,0x1000
    mov ss,ax
    xor sp,sp

    xor ax,ax
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax

    ; first zero out the 258 byte memory for the return function from getmodeinfo
    cld
    mov cx,(258/2)              ; 128 words + 1 word for the status return byte
    mov di,mode_info
    rep stosw

    mov ax,[vid_mode]
    xor ax,0x13
    jnz svga_mode

    ; Just a regular mode13
    mov ax,0x13
    int 0x10

    ; Fake a video mode structure with the stuff the kernel actually uses
    mov di, mode_info
    mov word [di+0x01],0xDD     ; mode attribs
    mov word [di+0x13],320      ; width
    mov word [di+0x15],200      ; height
    mov byte [di+0x1a],8        ; bpp
    mov byte [di+0x1c],1        ; memory model type = CGA
    mov dword [di+0x29],0xa0000 ; screen memory
    jmp done

svga_mode:
    mov ax,0x4f01               ; Get mode info function
    mov cx,[vid_mode]
    or cx,0x4000                ; always try to use linear buffer
    mov di,mode_info+0x01
    int 0x10
    mov [mode_info],ah
    or ah,ah
    jnz done

    mov ax,0x4f02               ; Now actually set the mode
    mov bx,[vid_mode]
    or bx,0x4000
    int 0x10

done:
    cli
    mov eax,cr0
    inc eax
    mov cr0,eax
    jmp dword CODE32SEL:pm1     ; To FAR JMP to address > 0xFFFF we need
                                ; to specify DWORD to allow a 32-bit address
                                ; in the offset portion. When this JMP is
                                ; complete CS will be CODE32SEL and processor
                                ; will be in 32-bit protected mode

bits 32
section .text
pm1:
    mov eax,DATA32SEL
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax
    mov dword esp,[save_esp]
    lidt [save_idt]
    popfd                       ; Restore flags (including Interrupt flag)
    popad                       ; Restore registers

    mov eax, mode_info          ; Return pointer to mode_info structure
    ret


vesadrv.h

#ifndef VESADRV_H
#define VESADRV_H

#include <stdint.h>

extern struct mode_info_t * do_vbe (const uint8_t video_mode);

struct mode_info_t {
    uint8_t status; /* Return value from Int 10/ax=4f01 */

    /* Rest of structure from OSDev Wiki
       http://wiki.osdev.org/VESA_Video_Modes#VESA_Functions
    */
    uint16_t attributes;
    uint8_t winA,winB;
    uint16_t granularity;
    uint16_t winsize;
    uint16_t segmentA, segmentB;

    /* Real mode FAR Pointer.  Physical address
     * computed as (segment<<4)+offset
     */
    uint16_t realFctPtr_offset; /* FAR Pointer offset */
    uint16_t realFctPtr_segment;/* FAR Pointer segment */

    uint16_t pitch; /* bytes per scanline */

    uint16_t Xres, Yres;
    uint8_t Wchar, Ychar, planes, bpp, banks;
    uint8_t memory_model, bank_size, image_pages;
    uint8_t reserved0;

    uint8_t red_mask, red_position;
    uint8_t green_mask, green_position;
    uint8_t blue_mask, blue_position;
    uint8_t rsv_mask, rsv_position;
    uint8_t directcolor_attributes;

    volatile void * physbase;  /* LFB (Linear Framebuffer) address */
    uint32_t reserved1;
    uint16_t reserved2;
} __attribute__((packed));

#endif


gdt.h

#ifndef GDT_H
#define GDT_H

#include <stdint.h>
#include <stdbool.h>

typedef struct
{
        unsigned short limit_low;
        unsigned short base_low;
        unsigned char base_middle;
        unsigned char access;
        unsigned char flags;
        unsigned char base_high;
} __attribute__((packed)) gdt_desc_t;

typedef struct {
    uint16_t limit;
    gdt_desc_t *gdt;
} __attribute__((packed)) gdtr_t;

extern void gdt_set_gate(gdt_desc_t gdt[], const int num, const uint32_t base,
                         const uint32_t limit, const uint8_t access,
                         const uint8_t flags);

static inline void gdt_load(gdtr_t * const gdtr, const uint16_t codesel,
                            const uint16_t datasel, const bool flush)
{    
    /* load the GDT register */
    __asm__ __volatile__ ("lgdt %[gdtr]"
                          :
                          : [gdtr]"m"(*gdtr),
                          /* Dummy constraint to ensure what gdtr->gdt points at is fully
                           * realized into memory before we issue LGDT instruction */
                            "m"(*(const gdt_desc_t (*)[]) gdtr->gdt));

    /* This flushes the selector registers to ensure the new
     * descriptors are used. */
    if (flush) {
        /* The indirect absolute jump is because we can't
         * assume that codesel is an immediate value
         * as it may be passed in a register. We build a
         * far pointer in memory and indirectly jump through
         * that pointer. This explicitly sets CS selector */
        __asm__  __volatile__ (
                 "pushl %[codesel]\n\t"
                 "pushl $1f\n\t"
                 "ljmpl *(%%esp)\n"
                 "1:\n\t"
                 "add $8, %%esp\n\t"
                 "mov %[datasel], %%ds\n\t"
                 "mov %[datasel], %%es\n\t"
                 "mov %[datasel], %%ss\n\t"
                 "mov %[datasel], %%fs\n\t"
                 "mov %[datasel], %%gs"
                 : /* No outputs */
                 : [datasel]"r"(datasel),
                   [codesel]"g"((uint32_t)codesel));
    }
    return;
}
#endif


gdt.c

#include "gdt.h"

/* Setup a descriptor in the Global Descriptor Table */
void gdt_set_gate(gdt_desc_t gdt[], const int num, const uint32_t base,
                  const uint32_t limit, const uint8_t access,
                  const uint8_t flags)
{
        /* Setup the descriptor base access */
        gdt[num].base_low = (base & 0xFFFF);
        gdt[num].base_middle = (base >> 16) & 0xFF;
        gdt[num].base_high = (base >> 24) & 0xFF;

        /* Setup the descriptor limits */
        gdt[num].limit_low = (limit & 0xFFFF);
        gdt[num].flags = ((limit >> 16) & 0x0F);

        /* Finally, set up the flags and access byte */
        gdt[num].flags |= (flags << 4);
        gdt[num].access = access;
}


multiboot.asm

%include "gdt.inc"

STACKSIZE equ 0x4000

bits 32
global mbentry

extern kmain

; Multiboot Header
section .multiboot
MBALIGN     equ 1<<0
MEMINFO     equ 1<<1
VIDINFO     equ 0<<2
FLAGS       equ MBALIGN | MEMINFO | VIDINFO
MAGIC       equ 0x1BADB002
CHECKSUM    equ -(MAGIC + FLAGS)

mb_hdr:
    dd MAGIC
    dd FLAGS
    dd CHECKSUM

section .text
mbentry:
    cli
    cld
    mov esp, stack_top

    ; EAX = magic number. Should be 0x2badb002
    ; EBX = pointer to multiboot_info
    ; Pass as parameters right to left
    push eax
    push ebx
    call kmain

    ; Infinite loop to end program
    cli
endloop:
    hlt
    jmp endloop

section .bss
align 32
stack:
    resb STACKSIZE
stack_top:


kernel.c

#include <stdint.h>
#include <stdbool.h>
#include "vesadrv.h"
#include "gdt.h"

#define CODE32SEL 0x08
#define DATA32SEL 0x10
#define CODE16SEL 0x18
#define DATA16SEL 0x20
#define NUM_GDT_ENTRIES 5

/* You can get this structure from GRUB's multiboot.h if needed
 * https://www.gnu.org/software/grub/manual/multiboot/html_node/multiboot_002eh.html
 */
struct multiboot_info;

/* Values made available by the linker script */
extern void *__realmode_lma_start;
extern void *__realmode_lma_end;
extern void *__realmode_vma_start;

/* Pointer to graphics memory.Mark as volatile since
 * video memory is memory mapped IO. Certain optimization
 * should not be performed. */
volatile uint32_t * video_gfx_ptr;

/* GDT descriptor table */
gdt_desc_t gdt[NUM_GDT_ENTRIES];

/* Copy the code and data in the realmode section down into the lower
 * 64kb of memory @ 0x00001000. */
static void realmode_setup (void)
{
    /* Each of these __realmode* values is generated by the linker script */
    uint32_t *src_addr = (uint32_t *)&__realmode_lma_start;
    uint32_t *dst_addr = (uint32_t *)&__realmode_vma_start;
    uint32_t *src_end  = (uint32_t *)&__realmode_lma_end;

    /* Copy a DWORD at a time from source to destination */
    while (src_addr < src_end)
        *dst_addr++ = *src_addr++;
}

void gdt_setup (gdt_desc_t gdt[], const int numdesc)
{
    gdtr_t gdtr = { sizeof(gdt_desc_t)*numdesc-1, gdt };

    /* Null descriptor */
    gdt_set_gate(gdt, 0, 0x00000000, 0x00000000, 0x00, 0x0);
    /* 32-bit Code descriptor, flat 4gb */
    gdt_set_gate(gdt, 1, 0x00000000, 0xffffffff, 0x9A, 0xC);
    /* 32-bit Data descriptor, flat 4gb */
    gdt_set_gate(gdt, 2, 0x00000000, 0xffffffff, 0x92, 0xC);
    /* 16-bit Code descriptor, limit 0xffff bytes */
    gdt_set_gate(gdt, 3, 0x00000000, 0x0000ffff, 0x9A, 0x0);
    /* 16-bit Data descriptor, limit 0xffffffff bytes */
    gdt_set_gate(gdt, 4, 0x00000000, 0xffffffff, 0x92, 0x8);

    /* Load global decriptor table, and flush the selectors */
    gdt_load(&gdtr, CODE32SEL, DATA32SEL, true);
}

int kmain(struct multiboot_info *mb_info, const uint32_t magicnum)
{
    struct mode_info_t *pMI;
    uint32_t pixel_colors = 0;

    /* Quiet compiler about unused variables */
    (void) mb_info;
    (void) magicnum;

    /* Setup the GDT */
    gdt_setup(gdt, NUM_GDT_ENTRIES);

    /* Setup real mode code and data */
    realmode_setup();

    /* Switch to video mode 0x13 (320x200x256)
     * The physical address of the mode 13 video memory is
     * 0xa0000 */
    pMI = do_vbe(0x13);
    video_gfx_ptr = pMI->physbase;

    /* Display part of the VGA palette as a test pattern */
    for (int pixelpos = 0; pixelpos < (320*200); pixelpos++) {
        if ((pixel_colors & 0xff) == (320/4))
            pixel_colors = 0;
        pixel_colors += 0x01010101;
        video_gfx_ptr[pixelpos] = pixel_colors;
    }
    return 0;
}


一组简单的命令,用于将上面的代码汇编/编译/链接到称为multiboot.elf的ELF可执行文件中:

nasm -f elf32 -g -F dwarf -o multiboot.o multiboot.asm
nasm -f elf32 -g -F dwarf -o vesadrv.o vesadrv.asm
i686-elf-gcc -std=c99 -g -m32 -O3 -c -fno-exceptions -nostdlib -ffreestanding -Wall -Wextra -o kernel.o kernel.c
i686-elf-gcc -std=c99 -g -m32 -O3 -c -fno-exceptions -nostdlib -ffreestanding -Wall -Wextra -pedantic -o gdt.o gdt.c -lgcc
i686-elf-gcc -m32 -Tlink.ld -ffreestanding -nostdlib -o multiboot.elf multiboot.o kernel.o gdt.o vesadrv.o -lgcc




您可以在my site上找到上述代码的副本。当我在QEMU中运行内核时,我看到的是:

enter image description here

关于assembly - 将16位实模式代码链接到兼容Multiboot的ELF可执行文件时出现LD错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41563879/

相关文章:

multithreading - LOCK XCHG和MOV+MFENCE在逻辑和性能上有什么区别?

assembly - 为什么从 _start 段错误返回?

汇编检查数字是否为偶数

c - (NASM, C)为什么你需要 malloc 两次来创建一个数组,如此处所示

c - 立即寻址比寄存器访问更快吗?

linux - 将寄存器值打印到控制台

x86 - 我的 macbook pro x86 linux 还是 x86_64 darwin?

c - X86 CPU 如何将地址转换为 VGA 文本缓冲区等 IO?

assembly - 带有前导 $ 的 NASM 外部符号

linux - 从不对齐 RSP 的函数调用时,glibc scanf 出现段错误