如何使用 GRUB 0.97 menu.lst 将参数传递给内核?

Posted

技术标签:

【中文标题】如何使用 GRUB 0.97 menu.lst 将参数传递给内核?【英文标题】:How to pass parameters to Kernel using GRUB 0.97 menu.lst? 【发布时间】:2017-10-26 12:36:53 【问题描述】:

我正在开发一个操作系统,我必须创建一个调试模式。为此,我想在 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 $1, 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

【问题讨论】:

多重引导信息块未在堆栈上传递。指针存储在 EBX 中,可以在内存中的任何位置(尽管通常在前 1mb 的某个位置)。 movl 16(%ebx), %ecx 应该将命令行参数字符指针移动到 ECX。您可能需要向我们展示您的所有代码。我也希望你编译/汇编为 32 位代码而不是 64 位代码。 我在 boot.S 和 menu.lst 的开头进行了编辑。我正在编译 32 位 是的,操作系统功能齐全 我想实现一个调试模式而不复制内核(这将是最简单的方法)如果你需要另一个文件,请告诉我,但我认为我不需要明白是怎么做的就是从menu.lst创建一个“命令行参数” 由于您仍然没有显示MBOOT_INFO_CMDLINE 的定义,我只能假设您没有意识到您没有使用MBOOT_INFO_CMDLINE 设置多引导标头标志(您正在做的可能是使 multiboot 标头以您不希望的方式运行 - 它可能提供启用 VBE 信息)。我相信您应该从 MBOOT_FLAGS 中删除 MBOOT_INFO_CMDLINE MBOOT_INFO_CMDLINE 是您从通过 EBX 传递的多重引导信息结构中返回的标志中读取的位值(标志位于 0(%ebx) ) 在使用任何将任何东西压入或弹出堆栈的指令之前,您应该创建自己的堆栈并相应地设置 ESP。 【参考方案1】:

您没有提供一个最小的完整可验证示例。我有一些以前没有放在 *** 上的代码。下面是一个简单的 C 文件,它带有一个多重引导头文件和一个内核的入口点,可以用作测试代码的基础。它依赖于作为参数传递给kmain 的多重引导信息结构(最初通过引导加载程序中的EBX)。

代码使用 GRUB Legacy 标头中的定义。如果您的系统上没有安装它,您可以找到copy on the GNU site。还提供了一个基本的链接描述文件。

运行时它应该清除屏幕并打印出传递给内核的命令行以及传递给每个模块的命令行。

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)
    


您可以使用以下命令编译并将这些文件链接到名为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,那么您可以使用 QEMUGDB 来调试代码,例如:

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 来调试 QEMU 会话。我们将kernel.elf 文件传递​​给调试器,以便我们可以使用符号调试。

当停在kmain(代码中的C入口点)时,您实际上可以使用如下命令查看整个mb_info结构(十六进制):

p/x *mb_info

你会得到类似这样的输出:

$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, drive_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-&gt;cmdline,您可以让调试器将命令行参数打印为字符串。

这段代码运行时QEMU的截图:

在我的 GRUB 配置中,我将 000 作为内核的命令行参数。我添加了几个命令行参数为001002 的模块。

【讨论】:

是的,很抱歉,我没有提供您需要的所有东西。在您的回答中对我有很大帮助的是看到 16(%ebx) 提供了一个地址而不是一个值。标记为已回答【参考方案2】:

我想回答我自己的问题,以解释我的模型为什么不起作用。 Michael Petch 的模型工作正常,但在我的实现中我遇到了不同的问题

GRUB 0.97 文档包含错误: https://ftp.gnu.org/old-gnu/Manuals/grub/html_node/kernel.html

该行的其余部分作为内核命令行逐字传递。

这不是“其余的行”,而是“整行”,在我的例子中: 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 $4, %ecx 
    stosl
bottom:
    movl (%ecx), %eax
    cmpl $0,%eax
    jne top 

其中#define MBOOT_KPARAM_OFFSET 0x00000008,对应于移位“kernel /kernel”所需的偏移量。 然后使用其余代码将参数放入内存中,无论其大小如何(使用 %edi 和 stosl)

【讨论】:

以上是关于如何使用 GRUB 0.97 menu.lst 将参数传递给内核?的主要内容,如果未能解决你的问题,请参考以下文章

GRUB如何改变默认系统启动项?

ubuntu遇到“initramfs无法进入”问题

安装ubuntu12.04,用easybcd2.2为其添加启动项,但是选择那个启动项后总是进入grub命令行,咋办?

linux征途之系统开机流程

如何设置开机启动项 Linux

如何从硬盘XP下安装UBUNTU