自制内核链接的全局变量和内联字符串无法访问 [英] Homemade Kernel linker global variables and inline Strings cannot be accessed

查看:281
本文介绍了自制内核链接的全局变量和内联字符串无法访问的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我按照网上一些教程,并创建了自己的内核。它引导与QEMU成功地GRUB。但是我对这太问题,我不能解决这个问题。我可以有一个解决方法的描述,但我还需要使用全局变量,它会使工作更容易,但我不明白我应该链接正确使用全局变量和内联串改。

I have followed some tutorials on the web and created my own kernel. It is booting on GRUB with QEMU succesfully. But I have the problem described in this SO question, and I cannot solve it. I can have that workaround described, but I also need to use global variables, it would make the job easier, but I do not understand what should I change in linker to properly use global variables and inline strings.

的main.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){};
}

的Makefile:

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特等

推荐答案

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

If you look at objdump -h output, you'll see that virtual and linear addresses do not match for any of the sections. If you look at objdump -d output, you'll see that the addresses are all in the 0xC0100000 range.

但是,你没有提供的的多重头结构;你只提供最小的三个字段。相反,引导装载程序将挑好的地址(1M在x86,即0x00100000,虚拟和线性地址),并加载code那里。

However, you do not provide any addressing information in the multiboot header structure; you only provide the minimum three fields. Instead, the boot loader will pick a good address (1M on x86, i.e. 0x00100000, for both virtual and linear addresses), and load the code there.

有人可能会认为那种差异应引起内核根本无法运行,但它只是碰巧上述的main.c 不使用地址除只读常量任何东西。特别是,GCC产生的跳跃和使用(相对于x86上的下一条指令的地址签订偏移)相对地址调用,所以code仍然运行。

One might think that that kind of discrepancy should cause the kernel to not run at all, but it just happens that the code generated by the above main.c does not use the addresses for anything except read-only constants. In particular, GCC generates jumps and calls that use relative addresses (signed offsets relative to the address of the next instruction on x86), so the code still runs.

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

There are two solutions, first one trivial.

在x86上的大多数引导程序加载在最小允许虚拟与线性地址的形象,1M(= 0x00100000 = 1048576)。因此,如果你告诉你的链接脚本使用虚拟和线性地址开始0x00100000,即

Most bootloaders on x86 load the image at the smallest allowed virtual and linear address, 1M (= 0x00100000 = 1048576). Therefore, if you tell your linker script to use both virtual and linear addresses starting at 0x00100000, i.e.

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

您的内核将只是工作。我已经验证了这个解决您遇到的问题,删除多余的无效的主要(无效)后从你的链接脚本,当然。具体而言,我构建了一个33 MB的虚拟磁盘,包含一个ext2分区,在其上安装GRUB2(使用1.99-21ubuntu3.10)及以上的内核,并在QEMU-KVM 1.0(1.0 + noroms-0ubuntu14成功运行的图像0.11)。

your kernel will Just Work. I have verified this fixes the issue you are having, after removing the extra void main(void) from your linker script, of course. To be specific, I constructed an 33 MB virtual disk, containing one ext2 partition, installed grub2 on it (using 1.99-21ubuntu3.10) and the above kernel, and ran the image successfully under qemu-kvm 1.0 (1.0+noroms-0ubuntu14.11).

第二个选项是设置在多重标志的第16位,并提供必要的另外五个的话,告诉那里的code预计将驻留在引导程序。然而,0xC0100000将无法正常工作 - 至少GRUB2只会吓坏了并重新启动 - ,而像0x00200000确实做工精细。这是因为多重真的是设计为使用虚拟==线性地址,并有可能成为其他东西了美元,最高地址p $ psent(类似于为什么下面1M地址避免)。

The second option is to set the bit 16 in the multiboot flags, and supply the five additional words necessary to tell the bootloader where the code expects to be resident. However, 0xC0100000 will not work -- at least grub2 will just freak out and reboot --, whereas something like 0x00200000 does work fine. This is because multiboot is really designed to use virtual == linear addresses, and there may be other stuff already present at the highest addresses (similar to why addresses below 1M is avoided).

注意引导加载程序不会为您提供一个堆栈,所以这是一个有点意外的code工作在所有。

Note that the boot loader does not provide you with a stack, so it's a bit of a surprise the code works at all.

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

I personally recommend you use a simple assembler file to construct the signature, and reserve some stack space. For example, start.asm simplified from 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

和修改链接脚本中使用启动而不是为切入点,

and modify your linker script to use start instead of main as the entry point,

ENTRY(start)

的main.c ,然后编译和链接内核使用如

Remove the multiboot stuff from your main.c, then compile and link to kernel using e.g.

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

和你有一个良好的开端在自己的核心工作。

and you have a good start to work on your own kernel.

问题吗?评论?

这篇关于自制内核链接的全局变量和内联字符串无法访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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