跳转至

11 设置工作模式与环境(中):建造二级引导器

你好,我是LMOS。

上节课,我们建造了属于我们的“计算机”,并且在上面安装好了GRUB。这节课我会带你一起实现二级引导器这个关键组件。

看到这儿你可能会问,GRUB不是已经把我们的操作系统加载到内存中了吗?我们有了GRUB,我们为什么还要实现二级引导器呢?

这里我要给你说说我的观点,二级引导器作为操作系统的先驱,它需要收集机器信息,确定这个计算机能不能运行我们的操作系统,对CPU、内存、显卡进行一些初级的配置,放置好内核相关的文件。

因为我们二级引导器不是执行具体的加载任务的,而是解析内核文件、收集机器环境信息,它具体收集哪些信息,我会在下节课详细展开。

设计机器信息结构

二级引导器收集的信息,需要地点存放,我们需要设计一个数据结构。信息放在这个数据结构中,这个结构放在内存1MB的地方,方便以后传给我们的操作系统。

为了让你抓住重点,我选取了这个数据结构的关键代码,这里并没有列出该结构的所有字段(Cosmos/initldr/include/ldrtype.h),这个结构如下所示。

typedef struct s_MACHBSTART
{
    u64_t   mb_krlinitstack;//内核栈地址
    u64_t   mb_krlitstacksz;//内核栈大小
    u64_t   mb_imgpadr;//操作系统映像
    u64_t   mb_imgsz;//操作系统映像大小
    u64_t   mb_bfontpadr;//操作系统字体地址
    u64_t   mb_bfontsz;//操作系统字体大小
    u64_t   mb_fvrmphyadr;//机器显存地址
    u64_t   mb_fvrmsz;//机器显存大小
    u64_t   mb_cpumode;//机器CPU工作模式
    u64_t   mb_memsz;//机器内存大小
    u64_t   mb_e820padr;//机器e820数组地址
    u64_t   mb_e820nr;//机器e820数组元素个数
    u64_t   mb_e820sz;//机器e820数组大小
    //……
    u64_t   mb_pml4padr;//机器页表数据地址
    u64_t   mb_subpageslen;//机器页表个数
    u64_t   mb_kpmapphymemsz;//操作系统映射空间大小
    //……
    graph_t mb_ghparm;//图形信息
}__attribute__((packed)) machbstart_t;

规划二级引导器

在开始写代码之前,我们先来从整体划分一下二级引导器的功能模块,从全局了解下功能应该怎么划分,这里我特意为你梳理了一个表格。

前面表格里的这些文件,我都放在了课程配套源码中了,你可以从这里下载。

上述这些文件都在lesson10~11/Cosmos/initldr/ldrkrl目录中,它们在编译之后会形成三个文件,编译脚本我已经写好了,下面我们用一幅图来展示这个编译过程。

这最后三个文件用我们前面说的映像工具打包成映像文件,其指令如下。

lmoskrlimg -m k -lhf initldrimh.bin -o Cosmos.eki -f initldrkrl.bin initldrsve.bin

实现GRUB头

我们的GRUB头有两个文件组成,一个imginithead.asm汇编文件,它有两个功能,既能让GRUB识别,又能设置C语言运行环境,用于调用C函数;第二就是inithead.c文件,它的主要功能是查找二级引导器的核心文件——initldrkrl.bin,然后把它放置到特定的内存地址上。

我们先来实现imginithead.asm,它主要工作是初始化CPU的寄存器,加载GDT,切换到CPU的保护模式,我们一步一步来实现。

首先是GRUB1和GRUB2需要的两个头结构,代码如下。

MBT_HDR_FLAGS   EQU 0x00010003
MBT_HDR_MAGIC   EQU 0x1BADB002
MBT2_MAGIC  EQU 0xe85250d6
global _start
extern inithead_entry
[section .text]
[bits 32]
_start:
    jmp _entry
align 4
mbt_hdr:
    dd MBT_HDR_MAGIC
    dd MBT_HDR_FLAGS
    dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
    dd mbt_hdr
    dd _start
    dd 0
    dd 0
    dd _entry
