【问题标题】:Linking two or more assembly files链接两个或多个程序集文件
【发布时间】:2019-10-17 22:33:03
【问题描述】:

我正在开发一个简单的小型 64 位操作系统。到目前为止,我只使用了一个文件并使用 NASM 编译它:

nasm -f bin os.asm -o os.bin

然后用 qemu 测试.bin 文件。
现在我需要在我的os.bin 文件中使用多个文件。我已经插入了这一行:

extern helper_func

然后在代码中调用它。在另一个 .asm 文件中,我创建了这个函数或过程。问题是bin格式不支持extern,所以我尝试使用ELF格式创建.obj文件,然后用gcc链接:

gcc -m32 -nostdlib -nodefaultlibs -lgcc os.obj helper.obj -t linker.ld

使用此链接器文件:

ENTRY(_start)

SECTIONS
{
    . = 0x7C00;

    .text :
    {
        *(.text);
    }
}

但是当我尝试运行已创建的.bin 时,qemu 无法识别该文件。我做错了什么?

(我用过gcc是因为以后打算用C代码)

实际上,我什至不知道所有标志在 gcc 中的作用;我是从网上复制过来的 XD。

这是我到目前为止所做的:

nasm -f elf os.asm -o os.obj
nasm -f elf helper.asm -o helper.obj
gcc -m32 -nostdlib -nodefaultlibs -lgcc os.obj helper.obj -t linker.ld -o myos.bin
objcopy --input-target=elf32-little --output-target=binary myos.bin myos.bin.new
qemu-system-x86_64 myos.bin.new

任何这些编译都没有错误。但是当我运行 qemu 时,我得到了这个:

os.asm:

[bits 16]

section .text

    global _start

_start:

    ; Zero segment
    cli
    jmp 0x0000:.zero_seg
    .zero_seg:
    xor ax, ax
        mov ss, ax
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov sp, _start
        cld
    sti

    ; Reset disk
    call reset_disk

    ; Load disk sectors
    mov al, 2               ; sectors to read
    mov cl, 2               ; start sector
    mov bx, second_sector   ; offset to load
    call read_disk

    ; Enable A20 line
    call enable_a20

    jmp second_sector

_end1:
    jmp $

%include "liba/disk.asm"
%include "liba/a20.asm"

; padding and magic number
times 510-($-$$) db 0
dw 0xaa55

second_sector:

    call check_long
    call switch_long

_hang:
    jmp $

%include "liba/long.asm"
%include "liba/gdt.asm"

[bits 64]

extern helper_func

long_mode:

    jmp kernel_code

_end2:
    jmp $

times 512-($-$$-512) db 0

kernel_code:

    ; two byte
    call helper_func

helper.asm:

[bits 64]

section .text

    global helper_func

helper_func:

    kernel_end:
    hlt
    jmp .kernel_end

ret

在 os.asm 中我使用了这个库:

磁盘.asm:

read_disk:
    pusha

    mov ah, 0x02    
    mov dl, 0x80    ; 0x00 Floppy/FlashDrive -- 0x80 HardDisk
    mov ch, 0       ; cylinder
    mov dh, 0       ; head

    int 0x13

    jc .disk_err
    popa
    ret

    .disk_err:
        jmp $

reset_disk:
    xor ax, ax
    mov bx, second_sector
    mov dl, 0x80
    int 0x13
    ret

a20.asm:

test_a20:
    pusha

    mov ax, [0x7dfe]

    push bx
    mov bx, 0xffff
    mov es, bx
    pop bx

    mov bx, 0x7e0e

    mov dx, [es:bx]

    cmp ax, dx
    je .cont

    popa
    mov ax, 1
    ret

    .cont:
        mov ax, [0x7dff]

        push bx
        mov bx, 0xffff
        mov es, bx
        pop bx

        mov bx, 0x7e0f
        mov dx, [es:bx]

        cmp ax, dx
        je .exit

        popa
        mov ax, 1
        ret

    .exit:
        popa
        xor ax, ax
        ret

enable_a20:
    pusha

    ;BIOS
    mov ax, 0x2401 
    int 0x15

    call test_a20
    cmp ax, 1
    je .done

    ;Keyboard
    sti

    call wait_c
    mov al, 0xad
    out 0x64, al

    call wait_c
    mov al, 0xd0
    out 0x64, al

    call wait_d 
    in al, 0x60
    push ax

    call wait_d
    mov al, 0xd1
    out 0x64, al

    call wait_c
    pop ax
    or al, 2
    out 0x60, al

    call wait_c
    mov al, 0xae
    out 0x64, al

    call wait_c

    sti

    call test_a20
    cmp ax, 1
    je .done

    ;FastA20
    in al, 0x92
    or al, 2
    out 0x92, al

    call test_a20
    cmp al, 1
    je .done

    jmp $

    .done:
        popa
        ret

wait_c:
    in al, 0x64
    test al, 2

    jnz wait_c
    ret

wait_d:
    in al, 0x64
    test al, 1

    jz wait_d
    ret

long.asm:

