OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 12:17 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 5 posts ] 
Author Message
 Post subject: Please, help me linking my .asm and .c!
PostPosted: Fri May 14, 2021 9:38 am 
Offline

Joined: Fri May 14, 2021 9:24 am
Posts: 5
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:
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:
#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:
#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!


Top
 Profile  
 
 Post subject: Re: Please, help me linking my .asm and .c!
PostPosted: Fri May 14, 2021 10:17 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
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:
    ;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:
    mov ss, ax
   ; mov sp, 0x8000

You've set SS but not SP, so you have no idea where the stack is.

Code:
    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:
    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:
    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:
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:
#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.


Top
 Profile  
 
 Post subject: Re: Please, help me linking my .asm and .c!
PostPosted: Sun May 16, 2021 1:35 am 
Offline

Joined: Fri May 14, 2021 9:24 am
Posts: 5
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:
    ;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:
    mov ss, ax
   ; mov sp, 0x8000

You've set SS but not SP, so you have no idea where the stack is.

Code:
    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:
    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:
    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:
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:
#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:
    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:
[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:
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.


Top
 Profile  
 
 Post subject: Re: Please, help me linking my .asm and .c!
PostPosted: Sun May 16, 2021 8:50 am 
Offline
Member
Member
User avatar

Joined: Thu Oct 13, 2016 4:55 pm
Posts: 1584
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


Top
 Profile  
 
 Post subject: Re: Please, help me linking my .asm and .c!
PostPosted: Sun May 16, 2021 9:47 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
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.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot], rdos and 83 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group