ALIGN 8
mbhdr:
    DD  0xE85250D6
    DD  0
    DD  mhdrend - mbhdr
    DD  -(0xE85250D6 + 0 + (mhdrend - mbhdr))
    DW  2, 0
    DD  24
    DD  mbhdr
    DD  _start
    DD  0
    DD  0
    DW  3, 0
    DD  12
    DD  _entry 
    DD  0  
    DW  0, 0
    DD  8
mhdrend:

然后是关中断并加载GDT,代码如下所示。

_entry:
    cli           ;关中断
    in al, 0x70 
    or al, 0x80 
    out 0x70,al  ;关掉不可屏蔽中断   
    lgdt [GDT_PTR] ;加载GDT地址到GDTR寄存器
    jmp dword 0x8 :_32bits_mode ;长跳转刷新CS影子寄存器
  ;………………
;GDT全局段描述符表
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009e000000ffff ;16位代码段描述符
k16da_dsc: dq 0x000092000000ffff ;16位数据段描述符
GDT_END:
GDT_PTR:
GDTLEN  dw GDT_END-GDT_START-1  ;GDT界限
GDTBASE dd GDT_START

最后是初始化段寄存器和通用寄存器、栈寄存器,这是为了给调用inithead_entry这个C函数做准备,代码如下所示。

_32bits_mode:
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    xor edx,edx
    xor edi,edi
    xor esi,esi
    xor ebp,ebp
    xor esp,esp
    mov esp,0x7c00 ;设置栈顶为0x7c00
    call inithead_entry ;调用inithead_entry函数在inithead.c中实现
    jmp 0x200000  ;跳转到0x200000地址

上述代码的最后调用了inithead_entry函数,这个函数我们需要另外在inithead.c中实现,我们这就来实现它,如下所示。

#define MDC_ENDGIC 0xaaffaaffaaffaaff
#define MDC_RVGIC 0xffaaffaaffaaffaa
#define REALDRV_PHYADR 0x1000
#define IMGFILE_PHYADR 0x4000000
#define IMGKRNL_PHYADR 0x2000000
#define LDRFILEADR IMGFILE_PHYADR
#define MLOSDSC_OFF (0x1000)
#define MRDDSC_ADR (mlosrddsc_t*)(LDRFILEADR+0x1000)

void inithead_entry()
{
    write_realintsvefile();
    write_ldrkrlfile();
    return;
}
//写initldrsve.bin文件到特定的内存中
void write_realintsvefile()
{
    fhdsc_t *fhdscstart = find_file("initldrsve.bin");
    if (fhdscstart == NULL)
    {
        error("not file initldrsve.bin");
    }
    m2mcopy((void *)((u32_t)(fhdscstart->fhd_intsfsoff) + LDRFILEADR),
            (void *)REALDRV_PHYADR, (sint_t)fhdscstart->fhd_frealsz);
    return;
}
//写initldrkrl.bin文件到特定的内存中
void write_ldrkrlfile()
{
    fhdsc_t *fhdscstart = find_file("initldrkrl.bin");
    if (fhdscstart == NULL)
    {
        error("not file initldrkrl.bin");
    }
    m2mcopy((void *)((u32_t)(fhdscstart->fhd_intsfsoff) + LDRFILEADR),
            (void *)ILDRKRL_PHYADR, (sint_t)fhdscstart->fhd_frealsz);
    return;
}
//在映像文件中查找对应的文件
fhdsc_t *find_file(char_t *fname)
{
    mlosrddsc_t *mrddadrs = MRDDSC_ADR;
    if (mrddadrs->mdc_endgic != MDC_ENDGIC ||
        mrddadrs->mdc_rv != MDC_RVGIC ||
        mrddadrs->mdc_fhdnr < 2 ||
        mrddadrs->mdc_filnr < 2)
    {
        error("no mrddsc");
    }
    s64_t rethn = -1;
    fhdsc_t *fhdscstart = (fhdsc_t *)((u32_t)(mrddadrs->mdc_fhdbk_s) + LDRFILEADR);
    for (u64_t i = 0; i < mrddadrs->mdc_fhdnr; i++)
    {
        if (strcmpl(fname, fhdscstart[i].fhd_name) == 0)
        {
            rethn = (s64_t)i;
            goto ok_l;
        }
    }
    rethn = -1;
ok_l:
    if (rethn < 0)
    {
        error("not find file");
    }
    return &fhdscstart[rethn];
}

