我有一个用汇编(boot.s
)编写的bootloader和一个用c(kernel.c
)编写的内核。
我还有一些其他文件,例如:linker.ld
和grub.cfg
,但我不知道如何使用它们......
如果我跑:
gcc -g -m32 -c -ffreestanding -o kernel.o kernel.c -lgcc
ld -melf_i386 -Tlinker.ld -nostdlib --nmagic -o kernel.elf kernel.o
objcopy -O binary kernel.elf kernel.bin
我收到错误:ld: Unrecognized emulation mode: elf_i386
PS:我正在使用Windows 10 Pro 32Bit并且还安装了VirtualBox(如果这有帮助)gcc即时使用cygwin。
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
static const uint8_t COLOR_BLACK = 0;
static const uint8_t COLOR_BLUE = 1;
static const uint8_t COLOR_GREEN = 2;
static const uint8_t COLOR_CYAN = 3;
static const uint8_t COLOR_RED = 4;
static const uint8_t COLOR_MAGENTA = 5;
static const uint8_t COLOR_BROWN = 6;
static const uint8_t COLOR_LIGHT_GREY = 7;
static const uint8_t COLOR_DARK_GREY = 8;
static const uint8_t COLOR_LIGHT_BLUE = 9;
static const uint8_t COLOR_LIGHT_GREEN = 10;
static const uint8_t COLOR_LIGHT_CYAN = 11;
static const uint8_t COLOR_LIGHT_RED = 12;
static const uint8_t COLOR_LIGHT_MAGENTA = 13;
static const uint8_t COLOR_LIGHT_BROWN = 14;
static const uint8_t COLOR_WHITE = 15;
uint8_t make_color(uint8_t fg, uint8_t bg)
{
return fg | bg << 4;
}
uint16_t make_vgaentry(char c, uint8_t color)
{
uint16_t c16 = c;
uint16_t color16 = color;
return c16 | color16 << 8;
}
size_t strlen(const char* str)
{
size_t ret = 0;
while ( str[ret] != 0 )
ret++;
return ret;
}
static const size_t VGA_WIDTH = 80;
static const size_t VGA_HEIGHT = 24;
size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;
uint16_t* terminal_buffer;
void terminal_initialize()
{
terminal_row = 0;
terminal_column = 0;
terminal_color = make_color(COLOR_LIGHT_GREY, COLOR_BLACK);
terminal_buffer = (uint16_t*) 0xB8000;
for ( size_t y = 0; y < VGA_HEIGHT; y++ )
for ( size_t x = 0; x < VGA_WIDTH; x++ )
{
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = make_vgaentry(' ', terminal_color);
}
}
void terminal_setcolor(uint8_t color)
{
terminal_color = color;
}
void terminal_putentryat(char c, uint8_t color, size_t x, size_t y)
{
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = make_vgaentry(c, color);
}
void terminal_putchar(char c)
{
terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
if ( ++terminal_column == VGA_WIDTH )
{
terminal_column = 0;
if ( ++terminal_row == VGA_HEIGHT )
{
terminal_row = 0;
}
}
}
void terminal_writestring(const char* data)
{
size_t datalen = strlen(data);
for ( size_t i = 0; i < datalen; i++ )
terminal_putchar(data[i]);
}
void kmain()
{
terminal_initialize();
terminal_writestring("Starting mOS...\n\n");
terminal_writestring("mOS Version alpha1 - Created by milan44\n");
}
.set ALIGN, 1<<0
.set MEMINFO, 1<<1
.set FLAGS, ALIGN | MEMINFO
.set MAGIC, 0x1BADB002
.set CHECKSUM, -(MAGIC + FLAGS)
.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
.section .bootstrap_stack
stack_bottom:
.skip 16384
stack_top:
.section .text
.global _start
_start:
movl $stack_top, %esp
call kmain
cli
hang:
hlt
jmp hang
ENTRY(_start)
SECTIONS
{
. = 1M;
.text BLOCK(4K) : ALIGN(4K)
{
*(.multiboot)
*(.text)
}
.rodata BLOCK(4K) : ALIGN(4K)
{
*(.rodata)
}
.data BLOCK(4K) : ALIGN(4K)
{
*(.data)
}
.bss BLOCK(4K) : ALIGN(4K)
{
*(COMMON)
*(.bss)
*(.bootstrap_stack)
}
}
答案 0 :(得分:2)
我强烈建议您构建一个生成 ELF 对象的 C 交叉编译器和工具链。这使您远离主机编译器和链接器的细微差别。默认Cygwin GCC和LD与通用 ELF 编译器和链接器有许多不同之处。 OSDev Wiki包含用于为Cygwin构建交叉编译器的信息。我没有亲自在Cygwin上构建一个交叉编译器,所以不能说这些指令对于那个环境是否准确。
Cygwin生成Windows PE32(32位)和PE32 +(64位)对象。这就是-melf_i386
无效的原因。构建 ELF 交叉编译器将允许您使用-melf_i386
。在您的情况下,您将需要一个 ELF 交叉编译器,因为多引导加载程序需要 ELF 可执行文件,即Cywgin的 GCC 和 LD 无法生成。
如果您使用的是64位Windows 10,那么您可以在Windows的Linux子系统(WSL)下执行此操作,因为默认情况下Ubuntu的GCC和LD将生成ELF exectuable。
尽管推动您使用交叉编译器是正确的方法,但有一种方法可以使它与Cygwin一起使用。
Cygwin GCC(与其他32位Windows编译器一样)将_
添加到具有全局可见范围的非静态函数中。这意味着您的kmain
实际上是_kmain
。修改您的boot.s
以call _kmain
代替call kmain
。这适用于您从程序集调用的任何 C 函数。您在 C 代码中访问的程序集文件中提供的任何函数都必须添加_
下划线。
Windows程序的一个重要区别是部分名称可能略有不同。 Cygwin中的rodata
可以是.rdata*
。可能有许多以rdata
开头的部分。您必须在链接描述文件中考虑到这一点:
ENTRY(_start)
SECTIONS
{
. = 1M;
.text BLOCK(4K) : ALIGN(4K)
{
*(.multiboot)
*(.text)
}
.rodata BLOCK(4K) : ALIGN(4K)
{
*(.rodata)
*(.rdata*) /* IMPORTANT - Windows uses rdata */
}
.data BLOCK(4K) : ALIGN(4K)
{
*(.data)
}
.bss BLOCK(4K) : ALIGN(4K)
{
*(COMMON)
*(.bss)
*(.bootstrap_stack)
}
}
这会产生很大的不同,因为如果您没有正确处理rdata
部分,那么它们可能会放置在多重引导标头之前,可能会导致GRUB之类的多重引导加载程序无法看到它。所以这种变化非常很重要。
构建可由Multiboot兼容的引导加载程序(或QEMU的-kernel
选项)使用的文件的命令不正确。由于LD无法输出 ELF 文件,因此您需要使用OBJCOPY将 PE32 可执行文件转换为32位 ELF 可执行文件。你的OBJCOPY命令做错了。您转换为二进制文件。不幸的是,编写Multiboot标头的方式并不起作用。
汇编和链接代码以及生成可由多重引导加载程序使用的最终kernel.elf
文件的命令可能如下所示:
gcc -g -m32 -c -ffreestanding -o kernel.o kernel.c
gcc -g -m32 -c -ffreestanding -o boot.o boot.s
ld -mi386pe -Tlinker.ld -nostdlib --nmagic -o kernel.pe kernel.o boot.o
objcopy -O elf32-i386 kernel.pe kernel.elf
这个程序有点棘手,因为Cygwin没有附带grub-legacy软件包。要在其上创建带有Grub的可引导ISO / CD映像,您需要获取文件stage2_eltorito
。您可以从this project下载副本。
您必须运行Cygwin安装程序并安装包genisoimage
在前面的部分中,我们构建了一个名为kernel.elf
的文件。现在我们有了构建ISO / CD映像所需的组件。
从您构建kernel.elf
的目录中,我们需要创建一系列子目录。这可以通过以下方式完成:
mkdir -p iso/boot/grub
您需要复制stage2_eltorito
文件并将其放在iso/boot/grub
目录中。您还需要在menu.lst
中创建文件iso/boot/grub
。
iso / boot / grub / menu.lst :
default 0
timeout 0
title MyOS
# kernel ELF file.
kernel /boot/kernel.elf
上述过程只需进行一次。这足以用Grub和我们的内核创建一个基本的可引导ISO。
现在构建内核,将文件复制到iso
目录并生成ISO / CD映像的过程可以这样完成:
gcc -g -m32 -c -ffreestanding -o kernel.o kernel.c
gcc -g -m32 -c -ffreestanding -o boot.o boot.s
ld -mi386pe -Tlinker.ld -nostdlib --nmagic -o kernel.pe kernel.o boot.o
objcopy -O elf32-i386 kernel.pe kernel.elf
cp kernel.elf iso/boot
genisoimage -R -b boot/grub/stage2_eltorito -no-emul-boot \
-boot-load-size 4 -boot-info-table -o myos.iso iso
genisoimage
命令创建一个名为myos.iso
的ISO / CD。您可以使用您喜欢的名称替换myos.iso
命令行上的genisoimage
,将名称更改为您喜欢的名称。
myos.iso
应该可以从大多数硬件,虚拟机和模拟器启动,作为简单的CD映像。使用内核运行时,它应该显示为:
上图是我在QEMU中使用以下命令启动ISO / CD时看到的:
qemu-system-i386 -cdrom myos.iso
如果你在VirtualBox中运行它,你应该会看到类似的内容。