引导装载程序混合C和汇编

pbpqsu0x  于 2023-05-28  发布在  其他
关注(0)|答案(1)|浏览(85)

我刚开始使用我的第一个操作系统(我是这个领域的初学者)。我想在汇编和C中都这样做,但在汇编代码中调用C函数时遇到了麻烦。

** Boot .asm**:

BITS 32
global start
extern dmain

start: 
    call dmain
    
    jmp $

times 510 - ($- $$) db 0
dw 0xAA55

main.c

#define NULL 0

void clear();

void clear() // clear entire screen
{
    char *mem = (char*)(0xb8000);
    while (*mem != NULL){
        *mem = NULL;
        mem++;
    }
}
void dmain(void *mbr, unsigned int magic) {
    clear();
}

Makefile

main:
    gcc -m32 -ffreestanding -c main.c -o main.o
    nasm -f win32 boot.asm -o boot.o
    ld -m i386pe -T NUL -o main.tmp -Ttext 0x100000 boot.o main.o
    objcopy -O binary main.tmp main.img

    qemu-system-x86_64 main.img

这是我的输出:

gcc -m32 -ffreestanding -c main.c -o main.o
nasm -f win32 boot.asm -o boot.o
ld -m i386pe -T NUL -o main.tmp -Ttext 0x100000 boot.o main.o
boot.o:boot.asm:(.text+0x1): undefined reference to `dmain'
make: *** [Makefile:4: main] Error 1
iovurdzv

iovurdzv1#

如果你想写32位代码(如BITS 32所示),你必须设置一个保护模式环境。现在,CPU处于真实的模式,必须置于保护模式。
我看到您正在尝试将 Boot 扇区和引导加载程序可执行文件链接到一个文件中。使用适当的链接器脚本可以实现这一点,但是如果您仍然处于真实的模式并且不支持分段内存模型,则GCC无法修复16位地址,这意味着保护模式是必需的。您还生成了一个带有 Boot 扇区的PE可执行文件,这肯定不会起作用。BIOS只会加载512字节并跳转到它(顺便说一句,DL被设置到 Boot 盘)。
更好的方法是将 Boot 加载程序构建为单独的二进制文件,并使用INT 13h从磁盘加载它。在32位保护模式环境中进入第二阶段,其中为平面模型设置了全局描述符表。第二阶段加载器可以是构建为在特定加载地址处运行的平面二进制。
因为你所有的地址都将被链接器固定到你想加载第二阶段 Boot 加载程序的任何地方,所以当访问加载程序外部的内存(例如视频内存)时,有必要减去基址。

#define LOAD_POINT 0x7E00 /* Right after the boot sector */
#define PHYS(a) (void*(a-LOAD_POINT))

请注意,如果您决定进入受保护模式环境(在使用GCC时强烈建议),除非您设置虚拟8086模式和任务状态段,否则BIOS服务将不可用。

进入保护模式环境

BITS 32是一个汇编指令,实际上并不改变CPU的状态。必须设置CR 0的第一位才能进入,加载带有平面模型的GDT。

mov  eax,cr0
inc  ax
mov  cr0,eax
lgdt [gdtr]

即使这样,您仍然只处于16位保护模式。需要跳转到标记为32位的代码段。

全局描述符表

每个条目的长度为8个字节。第一个是NULL段,必须归零。GDT可以放置在内存中的任何位置。要加载GDT,我们必须用LGDT设置GDTR。
以下示例显示了GDTR和GDT以及平面模型段之间的关系。

org 7C00h

_GDTR:
        DW      _GDT_end - _GDT - 1     ; GDT limit in bytes (highest offset)
        DD      _GDT                    ; Linear address of GDT
_GDT:
        DQ  0 ; NULL segment
        ; Flat code segment (selector 0x8)
        DB      0FFh,0FFh,0,0,0,1_00_11010b,11_001111b,0
        ; Flat data segment (selector 0x10)
        DB      0FFh,0FFh,0,0,0,1_00_10010b,11_001111b,0
_GDT_end:

GDT的确切结构如下所示:https://i.stack.imgur.com/KNZeO.gif
GDT条目由选择器引用。位0和1是所请求的特权级别(出于输出目的为零),位2是GDT/LDT(此处也为GDT为零)。其余部分是表的13位索引。基本上,在这种情况下,通过简单地将索引移位三来引用段。

中断描述符表

如果要在保护模式下接收中断,IDT是必不可少的。它包含一个代码段选择器和一个32位地址。这可以在 Boot 加载程序环境中完成。
需要将PIC重新编程为适当的基向量,以便它们不会像在真实的模式中那样与x86异常冲突。
下面是8259 PIC(标准PC中断控制器)https://stanislavs.org/helppc/8259.html的硬件规格
C中的一个例子:https://wiki.osdev.org/8259_PIC#Code_Examples

虚拟8086模式

虚拟8086是一个复杂的主题,所以我将给予一些简短的细节,应该有助于避免陷阱。V86模式允许在保护模式下运行真实的模式代码。使用它需要一个至少带有#GP处理程序的IDT。
V86自动将用户的当前权限级别设置为3。任何特权指令或INT指令将导致一般保护故障,以便监视器可以模拟它。在#GP的堆栈上,保存的指令指针指向导致它的确切指令,因此请确保在模拟指令后递增它。
BIOS例程肯定有IO指令,因此请确保TSS有IO权限位图,并将所有位设置为零,以便允许所有端口。
TSS还具有称为ESP 0的字段,其是在进入环3时将被加载到ESP的栈指针值。必须在进入3号环或V86之前进行设置。用于进入V86的汇编例程可以将其设置为当前堆栈指针。
请参阅OSDev文章(我不久前曾对此做出贡献),以了解更多具体细节:https://wiki.osdev.org/Virtual_8086_Mode

进一步阅读和建议

这里有一个关于IA 32体系结构细节的优秀资源。这是英特尔i386的官方手册。
https://pdos.csail.mit.edu/6.828/2014/readings/i386/toc.htm
应关注以下主题:

  • 虚拟8086模式
  • IDT/GDT/TSS
  • 例外

相关问题