You don't present a minimal complete verifiable example. I had some code on the shelf that I hadn't placed onto Stackoverflow previously. The following is simply a C file with a multiboot header and the entry point for a kernel that could be used as a base to test out your code. It relies on the multiboot info structure being passed as a parameter to kmain
(originally via EBX from the bootloader).
The code uses the defines in the GRUB Legacy header. If it isn't installed on your system you can find a copy on the GNU site. A basic linker script is also presented.
When run it should clear the screen and print out the command line that was passed to the kernel and the command line passed to each of the modules.
kernel.c
#include <multiboot.h>
#include <stdint.h>
/* STRINGIZE is a C macro that allow us to convert an integer to a string
* for use by the C pre-processor */
#define STRINGIZE_INTERNAL(x) #x
#define STRINGIZE(x) STRINGIZE_INTERNAL(x)
/* 32k stack */
#define STACK_SIZE 32768
/* Define the multiboot structure that will be detectable by the multiboot
* loader. Request the loader to provide us a memory information */
#define MULTIBOOT_FLAGS (MULTIBOOT_MEMORY_INFO | MULTIBOOT_PAGE_ALIGN)
struct multiboot_header mb_header
__attribute__ ((aligned (4), section(".multiboot"))) = {
.magic = MULTIBOOT_HEADER_MAGIC,
.flags = MULTIBOOT_FLAGS,
.checksum = -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_FLAGS)
};
/* Allocate space for a stack */
uint8_t stack[STACK_SIZE];
/* Entry point set in linker script that the mulitboot loader will transfer control to */
extern void start(void);
__asm__ (".global start\n"
"start:\n\t"
/* Set stack pointer to end of stack variable.
Stack grows down. Align stack to 16 byte boundary */
"mov $stack + " STRINGIZE(STACK_SIZE) ", %esp\n\t"
"and $-16, %esp\n\t"
"cld\n\t" /* Ensure string instructions have forward movement */
"sub $8, %esp\n\t"/* For alignment on call to kmain */
"push %eax\n\t" /* Pass magicnum in EAX as 2nd parameter */
"push %ebx\n\t" /* Pass multiboot info struct in EBX as 1st parameter */
"call kmain\n\t" /* At this point stack 16 byte aligned, call kernel */
"add $16, %esp\n\t"
/* Infinite loop to end */
"cli\n"
".L0:\n\t"
"hlt\n\t"
"jmp .L0\n"
);
/* Text mode video pointer */
volatile uint16_t *const video_memory = (uint16_t *)0xb8000;
#define VID_TEXT_COLUMNS 80
#define VID_TEXT_ROWS 25
void clear_screen_attr (uint8_t attr)
{
uint16_t curpos = 0;
while (curpos < VID_TEXT_COLUMNS * VID_TEXT_ROWS)
video_memory[curpos++] = attr << 8 | ' ';
}
void print_string_xyattr (const char *str, uint16_t x, uint16_t y, uint8_t attr)
{
uint16_t curpos = (x + y * VID_TEXT_COLUMNS);
while (*str)
video_memory[curpos++] = attr << 8 | *str++;
}
/* kmain is main C entry point */
void kmain(multiboot_info_t *mb_info, uint32_t magicnum)
{
uint16_t curline = 0;
multiboot_module_t *mb_modules;
uint16_t modindex;
clear_screen_attr (0x07);
/* Verify we were booted from multiboot loader and print MB to the display */
if (magicnum == MULTIBOOT_BOOTLOADER_MAGIC) {
print_string_xyattr ("Multiboot Magic found", 0, curline++, 0x07);
print_string_xyattr ("Command line: ", 0, curline, 0x07);
print_string_xyattr ((const char *)mb_info->cmdline, 14, curline++, 0x57);
/* For each module print out the command line arguments */
mb_modules = (multiboot_module_t *)mb_info->mods_addr;
for (modindex = 0; modindex < mb_info->mods_count; modindex++) {
print_string_xyattr ("Module Cmd line:", 0, curline, 0x07);
print_string_xyattr ((const char *)mb_modules[modindex].cmdline,
17, curline++, 0x57);
}
}
else
print_string_xyattr ("Multiboot Magic not found", 0, curline++, 0x07);
}
linker.ld
:
OUTPUT_FORMAT("elf32-i386")
ENTRY(start)
SECTIONS
{
. = 1M;
.text : {
*(.multiboot)
*(.text)
}
.rodata : {
*(.rodata)
}
.data : {
*(.data)
}
.bss : {
*(COMMON)
*(.bss)
}
}
You can compile and link these files to a final ELF executable called kernel.elf
with commands like:
i686-elf-gcc -c -m32 -std=gnu99 -ffreestanding -nostdlib -O3 -Wall -Wextra \
-g3 -I/usr/include/multiboot -o kernel.o kernel.c
i686-elf-gcc -m32 -Wl,--build-id=none -T linker.ld -ffreestanding -nostdlib \
-lgcc -o kernel.elf kernel.o
This assumes you are using a cross compiler. You may be able to get away with using just gcc
(instead of i686-elf-gcc
) in your host environment although I personally don't recommend it.
Debugging
You can build up an ISO with GRUB using kernel.elf
. If you create an ISO called myos.iso
then you can use QEMU and GDB to debug the code with something like:
qemu-system-i386 -cdrom myos.iso -d int -no-reboot -no-shutdown -S -s &
gdb kernel.elf \
-ex 'target remote localhost:1234' \
-ex 'break *kmain' \
-ex 'continue'
The -no-reboot -no-shutdown -d int
options are useful if you are debugging faults and interrupts. This first launches QEMU with a GDB stub and then GDB is used to debug the QEMU session. We pass the kernel.elf
file to the debugger so we can use symbolic debugging.
When stopped at kmain
(the C entry point in the code) you can actually view the entire mb_info
structure (in hex) with a command like:
p/x *mb_info
You'd get output that may look similar to this:
$1 = {flags = 0x1a6f, mem_lower = 0x27f, mem_upper = 0x1fb80, boot_device = 0xe0ffffff,
cmdline = 0x10078, mods_count = 0x2, mods_addr = 0x100ac, u = {aout_sym = {tabsize = 0x12,
strsize = 0x28, addr = 0x10164, reserved = 0xf}, elf_sec = {num = 0x12, size = 0x28,
addr = 0x10164, shndx = 0xf}}, mmap_length = 0x90, mmap_addr = 0x100d4,
drives_length = 0x0, drives_addr = 0x0, config_table = 0x0, boot_loader_name = 0x1007c,
apm_table = 0x0, vbe_control_info = 0x10434, vbe_mode_info = 0x10634, vbe_mode = 0x3,
vbe_interface_seg = 0xffff, vbe_interface_off = 0x6000, vbe_interface_len = 0x4f,
framebuffer_addr = 0xb8000, framebuffer_pitch = 0xa0, framebuffer_width = 0x50,
framebuffer_height = 0x19, framebuffer_bpp = 0x10, framebuffer_type = 0x2, {{
framebuffer_palette_addr = 0x0, framebuffer_palette_num_colors = 0x0}, {
framebuffer_red_field_position = 0x0, framebuffer_red_mask_size = 0x0,
framebuffer_green_field_position = 0x0, framebuffer_green_mask_size = 0x0,
framebuffer_blue_field_position = 0x0, framebuffer_blue_mask_size = 0x0}}}
If you were to use the command p (char *)mb_info->cmdline
you can get the debugger to print the command line parameter as a string for you.
A screenshot of QEMU when this code is run:

In my GRUB configuration I had placed 000
as the command line parameter to the kernel. I added a couple of modules with command line parameters of 001
and 002
.