Calling some function makes kernel unbootable

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
Mikoskay
Posts: 9
Joined: Sat Oct 24, 2009 7:29 am

Calling some function makes kernel unbootable

Post 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.
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Re: Calling some function makes kernel unbootable

Post 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.
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
Mikoskay
Posts: 9
Joined: Sat Oct 24, 2009 7:29 am

Re: Calling some function makes kernel unbootable

Post 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*)...
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Re: Calling some function makes kernel unbootable

Post 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
Mikoskay
Posts: 9
Joined: Sat Oct 24, 2009 7:29 am

Re: Calling some function makes kernel unbootable

Post 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?
User avatar
Creature
Member
Member
Posts: 548
Joined: Sat Dec 27, 2008 2:34 pm
Location: Belgium

Re: Calling some function makes kernel unbootable

Post 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.
When the chance of succeeding is 99%, there is still a 50% chance of that success happening.
Mikoskay
Posts: 9
Joined: Sat Oct 24, 2009 7:29 am

Re: Calling some function makes kernel unbootable

Post 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?
Kevin
Member
Member
Posts: 1071
Joined: Sun Feb 01, 2009 6:11 am
Location: Germany
Contact:

Re: Calling some function makes kernel unbootable

Post 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.
Developer of tyndur - community OS of Lowlevel (German)
Mikoskay
Posts: 9
Joined: Sat Oct 24, 2009 7:29 am

Re: Calling some function makes kernel unbootable

Post 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).
Post Reply