【问题标题】:Homemade Kernel linker global variables and inline Strings cannot be accessed无法访问自制内核链接器全局变量和内联字符串
【发布时间】:2013-09-08 18:46:29
【问题描述】:

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

ma​​in.c

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

linker.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

【问题讨论】:

  • 你能发布编译器生成的程序集吗? (gcc main.c -S,然后发布 main.s)。另外,我会尝试将-fpic 添加到 CFLAGS,即使如果您的内核加载到您认为的位置,则不需要它。
  • @ughoavgfhw 我添加了main.s-fpic 解决了内联声明,但全局声明仍然错误。谢谢。
  • 我已经有一段时间没有做任何自制操作系统黑客了,但我认为您需要在链接器脚本中添加一个rodata部分。
  • thedaver64 的建议可能有效。如果不是,您发布的程序集是哪个版本?我们需要看看那个不工作的。
  • 这是不工作的,我尝试使用xv6的链接器脚本,但仍然失败。 Xv6's linker script

标签: c linux linker kernel ld


【解决方案1】:

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

但是,您没有在multiboot header structure 中提供任何地址信息;您只提供最少三个字段。相反,引导加载程序会选择一个好的地址(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 简化为 here

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

您在自己的内核上工作有了一个良好的开端。

问题?评论?

【讨论】:

  • 感谢您的详细解答!但是,我仍然遇到问题,它显示错误:不支持的标志:grub 2 下的 0xe4524ffe 并拒绝启动。我也用 GRUB2 制作了一个简单的 50MB ext2 文件系统,为了方便起见,我将它上传到github:mustafaakin/myos。当我再次将引导签名移动到 main.c 时,它可以工作,并且解决了全局变量问题。那么,到底发生了什么?
  • @Mustafa:程序集中缺少逗号(FLAGS-(MAGIC+FLAGS) 之间),导致只生成两个(而不是三个)单词。您看到的错误是引导加载程序找不到有效的多重引导标头的结果。现已修复。
  • 哦,我明白了,dd 就是这样工作的,现在运行完美,现在用更改更新 repo。我可以在 10 小时内提供赏金,如果我忘记了,请通知我 :) 非常感谢,你让我明白了很多事情。
猜你喜欢
  • 1970-01-01
  • 2011-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-10
相关资源
最近更新 更多