How to pass parameters to Kernel using GRUB 0.97 menu.lst?
I'm working on an OS and I have to create a debug mode. In order to do this, I want to add an entry in menu.lst, pointing to the same kernel, but with an added argument.
In the GRUB manual, it's written that everything after kernel's address in the kernel command is passed verbatim to the kernel command line: https://ftp.gnu.org/old-gnu/Manuals/grub/html_node/kernel.html
So I did something like this in menu.lst:
title os-debug
root (fd0)
kernel /kernel 001
module /initrd.img
In the stack created by GRUB, the command-line is available at offset 16, as stated here: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Specification
So in my file boot.SI did like this to find my parameter in the stack:
movl 16(%ebx), %ecx
And... It doesn't work (I created a gdbserver in order to debug this specific boot file), but I'm sure I can access the stack correctly , because I'm accessing initrd like this:
movl 24(%ebx), %eax
I have also correctly defined my flags:
#define MBOOT_FLAGS (MBOOT_PAGE_ALIGN | MBOOT_MEMORY_INFO |
MBOOT_INFO_CMDLINE)'
Any idea how can i get a parameter to be passed from menu.lst to boot.S? Here's all the beginning of my boot.S file:
/* Multiboot flags. */
#define MBOOT_FLAGS (MBOOT_PAGE_ALIGN | MBOOT_MEMORY_INFO |
MBOOT_INFO_CMDLINE)
/* Exported symbols. */
.globl start
.globl idle_pgdir
.section .bootstrap
/*
* Grub multiboot header.
*/
.align 4
mboot_header:
.long MBOOT_MAGIC /* Magic number. */
.long MBOOT_FLAGS /* Flags. */
.long -(MBOOT_MAGIC + MBOOT_FLAGS) /* Checksum. */
.long mboot_header /* Pointer to this structure. */
/*
* Kernel entry point.
*/
start:
cmpl $1, 20(%ebx)
jne halt
/* Retrieve initrd location. */
movl 24(%ebx), %eax
movl (%eax), %eax
movl 16(%ebx), %ecx
pushl %ecx
after, the init RAM is built so I have to process with my stack before, but I'm not able to have my argument at this point considering my tests
My menu.lst:
timeout 10
title OS
root (fd0)
kernel /kernel
module /initrd.img
title OS-debug
root (fd0)
kernel /kernel 001
module /initrd.img
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 startn"
"start:nt"
/* Set stack pointer to end of stack variable.
Stack grows down. Align stack to 16 byte boundary */
"mov $stack + " STRINGIZE(STACK_SIZE) ", %espnt"
"and $-16, %espnt"
"cldnt" /* Ensure string instructions have forward movement */
"sub $8, %espnt"/* For alignment on call to kmain */
"push %eaxnt" /* Pass magicnum in EAX as 2nd parameter */
"push %ebxnt" /* Pass multiboot info struct in EBX as 1st parameter */
"call kmainnt" /* At this point stack 16 byte aligned, call kernel */
"add $16, %espnt"
/* Infinite loop to end */
"clin"
".L0:nt"
"hltnt"
"jmp .L0n"
);
/* 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
.
I'd like to answer my own question to explain why my model was not working. The model of Michael Petch works correctly, but in my implementation I had a different problem
The GRUB 0.97 documentation contains an error : https://ftp.gnu.org/old-gnu/Manuals/grub/html_node/kernel.html
The rest of the line is passed verbatim as the kernel command-line.
It's not "the rest of the line" but instead " the whole line ", in my case: kernel /kernel 001
So, I wasn't checking enough memory, and so I always had "rek" as an output, which was corresponding to the first 3 char. I resolved my issue with this assembly code:
/* Retrieve command-line passed by GRUB. */
movl $cmdline, %edi
movl 16(%ebx), %ecx
addl $MBOOT_KPARAM_OFFSET, %ecx
jmp bottom
top:
addl $4, %ecx
stosl
bottom:
movl (%ecx), %eax
cmpl $0,%eax
jne top
Where #define MBOOT_KPARAM_OFFSET 0x00000008
, which is corresponding to the offset needed to shift "kernel /kernel ". The rest of the code is then used to put a parameter in the memory, no matter of its size (using %edi and stosl)
上一篇: 如何从C中的串口打开,读取和写入