Page 1 of 2
[SOLVED] Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 12:05 pm
by Tinei
Hello,
I'm writing my own bootloader (with two stages), everything seems ok, it gets the low and high memory size, it switches to protected mode, it runs the kernel...
But I would like to pass my datas collected in real mode in my "boot1.asm" file (the low and high memory size) to a function in C that will fill the multiboot structure fields, so that the kernel can use it.
Here is what my bootloader do (with "low_memory" and "high_memory" labels for words):
Code: Select all
;Ask for low memory size
printString msg_low_mem
int 0x12
mov [low_memory], ax
;Ask for high memory size
printString msg_high_mem
mov ah, 0x88
int 0x15
mov [high_memory], ax
jc error_high_mem
;Switching to protected mode...
;...
; Jump to multiboot loader
jmp dword 0x8:0x8000
My "multiboot.c" file:
Code: Select all
#include "../../kernel/include/multiboot.h"
_start()
{
multiboot_info mbi;
mbi.low_mem = ???;
mbi.high_mem = ???;
// Put the address of the multiboot structure in EBX, boot.asm will use it
asm("movl %0, %%ebx"
:
: "a" (&mbi)
: "ebx"
);
// Jump to the kernel
__asm__("jmp $0x8, $0x9000");
}
__asm__(". = 449");
I've tried with the stack but it didn't work (push [low_memory] and push [high_memory]).
Can you help me?
Thank you in advance.
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 12:19 pm
by BrightLight
If you want it to be multiboot-compliant, EAX has to be 0x2BADB002 when the kernel is executed and EBX has to be a physical pointer to the multiboot structure. Set up a multiboot structure in memory following the multiboot standard.
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 12:36 pm
by iocoder
In C you can declare variables with "extern" modifier to inform the compiler that these variables are allocated in other object files.
So your code would be like this:
Code: Select all
#include "../../kernel/include/multiboot.h"
_start()
{
multiboot_info mbi;
extern multiboot_uint32_t low_memory, high_memory;
mbi.low_mem = low_memory;
mbi.high_mem = high_memory;
// Put the address of the multiboot structure in EBX, boot.asm will use it
asm("movl %0, %%ebx"
:
: "a" (&mbi)
: "ebx"
);
// Jump to the kernel
__asm__("jmp $0x8, $0x9000");
}
__asm__(". = 449");
Make sure low_memory and high_memory are set as global symbols by the assembler.
EDIT: I just noticed that you may not be linking the two stages together. In this case one nasty solution is to copy low_memory and high_memory to well known memory addresses, then read these addresses in _start(). However, doing the whole thing in real mode assembly is better as others pointed out.
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 12:40 pm
by Brendan
Hi,
Tinei wrote:I'm writing my own bootloader (with two stages), everything seems ok, it gets the low and high memory size, it switches to protected mode, it runs the kernel...
But I would like to pass my datas collected in real mode in my "boot1.asm" file (the low and high memory size) to a function in C that will fill the multiboot structure fields, so that the kernel can use it.
Why? Just put your multiboot structure somewhere that real mode assembly can access and store the data directly in it (in assembly); instead of storing it in the wrong place and then shifting it to the right place later in C.
Note that your C code will need to be 32-bit protected mode (as that's what multiboot requires when passing control to the kernel, and real mode can't even access the memory that the kernel and modules need to be loaded into). This means you're going to be switching between real mode and protected mode and passing a lot of information (the full memory map from "int 0x15, eax=0xE820", a whole bunch of stuff from VBE, data read from disk, etc). For this reason you're going to want to plan how you use the "real mode accessible" memory to minimise the hassle/overhead; and you're probably going to want to implement generic code to switch between real mode and protected mode (e.g. some sort of "switch to real mode, call routine and then return to protected mode" function like "void callRealMode(void)", where real mode registers are stored in a structure beforehand and retrieved from that structure after).
Also, don't forget that the low and high memory size fields in multiboot are almost entirely worthless, and that if you're writing a boot loader specifically for your OS there's no point bothering with the crippling limitations of multiboot.
Cheers,
Brendan
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 1:05 pm
by Tinei
Thank you iocoder, I had not thought about that.
But the problem now is that I end up with weird values:
I get 21488 kB for low memory and 255 kB for high memory it's not normal.
Thanks Brendan, I'm going to see that.
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 1:56 pm
by iocoder
Tinei wrote:Thank you iocoder, I had not thought about that.
But the problem now is that I end up with weird values:
I get 21488 kB for low memory and 255 kB for high memory it's not normal.
Thanks Brendan, I'm going to see that.
Hi, I think this is because the variables are allocated as "words" in assembly code, while they should be allocated as double words, initialized with zero in the beginning.
Or you can just replace "multiboot_uint32_t" in your C code with "multiboot_uint16_t". However don't forget that the low_mem and high_mem members of mbi are 32-bit.
Did you consider checking the AX value after calling int 0x12 and int 0x15? what do they contain?
I have just looked up at the wiki and it seems that int 0x12 and int 0x15 return sizes in KBs... maybe you ar reading the wrong memory location in your C code? can we see your new code?
Best.
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 3:14 pm
by Tinei
Here is a part of my boot1.asm:
Code: Select all
[BITS 16]
global low_memory
global high_memory
jmp start
%include "boot1_defines.inc"
%include "screen.inc"
%include "floppy.inc"
%include "a20_activation.inc"
start:
; Segments initialization
mov ax, 0x0100
mov ds, ax
mov es, ax
; Stack from 0x8F000 to 0x80000
mov ax, 0x8000
mov ss, ax
mov sp, 0xf000
; ----------------------------------
; Enable A20 Gate for compatibility
; ----------------------------------
call a20_activation
; ---------------------------------------------
; Get informations for the multiboot structure
; ---------------------------------------------
;Ask for low memory size
printString msg_low_mem
int 0x12
mov [low_memory], ax
;Ask for high memory size
printString msg_high_mem
mov ah, 0x88
int 0x15
mov [high_memory], ax
jc error_high_mem
; -------------------------------------------------------
; Copy the multiboot loader and the kernel in the memory
; -------------------------------------------------------
; Load the multiboot loader from the floppy to the memory
readSectors 0x800, 1, 5
; Some code...
; ------------------------------------
; Switch to protected mode
; ------------------------------------
cli ; Disable interrupts because they won't be valid anymore after switching to protected mode
lgdt [gdt_struct] ; Load the GDT in GDTR register, to indicate to the CPU where is located the GDT
mov eax, cr0
or ax, 1
mov cr0, eax ; Set the "PE" (Protection Enable) bit in CR0 register
jmp run_kernel
run_kernel:
mov ax, 0x10 ; Initialize data segments to data selector (0x10)
mov ds, ax
mov cx, ax
mov fs, ax
mov gs, ax
mov es, ax
mov ss, ax
mov esp, 0x9F000
; Jump to multiboot loader
jmp dword 0x8:0x8000
And multiboot.c:
Code: Select all
#include "../../kernel/include/multiboot.h"
_start()
{
multiboot_info mbi;
extern uint32 low_memory, high_memory;
mbi.low_mem = low_memory;
mbi.high_mem = high_memory;
// Put the address of the multiboot structure in EBX, boot.asm will use it
asm("movl %0, %%ebx"
:
: "a" (&mbi)
: "ebx"
);
// Jump to the kernel
__asm__("jmp $0x8, $0x9000");
}
__asm__(". = 449");
I don't know why, now my kernel print "16733168" for low and high memory
The commands I use in my makefile:
nasm -f elf32 boot1.asm -o boot1.elf
i686-elf-gcc -c multiboot.c -o multiboot.o
i686-elf-ld --oformat=binary -Ttext 0x0 $(BUILD_DIR)/boot1.elf $(BUILD_DIR)/multiboot.o -o $(BUILD_DIR)/multiboot.bin
cat boot0.bin multiboot.bin > bootloader.img
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 3:25 pm
by Brendan
Hi,
Tinei wrote:I don't know why, now my kernel print "16733168" for low and high memory
How does a 16-bit variable contain a value that's larger than 65536 (e.g. 16733168)?
Cheers,
Brendan
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 3:38 pm
by Tinei
I don't know, I think I made the things good, I don't understand what happens
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 3:39 pm
by iocoder
Tinei wrote:Here is a part of my boot1.asm:
Code: Select all
[BITS 16]
global low_memory
global high_memory
jmp start
%include "boot1_defines.inc"
%include "screen.inc"
%include "floppy.inc"
%include "a20_activation.inc"
start:
; Segments initialization
mov ax, 0x0100
mov ds, ax
mov es, ax
; Stack from 0x8F000 to 0x80000
mov ax, 0x8000
mov ss, ax
mov sp, 0xf000
; ----------------------------------
; Enable A20 Gate for compatibility
; ----------------------------------
call a20_activation
; ---------------------------------------------
; Get informations for the multiboot structure
; ---------------------------------------------
;Ask for low memory size
printString msg_low_mem
int 0x12
mov [low_memory], ax
;Ask for high memory size
printString msg_high_mem
mov ah, 0x88
int 0x15
mov [high_memory], ax
jc error_high_mem
; -------------------------------------------------------
; Copy the multiboot loader and the kernel in the memory
; -------------------------------------------------------
; Load the multiboot loader from the floppy to the memory
readSectors 0x800, 1, 5
; Some code...
; ------------------------------------
; Switch to protected mode
; ------------------------------------
cli ; Disable interrupts because they won't be valid anymore after switching to protected mode
lgdt [gdt_struct] ; Load the GDT in GDTR register, to indicate to the CPU where is located the GDT
mov eax, cr0
or ax, 1
mov cr0, eax ; Set the "PE" (Protection Enable) bit in CR0 register
jmp run_kernel
run_kernel:
mov ax, 0x10 ; Initialize data segments to data selector (0x10)
mov ds, ax
mov cx, ax
mov fs, ax
mov gs, ax
mov es, ax
mov ss, ax
mov esp, 0x9F000
; Jump to multiboot loader
jmp dword 0x8:0x8000
And multiboot.c:
Code: Select all
#include "../../kernel/include/multiboot.h"
_start()
{
multiboot_info mbi;
extern uint32 low_memory, high_memory;
mbi.low_mem = low_memory;
mbi.high_mem = high_memory;
// Put the address of the multiboot structure in EBX, boot.asm will use it
asm("movl %0, %%ebx"
:
: "a" (&mbi)
: "ebx"
);
// Jump to the kernel
__asm__("jmp $0x8, $0x9000");
}
__asm__(". = 449");
I don't know why, now my kernel print "16733168" for low and high memory
The commands I use in my makefile:
nasm -f elf32 boot1.asm -o boot1.elf
i686-elf-gcc -c multiboot.c -o multiboot.o
i686-elf-ld --oformat=binary -Ttext 0x0 $(BUILD_DIR)/boot1.elf $(BUILD_DIR)/multiboot.o -o $(BUILD_DIR)/multiboot.bin
cat boot0.bin multiboot.bin > bootloader.img
Does boot0.bin load the whole multiboot.bin to memory, or just the first sector of multiboot.bin? What does the line "readSectors 0x800, 1, 5" do? It seems to me you have linkage inconsistency.
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 3:53 pm
by Tinei
Boot0 (located in the first sector) loads Boot1 (that is 3 sectors sized) to 0x100:0, then boot1 loads the multiboot.c to 0x800:0 ("readSectors 0x800, 1, 5" one sector to load from the 5th sector). Then boot1 loads the kernel at 0x900:0.
I'm going to check one more time all that (the sectors, the linkage..)
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 4:06 pm
by iocoder
Tinei wrote:Boot0 (located in the first sector) loads Boot1 (that is 3 sectors sized) to 0x100:0, then boot1 loads the multiboot.c to 0x800:0 ("readSectors 0x800, 1, 5" one sector to load from the 5th sector). Then boot1 loads the kernel at 0x900:0.
I'm going to check one more time all that (the sectors, the linkage..)
Obviously, the linker thinks that multiboot.c is located in memory just after boot1
in the same segment, which is not true. So if low_memory variable is in .text section and is located at offset x from the beginning of boot1 .text section, then the linker shall substitute the "extern low_memory" with x, although DS base might not be 0x100 while multiboot is executing [I think 0x8 and 0x10 segments in your GDT structure have base of 0, right?]
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 4:14 pm
by iocoder
Some Suggestions:
- Instead of using 0x100:0x0000 to refer to 0x1000, use 0x0000:0x1000. I mean make your bootloader jump to 0x0000:0x1000 instead of 0x100:0x0000, and in boot1.asm, set DS and ES to 0x0000 instead of 0x100. You would also need to insert ".org 0x1000" at the beginning of boot1.asm to tell the assembler that our code starts at 0x1000 offset. Now tell your linker that .text section starts at 0x1000 [-Ttext 0x1000], now all variables will be adjusted w.r.t the base of physical memory.
- You may also just pass the variables using explicit memory areas instead of relying on the linker [bad solution].
- You may try again to use stack [good solution].
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 4:38 pm
by Tinei
To better see the problem, I just created 2 files in a folder with just a few commands:
boot.asm:
Code: Select all
[BITS 16]
global test_var
jmp start
%include "screen.inc"
%include "floppy.inc"
start:
; Segments initialization at 0x07C00
mov ax, 0x07C0
mov ds, ax
mov es, ax
; Stack from 0x8F000 to 0x80000
mov ax, 0x8000
mov ss, ax
mov sp, 0xf000
readSectors 0x100, 5, 2
; ------------------------------------
; Prepare to switch to protected mode
; ------------------------------------
; Compute the limit (=GDT size)
mov ax, gdtend
mov bx, gdt
sub ax, bx ; Compute the difference between the first data and the last data of the GDT (gdtend - gdt)
mov word [gdt_struct], ax ; Store the result in the first field of the structure
; Compute the base (compute the physical address)
xor eax, eax ; Set EAX to 0
xor ebx, ebx ; Set EBX to 0
mov ax, ds ; Store the data segment value in AX
mov ecx, eax ; Store it in ECX via EAX
shl ecx, 4 ; Shift the value in ECX left 4 bits (the same as multiplying it by 16)
mov bx, gdt ; Store label "gdt" address in BX
add ecx, ebx ; Add data segment address and label "gdt" address
mov dword [gdt_struct+2], ecx ; Store the result in the second field of the structure
; ------------------------------------
; Switch to protected mode
; ------------------------------------
cli ; Disable interrupts because they won't be valid anymore after switching to protected mode
lgdt [gdt_struct] ; Load the GDT in GDTR register, to indicate to the CPU where is located the GDT
mov eax, cr0
or ax, 1
mov cr0, eax ; Set the "PE" (Protection Enable) bit in CR0 register
jmp run_kernel
run_kernel:
mov ax, 0x10 ; Initialize data segments to data selector (0x10)
mov ds, ax
mov cx, ax
mov fs, ax
mov gs, ax
mov es, ax
mov ss, ax
mov esp, 0x9F000
; Jump to the kernel
jmp dword 0x8:0x1000
test_var: dd 11
msg_reading_error: db "Error by reading on the floppy disk.", 13, 10, 0
msg_reboot: db "Press a key to restart the computer", 13, 10, 0
bootdisk: db 0
; GDT Descriptors for code segment and data segment
gdt: ; NULL descriptor
db 0, 0, 0, 0, 0, 0, 0, 0
gdt_cs: ; Code segment descriptor
db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10011011b, 11011111b, 0x0
gdt_ds: ; Data segment descriptor
db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10010011b, 11011111b, 0x0
gdtend: ; Indicate the end of the table (useful to compute the limit)
; Structure containing the limit and the base of the GDT, which have to be loaded in the GDTR register (via "lgdt" instruction)
gdt_struct:
dw 0 ; Limit
dd 0 ; Base
; Fill the remaining bytes with 0 (to have 512 bytes in total)
times 510-($-$$) db 0
dw 0xAA55 ; Boot signature: so that the bios recognize it as bootable
kernel.c:
Code: Select all
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25
asm("jmp _start");
typedef unsigned int size_t;
char* vidmem=(char*)0xb8000;
int cursorX, cursorY = 0;
void itoa(char *buf, unsigned long int n, int base)
{
unsigned long int tmp;
int i, j;
tmp = n;
i = 0;
do {
tmp = n%base;
buf[i++] = (tmp < 10) ? (tmp + '0') : (tmp + 'a' - 10);
} while (n /= base);
buf[i--] = 0;
for (j=0; j<i; j++, i--) {
tmp = buf[j];
buf[j] = buf[i];
buf[i] = tmp;
}
}
void putc(char character)
{
size_t index = (cursorY * SCREEN_WIDTH + cursorX)*2;
vidmem[index] = character;
vidmem[index+1] = 0x07;
if(++cursorX == SCREEN_WIDTH)
{
cursorX = 0;
cursorY++;
}
}
size_t strlen(const char* str)
{
size_t ret = 0;
while ( str[ret] != 0 )
ret++;
return ret;
}
void print(const char* string)
{
size_t size = strlen(string);
size_t i = 0;
for (i; i < size; i++)
putc(string[i]);
}
_start()
{
//asm("movb $0, %ah; movb $3, %al; int $0x10");
print("Hello");
extern unsigned long int test_var;
char test[20]; itoa(test, test_var, 10);
print(test);
}
Commands:
nasm -f elf32 boot.asm -o boot.elf
i686-elf-gcc -c kernel.c -o kernel.o
i686-elf-ld --oformat=binary -Ttext 0x0 boot.elf kernel.o -o test.bin
qemu-system-i386 -boot a -fda test.bin
It doesn't work too.
Re: Pass arguments (bootloader)
Posted: Sat Jan 02, 2016 5:34 pm
by iocoder
What about this code, does this work?
boot.asm:
Code: Select all
[BITS 16]
global test_var
extern _start
jmp start
start:
; Segments initialization at 0x0000:0x07C00
mov ax, 0x0000
mov ds, ax
mov es, ax
jmp 0x0000:begin ; set CS to 0x0000 and IP to 0x7Cxx
begin:
; Stack from 0x8F000 to 0x80000
mov ax, 0x8000
mov ss, ax
mov sp, 0xf000
nop
;readSectors 0x100, 5, 2
pusha
mov ah, 02h
mov al, 15
mov ch, 0
mov cl, 2
mov dh, 0
mov bx, 0x0000
mov es, bx
mov bx, 0x7E00
int 0x13
popa
; ------------------------------------
; Prepare to switch to protected mode
; ------------------------------------
; Compute the limit (=GDT size)
mov ax, gdtend
mov bx, gdt
sub ax, bx ; Compute the difference between the first data and the last data of the GDT (gdtend - gdt)
mov word [gdt_struct], ax ; Store the result in the first field of the structure
; Compute the base (compute the physical address)
xor eax, eax ; Set EAX to 0
xor ebx, ebx ; Set EBX to 0
mov ax, ds ; Store the data segment value in AX
mov ecx, eax ; Store it in ECX via EAX
shl ecx, 4 ; Shift the value in ECX left 4 bits (the same as multiplying it by 16)
mov bx, gdt ; Store label "gdt" address in BX
add ecx, ebx ; Add data segment address and label "gdt" address
mov dword [gdt_struct+2], ecx ; Store the result in the second field of the structure
; ------------------------------------
; Switch to protected mode
; ------------------------------------
cli ; Disable interrupts because they won't be valid anymore
; after switching to protected mode
lgdt [gdt_struct] ; Load the GDT in GDTR register, to indicate to the CPU
; where is located the GDT
mov eax, cr0
or ax, 1
mov cr0, eax ; Set the "PE" (Protection Enable) bit in CR0 register
jmp run_kernel
run_kernel:
mov ax, 0x10 ; Initialize data segments to data selector (0x10)
mov ds, ax
mov cx, ax
mov fs, ax
mov gs, ax
mov es, ax
mov ss, ax
mov esp, 0x9F000
; Jump to the kernel
jmp dword 0x8:0x7E00
test_var: dd 11
msg_reading_error: db "Error by reading on the floppy disk.", 13, 10, 0
msg_reboot: db "Press a key to restart the computer", 13, 10, 0
bootdisk: db 0
; GDT Descriptors for code segment and data segment
gdt: ; NULL descriptor
db 0, 0, 0, 0, 0, 0, 0, 0
gdt_cs: ; Code segment descriptor
db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10011011b, 11011111b, 0x0
gdt_ds: ; Data segment descriptor
db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10010011b, 11011111b, 0x0
gdtend: ; Indicate the end of the table (useful to compute the limit)
; Structure containing the limit and the base of the GDT, which have to be loaded in the GDTR register (via "lgdt" instruction)
gdt_struct:
dw 0 ; Limit
dd 0 ; Base
; Fill the remaining bytes with 0 (to have 512 bytes in total)
times 510-($-$$) db 0
dw 0xAA55 ; Boot signature: so that the bios recognize it as bootable
kernel.c:
Code: Select all
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25
asm("jmp _start");
typedef unsigned int size_t;
char* vidmem=(char*)0xb8000;
int cursorX, cursorY = 0;
void itoa(char *buf, unsigned long int n, int base)
{
unsigned long int tmp;
int i, j;
tmp = n;
i = 0;
do {
tmp = n%base;
buf[i++] = (tmp < 10) ? (tmp + '0') : (tmp + 'a' - 10);
} while (n /= base);
buf[i--] = 0;
for (j=0; j<i; j++, i--) {
tmp = buf[j];
buf[j] = buf[i];
buf[i] = tmp;
}
}
void putchr(char character)
{
size_t index = (cursorY * SCREEN_WIDTH + cursorX)*2;
vidmem[index] = character;
vidmem[index+1] = 0x07;
if(++cursorX == SCREEN_WIDTH)
{
cursorX = 0;
cursorY++;
}
}
size_t strlen(const char* str)
{
size_t ret = 0;
while ( str[ret] != 0 )
ret++;
return ret;
}
void print(const char* string)
{
size_t size = strlen(string);
size_t i = 0;
for (i; i < size; i++)
putchr(string[i]);
}
int _start()
{
//asm("movb $0, %ah; movb $3, %al; int $0x10");
print("Hello");
extern unsigned long int test_var;
char test[20];
itoa(test, test_var, 10);
print(test);
while(1);
}
Makefile:
Code: Select all
MM=-m32 -march=i686 -mtune=generic
all:
nasm -f elf32 boot.asm -o boot.elf
gcc $(MM) -c kernel.c -o kernel.o
gcc $(MM) -nostartfiles -nostdlib -nodefaultlibs -Xlinker \
--oformat=binary -Xlinker -Ttext -Xlinker 0x7C00 \
-o test.bin boot.elf kernel.o
dd if=/dev/zero of=dumpsect.bin count=1 bs=512
cat test.bin dumpsect.bin > test2.bin
qemu-system-i386 -boot a -fda test2.bin