Linking two or more assembly files
Asked Answered
C

1

6

I am developing a simple and small 64bit OS. Until now I've used a single file and compile it with NASM:

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

Then tested the .bin file with qemu.
Now I need to use multiple files so in my os.bin file. I've inserted this line:

extern helper_func

Then called it in the code. In another .asm file i've created this function or procedure. The problem is that the bin format does not support extern, so I've tried to use the ELF format to create the .obj files and then link them with gcc:

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

with this linker file:

ENTRY(_start)

SECTIONS
{
    . = 0x7C00;

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

But when I try to run the .bin that has been created, qemu does not recognize the file. What have I done wrong?

(I've used gcc because I plan to use C code in the future)

Actually, I don't even know what all the flags do in gcc; I've copied them from the internet XD.

This is what i've done so far:

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

No errors from any of those compilation. But when i run qemu i get this:

enter image description here

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

Inside os.asm i've used this libs:

disk.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
Counsellor answered 1/6, 2019 at 18:11 Comment(17)
You need to convert the linked binary back to a raw binary using objcopy.Inspectorate
@Inspectorate i've searched online and tried this: objcopy --input-target=binary --output-target=elf32-little myos.bin myos.bin.elf but it does not work what do i have to do?Counsellor
That converts from binary to ELF32. You want to do it the other way round: from ELF32 to binary.Inspectorate
@Inspectorate done this objcopy --input-target=elf32-little --output-target=binary myos.bin myos.bin.new but still does not work... do i have to specify some special tags on qemu?Counsellor
It should just work. If it doesn't please make a minimal reproducible example and post it. It is very hard to say what exactly went wrong otherwise. Make sure to include the exact commands you typed to assemble, link, convert, and run the binary so users on this site can reproduce your problem.Inspectorate
@Inspectorate I've posted it as an answer it should be ok i supposeCounsellor
Please don't do this. Answers are meant for answers, not for additional information. Instead, edit your question to add this information. Then delete the answer you wrote as it does not actually answer the question. Also note that this is no minimal reproducible example as you forgot to include the actual source code.Inspectorate
The easy option is %include at the source level. But remember that an MBR boot sector is limited to 512 bytes (including the 2-byte magic number at the end), and %include or linking can't overcome that.Glaucescent
@PeterCordes Yes, I know, i've already expanded the secor by reading from the disk the rest of the bootloader. since now i've used the %include instruction but now since i have to also link c files i really need this to workCounsellor
@Counsellor Please make sure to post your source code so I can have a look at this.Inspectorate
@Inspectorate done, i am sorry this is my first question i am a bit inexpertCounsellor
@Counsellor No problem! That's why I tell you what you need to do.Inspectorate
@Counsellor The code doesn't seem to be complete. Please make a minimal reproducible example which means that you need to cut down your code to just contain the bare minimum needed to show the problem you have.Inspectorate
@Inspectorate maybe as so is to minimal? i've tried to include just the parts when i call the functionCounsellor
@Counsellor Yeah, but now I cannot assemble your code to see why it fails. An minimal reproducible example should be made such that it is minimal and I can assemble and run it on my own system without any problems. So perhaps try to delete code from your project until it just so has the problem you have.Inspectorate
i've just recompiled it to double check and i get this warning: warning: 64-bit unsigned relocation zero-extended from 32 bits [-w+zext-reloc]Counsellor
@Inspectorate i've also included the library that were missing.. i think this is the minimal code for it to not crush. since is an OS it needs to enable all the stuffs to startupCounsellor
O
3

I don't know anything about the environment you are building with. I strongly recommend building an x86-64 cross compiler.

I can make a reasonable guess at some of the possible problems. Using GCC to link will generate a .note.gnu.build-id section and place it before all other sections in your binary. This will have the effect of moving your disk boot signature (0xaa55) to a location beyond the last 2 bytes in the first sector. It is the only reason I can think that your disk wouldn't be identified as bootable which appears to be the case in your screenshots. You need to add -Wl,build-id=none to your GCC link options to prevent this section from being generated.

To build a custom 64-bit bootloader in the fashion you are you should be generating everything as 64-bit objects and not 32-bit objects. Use -m64 when compiling/linking with GCC, and -felf64 when assembling with NASM (--64 if assembling with as GNU Assembler).

Ensure that you aren't generating relocatable code with -static and I recommend eliminating the .eh_frame sections with the option -fno-asynchronous-unwind-tables.

The linker script is passed using the -T option, not the -t option. In your linking options you have-t linker.ldand it should be -T linker.ld

You can simplify OBJCOPY arguments by letting it determine the source object/executable type. You can do objcopy -O binary myos.bin myos.bin.new instead.

The commands that I think you should use could look like:

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

Observations and Notes

  • In disk.asm you hard code the drive number. You can reuse/save the value of the DL register when your bootloader first starts. The BIOS passes the boot drive number in DL for you. If you reuse that value you don't need to change your code depending on the drive type you boot as (floppy, hard drive etc.)

  • I have some General Bootloader Tips that may be of value.

  • In the future when you start compiling C files I recommend these options:

    gcc -m64 -ffreestanding -mcmodel=kernel -mno-red-zone \
        -mno-mmx -mno-sse -mno-sse2 -c filename.c -o filename.o
    
Offcenter answered 2/6, 2019 at 2:0 Comment(4)
Thankyou very much. i just have a compilation problem when using this nasm tags: ``` relocation truncated to fit: R_X86_64_16 against `.text' ``` how can i fix it?Counsellor
@Counsellor : Did you copy and paste the commands to build right from my answer? There was another problem I didn't mention in the answer but had to fix. Your command line options for linking uses -r linker.ld. -t needs to be capitalized as -T. It should be -T linker.ldOffcenter
I'm actually feeling really stupid.. It was just this causing all the issue. thank you very much XDCounsellor
Instead of manually disabling MMX, SSE, etc., you often want -mgeneral-regs-only. That also disables usage of x87.Glaucescent

© 2022 - 2024 — McMap. All rights reserved.