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:
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
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.ld
and 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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With