我们实现了inithead_entry函数,它主要干了两件事,即分别调用write_realintsvefile();、write_ldrkrlfile()函数,把映像文件中的initldrsve.bin文件和initldrkrl.bin文件写入到特定的内存地址空间中,具体地址在上面代码中的宏有详细定义。

这两个函数分别依赖于find_file和m2mcopy函数。

正如其名,find_file函数负责扫描映像文件中的文件头描述符,对比其中的文件名,然后返回对应的文件头描述符的地址,这样就可以得到文件在映像文件中的位置和大小了。

find_file函数的接力队友就是m2mcopy函数,因为查找对比之后,最后就是m2mcopy函数负责把映像文件复制到具体的内存空间里。

代码中的其它函数我就不展开了,感兴趣的同学请自行研究,或者自己改写。

进入二级引导器

你应该还有印象,刚才说的实现GRUB头这个部分,在imghead.asm汇编文件代码中,我们的最后一条指令是“jmp 0x200000”,即跳转到物理内存的0x200000地址处。

请你注意,这时地址还是物理地址,这个地址正是在inithead.c中由write_ldrkrlfile()函数放置的initldrkrl.bin文件,这一跳就进入了二级引导器的主模块了。

由于模块的改变,我们还需要写一小段汇编代码,建立下面这个initldr32.asm(配套代码库中对应ldrkrl32.asm)文件,并写上如下代码。

_entry:
    cli
    lgdt [GDT_PTR];加载GDT地址到GDTR寄存器
    lidt [IDT_PTR];加载IDT地址到IDTR寄存器
    jmp dword 0x8 :_32bits_mode;长跳转刷新CS影子寄存器
_32bits_mode:
    mov ax, 0x10    ; 数据段选择子(目的)
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    xor edx,edx
    xor edi,edi
    xor esi,esi
    xor ebp,ebp
    xor esp,esp
    mov esp,0x90000 ;使得栈底指向了0x90000
    call ldrkrl_entry ;调用ldrkrl_entry函数
    xor ebx,ebx
    jmp 0x2000000 ;跳转到0x2000000的内存地址
    jmp $
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9a000000ffff ;a-e
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009a000000ffff ;16位代码段描述符
k16da_dsc: dq 0x000092000000ffff ;16位数据段描述符
GDT_END:
GDT_PTR:
GDTLEN  dw GDT_END-GDT_START-1  ;GDT界限
GDTBASE dd GDT_START

IDT_PTR:
IDTLEN  dw 0x3ff
IDTBAS  dd 0  ;这是BIOS中断表的地址和长度

我来给你做个解读,代码的1~4行是在加载GDTR和IDTR寄存器,然后初始化CPU相关的寄存器。

和先前一样,因为代码模块的改变,所以我们要把GDT、IDT,寄存器这些东西重新初始化,最后再去调用二级引导器的主函数ldrkrl_entry。

巧妙调用BIOS中断

我们不要急着去写ldrkrl_entry函数,因为在后面我们要获得内存布局信息,要设置显卡图形模式,而这些功能依赖于BIOS提供中断服务。

可是,要在C函数中调用BIOS中断是不可能的,因为C语言代码工作在32位保护模式下,BIOS中断工作在16位的实模式。

所以,C语言环境下调用BIOS中断,需要处理的问题如下:

1.保存C语言环境下的CPU上下文 ,即保护模式下的所有通用寄存器、段寄存器、程序指针寄存器,栈寄存器,把它们都保存在内存中。
2.切换回实模式,调用BIOS中断,把BIOS中断返回的相关结果,保存在内存中。
3.切换回保护模式,重新加载第1步中保存的寄存器。这样C语言代码才能重新恢复执行。

要完成上面的功能,必须要写一个汇编函数才能完成,我们就把它写在ldrkrl32.asm文件中,如下所示 。

realadr_call_entry:
    pushad     ;保存通用寄存器
    push    ds
    push    es
    push    fs ;保存4个段寄存器
    push    gs
    call save_eip_jmp ;调用save_eip_jmp 
    pop gs
    pop fs
    pop es      ;恢复4个段寄存器
    pop ds
    popad       ;恢复通用寄存器
    ret
