使用Cygwin gcc编译+链接自定义OS:无法识别的仿真模式:elf_i386 [英] Compiling + linking a custom OS with Cygwin gcc: Unrecognized emulation mode: elf_i386

查看:232
本文介绍了使用Cygwin gcc编译+链接自定义OS:无法识别的仿真模式:elf_i386的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个用汇编语言(boot.s)编写的引导程序和一个用c(kernel.c)编写的内核.

I have a bootloader written in assembly (boot.s) and a kernel written in c (kernel.c).

我还有一些其他文件,例如:linker.ldgrub.cfg,但是我不知道如何使用它们...

I also have some other files such as: linker.ld and grub.cfg but I have no clue how to use them...

如果我跑步:

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,并且还为cygwin的gcc im安装了VirtualBox(如果有帮助).

PS.: Im using Windows 10 Pro 32Bit and also have VirtualBox installed (If this helps) for gcc im using 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");
}

boot.s

.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

linker.ld

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)
    }
}

推荐答案

使用交叉编译器工具链

我强烈建议您构建一个生成 ELF 对象的 C 交叉编译器和工具链.这使您摆脱了主机编译器和链接器的细微差别.默认的Cygwin GCC和LD与通用的 ELF 编译器和链接器有许多区别. OSDev Wiki 包含有关为Cygwin构建交叉编译器的信息.我没有亲自在Cygwin上构建交叉编译器,因此不能说指令在该环境下是否准确.

Use a Cross Compiler Tool Chain

I highly recommend you build a C cross compiler and tool chain that generates ELF objects. This breaks you from the nuances of host compilers and linkers. Default Cygwin GCC and LD have a number of differences from a generic ELF compiler and linker. The OSDev Wiki include information for building a cross compiler for Cygwin. I haven't personally built a cross compiler on Cygwin so can't say if the instructions are accurate for that environment.

Cygwin生成Windows PE32(32位)和PE32 +(64位)对象.这就是为什么-melf_i386不起作用的原因.构建 ELF 交叉编译器将允许您使用-melf_i386.在这种情况下,您将需要一个 ELF 交叉编译器,因为多重引导加载程序需要一个 ELF 可执行文件,其中Cywgin的 GCC LD 无法生成.

Cygwin generates Windows PE32(32-bit) and PE32+ (64-bit) objects. This is why -melf_i386 doesn't work. Building an ELF cross compiler would allow you to use -melf_i386. You will need an ELF cross compiler in your case because the multiboot loaders require an ELF executable which Cywgin's GCC and LD can't generate.

如果您使用的是64位Windows 10,则可以在Linux的Windows子系统(WSL)下执行此操作,因为Ubuntu的GCC和LD默认会生成ELF可执行文件.

Had you been using 64-bit Windows 10 you would have been able to do this under Windows Subsystem for Linux (WSL) since Ubuntu's GCC and LD will generate ELF exectuables by default.

尽管敦促您使用交叉编译器是执行此操作的正确方法,但仍有一种方法可以使其与Cygwin一起使用.

Although pushing you to have a cross compiler is the right way to do this, there is a way to make it work with Cygwin.

Cygwin GCC(与其他32位Windows编译器一样)将在全局范围内的非静态函数之前添加_.这意味着您的kmain实际上是_kmain.修改您的boot.s以执行call _kmain而不是call kmain.这适用于您从汇编中调用的所有 C 函数.您在汇编文件中提供的任何要通过 C 代码访问的功能,都必须在其上添加_下划线.

Cygwin GCC (like other 32-bit Windows Compilers) will prepend an _ to the non-static functions that will be of globally visible scope. That means your kmain is actually _kmain. Modify your boot.s to do call _kmain instead of call kmain. This applies to any C functions you call from assembly. Any functions you make available in assembly files to be accessed in C code will have to have an _ underscore added to them as well.

与Windows程序的一个大不同是节名可能有所不同. Cygwin中的rodata可以是.rdata*. rdata开头可能有许多部分.您必须在链接描述文件中对此进行说明:

One big difference with Windows program is that the section names may be a bit different. rodata in Cygwin can be .rdata*. There may be a number of sections starting with rdata. You will have to account for this in your linker script:

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部分,它们可能会放在Multiboot标头之前,可能会导致GRUB之类的Multiboot加载器看不到它.因此,此更改非常重要.

This makes a big difference given that if you don't properly deal with the rdata sections they may be placed before your Multiboot header possiblly causing it to be not seen by a Multiboot loader like GRUB. So this change is very important.

您用来构建兼容Multiboot的引导加载程序(或QEMU的-kernel选项)可用的文件的命令不正确.由于LD无法输出 ELF 文件,因此您需要具有OBJCOPY,才能将 PE32 可执行文件转换为32位的 ELF 可执行文件.您的OBJCOPY命令执行了错误的操作.您将转换为二进制文件.不幸的是,Multiboot标头的编写方式无效.

Your commands to build a file usable by a Multiboot compliant bootloader (or QEMU's -kernel option) is incorrect. Since LD can't output an ELF file you will need to have OBJCOPY to convert a PE32 executable to a 32-bit ELF executable. Your OBJCOPY command does the wrong thing. You convert to a binary file. Unfortunately the way your Multiboot header is written that doesn't work.

用于汇编和链接代码以及生成最终的kernel.elf文件(可由Multiboot加载程序使用)的命令如下所示:

The commands to assemble and link your code, and to produce the final kernel.elf file that can be use by a Multiboot loader could look like:

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


使用Grub和内核制作可引导的ISO/CD

此过程仅在Cygwin不带有grub-legacy软件包的情况下才有些棘手.要制作上面带有Grub的可启动ISO/CD映像,您需要获取文件stage2_eltorito.您可以从此项目下载副本.


Making a Bootable ISO/CD with Grub and your Kernel

This procedure is a bit tricky only in that Cygwin doesn't come with a grub-legacy package. To make a bootable ISO/CD image with Grub on it, you need to acquire the file stage2_eltorito. You can download a copy from this project.

您将必须运行Cygwin安装程序并安装软件包genisoimage

You will have to run the Cygwin installer and install the package genisoimage

在前面的部分中,我们构建了一个名为kernel.elf的文件.现在我们有了构建ISO/CD映像所需的组件.

In the previous sections we built a file called kernel.elf. Now we have the components needed to build an ISO/CD image.

在生成kernel.elf的目录中,我们需要创建一系列子目录.可以通过以下方式完成:

From the directory where you built kernel.elf we need to create a series of sub-directories. That can be done with:

mkdir -p iso/boot/grub

您需要复制stage2_eltorito文件并将其放置在iso/boot/grub目录中.您还需要在iso/boot/grub中创建文件menu.lst.

You need to copy the stage2_eltorito file and place it in iso/boot/grub directory. You will need to create the file menu.lst in iso/boot/grub as well.

iso/boot/grub/menu.lst :

default 0
timeout 0

title MyOS
# kernel ELF file.
kernel /boot/kernel.elf

上述过程仅需执行一次.这足以使用Grub和我们的内核创建基本的可启动ISO.

The process above only has to be done once. This is enough to create a basic bootable ISO with Grub and our kernel.

现在可以像下面这样完成构建内核,将文件复制到iso目录并生成ISO/CD映像的过程:

Now the process of building the kernel, copying the file into the iso directory and generating the ISO/CD image can be done like this:

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.您可以通过将genisoimage命令行上的myos.iso替换为您喜欢的名称来将其更改为任意名称.

The genisoimage command creates an ISO/CD called myos.iso . You can change the name to whatever you please by replacing myos.iso on the genisoimage command line with the name you prefer.

myos.iso应该可以从大多数硬件,虚拟机和仿真器作为简单的CD映像启动.当使用您的内核运行时,它应该显示为:

The myos.iso should be bootable from most hardware, virtual machines, and emulators as a simple CD image. When run with your kernel it should appear something like:

上面的图像是我使用以下命令在QEMU中引导ISO/CD时看到的:

The image above is what I saw when I booted the ISO/CD in QEMU with the command:

qemu-system-i386 -cdrom myos.iso

如果您也在VirtualBox中运行它,您应该会看到类似的内容.

You should see similar if you run it in VirtualBox as well.

这篇关于使用Cygwin gcc编译+链接自定义OS:无法识别的仿真模式:elf_i386的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