2

我在网上遵循了一些教程并创建了自己的内核。它正在使用 QEMU 在 GRUB 上成功启动。但是我有这个 SO question中描述的问题,我无法解决它。我可以描述该解决方法,但我还需要使用全局变量,这会使工作更容易,但我不明白我应该在链接器中更改什么以正确使用全局变量和内联字符串。

主程序

struct grub_signature {
    unsigned int magic;
    unsigned int flags;
    unsigned int checksum;
};

#define GRUB_MAGIC 0x1BADB002
#define GRUB_FLAGS 0x0
#define GRUB_CHECKSUM (-1 * (GRUB_MAGIC + GRUB_FLAGS))

struct grub_signature gs __attribute__ ((section (".grub_sig"))) =
    { GRUB_MAGIC, GRUB_FLAGS, GRUB_CHECKSUM };


void putc(unsigned int pos, char c){
    char* video = (char*)0xB8000;
    video[2 * pos ] = c;
    video[2 * pos + 1] = 0x3F;
}

void puts(char* str){
    int i = 0;
    while(*str){        
        putc(i++, *(str++));
    }
}

void main (void)
{
    char txt[] = "MyOS";
    puts("where is this text"); // does not work, puts(txt) works.
    while(1){};
}

生成文件:

CC = gcc
LD = ld

CFLAGS = -Wall -nostdlib -ffreestanding -m32 -g
LDFLAGS = -T linker.ld -nostdlib -n -melf_i386

SRC = main.c
OBJ = ${SRC:.c=.o}

all: kernel

.c.o:
    @echo CC $<
    @${CC} -c ${CFLAGS} $<

kernel: ${OBJ} linker.ld
    @echo CC -c -o $@
    @${LD} ${LDFLAGS} -o kernel ${OBJ}

clean:
    @echo cleaning
    @rm -f ${OBJ} kernel

.PHONY: all

链接器.ld

OUTPUT_FORMAT("elf32-i386")
ENTRY(main)
SECTIONS
{
    .grub_sig 0xC0100000 : AT(0x100000)
    {
        *(.grub_sig)
    }
    .text :
    {
        *(.text)
    }
    .data :
    {
        *(.data)void main (void)
    }
    .bss :
    {
        *(.bss)
    }
    /DISCARD/ :
    {
        *(.comment)
        *(.eh_frame)
    }
}

什么有效:

void main (void)
{
char txt[] = "MyOS";
puts(txt);
while(1) {}
}

什么不起作用:

1)

char txt[] = "MyOS";
void main (void)
{
    puts(txt);
    while(1) {}
}

2)

void main (void)
{
    puts("MyOS");
    while(1) {}
}

汇编输出:(外部链接,因为有点长)http://hastebin.com/gidebefuga.pl

4

1 回答 1

4

如果查看objdump -h输出,您会发现虚拟地址和线性地址对于任何部分都不匹配。如果查看objdump -d输出,您会看到地址都在 0xC0100000 范围内。

但是,您没有在多重引导标头结构中提供任何寻址信息;您只提供最少三个字段。相反,引导加载程序将选择一个好的地址(x86 上的 1M,即 0x00100000,用于虚拟地址和线性地址),并在那里加载代码。

有人可能会认为这种差异应该导致内核根本无法运行,但碰巧上面生成的代码main.c除了只读常量之外没有使用任何地址。特别是,GCC 生成使用相对地址(相对于 x86 上下一条指令的地址的有符号偏移)的跳转和调用,因此代码仍然可以运行。

有两种解决方案,第一种是微不足道的。

x86 上的大多数引导加载程序在允许的最小虚拟和线性地址 1M (= 0x00100000 = 1048576) 处加载映像。因此,如果您告诉链接描述文件使用从 0x00100000 开始的虚拟地址和线性地址,即

  .grub_sig 0x00100000 : AT(0x100000)
  {
      *(.grub_sig)
  }

您的内核将正常工作。void main(void)当然,在从链接描述文件中删除额外内容后,我已经验证这可以解决您遇到的问题。具体来说,我构建了一个33MB的虚拟磁盘,包含一个ext2分区,上面安装了grub2(使用1.99-21ubuntu3.10)和上面的内核,在qemu-kvm 1.0(1.0+noroms-0ubuntu14)下成功运行镜像.11)。

第二个选项是设置多引导标志中的第 16 位,并提供五个额外的字来告诉引导加载程序代码预期驻留的位置。然而, 0xC0100000 将不起作用——至少 grub2 会吓坏并重新启动——而像 0x00200000 这样的东西却可以正常工作。这是因为多重引导实际上是为使用虚拟 == 线性地址而设计的,并且可能在最高地址处已经存在其他东西(类似于为什么要避免低于 1M 的地址)。

请注意,引导加载程序不为您提供堆栈,因此代码完全正常工作有点令人惊讶。

我个人建议你使用一个简单的汇编文件来构造签名,并保留一些堆栈空间。例如,从这里start.asm简化,

BITS 32
EXTERN main
GLOBAL start

SECTION .grub_sig
signature:
    MAGIC equ 0x1BADB002
    FLAGS equ 0
    dd MAGIC, FLAGS, -(MAGIC+FLAGS)

SECTION .text
start:
    mov esp, _sys_stack     ; End of stack area
    call main
    jmp $                   ; Infinite loop

SECTION .bss
    resb 16384              ; reserve 16384 bytes for stack
_sys_stack:                 ; end of stack

编译使用

nasm -f elf start.asm -o start.o

并修改您的链接器脚本以使用start而不是main作为入口点,

ENTRY(start)

从你的中删除多重引导的东西main.c,然后编译并链接到kernel使用例如

gcc -Wall -nostdlib -ffreestanding -fno-stack-protector -O3 -fomit-frame-pointer -m32 -c main.c -o main.o
ld -T linker.ld -nostdlib -n -melf_i386 start.o main.o -o kernel

并且你有一个很好的开始在你自己的内核上工作。

问题?注释?

于 2013-09-15T17:28:58.047 回答