save_eip_jmp:
    pop esi  ;弹出call save_eip_jmp时保存的eip到esi寄存器中, 
    mov [PM32_EIP_OFF],esi ;把eip保存到特定的内存空间中
    mov [PM32_ESP_OFF],esp ;把esp保存到特定的内存空间中
    jmp dword far [cpmty_mode];长跳转这里表示把cpmty_mode处的第一个4字节装入eip,把其后的2字节装入cs
cpmty_mode:
    dd 0x1000
    dw 0x18
    jmp $

上面的代码我列了详细注释,你一看就能明白。不过这里唯一不好懂的是jmp dword far [cpmty_mode]指令,别担心,听我给你解读一下。

其实这个指令是一个长跳转,表示把[cpmty_mode]处的数据装入CS:EIP,也就是把0x18:0x1000装入到CS:EIP中。

这个0x18就是段描述索引(这个知识点不熟悉的话,你可以回看我们第五节课),它正是指向GDT中的16位代码段描述符;0x1000代表段内的偏移地址,所以在这个地址上,我们必须放一段代码指令,不然CPU跳转到这里将没指令可以执行,那样就会发生错误。

因为这是一个16位代码,所以我们需要新建立一个文件realintsve.asm,如下所示。

[bits 16]
_start:
_16_mode:
    mov bp,0x20 ;0x20是指向GDT中的16位数据段描述符 
    mov ds, bp
    mov es, bp
    mov ss, bp
    mov ebp, cr0
    and ebp, 0xfffffffe
    mov cr0, ebp ;CR0.P=0 关闭保护模式
    jmp 0:real_entry ;刷新CS影子寄存器,真正进入实模式
real_entry:
    mov bp, cs
    mov ds, bp
    mov es, bp
    mov ss, bp ;重新设置实模式下的段寄存器 都是CS中值,即为0 
    mov sp, 08000h ;设置栈
    mov bp,func_table
    add bp,ax
    call [bp] ;调用函数表中的汇编函数,ax是C函数中传递进来的
    cli
    call disable_nmi
    mov ebp, cr0
    or  ebp, 1
    mov cr0, ebp ;CR0.P=1 开启保护模式
    jmp dword 0x8 :_32bits_mode
[BITS 32]
_32bits_mode:
    mov bp, 0x10
    mov ds, bp
    mov ss, bp;重新设置保护模式下的段寄存器0x10是32位数据段描述符的索引
    mov esi,[PM32_EIP_OFF];加载先前保存的EIP
    mov esp,[PM32_ESP_OFF];加载先前保存的ESP
    jmp esi ;eip=esi 回到了realadr_call_entry函数中

func_table:  ;函数表
    dw _getmmap ;获取内存布局视图的函数
    dw _read ;读取硬盘的函数
    dw _getvbemode ;获取显卡VBE模式 
    dw _getvbeonemodeinfo ;获取显卡VBE模式的数据
    dw _setvbemode ;设置显卡VBE模式

上面的代码我们只要将它编译成16位的二进制的文件,并把它放在0x1000开始的内存空间中就可以了。这样在realadr_call_entry函数的最后,就运行到这段代码中来了。

上述的代码的流程是这样的:首先从 _16_mode:标号处进入实模式,然后根据传递进来(由ax寄存器传入)的函数号,到函数表中调用对应的函数,里面的函数执行完成后,再次进入保护模式,加载EIP和ESP寄存器从而回到realadr_call_entry函数中。GDT还是imghead.asm汇编代码文件中的GDT,这没有变,因为它是由GDTR寄存器指向的。

说到这里,相信你会立刻明白,之前write_realintsvefile()函数的功能与意义了。它会把映像文件中的initldrsve.bin文件写入到特定的内存地址空间中,而initldrsve.bin正是由上面的realintsve.asm文件编译而成的。

二级引导器主函数

好,现在我们准备得差不多了,从二级引导器的主函数开始,这个函数我们要用C来写,估计你也感受到了写汇编语言的压力,所以不能老是写汇编。

我们先建立一个C文件ldrkrlentry.c,在其中写上一个主函数,代码如下。

void ldrkrl_entry()
{
    init_bstartparm();
    return;
}

