我正在编写一个包含32位内核的Multiboot兼容 ELF 可执行文件。我的主要问题是我在生成可执行文件时收到一系列链接器错误:
重定位被截断以适合:R_386_16对.`text'
链接器脚本,代码和构建脚本
我决定尝试在我的操作系统中实现VESA VBE图形。我在OSDev forum中找到了一个现有的VESA驱动程序,并尝试将其集成到我自己的操作系统中。我尝试将其添加到我的源目录,使用 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
我还用" Linker错误"
评论了这些行这是文件(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
答案 0 :(得分:6)
您收到此错误:
重定位被截断以适合:R_386_16与`.text&#39;
有效地告诉您,当链接器尝试在.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
指令现在添加了0x66
和0x67
前缀,地址在指令中占用4个字节。每个重定位都是R_386_32
类型,它告诉链接器要重定位的地址是32位宽。
虽然上一节中的更改将消除链接期间的警告,但运行时可能无法按预期工作(包括崩溃)。在80386+上,您可以生成16位实模式代码,该代码使用32位地址作为数据,但CPU必须进入允许此类访问的模式。允许通过 DS 段访问的值为0xFFFF以上的32位指针的模式称为Unreal Mode。 OSDev Wiki有一些代码可以作为此类支持的基础。假设PIC没有重新映射并处于初始配置状态,那么实现按需虚幻模式的通常方法是用以下内容替换0x0d中断处理程序:
如果已重新映射PIC1以便不与x86异常处理中断冲突(int 0x08到int 0x0f),则步骤1,2,3不再适用。 Remapping the PICs避免这种冲突在x86 OS设计中很常见。问题中的代码不进行任何PIC重映射。
如果您想在VM8086任务中使用代码而不是进入实模式,这种机制将无法运行。
DOS的HIMEM.SYS在20世纪80年代做了类似的事情,如果你感兴趣,你可以在article找到关于它的讨论。注意:虽然我提供了使用虚幻模式的一般说明,但我不推荐使用此方法。它需要更多的实模式,保护模式,中断处理知识。
不是使用大于0xFFFF的32位数据指针,并确保处理器处于虚幻模式,而是有一个可能更容易理解的解决方案。一种这样的解决方案是将实际模式代码和数据从多路径加载器物理加载到0x100000以上的RAM中复制到实模式中断向量表(IVT)正上方的第一个64KB内存中。这允许您继续使用16位指针,因为第一个64KB的内存可通过16位指针(0x0000到0xFFFF)进行寻址。如果需要,32位代码仍然可以访问实模式数据。
为此,您必须创建一个更复杂的GNU LD linker script(link.ld
),它在较低内存中使用虚拟内存地址(原点)。地址0x01000是个不错的选择。 Multiboot标头仍然必须出现在 ELF 可执行文件的开头。
必须克服的一个问题是Multiboot加载程序会将代码和数据读入0x100000以上的内存中。在可以使用实模式代码之前,必须手动将16位实模式代码和数据复制到地址0x01000。链接描述文件可以帮助生成符号以计算此类副本的起始和结束地址。
请参阅上一节中的代码,了解执行此操作的链接描述文件link.ld
以及执行复制的kernel.c
文件。
通过适当调整的VESA代码,您尝试做的事情应该有效。
[bits 16]
指令下。作者有这些评论:
启动时,使用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, and my favorite cup size
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 granularity;
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 gran);
static inline void gdt_load(gdtr_t * const gdtr, const uint16_t codesel,
const uint16_t datasel, const bool flush)
{
/* Dummy variable used as a fake dependency to avoid optimization
* reordering of the inline assembly. The flush (if requested) must
* always come after we set the GDT, not before */
int dummy;
/* load the GDT register */
__asm__ __volatile__ ("lgdt %[gdtr]"
: "=X"(dummy)
: [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 */
: "X"(dummy),
[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 gran)
{
/* 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].granularity = ((limit >> 16) & 0x0F);
/* Finally, set up the granularity and access flags */
gdt[num].granularity |= (gran & 0xF0);
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, 0x00);
/* 32-bit Code descriptor, flat 4gb */
gdt_set_gate(gdt, 1, 0x00000000, 0xffffffff, 0x9A, 0xC0);
/* 32-bit Data descriptor, flat 4gb */
gdt_set_gate(gdt, 2, 0x00000000, 0xffffffff, 0x92, 0xC0);
/* 16-bit Code descriptor, limit 0xffff bytes */
gdt_set_gate(gdt, 3, 0x00000000, 0x0000ffff, 0x9A, 0x00);
/* 16-bit Data descriptor, limit 0xffffffff bytes */
gdt_set_gate(gdt, 4, 0x00000000, 0xffffffff, 0x92, 0x8f);
/* 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 中运行内核时,这就是我所看到的: