Please, help me linking my .asm and .c!

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
geomal
Posts: 5
Joined: Fri May 14, 2021 9:24 am

Please, help me linking my .asm and .c!

Post by geomal »

Hello everyone!

I am a fullstack dev taking his first steps in kernel programming. Please don't laugh at me for being a total n00b.

I've written an .asm that enables protected mode, and (supposedly) jumps to the address of a kernel. Yet I literally have no idea how I can link the two files. I've tried several solutions I found on google, yet no one would work -not running in qemu, in particular. I only either encounter a "not a bootable disk" message, or it would just do nothing after enabling A20.

I would really appreciate it if someone gave me a useful guide for linkers (Google didn't, so far).

If someone is willing to give me a ready solution as well, here are the codes:
stage0.asm:

Code: Select all

org  0x7c00 ;endast för -f bin
[BITS 16] ; den tha doulepsei alliws. 


_start: 
    cli
    cld ; clear direction flags
    mov ax, 03h  ;Clear the screen
    int 10h ; BIOS call for clearing 
    

    xor ax,ax
    ;mov cs, ax ;it fucks the printing up!!!
    mov ds, ax
    mov es, ax
    mov ss, ax
   ; mov sp, 0x8000
    mov ah, 0x0e
    mov si, realModeMsg 
    call printmsg  
    call a20_gate_fast
    
    hlt
    
    
    
    
a20_gate_fast: 
    xor ax,ax
    ;There is no guarantee that this works on a system! It works in most cases,
    in al, 0x92 ;but not always. TODO: Implement at least one more algorithm enabling A20
    or al, 2
    out 0x92, al
    call check_a20
    xchg bx, bx
    cmp  ax, 0
    jne  enable_A20__done ;jump if A20 enabled
    hlt
    


enable_A20__done: 
    ;xor ax,ax
    mov ah, 0x0e
    mov si, A20sucess
    ;mov al, [si]
    jmp printmsg 
    jmp load_gdt
    
    
    ;http://www.independent-software.com/operating-system-development-enabling-a20-line.html
    ; https://stackoverflow.com/questions/52668637/is-my-understanding-of-the-a20-line-check-code-correct
check_a20:
    pushf                                  ;Backup the current flags onto the stack
                                           ;Backup the below registers onto the stack
    push ds                                ;|
    push es                                ;|
    push di                                ;|
    push si                                ;-----

    cli                                    ;Disable interupts

    xor ax, ax                             ; ax = 0
    mov es, ax                             ;es = ax

    not ax                                 ; ax = 0xFFFF
    mov ds, ax                             ; ds = ax

    mov di, 0x0500                         ;Boot signature part one (0x55)
    mov si, 0x0510                         ;Boot signature part two (0xAA)

    mov al, byte [es:di]                   ;al = value at AA:55
    push ax                                ;Backup ax register onto the stack

    mov al, byte [ds:si]                   ;al = value at 55:AA
    push ax                                ;Backup al onto the stack

    mov byte [es:di], 0x00                 ;Memory location AA:55 = 0
    mov byte [ds:si], 0xFF                 ;Memory location at 55:AA = 0xFF

    cmp byte [es:di], 0xFF                 ;Does value at AA:55 = 0xFF? If so, this means A20 is disabled

    pop ax                                 ;Restore saved ax register
    mov byte [ds:si], al                   ;Set 55:AA to al

    pop ax                                 ;Restore ax register
    mov byte [es:di], al                   ;set AA:55 to al

    mov ax, 0                              ;Return status of this function = 0 (Disabled)
    je check_a20__exit                     ;A20 is disabled. Go to check_a20__exit

    mov ax, 1                               ;Return status of this function = 1 (Enabled)
    jmp check_a20__exit
    ret 
    
check_a20__exit:
                                           ;Backup registers
    pop si
    pop di
    pop es
    pop ds
    popf                                   ;Backup flags

    ret                                    ;Return

  ;FOUND ON OSDev.org. Similarly implemented on simple-x86-bootloader 
load_gdt:
    mov ax, gdtend
    mov bx, gdt
    sub ax, bx ; compute GDT's limit
    mov WORD [gdtptr], ax

    xor eax, eax ; compute linear of GDT
    mov ax, ds
    shl eax, 4
    xor ebx, ebx
    mov bx, gdt
    add eax, ebx
    mov DWORD [gdtptr + 2], eax
    jmp protected_mode

  
  
protected_mode: 
    cli
    lgdt [gdtptr]    ; load  gdt
    mov eax, cr0
    or  ax, 1
    mov cr0, eax   
    jmp 0x08:init_pm
    
    
    
second_msg:
    mov si, realModeMsgg 
    call printmsg  
    
    
    
    ;;functions for general use
  printmsg:
    mov al, [si]
    add si, 1
    cmp al, 0xa
    jne print_interrupt
    ret
    ; alternative way: 
    ;lodsb 
    ;or al,al
    ;jz print_interrupt
    
print_interrupt: 
    int 0x10
    jmp printmsg


    
[bits 32]
init_pm:
    mov ax, 0x10
    mov ds, ax
    mov ss, ax

    mov esp, 0x090000 ; set up stack pointer

    mov byte [0xB8000], 88
    mov byte [0xB8000+1], 0x1B

    call dword 0x08:0x01000 ; go to C code

    mov byte [0xB8000+4], 89
    mov byte [0xB8000+5], 0x1B
    jmp $
    
    
    
    
    
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


[bits 16]    
    
.data
realModeMsg db 'Real mode...', 0xa
realModeMsgg db 'EWWWWWWWWWWWWWWWWWWW', 0xa
A20errorMsg db 'Error enabling A20', 0xa
A20sucess db 'A20 successfully enabled!', 0xa

gdt: ;gdt based on simple-x86-bootloader
     db 0, 0, 0, 0, 0, 0, 0, 0
gdt_cs: ; flat model
     db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10011011b, 11011111b, 0x0
gdt_ds: ; flat model
     db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10010011b, 11011111b, 0x0
gdtend:
     ;---------------------------------------------------------
gdtptr:
    dw 0  ; limite
    dd 0  ; base
;--------------------------------------------------------------------

;clc 
; Switch to the BIOS (= request low memory size)
;int 0x12
;jmp 0x9d0000
 
;----------------------------------------------;
; Bootloader signature must be located
; at bytes #511 and #512.
; Fill with 0 in between.
; $  = address of the current line
; $$ = address of the 1st instruction
;----------------------------------------------;
times 510 - ($-$$) db 0
dw        0xaa55
hello.c (version 1):

Code: Select all

 #define WHITE_TXT 0x07 /* light gray on black text */

void k_clear_screen();
unsigned int k_printf(char *message, unsigned int line);
void main(void);

void main(void) {
       unsigned char* vga = (unsigned char*) 0xb8000;
   vga[0] = 'H'; //need to make sure that this is a character
   vga[1] = 0x09; //append the attribute byte
       k_main();

   for(;;); //make sure our kernel never stops, with an infinite loop

}



/* simple kernel written in C */
void k_main() 
{
	k_clear_screen();
	k_printf("Jumped to the kernel!", 0);
};

/* k_clear_screen : to clear the entire text screen */
void k_clear_screen()
{
	char *vidmem = (char *) 0xb8000;
	unsigned int i=0;
	while(i < (80*25*2))
	{
		vidmem[i]=' ';
		i++;
		vidmem[i]=WHITE_TXT;
		i++;
	};
};

/* k_printf : the message and the line # */
unsigned int k_printf(char *message, unsigned int line)
{
	char *vidmem = (char *) 0xb8000;
	unsigned int i=0;

	i=(line*80*2);

	while(*message!=0)
	{
		if(*message=='\n') // check for a new line
		{
			line++;
			i=(line*80*2);
			*message++;
		} else {
			vidmem[i]=*message;
			*message++;
			i++;
			vidmem[i]=WHITE_TXT;
			i++;
		};
	};

	return(1);
}

hello.c (version 2):

Code: Select all

 #include <stdio.h>
#include <sys/sysinfo.h>
#include <unistd.h>
#include <stdlib.h>


void main() {
    /*const short color = 0x0F00;
    const char* hello = "Hello from c file!";
    short* vga = (short*)0xb8000;
    for (int i = 0; i<16;++i)
        vga[i+80] = color | hello[i];*/
			//clrscr();
	printf("Hello!");
	for(;;) {}
}
Thank you in advance!
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Please, help me linking my .asm and .c!

Post by Octocontrabass »

geomal wrote:Please don't laugh at me for being a total n00b.
Everyone has to start somewhere.
geomal wrote:I've written an .asm that enables protected mode, and (supposedly) jumps to the address of a kernel.
That's called a bootloader. Writing a bootloader can be pretty involved, and it doesn't have a whole lot to do with writing an operating system, so you might want to try using an existing bootloader like GRUB instead of writing your own. (You can always use an existing bootloader for now and write your own later.)
geomal wrote:Yet I literally have no idea how I can link the two files.
If you use GRUB, you don't link it with your kernel at all. You put the kernel on the disk as an ordinary file and tell GRUB where to find it, and GRUB handles the rest.

Since you're writing your own bootloader, you get to decide where the kernel needs to be for the bootloader to find it. The easiest option is to just concatenate the bootloader and kernel binaries using dd or something. That way, the kernel is always at the same place on the disk.

You might have already tried that and noticed it didn't work. And if you didn't try it yet, don't bother, because it's not going to work. How can I tell? Easy: your bootloader is missing the "loader" part. The BIOS only loads one sector from the disk, and you have to write code to load the rest!

Whichever bootloader you use, you'll probably need to customize how the kernel is linked into a binary before you put it on the disk with the bootlodaer. GRUB loads ELF executables, but it needs a multiboot header near the beginning of the file. You can put the multiboot header in a section of its own and tell the linker to put that section first. If you're writing your own bootloader, you'll probably want to load a flat binary to start out. For a flat binary, you need to control exactly what code goes into the first part of the file, which means a separate section that you tell the linker to put at the beginning of the file.

I'm sure this isn't everything you need to know, but it should help you figure out what to do next.

Now for the fun part: looking at your code.

Code: Select all

    ;mov cs, ax ;it fucks the printing up!!!
The MOV instruction cannot be used to load the CS register. If you want to do that, you'll have to use a control flow instruction, such as a far JMP, far CALL, far RET, or IRET.

Code: Select all

    mov ss, ax
   ; mov sp, 0x8000
You've set SS but not SP, so you have no idea where the stack is.

Code: Select all

    hlt
HLT isn't guaranteed to stop the CPU forever. It's a good idea to use an infinite loop with HLT inside so that if the CPU resumes for any reason it'll jump back into the HLT instruction again.

Code: Select all

    jmp printmsg 
    jmp load_gdt
Since you use JMP instead of CALL, your printmsg function will return to the code that called enable_A20__done and never reach load_gdt.

Code: Select all

    sub ax, bx ; compute GDT's limit
Your bootloader's GDT isn't going to change at runtime, so you don't need runtime code to compute anything. Make your assembler come up with appropriate constants instead.

Code: Select all

void main(void);
You probably shouldn't have a function named main() in your kernel. The entry point is defined by the linker anyway, so you can use whatever name you want. (And if you're using a flat binary, the entry point has to be written in assembly so you can make sure it ends up exactly where it needs to be.)

Code: Select all

#include <stdio.h>
The compiler should give you an error for trying to include this. If you don't see an error, you're using the wrong compiler. You should be using a cross-compiler.
geomal
Posts: 5
Joined: Fri May 14, 2021 9:24 am

Re: Please, help me linking my .asm and .c!

Post by geomal »

Octocontrabass wrote:
geomal wrote:Please don't laugh at me for being a total n00b.
Everyone has to start somewhere.
geomal wrote:I've written an .asm that enables protected mode, and (supposedly) jumps to the address of a kernel.
That's called a bootloader. Writing a bootloader can be pretty involved, and it doesn't have a whole lot to do with writing an operating system, so you might want to try using an existing bootloader like GRUB instead of writing your own. (You can always use an existing bootloader for now and write your own later.)
geomal wrote:Yet I literally have no idea how I can link the two files.
If you use GRUB, you don't link it with your kernel at all. You put the kernel on the disk as an ordinary file and tell GRUB where to find it, and GRUB handles the rest.

Since you're writing your own bootloader, you get to decide where the kernel needs to be for the bootloader to find it. The easiest option is to just concatenate the bootloader and kernel binaries using dd or something. That way, the kernel is always at the same place on the disk.

You might have already tried that and noticed it didn't work. And if you didn't try it yet, don't bother, because it's not going to work. How can I tell? Easy: your bootloader is missing the "loader" part. The BIOS only loads one sector from the disk, and you have to write code to load the rest!

Whichever bootloader you use, you'll probably need to customize how the kernel is linked into a binary before you put it on the disk with the bootlodaer. GRUB loads ELF executables, but it needs a multiboot header near the beginning of the file. You can put the multiboot header in a section of its own and tell the linker to put that section first. If you're writing your own bootloader, you'll probably want to load a flat binary to start out. For a flat binary, you need to control exactly what code goes into the first part of the file, which means a separate section that you tell the linker to put at the beginning of the file.

I'm sure this isn't everything you need to know, but it should help you figure out what to do next.

Now for the fun part: looking at your code.

Code: Select all

    ;mov cs, ax ;it fucks the printing up!!!
The MOV instruction cannot be used to load the CS register. If you want to do that, you'll have to use a control flow instruction, such as a far JMP, far CALL, far RET, or IRET.

Code: Select all

    mov ss, ax
   ; mov sp, 0x8000
You've set SS but not SP, so you have no idea where the stack is.

Code: Select all

    hlt
HLT isn't guaranteed to stop the CPU forever. It's a good idea to use an infinite loop with HLT inside so that if the CPU resumes for any reason it'll jump back into the HLT instruction again.

Code: Select all

    jmp printmsg 
    jmp load_gdt
Since you use JMP instead of CALL, your printmsg function will return to the code that called enable_A20__done and never reach load_gdt.

Code: Select all

    sub ax, bx ; compute GDT's limit
Your bootloader's GDT isn't going to change at runtime, so you don't need runtime code to compute anything. Make your assembler come up with appropriate constants instead.

Code: Select all

void main(void);
You probably shouldn't have a function named main() in your kernel. The entry point is defined by the linker anyway, so you can use whatever name you want. (And if you're using a flat binary, the entry point has to be written in assembly so you can make sure it ends up exactly where it needs to be.)

Code: Select all

#include <stdio.h>
The compiler should give you an error for trying to include this. If you don't see an error, you're using the wrong compiler. You should be using a cross-compiler.
Thank you so much for your help and commentary!

I thought JMP and CALL were virtually the same, but that's far from being the case...
I changed the code so that it directly jumps to load_gdt, and it seems like qemu has trouble with this command:

Code: Select all

    mov cr0, eax 
It just crashes and restarts upon running it. I assume something is wrong, either with the gdt, or with the code before it.
But my knowledge of assembly is very limited so it would be a waste of time to try to debug it.

I resorted to using the following bootloader I found on the internet:

Code: Select all

[BITS 16]
[ORG 0x7c00]	;First instruction adress

reset_drive:
  mov ah, 0
  int 0x13
  or ah, ah
  jnz reset_drive

  ; load from disk
  xor ax, ax
  mov es, ax
  mov ch, ah
  mov dh, ah
  mov bx, 0x1000
  mov cl, 0x02
  mov ah, 0x02
  mov al, 0x02
  int 0x13
  or ah, ah
  jnz reset_drive

  cli ; disable iterrupts

  ; set the ds register
  xor ax, ax
  mov ds, ax

  lgdt [gtd_desc] ; load segments table

  ; go to protected mode
  mov eax, cr0
  or eax, 0x01
  mov cr0, eax

  jmp 0x08:clear_pipe32 ; go to 32-bit code


[BITS 32]
clear_pipe32:
  ; set segment registers
  mov ax, 0x10
  mov ds, ax
  mov ss, ax

  mov esp, 0x090000 ; set up stack pointer

  mov byte [0xB8000], 88
  mov byte [0xB8000+1], 0x1B

  call dword 0x08:0x01000 ; go to C code
  ;jmp 0x1000

  mov byte [0xB8000+4], 89
  mov byte [0xB8000+5], 0x1B
  jmp $


  
gdt:

gdt_null:
  dd 0
  dd 0

gdt_code:
  dw 0xFFFF
  dw 0
  db 0
  db 10011010b
  db 11001111b
  db 0

gdt_data:
  dw 0xFFFF
  dw 0
  db 0
  db 10010010b
  db 11001111b
  db 0

gdt_end:

gtd_desc:
   dw gdt_end - gdt - 1
   dd gdt

TIMES 510 - ($ - $$) db 0	; Fill the rest of sector with 0
DW 0xAA55 ; Add boot signature at the end of bootloader
And compile with the following commands:


Code: Select all

nasm -f bin stage0.asm -o boot.o
gcc -m32  -ffreestanding -c kernel.c -o kernel.o
ld -melf_i386 -N -Ttext 0x01000 -e0x01000 -o MyOS.o kernel.o
objcopy -R .note -R .comment -S -O binary MyOS.o MyOS.bin 
cat boot.o MyOS.bin > os-image
qemu-system-i386  -drive format=raw,file=os-image
But qemu will just stack on "loading from hard disk", despite the code above is (supposedly) written by an experienced kernel dev. Could it be something wrong with the creation of the binary?

I downloaded some .pdfs about bootloaders and OSs, but for the time being it would help if I finally made the code above run.
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: Please, help me linking my .asm and .c!

Post by bzt »

geomal wrote:It just crashes and restarts upon running it. I assume something is wrong, either with the gdt, or with the code before it.
Use a debugger to figure out what's wrong.
geomal wrote:But my knowledge of assembly is very limited so it would be a waste of time to try to debug it.
Nobody can learn that instead of you, so the only option left you'll have to learn yourself.
geomal wrote:I resorted to using the following bootloader I found on the internet:
Ah, bad idea, never do that. Read this. If you're not experienced with assembly and boot loaders, consider using an already made loader, like BOOTBOOT for example, but never copy'n'paste code without understanding what it's actually doing.
geomal wrote:But qemu will just stack on "loading from hard disk", despite the code above is (supposedly) written by an experienced kernel dev. Could it be something wrong with the creation of the binary?
Most definitely not written by an expert, and if it were something with the creation of the binary then you wouldn't be able to compile. There's no syntax error in it, but it is full of semantic errors. The problem is, a boot loader has lots of things to set up. It only gets the drive code in DL, but that's about it, everything else has to be configured: segments, CPU flags, stack, etc. Only after that can you make calls to functions, BIOS services and set up protected mode, but not before. Furthermore there's a lot more to protected mode, you'll have to reconfigure the memory bus too for example (by enabling A20).
geomal wrote:I downloaded some .pdfs about bootloaders and OSs, but for the time being it would help if I finally made the code above run.
That's a job for you. Nobody will write your OS for you. If you're unsure, use a working boot loader project at first.

Cheers,
bzt
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Please, help me linking my .asm and .c!

Post by Octocontrabass »

geomal wrote:But my knowledge of assembly is very limited so it would be a waste of time to try to debug it.
If you want to write a bootloader, you will need to learn how to debug a bootloader. If debugging your bootloader is a waste of time, why aren't you using a bootloader that has already been debugged, such as GRUB?
geomal wrote:Could it be something wrong with the creation of the binary?
You're still not using a linker script, and you still don't have an assembly stub to provide an entry point in your flat binary. You're also missing code to initialize some of the CPU registers to the state required by your compiler's ABI, but that doesn't necessarily have to go in the bootloader itself. The bootloader itself only loads 1kB, which your kernel binary will quickly outgrow.
Post Reply