Page 1 of 1

Calling some function makes kernel unbootable

Posted: Sat Oct 24, 2009 7:59 am
by Mikoskay
I hope somebody could help with very strange problem I've ecountered (it seems stange to me, but it's quite possible that it's something very normal). I'm writing simple, 32-bit OS for x86, for educational purposes (so its source is damn readable:) ). Here you got some files that could be useful:

linker.ld

Code: Select all

OUTPUT_FORMAT("elf32-i386")
ENTRY(loader)
phys = 0x00100000;

SECTIONS
{
    .text phys : AT(phys) {
        code = .;
        *(.text)
        *(.rodata)
        . = ALIGN(4096);
    }

    .data : AT(phys + (data - code))
    {
        data = .;
        *(.data)
        . = ALIGN(4096);
    }

    .bss : AT(phys + (bss - code))
    {
        bss = .;
        *(.bss)
        . = ALIGN(4096);
    }

    end = .;
}
loader.asm

Code: Select all

[BITS 32]

global loader

mboot:

    ; Multiboot macros to make a few lines later more readable
    MULTIBOOT_PAGE_ALIGN    equ 1<<0
    MULTIBOOT_MEMORY_INFO   equ 1<<1
    MULTIBOOT_AOUT_KLUDGE   equ 1<<16
    MULTIBOOT_HEADER_MAGIC  equ 0x1BADB002
    MULTIBOOT_HEADER_FLAGS  equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_AOUT_KLUDGE
    MULTIBOOT_CHECKSUM      equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)

    ; This part MUST be 4byte aligned
    align 4

    ; Declared in linker.ld
    extern code, bss, end

    ; This is the GRUB Multiboot header. A boot signature
    dd MULTIBOOT_HEADER_MAGIC
    dd MULTIBOOT_HEADER_FLAGS
    dd MULTIBOOT_CHECKSUM

    dd mboot
    dd code
    dd bss
    dd end
    dd loader

loader:
    ; This points the stack to our new stack area
    mov esp, _system_stack

    push eax ; Multiboot magic number
    push ebx ; Multiboot info structure

    extern k_main
    call k_main

    hlt

; Remember that a stack actually grows
; downwards, so we declare the size of the data before declaring
; the identifier '_system_stack'
SECTION .bss

    resb 8192               ; This reserves 8KBytes of memory here

_system_stack:

include/kernel/terminal.h

Code: Select all

#ifndef _KERNEL_TERMINAL_H
#define _KERNEL_TERMINAL_H

extern const uint64_t VIDEO_RAM_ADDRESS;
extern const uint16_t TERMINAL_COLUMNS;
extern const uint16_t TERMINAL_LINES;

typedef struct terminal_cursor
{
    uint16_t x;
    uint16_t y;
} terminal_cursor_t;

extern void itoa (char *destination, const char base, const int32_t integer);
extern void putchar (const char ch);
extern void print_formatted (const char *format, ...);
extern void clear_screen ();
extern void init_terminal ();

#endif /* _KERNEL_TERMINAL_H */
kernel/terminal.c

Code: Select all

#include <kernel/types.h>
#include <kernel/terminal.h>

const uint64_t VIDEO_RAM_ADDRESS = 0xB8000;
const uint16_t TERMINAL_COLUMNS = 80;
const uint16_t TERMINAL_LINES = 25;

const uint8_t ATTRIBUTE = 0x07;

volatile uint8_t *video_ram;
static terminal_cursor_t cursor;

void
itoa (char *destination, const char base, const int32_t integer)
{
    char *buffer = destination;
    char *temp1, *temp2;
    uint32_t unsigned_integer = integer;
    uint16_t divisor = 10;

    /* If %d is specified and integer is minus, put `-' in the head. */
    if (base == 'd' && integer < 0) {
        *buffer++ = '-';
        destination++;
        unsigned_integer = -integer;
    } else if (base == 'x') {
        divisor = 16;
    }

    /* Divide UD by DIVISOR until UD == 0. */
    do {
        int remainder = unsigned_integer % divisor;
        *buffer++ = ((remainder < 10) ? remainder + '0' : remainder + 'a' - 10);
    } while (unsigned_integer /= divisor);

    /* Terminate BUF. */
    *buffer = 0;

    /* Reverse BUF. */
    temp1 = buffer;
    temp2 = buffer - 1;
    while (temp1 < temp2) {
        uint8_t temp = *temp1;
        *temp1 = *temp2;
        *temp2 = temp;
        temp1++;
        temp2--;
    }
}

void
put_character (const char ch)
{
    if (ch == '\n' || ch == '\r')
    {
        _NEWLINE:
            cursor.x = 0;
            cursor.y++;
            if (cursor.y >= TERMINAL_LINES)
                cursor.y = 0;
            return;
    }

    *(video_ram + (cursor.x + cursor.y * TERMINAL_COLUMNS) * 2) = ch & 0xFF;
    *(video_ram + (cursor.x + cursor.y * TERMINAL_COLUMNS) * 2 + 1) = ATTRIBUTE;

    cursor.x++;
    if (cursor.x >= TERMINAL_COLUMNS)
        goto _NEWLINE;
}

/* not important things... */

void
print_formatted (const char *format, ...)
{
    char **arguments = (char **) &format;
    char ch;
    char buffer[20];

    arguments++;

    while ((ch = *format++) != 0) {
        if (ch != '%') {
            put_character(ch);
        } else {
            char *next_ch;
            ch = *format++;
            switch (ch)
            {
                case 'd':
                case 'u':
                case 'x':
                    itoa(buffer, ch, *((int32_t *)arguments++));
                    next_ch = buffer;
                    goto _STRING;
                    break;
                case 's':
                    next_ch = *arguments++;
                    if (!next_ch)
                        next_ch = "(null)";
                    _STRING:
                        while (*next_ch)
                            put_character(*next_ch++);
                    break;
                default:
                    put_character(*((char *)arguments++));
                    break;
            }
        }
    }
}

void
init_terminal ()
{
    video_ram = (volatile uint8_t *)VIDEO_RAM_ADDRESS;
    clear_screen();
    print_formatted("Hello World!\n");
    print_formatted("2 + 2 = %d!\n", 2 + 2);
    print_formatted("...which is in hexadecimal... %x!\n", 2 + 2);
}
and finally... main.c

Code: Select all

#include <kernel/types.h>
#include <kernel/terminal.h>

void
k_main (uint64_t magic, uint64_t *multiboot_informations)
{
    init_terminal();
    put_character('a');              // Just fine

    /* HERE'S THE PROBLEM */
        print_formatted("Anything");
    /* / HERE'S THE PROBLEM */

    while (0==0)
        /* nothing */;
}
I compile loader.asm with nasm -f elf and C files with gcc -Wall -Wextra -O -fstrength-reduce -fomit-frame-pointer -finline-functions -nostdinc -fno-builtin -fno-leading-underscore.

The problem is, that using print_formatted() function in k_main[] makes kernel image not multiboot-compatible...

Without print_formatted() calling:

Code: Select all

mikoskay@mikoskay:~/Projects/os$ mbchk bin/kernel.bin
bin/kernel.bin: The Multiboot header is found at the offset 4736.
bin/kernel.bin: Page alignment is turned on.
bin/kernel.bin: Memory information is turned on.
bin/kernel.bin: Address fields is turned on.
bin/kernel.bin: All checks passed.
mikoskay@mikoskay:~/Projects/os$ objdump -h bin/kernel.bin                                                                                         

bin/kernel.bin:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00001000  00100000  00100000  00001000  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rodata.str1.1 00000022  00101000  00101000  00002000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .rodata.str1.4 00000024  00101024  00101024  00002024  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .data         00000fb8  00101048  00101048  00002048  2**3
                  CONTENTS, ALLOC, LOAD, DATA
  4 .bss          00003004  00102000  00102000  00003000  2**3
                  ALLOC
  5 .comment      0000001c  00000000  00000000  00003000  2**0
                  CONTENTS, READONLY
And with:

Code: Select all

mikoskay@mikoskay:~/Projects/os$ mbchk bin/kernel.bin
bin/kernel.bin: No Multiboot header.
mikoskay@mikoskay:~/Projects/os$ objdump -h bin/kernel.bin 

bin/kernel.bin:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .rodata.str1.1 0000002b  00000000  00000000  00001000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .rodata.str1.4 00000024  0000002c  0000002c  0000102c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .text         00001000  00100000  00100000  00002000  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  3 .bss          00003004  00101000  00101000  00003000  2**3
                  ALLOC
  4 .comment      0000001c  00000000  00000000  00003000  2**0
                  CONTENTS, READONLY
I see there's something wrong with .data section apperance and generally sections mixing, but I don't understand what all of this mean.

PS. Non-related comments and advices are also welcome.

Re: Calling some function makes kernel unbootable

Posted: Sat Oct 24, 2009 10:05 am
by Creature
Mikoskay wrote: extern const uint64_t VIDEO_RAM_ADDRESS;
Why are you trying to define a 64-bit integer when you're obviously in 32-bit mode? I'm not sure what uint64_t is, but it looks to me as if it is supposed to be an "unsigned long long" and I don't think you can use that type on GCC in 32-bits OSDev (at least not without additional setup). "unsigned long" isn't 64-bits when you're not booting a 64-bits kernel. Anyhow, if it's just a typedef of a 32-bit integer, there shouldn't be a problem.
Mikoskay wrote: *(.rodata)
I also think you're supposed to put an asterisk behind "rodata" to indicate the linker has to append all symbols starting with "rodata" here. So I guess this would need to be:
Mikoskay wrote: *(.rodata*)
I think this goes for the other symbols as well. Please do correct me if I'm wrong.

