Kernel linker global variables and inline strings unavailable

I followed some tutorials on the Internet and created my own core. It successfully boots onto GRUB with QEMU. But I have the problem described in this SO question and I cannot solve it. I can describe this workaround, but I also need to use global variables, this will simplify the work, but I don’t understand what I need to change in the linker to use global variables and inline strings correctly.

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 .co: @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) } } 

What works:

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

What does not work:

1)

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

2)

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

Build output: (external link because it is a bit long) http://hastebin.com/gidebefuga.pl

+2
source share
1 answer

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

However, you do not specify any address information in the multiboot header structure ; You provide at least three fields. Instead, the bootloader will select a good address (1M on x86, i.e. 0x00100000, for both virtual and linear addresses) and load the code there.

You might think that such a mismatch should lead to the kernel not starting at all, but it just happens that the code generated above main.c does not use addresses for anything other than read-only constants. In particular, GCC generates transitions and calls that use relative addresses (signed offsets relative to the address of the next x86 instruction), so the code still works.

There are two solutions: the first is trivial.

Most x86 downloaders download the image with 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 with 0x00100000, i.e.

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

your core will work. I confirmed that this is a fix for the problem you are having after removing the extra void main(void) from your linker script, of course. To be specific, I created a 33 MB virtual disk containing one ext2 partition, grub2 installed on it (using 1.99-21ubuntu3.10) and the above kernel, and successfully launched the image under qemu-kvm 1.0 (1.0 + noroms-0ubuntu14 .eleven).

The second option is to set bit 16 in the multiboot flags and provide five additional words necessary to indicate to the loader where the code expects it to be resident. However, 0xC0100000 will not work - at least grub2 will just worry and reboot - while something like 0x00200000 works fine. This is because multiboot is really designed to use virtual == linear addresses, and there may be other things already present at the highest addresses (similar to why addresses below 1M are eliminated).

Note that the bootloader does not provide you with a stack, so it’s a little surprising that the code works at all.

I personally recommend you use a simple assembler file to create a 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 

compile with

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

and change your linker script to use start instead of main as an entry point,

 ENTRY(start) 

Remove multitasking from your main.c , then compile and link to kernel using, for example,

 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 working with your own kernel.

Questions? Comments?

+4
source

All Articles