上述代码中的 ldrkrl_entry()函数在initldr32.asm文件(配套代码库中对应ldrkrl32.asm)中被调用,从那条call ldrkrl_entry 指令开始进入了ldrkrl_entry()函数,在其中调用了init_bstartparm()函数,这个函数我们还没有实现,但通过名字我们不难推测,它是负责处理开始参数的。

你还记不记得,我们建造二级引导器的目的,就是要收集机器环境信息。我们要把这些信息形成一个有结构的参数,传递给我们的操作系统内核以备后续使用。

由此,我们能够确定,init_bstartparm()函数成了收集机器环境信息的主函数,下节课我们就会去实现它。

重点回顾

今天我们开始实现二级引导器了,但是我们还没有完全实现,我们下一节课再接着继续这项工作。

现在,我们来梳理一下这节课的内容,回顾一下我们今天的成果。

1.我们设计了机器信息结构,用于存放后面二级引导器收集到的机器信息。
2.对二级引导器代码模块进行了规划,确定各模块的主要功能。
3.实现了GRUB规定的GRUB头,以便被GRUB识别,在GRUB头中初始化了CPU寄存器,并且跳转到物理内存的0x200000地址处,真正进入到二级引导器中开始运行。
4.为了二级引导器能够调用BIOS中断服务程序,我们实现了专门用来完成调用BIOS中断服务程序的realintsve.asm模块。
5.最后,我们实现了二级引导器的主函数,由它调用完成其它功能的函数。

这里我还想聊聊,为什么我们要花这么多功夫,去设计二级引导器这个组件呢?

我们把这些处理操作系统运行环境的工作独立出来,交给二级引导器来做,这会大大降低后面开发操作系统的难度,也能增加操作系统的通用性。而且,针对不同的硬件平台,我们只要开发不同的二级引导器就好了。

思考题

请问GRUB头中为什么需要_entry标号和_start标号的地址?

欢迎你在留言区跟我交流活动。如果你身边的同事、朋友,对二级引导器的建立有兴趣,也欢迎你把这节课分享给他。

好,我是LMOS,我们下节课见!

