如何使用 GRUB 0.97 menu.lst 将参数传递给内核?
How to pass parameters to Kernel using GRUB 0.97 menu.lst?
我正在开发 OS,我必须创建一个调试模式。为此,我想在 menu.lst 中添加一个条目,指向同一个内核,但要添加一个参数。
在GRUB手册中,内核命令中内核地址之后的所有内容都被逐字传递给内核命令行:
https://ftp.gnu.org/old-gnu/Manuals/grub/html_node/kernel.html
所以我在 menu.lst 中做了类似的事情:
title os-debug
root (fd0)
kernel /kernel 001
module /initrd.img
在 GRUB 创建的堆栈中,命令行位于偏移量 16 处,如下所述:https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Specification
所以在我的文件中 boot.S 我确实喜欢这样在堆栈中找到我的参数:
movl 16(%ebx), %ecx
而且...它不起作用(我创建了一个 gdbserver 以调试这个特定的引导文件),但我确定我可以正确访问堆栈,因为我正在像这样访问 initrd:
movl 24(%ebx), %eax
我也正确定义了我的标志:
#define MBOOT_FLAGS (MBOOT_PAGE_ALIGN | MBOOT_MEMORY_INFO |
MBOOT_INFO_CMDLINE)'
知道如何获取要从 menu.lst 传递到 boot.S 的参数吗?
这是我的 boot.S 文件的全部开头:
/* 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 , 20(%ebx)
jne halt
/* Retrieve initrd location. */
movl 24(%ebx), %eax
movl (%eax), %eax
movl 16(%ebx), %ecx
pushl %ecx
之后,初始化 RAM 已构建,因此我之前必须处理我的堆栈,但考虑到我的测试,此时我无法提出我的论点
我的menu.lst:
timeout 10
title OS
root (fd0)
kernel /kernel
module /initrd.img
title OS-debug
root (fd0)
kernel /kernel 001
module /initrd.img
您没有提供最小的完整可验证示例。我在架子上有一些我以前没有放在 Whosebug 上的代码。以下只是一个 C 文件,带有多重启动 header 和内核的入口点,可以用作测试代码的基础。它依赖于作为参数传递给 kmain
的多重启动信息结构(最初通过 EBX 来自引导加载程序)。
该代码使用 GRUB Legacy header 中的定义。如果您的系统上没有安装它,您可以找到 copy on the GNU site。还提供了基本的 linker 脚本。
当 运行 它应该清除屏幕并打印出传递给内核的命令行和传递给每个模块的命令行。
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 , %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 , %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)
}
}
您可以将这些文件编译并 link 为名为 kernel.elf
的最终 ELF 可执行文件,使用如下命令:
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
这假定您使用的是交叉编译器。尽管我个人不推荐,但您可以在主机环境中仅使用 gcc
(而不是 i686-elf-gcc
)。
调试
您可以使用 kernel.elf
使用 GRUB 构建 ISO。如果您创建一个名为 myos.iso
的 ISO,那么您可以使用 QEMU 和 GDB 来调试代码,例如:
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'
如果您正在调试故障和中断,-no-reboot -no-shutdown -d int
选项很有用。这首先使用 GDB 存根启动 QEMU 然后 GDB 用于调试 QEMUsession。我们将 kernel.elf
文件传递给调试器,以便我们可以使用符号调试。
当停在 kmain
(代码中的 C 入口点)时,您实际上可以查看整个 mb_info
结构(十六进制)命令如:
p/x *mb_info
你会得到类似这样的输出:
= {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}}}
如果您要使用命令 p (char *)mb_info->cmdline
,您可以让调试器为您打印命令行参数作为字符串。
QEMU 代码为运行:
时的截图
在我的 GRUB 配置中,我将 000
作为内核的命令行参数。我添加了几个模块,命令行参数为 001
和 002
.
我想回答我自己的问题来解释为什么我的模型不起作用。
Michael Petch 的模型工作正常,但在我的实现中我遇到了不同的问题
GRUB 0.97 文档包含错误:
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.
不是"the rest of the line"而是“整行”,在我的例子中:
kernel /kernel 001
所以,我没有检查足够的内存,所以我总是有 "rek" 作为输出,它对应于前 3 个字符。我用这个汇编代码解决了我的问题:
/* Retrieve command-line passed by GRUB. */
movl $cmdline, %edi
movl 16(%ebx), %ecx
addl $MBOOT_KPARAM_OFFSET, %ecx
jmp bottom
top:
addl , %ecx
stosl
bottom:
movl (%ecx), %eax
cmpl [=10=],%eax
jne top
其中#define MBOOT_KPARAM_OFFSET 0x00000008
,对应需要移位的偏移量"kernel /kernel "。
其余代码然后用于将参数放入内存,无论其大小如何(使用 %edi 和 stosl)
我正在开发 OS,我必须创建一个调试模式。为此,我想在 menu.lst 中添加一个条目,指向同一个内核,但要添加一个参数。
在GRUB手册中,内核命令中内核地址之后的所有内容都被逐字传递给内核命令行: https://ftp.gnu.org/old-gnu/Manuals/grub/html_node/kernel.html
所以我在 menu.lst 中做了类似的事情:
title os-debug
root (fd0)
kernel /kernel 001
module /initrd.img
在 GRUB 创建的堆栈中,命令行位于偏移量 16 处,如下所述:https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Specification
所以在我的文件中 boot.S 我确实喜欢这样在堆栈中找到我的参数:
movl 16(%ebx), %ecx
而且...它不起作用(我创建了一个 gdbserver 以调试这个特定的引导文件),但我确定我可以正确访问堆栈,因为我正在像这样访问 initrd:
movl 24(%ebx), %eax
我也正确定义了我的标志:
#define MBOOT_FLAGS (MBOOT_PAGE_ALIGN | MBOOT_MEMORY_INFO |
MBOOT_INFO_CMDLINE)'
知道如何获取要从 menu.lst 传递到 boot.S 的参数吗? 这是我的 boot.S 文件的全部开头:
/* 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 , 20(%ebx)
jne halt
/* Retrieve initrd location. */
movl 24(%ebx), %eax
movl (%eax), %eax
movl 16(%ebx), %ecx
pushl %ecx
之后,初始化 RAM 已构建,因此我之前必须处理我的堆栈,但考虑到我的测试,此时我无法提出我的论点
我的menu.lst:
timeout 10
title OS
root (fd0)
kernel /kernel
module /initrd.img
title OS-debug
root (fd0)
kernel /kernel 001
module /initrd.img
您没有提供最小的完整可验证示例。我在架子上有一些我以前没有放在 Whosebug 上的代码。以下只是一个 C 文件,带有多重启动 header 和内核的入口点,可以用作测试代码的基础。它依赖于作为参数传递给 kmain
的多重启动信息结构(最初通过 EBX 来自引导加载程序)。
该代码使用 GRUB Legacy header 中的定义。如果您的系统上没有安装它,您可以找到 copy on the GNU site。还提供了基本的 linker 脚本。
当 运行 它应该清除屏幕并打印出传递给内核的命令行和传递给每个模块的命令行。
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 , %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 , %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)
}
}
您可以将这些文件编译并 link 为名为 kernel.elf
的最终 ELF 可执行文件,使用如下命令:
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
这假定您使用的是交叉编译器。尽管我个人不推荐,但您可以在主机环境中仅使用 gcc
(而不是 i686-elf-gcc
)。
调试
您可以使用 kernel.elf
使用 GRUB 构建 ISO。如果您创建一个名为 myos.iso
的 ISO,那么您可以使用 QEMU 和 GDB 来调试代码,例如:
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'
如果您正在调试故障和中断,-no-reboot -no-shutdown -d int
选项很有用。这首先使用 GDB 存根启动 QEMU 然后 GDB 用于调试 QEMUsession。我们将 kernel.elf
文件传递给调试器,以便我们可以使用符号调试。
当停在 kmain
(代码中的 C 入口点)时,您实际上可以查看整个 mb_info
结构(十六进制)命令如:
p/x *mb_info
你会得到类似这样的输出:
= {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}}}
如果您要使用命令 p (char *)mb_info->cmdline
,您可以让调试器为您打印命令行参数作为字符串。
QEMU 代码为运行:
时的截图在我的 GRUB 配置中,我将 000
作为内核的命令行参数。我添加了几个模块,命令行参数为 001
和 002
.
我想回答我自己的问题来解释为什么我的模型不起作用。 Michael Petch 的模型工作正常,但在我的实现中我遇到了不同的问题
GRUB 0.97 文档包含错误: 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.
不是"the rest of the line"而是“整行”,在我的例子中:
kernel /kernel 001
所以,我没有检查足够的内存,所以我总是有 "rek" 作为输出,它对应于前 3 个字符。我用这个汇编代码解决了我的问题:
/* Retrieve command-line passed by GRUB. */
movl $cmdline, %edi
movl 16(%ebx), %ecx
addl $MBOOT_KPARAM_OFFSET, %ecx
jmp bottom
top:
addl , %ecx
stosl
bottom:
movl (%ecx), %eax
cmpl [=10=],%eax
jne top
其中#define MBOOT_KPARAM_OFFSET 0x00000008
,对应需要移位的偏移量"kernel /kernel "。
其余代码然后用于将参数放入内存,无论其大小如何(使用 %edi 和 stosl)