Re: Calling some function makes kernel unbootable

Posted: Sat Oct 24, 2009 2:48 pm
by Mikoskay
Creature wrote:
Mikoskay wrote: extern const uint64_t VIDEO_RAM_ADDRESS;
Why are you trying to define a 64-bit integer when you're obviously in 32-bit mode? I'm not sure what uint64_t is, but it looks to me as if it is supposed to be an "unsigned long long" and I don't think you can use that type on GCC in 32-bits OSDev (at least not without additional setup). "unsigned long" isn't 64-bits when you're not booting a 64-bits kernel. Anyhow, if it's just a typedef of a 32-bit integer, there shouldn't be a problem.
My integer types definitions are the same as in C99 standard header stdint.h. But IMHO size of variable doesn't really matter, while variable is constant - compiler just uses type to check some of its strongly typed restrictions. Correct me if I'm wrong.
Creature wrote:
Mikoskay wrote: *(.rodata)
I also think you're supposed to put an asterisk behind "rodata" to indicate the linker has to append all symbols starting with "rodata" here. So I guess this would need to be:
Mikoskay wrote: *(.rodata*)
I think this goes for the other symbols as well. Please do correct me if I'm wrong.
And here you are perfectly correct! Main problem is solved, thanks to you. But I have to confess - I don't really understand how linker does things and what is the meaning of every line of my linker script. I've just copied some other's solutions and made some changes intuitively. Could you recommend some article/tutorial/manual which describes linking (especially of kernels) little bit more exhaustively?

edit

No, seriously. I've read ld manual and I've not found anything explaining *(something) -> *(something*)...

Re: Calling some function makes kernel unbootable

Posted: Sat Oct 24, 2009 8:30 pm
by pcmattman
No, seriously. I've read ld manual and I've not found anything explaining *(something) -> *(something*)...
The asterisk is just a wildcard, so ".rodata*" will match:

Code: Select all

.rodata
.rodata.hello 
.rodata.randomstring 
.rodata.really.bizarre.section.name

Re: Calling some function makes kernel unbootable

Posted: Sat Oct 24, 2009 11:36 pm
by Mikoskay
pcmattman wrote:
No, seriously. I've read ld manual and I've not found anything explaining *(something) -> *(something*)...
The asterisk is just a wildcard, so ".rodata*" will match:

Code: Select all

.rodata
.rodata.hello 
.rodata.randomstring 
.rodata.really.bizarre.section.name
Ok. If I understood the manual correctly .rodata sections stores values hardcoded by GCC, so where i write eg. a string literal, or int i = 17; linker stores it as .rodata.foo = "my literal" and .rodata.bar = 17. So, why in all the kernel tutorials (eg. http://wiki.osdev.org/Bare_bones or http://osdever.net/bkerndev/) there's no second asterisk? What it depends on?

Re: Calling some function makes kernel unbootable

Posted: Sun Oct 25, 2009 3:01 am
by Creature
I think it's either one of the following options:
  • They forgot...
  • The linker apparently bundles them into the right sections (.text.foo gets put into .text automatically and ld will put everything it found into ".text"), but that sounds pretty strange to me.
I remember that I had problems because I didn't do that too, I turned it on a long time after I started and it instantaneously fixed a couple of bugs.

Re: Calling some function makes kernel unbootable

Posted: Sun Oct 25, 2009 1:31 pm
by Mikoskay
Ok. So, according to Linux x86 setup linker script (http://lxr.linux.no/#linux+v2.6.31/arch ... t/setup.ld) second aterisk should be only in .data and .rodata sections (I guess if I want C++ support, second asterisk is required for global constructors and destructors sections). Is that right?

Re: Calling some function makes kernel unbootable

Posted: Sun Oct 25, 2009 3:28 pm
by Kevin
It's still not right. It might work, but only by chance. You need to take care that the multiboot header is in the first 8k of the binary. To be sure that it is you should put it in its own section and include this section as the very first thing in your linker script.

Re: Calling some function makes kernel unbootable

Posted: Sun Oct 25, 2009 4:45 pm
by Mikoskay
Kevin wrote:It's still not right. It might work, but only by chance. You need to take care that the multiboot header is in the first 8k of the binary. To be sure that it is you should put it in its own section and include this section as the very first thing in your linker script.
Something like this? It seems strange to me...

Code: Select all


multiboot_section_start = 0x00100000;
multiboot_section_end = 0x00102000;

(...)

    .text : {
        . = multiboot_section_start;
        mboot = .;
        . = multiboot_section_end;
        code = .;
        *(.text)
        *(.rodata)
        . = ALIGN(4096);
    }

(...)
edit

BTW, I've just discovered that without second asterisk in text section of binary (*(.text*)) C++ constants don't work (at least with GCC).