精选留言(15)
  • 嗣树 👍(6) 💬(3)

    回答一下问题,GRUB 头中为什么需要 _entry 标号和 _start 标号的地址? 我们定义的 flags 值为: MBT_HDR_FLAGS EQU 0x00010003 根据 Multiboot Specification 定义的头结构 Offset Type Field Name Note 0 u32 magic required 4 u32 flags required 8 u32 checksum required 12 u32 header_addr if flags[16] is set 16 u32 load_addr if flags[16] is set 20 u32 load_end_addr if flags[16] is set 24 u32 bss_end_addr if flags[16] is set 28 u32 entry_addr if flags[16] is set 32 u32 mode_type if flags[2] is set 36 u32 width if flags[2] is set 40 u32 height if flags[2] is set 44 u32 depth if flags[2] is set flags[16] 解释如下: If bit 16 in the ‘flags’ word is set, then the fields at offsets 12-28 in the Multiboot header are valid, and the boot loader should use them instead of the fields in the actual executable header to calculate where to load the OS image. This information does not need to be provided if the kernel image is in ELF format, but it must be provided if the images is in a.out format or in some other format. Compliant boot loaders must be able to load images that either are in ELF format or contain the load address information embedded in the Multiboot header; they may also directly support other executable formats, such as particular a.out variants, but are not required to. 也就是如果我们用的是标准的 ELF 文件就不需要提供额外的地址信息,而我们用的是自己定义的格式就需要人家从哪里加载哪里运行,所以需要将 bit 16 使能,填充相应的字段。 再解释下两个字段的含义: load_addr Contains the physical address of the beginning of the text segment. The offset in the OS image file at which to start loading is defined by the offset at which the header was found, minus (header_addr - load_addr). load_addr must be less than or equal to header_addr. entry_addr The physical address to which the boot loader should jump in order to start running the operating system.

    2021-06-03

  • neohope 👍(91) 💬(16)

    大体上整理了一下: 1、grub启动后,选择对应的启动菜单项,grub会通过自带文件系统驱动,定位到对应的eki文件 2、grub会尝试加载eki文件【eki文件需要满足grub多协议引导头的格式要求】 这些是在imginithead.asm中实现的,所以要包括: A、grub文件头,包括魔数、grub1和grub2支持等 B、定位的_start符号等 3、grub校验成功后,会调用_start,然跳转到_entry A、_entry中:关闭中断 B、加载GDT C、然后进入_32bits_mode,清理寄存器,设置栈顶 D、调用inithead_entry【C】 4、inithead_entry.c A、从imginithead.asm进入后,首先进入函数调用inithead_entry B、初始化光标,清屏 C、从eki文件内部,找到initldrsve.bin文件,并分别拷贝到内存的指定物理地址 D、从eki文件内部,找到initldrkrl.bin文件,并分别拷贝到内存的指定物理地址 E、返回imginithead.asm 5、imginithead.asm中继续执行 jmp 0x200000 而这个位置,就是initldrkrl.bin在内存的位置ILDRKRL_PHYADR 所以后面要执行initldrkrl.bin的内容 6、这样就到了ldrkrl32.asm的_entry A、将GDT加载到GDTR寄存器【内存】 B、将IDT加载到IDTR寄存器【中断】 C、跳转到_32bits_mode 初始寄存器 初始化栈 调用ldrkrl_entry【C】 7、ldrkrlentry.c A、初始化光标,清屏 B、收集机器参数init_bstartparm【C】 8、bstartparm.c A、初始化machbstart_t B、各类初始化函数,填充machbstart_t的内容 C、返回 9、ldrkrlentry.c A、返回 10、ldrkrl32.asm A、跳转到0x2000000地址继续执行

    2021-06-02

  • springXu 👍(20) 💬(3)

    真实进入实际写代码的课程了。 对于grub的头格式在的第二节写个HelloOS.bin就已经有了,这次头格式还是会有。理由是grub是一级引导器。 这节课的内容就是围绕着由一级转到二级引导器的过程展开了。 关于二级引导器的加载过程,简单点说就是把我们内核加载到指定内存的位置并执行,这个加载函数核心是m2mcopy函数,东哥留给我们自己分析了,但东哥强调分析了下为什么会有32位下的代码和16位汇编代码共存的现象。 其实是为了让BIOS提供的获取硬件信息的操作函数(也就是实模式下的BIOS中断号来获取的)做成了c语言环境下也可以调用的功能。这就像跨语言互相调用的技术。汇编调用c语言的方法,反过来c语言调用汇编方法。但更为复杂些,原因是保护模式到实模式再回到保护模式的切换过程。内核可以获取硬件信息就可以根据硬件环境参数,配置自身参数开始工作了。如何配置参数,那又是下节继续播讲。周五见。 关于思考题,这个是grub也是要把控制权交给我们二级引导器的入口地址。 至于为什么不光有一个_start就可以了,我猜测是为了做验证吧。_start的操作是jmp 地址。这个地址正好是_entry。有错误还请东哥指正。哈哈

    2021-06-02

  • pedro 👍(17) 💬(5)

    早起看专栏! 先回答思考题,grub是multiboot规范,因此引导器头部数据必须得满足一定的规则才能被grub所加载,本文中的MBT_HDR_FLAGS为0x001003,第16位被置为1,因此load_addr和entry_addr都是有效的,而它们正好分别对应_start和_entry。 其中load_addr是引导器二进制文件text段的起始地址,即_start,grub解析头部数据后,拿到_start地址,并从该地址处开始执行二级引导器代码。 而entry_addr对应的是操作系统的入口点,也就是_entry。引导程序最后将跳转到这里,不过本文的实现并没有完全按照这种思路来,_start直接跳到_entry,然后由_entry负责二级引导工作。

    2021-06-02

  • Amerny 👍(13) 💬(1)

    感觉代码也看不懂,稀里糊涂的,是不是得需要把代码看懂了才能进行下一步呀,脑子不够用了

    2021-09-18

  • 👍(6) 💬(1)

    对应grub2的代码定义结构: struct multiboot_header { multiboot_uint32_t magic; // 魔数 0xE85250D6 multiboot_uint32_t architecture; // 架构 0表示x86 multiboot_uint32_t header_length; // 表示mb header 长度 multiboot_uint32_t checksum; // 校验和 }; 在 之后是multiboot_header_tag, struct multiboot_header_tag_address { multiboot_uint16_t type; // 2 表示 multiboot_header_tag_address multiboot_uint16_t flags; // 0 multiboot_uint32_t size; // 24 multiboot_uint32_t header_addr; // mbhdr multiboot_uint32_t load_addr; // _start multiboot_uint32_t load_end_addr; // 0 表示数据段 和 代码段一样 占用整个空间 multiboot_uint32_t bss_end_addr; // 0 }; 再之后是 multiboot_header_tag_entry_address struct multiboot_header_tag_entry_address { multiboot_uint16_t type; // type = 3 表示 multiboot_header_tag_entry_address multiboot_uint16_t flags; multiboot_uint32_t size; multiboot_uint32_t entry_addr; }; mbhdr: DD 0xE85250D6 magic DD 0 architecture DD mhdrend - mbhdr header_length DD -(0xE85250D6 + 0 + (mhdrend - mbhdr)) checksum ;multiboot_header_tag DW 2, 0 ; type = 2 ;flag = 0; DD 24 ; size =24 DD mbhdr DD _start DD 0 DD 0 ;multiboot_header_tag_entry_address DW 3, 0 ;;type = 3 flag = 0 DD 12 DD _entry ; entry_addr = _entry ; DD 0 DW 0, 0 DD 8

    2021-08-24

  • 沈畅 👍(5) 💬(1)

    如果我们不用grub作为引导程序,自己写一个引导程序,在实模式下,是不是就可以直接调用bois中断收集硬件信息,而不需要后面在保护模式下切换回实模式,再收集?

    2021-08-15

  • 熊光红 👍(5) 💬(4)

    请问老师,如何调试内核?怎么搭建调试环境

    2021-06-03

  • Qfeng 👍(4) 💬(1)

    这篇文章开始烧脑了,全文汇编,第一次看这篇云里雾里不知所云,汇编基础差决心回头花了3个礼拜啃完了《汇编原理》,这周再次看这篇又理解了些。 和《汇编原理》书上介绍的8086不同,这篇里面是32位的x86,不过底层原理差不多。 对着这篇文章结合源码,画了一份visio流程图,流程上清晰了些,总结如下: 1. 实现了被GRUB识别的GRUB Head,GRUB Head里面做了2件事: 1)inithead.c,从内存中查找和拷贝2个bin文件(二级引导器和BIOS中断服务程序的bin文件)到指定位置; 2)跳转到物理地址0x200000执行二级引导器代码(第一步已经将机器码拷贝到这里)。 2. 二级引导器 ldrkrl32.asm (0x200000) 1)引导主函数ldrkrl_entry() (暂未实现); 2)准备BIOS中断服务程序调用框架,核心是通过 jmp 0x18:0x1000 跳转到 BIOS中断服务程序(readintsve.asm)所在位置执行。 3. BIOS终端服务程序 readintsve.asm (0x18:0x1000) 1)首先进入16位实模式; 2)实模式下,有一个func_table,通过这些函数可以获取内存、硬盘和显卡等硬件信息; 3)最后返回32位保护模式,恢复进入前保存的EIS+EIP返回调用前的32位保护模式状态。 思考题:_start和_entry的用途是GRUB用来识别硬件厂商客制化的GRUB头的,作为厂商二级引导器的入口,详细信息上面已经有很多大佬解释的很详细就不写了。

    2022-05-08

  • Geek_e42826 👍(2) 💬(2)

    make 报错指南 1, 需要先安装nasm。sudo apt install nasm

    hello@hello-VirtualBox:~&#47;01OS&#47;01HelloOS&#47;cosmos&#47;lesson10~11&#47;Cosmos$ make all 
    清理全部已构建文件... ^_^
    *********正在开始编译构建系统*************
    make[3]: nasm: 没有那个文件或目录
    make[3]: *** [krnlbuidrule.mh:10imginithead.o] 错误 127
    make[2]: *** [Makefile:27all] 错误 2
    make[1]: *** [Makefile.x86:22all] 错误 2
    make: *** [Makefile:58all] 错误 2
    
    2,需要安装gcc。sudo apt install gcc
    hello@hello-VirtualBox:~&#47;01OS&#47;01HelloOS&#47;cosmos&#47;lesson10~11&#47;Cosmos$ make all 
    清理全部已构建文件... ^_^
    *********正在开始编译构建系统*************
    AS -[M] 正在构建... ..&#47;ldrkrl&#47;imginithead.asm
    make[3]: gcc: 没有那个文件或目录
    make[3]: *** [krnlbuidrule.mh:13inithead.o] 错误 127
    make[2]: *** [Makefile:27all] 错误 2
    make[1]: *** [Makefile.x86:22all] 错误 2
    make: *** [Makefile:58all] 错误 2
    
    3、make成功显示如下
    hello@hello-VirtualBox:~&#47;01OS&#47;01HelloOS&#47;cosmos&#47;lesson10~11&#47;Cosmos$ make all 
    清理全部已构建文件... ^_^
    *********正在开始编译构建系统*************
    AS -[M] 正在构建... ..&#47;ldrkrl&#47;imginithead.asm
    CC -[M] 正在构建... ..&#47;ldrkrl&#47;inithead.c
    CC -[M] 正在构建... ..&#47;ldrkrl&#47;vgastr.c
    AS -[M] 正在构建... ..&#47;ldrkrl&#47;ldrkrl32.asm
    CC -[M] 正在构建... ..&#47;ldrkrl&#47;ldrkrlentry.c
    CC -[M] 正在构建... ..&#47;ldrkrl&#47;fs.c
    CC -[M] 正在构建... ..&#47;ldrkrl&#47;chkcpmm.c
    CC -[M] 正在构建... ..&#47;ldrkrl&#47;graph.c
    CC -[M] 正在构建... ..&#47;ldrkrl&#47;bstartparm.c
    AS -[M] 正在构建... ..&#47;ldrkrl&#47;realintsve.asm
    OBJCOPY -[M] 正在构建... initldrimh.bin
    OBJCOPY -[M] 正在构建... initldrkrl.bin
    OBJCOPY -[M] 正在构建... initldrsve.bin
    文件数2
    映像文件大小20480
    恭喜我系统编译构建完成 ^_^
    恭喜我系统编译构建完成 ^_^
    恭喜我系统编译构建完成 ^_^
    hello@hello-VirtualBox:~&#47;01OS&#47;01HelloOS&#47;cosmos&#47;less
    

    2021-12-07

  • Jayying 👍(2) 💬(1)

    不大会汇编语言,感觉看汇编还有些难度

    2021-07-31

  • 🎋 🎋 🎋 👍(2) 💬(1)

    老师 lmoskrlimg:未找到命令 是为什么呀

    2021-07-27

  • Geek_032c4a 👍(2) 💬(1)

    请问下lmoskrlimg源代码是哪个,我搜了一圈没看到工具的源代码,是只给了个二进制吗?

    2021-06-17

  • blentle 👍(2) 💬(1)

    越来越有意思了

    2021-06-02

  • X 👍(1) 💬(2)

    make 老师给的工具的时候各种问题啊。 第一个问题: /usr/include/features.h:367:25: fatal error: sys/cdefs.h: No such file or directory 解决方法: 第一步:sudo apt-get –fix-broken install; 第二步:sudo apt-get install libc6-dev-i386; 问题出现原因是老师这个工具依赖的库文件是32位库,自己的虚拟机是64位库。 第二个问题: /usr/bin/ld: i386:x86-64 architecture of input file `lmoskrlimg.o' is incompatible with i386 output /usr/bin/ld: i386:x86-64 architecture of input file `imgcore.o' is incompatible with i386 output /usr/bin/ld: i386:x86-64 architecture of input file `imgmgrhead.o' is incompatible with i386 output /usr/bin/ld: i386:x86-64 architecture of input file `param.o' is incompatible with i386 output /usr/bin/ld: i386:x86-64 architecture of input file `file.o' is incompatible with i386 output /usr/bin/ld: i386:x86-64 architecture of input file `imgundo.o' is incompatible with i386 output /usr/bin/ld: i386:x86-64 architecture of input file `memdisk.o' is incompatible with i386 output /usr/bin/ld: i386:x86-64 architecture of input file `mem.o' is incompatible with i386 output /usr/bin/ld: i386:x86-64 architecture of input file `limgerror.o' is incompatible with i386 output 老师工具编译之后输出的编译文件 *.o 是32位的,可是我自己虚拟机是64位的架构,ld连接器连接不上。 有点无语,一通百度是gcc的时候要加上 -m32,可是找不到老师是在哪里gcc的……

    2022-07-28