enable_long:
    cli

    call check_long

    mov edi, 0x1000
    mov cr3, edi
    xor eax, eax
    mov ecx, 4096
    rep stosd
    mov edi, 0x1000

    mov dword [edi], 0x2003
    add edi, 0x1000
    mov dword [edi], 0x3003
    add edi, 0x1000
    mov dword [edi], 0x4003
    add edi, 0x1000

    mov dword ebx, 3
    mov ecx, 512

    .setEntry:
        mov dword [edi], ebx
        add ebx, 0x1000
        add edi, 8
    loop .setEntry

    mov eax, cr4
    or eax, 1 << 5
    mov cr4, eax

    mov ecx, 0xc0000080
    rdmsr
    or eax, 1 << 8
    wrmsr

    mov eax, cr0
    or eax, 1 << 31
    or eax, 1 << 0
    mov cr0, eax

    ret

switch_long:

    call enable_long

    lgdt [GDT.Pointer]
    jmp GDT.Code:long_mode

    ret

check_long:
    pusha

    pushfd
    pop eax
    mov ecx, eax

    xor eax, 1 << 21

    push eax
    popfd

    pushfd
    pop eax

    xor eax, ecx
    jz .done

    mov eax, 0x80000000
    cpuid
    cmp eax, 0x80000001
    jb .done

    mov eax, 0x80000001
    cpuid
    test edx, 1 << 29
    jz .done

    popa
    ret

    .done:
        popa
        jmp $

gdt.asm:

GDT:
    .Null: equ $ - GDT
        dw 0
        dw 0
        db 0
        db 0
        db 0
        db 0

    .Code: equ $ - GDT
        dw 0
        dw 0
        db 0
        db 10011000b
        db 00100000b
        db 0

    .Data: equ $ -GDT
        dw 0
        dw 0
        db 0
        db 10000000b
        db 0
        db 0

    .Pointer:
        dw $ - GDT - 1
        dq GDT

【问题讨论】:

  • 您需要使用 objcopy 将链接的二进制文件转换回原始二进制文件。
  • @fuz 我在网上搜索并尝试了这个:objcopy --input-target=binary --output-target=elf32-little myos.bin myos.bin.elf 但它不起作用我该怎么办?
  • 从二进制转换为 ELF32。你想反过来做:从 ELF32 到二进制。
  • 它应该可以正常工作。如果没有,请发送minimal reproducible example 并发布。很难说到底出了什么问题。确保包含您键入的用于组装、链接、转换和运行二进制文件的确切命令,以便该站点上的用户可以重现您的问题。
  • 简单的选项是源代码级别的%include。但请记住,MBR 引导扇区限制为 512 字节(包括末尾的 2 字节幻数),%include 或链接无法克服这一点。

标签: assembly x86 nasm bootloader osdev


【解决方案1】:

我对您正在构建的环境一无所知。我强烈建议建立一个x86-64 cross compiler。

我可以对一些可能的问题做出合理的猜测。使用 GCC 链接将生成一个 .note.gnu.build-id 部分并将其放在二进制文件中的所有其他部分之前。这会将您的磁盘引导签名 (0xaa55) 移动到第一个扇区中最后 2 个字节之外的位置。这是我认为您的磁盘不会被识别为可引导的唯一原因,您的屏幕截图中似乎就是这种情况。您需要在 GCC 链接选项中添加 -Wl,build-id=none 以防止生成此部分。

要以您的方式构建自定义 64 位引导加载程序,您应该将所有内容生成为 64 位对象,而不是 32 位对象。使用 GCC 编译/链接时使用-m64,使用 NASM 组装时使用-felf64(如果使用as GNU Assembler 组装,则使用--64。

确保您不会使用-static 生成可重定位代码,我建议使用选项-fno-asynchronous-unwind-tables 消除.eh_frame 部分。

链接描述文件是使用-T 选项而不是-t 选项传递的。在你的链接选项中你有-t linker.ld它应该是-T linker.ld

您可以通过让 OBJCOPY 参数确定源对象/可执行文件类型来简化它。你可以改用objcopy -O binary myos.bin myos.bin.new。

我认为您应该使用的命令可能如下所示:

nasm -f elf64 os.asm -o os.obj
nasm -f elf64 helper.asm -o helper.obj
gcc -m64 -wl,--build-id=none -static -fno-asynchronous-unwind-tables \
        -nostdlib -nodefaultlibs -lgcc os.obj helper.obj -T linker.ld -o myos.bin
objcopy -O binary myos.bin myos.bin.new

观察和注释

  • 在disk.asm 中硬编码驱动器号。您可以在引导加载程序首次启动时重用/保存 DL 寄存器的值。 BIOS 会为您传递 DL 中的引导驱动器号。如果您重用该值,则无需根据您引导的驱动器类型(软盘、硬盘驱动器等)更改代码。

  • 我有一些 General Bootloader Tips 可能很有价值。

  • 以后当你开始编译 C 文件时,我推荐这些选项:

    gcc -m64 -ffreestanding -mcmodel=kernel -mno-red-zone \
        -mno-mmx -mno-sse -mno-sse2 -c filename.c -o filename.o
    

【讨论】:

  • 非常感谢。我只是在使用这个 nasm 标签时遇到了一个编译问题:``` relocation truncated to fit: R_X86_64_16 against `.text' ```我该如何解决?
  • @Mimmo :您是否根据我的回答复制并粘贴了构建命令?还有一个问题我没有在答案中提到,但必须解决。您用于链接的命令行选项使用-r linker.ld。 -t 需要大写为-T。应该是-T linker.ld
  • 我真的觉得自己很愚蠢。正是这导致了所有问题。非常感谢XD
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-30
  • 1970-01-01
  • 2014-08-05
  • 1970-01-01
  • 1970-01-01
  • 2010-11-11
相关资源
最近更新 更多