无法访问自制内核链接器全局变量和内联字符串

时间:2013-09-08 18:46:29

标签: c linux linker kernel ld

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

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

生成文件:

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

1 个答案:

答案 0 :(得分:4)

如果查看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)之后所遇到的问题。具体来说,我构建了一个33 MB的虚拟磁盘,包含一个ext2分区,在其上安装了grub2(使用1.99-21ubuntu3.10)和上面的内核,并在qemu-kvm 1.0(1.0 + noroms-0ubuntu14)下成功运行了映像0.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

您可以开始使用自己的内核。

有问题吗?评论