Unexpected output when printing directly to text video memory - c

I am developing a kernel in C and created something to print on screen on video memory. I expected that the first byte in video memory would be the character to print and the second byte tells the color. But my program has something different but it works!! It is very unexpected and unusual.
My kernel code -
#define VIDEO_MEM 0xb8000
void write_string( int colour, const unsigned char *string );
void main()
{
unsigned char *vid = (unsigned char*) VIDEO_MEM;
int i=0;
for (i = 0; i < 2000; i++)
{
*vid = ' ';
*(vid+2) = 0x1f;
vid += 2;
}
write_string(0x1f,"The Kernel has been loaded successfully!!");
}
void write_string( int colour, const unsigned char *string ) {
unsigned char *vid = (unsigned char*) VIDEO_MEM;
while(*string != 0)
{
*(vid) = *string;
*(vid+2) = colour;
++string;
vid+=2;
}
}
It prints the character on *vid and the color on *(vid+2) and then increments the vid by 2. It should then replace and print the next char on *(vid+2). So, the color should go but it still works.
Also, the color should be on *(vid+1)
When I use *(vid+1) instead of *(vid+2) to print the string, the screen shows down arrow characters (with ACII code 0x1f which I wanted to be the color) replacing the entire string.
Why does the code behave so unusual??
Can anyone help?
EDIT
I have edited my code and now it prints string. But another problem arose. I added a support for printing on particular line number. But now this shifts the string backwards by one character.
void write_string( int colour, const unsigned char *string, int pos ) {
unsigned char *vid = (unsigned char*) VIDEO_MEM;
vid+=pos*160;
while(*string != 0)
{
*vid = colour;
*(vid+1) = *string;
++string;
vid+=2;
}
}
So, If I tell it to print on line 10, it prints the first character on the last character of the 9th line and then continues.
I also have a character printing function that justs prints curly braces (}) instead of the given character and that too one character backwards of the given position (like the error in the write_string function). Also it doen't change the character background color given as argument.
void putChar(char character, short col, short row, char attr) {
unsigned char* vid_mem = (unsigned char *) VIDEO_MEM;
int offset = (row*80 + col)*2;
vid_mem += offset;
if(!attr) {
attr = 0x0f;
}
*vid_mem = (attr<<8)+character;
}
EDIT 2
My Boot Loader:
[org 0x7c00]
KERNEL equ 0x1000
mov [BOOT_DRIVE],dl
mov bp,0x9000
mov sp,bp
mov bx, msgReal
call print_string
call load_kernel
call switch_to_pm
jmp $
%include 'boot/bios.ASM'
%include 'boot/gdt.ASM'
%include 'boot/protected_mode.ASM'
%include 'boot/print32.ASM'
[bits 16]
load_kernel:
mov bx,msgKernel
call print_string
mov bx, KERNEL
mov dh, 15
mov dl, [BOOT_DRIVE]
call disk_load
ret
[bits 32]
BEGIN_PM:
mov ebx, msgProt
call print_string32
call KERNEL
jmp $
BOOT_DRIVE db 0
msgReal db "Booted in 16-bit mode",0
msgProt db "Successfully switched to 32-bit mode",0
msgKernel db "Loading the kernel onto memory",0
times 510-($-$$) db 0
dw 0xaa55
bios.ASM -
;BIOS Functions
[bits 16]
print_string:
pusha
mov cx,bx
mov ah,0x0e
printStringStart:
mov al,[bx]
cmp al,0
je done
int 0x10
inc bx
jmp printStringStart
done:
popa
ret
print_word:
pusha
mov ax,0x0000
mov cl,0x10
mov al,bh
div cl
call printDig
mov al,bh
and al,0x0f
call printDig
mov ax,0x0000
mov al,bl
div cl
call printDig
mov al,bl
and al,0x0f
call printDig
popa
ret
printDig:
cmp al,0x9
jg alpha
add al,'0'
mov ah,0x0e
int 0x10
jmp pDigDone
alpha:
sub al,0xa
add al,'A'
mov ah,0x0e
int 0x10
pDigDone:
ret
hex_prefix: db '0x',0
disk_load:
push dx
mov ah,0x02
mov al,dh
mov ch,0x00
mov dh,0x00
mov cl,0x02
int 0x13
jc disk_error
pop dx
cmp dh,al
jne disk_error
ret
disk_error:
mov ah,0x0e
mov al,'X'
int 0x10
mov bx,errMsg
call print_string
jmp $
errMsg:
db "Disk Read Error....."
times 80-20 db " "
db 0
gdt.ASM -
gdt_start:
gdt_null:
dd 0x0
dd 0x0
gdt_code:
dw 0xffff
dw 0x0
db 0x0
db 10011010b
db 11001111b
db 0x0
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
protected_mode.ASM -
[bits 16]
switch_to_pm:
cli
lgdt [gdt_descriptor]
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp CODE_SEG:init_pm
[bits 32]
init_pm:
mov ax, DATA_SEG
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp,0x90000
mov esp,0x90000
call BEGIN_PM
print32.ASM -
[bits 32]
VIDEO_MEM equ 0xb8000
DEF_COLOR equ 0x0f
print_string32:
pusha
mov edx,VIDEO_MEM
print_string32_loop:
mov al, [ebx]
mov ah, DEF_COLOR
cmp al,0
je print_string32_end
mov [edx],ax
inc ebx
add edx,2
jmp print_string32_loop
print_string32_end:
popa
ret
I also add a kernel_start.asm file just before the kernel while linking to call the main function -
[bits 32]
[extern main]
call main
jmp $
And here's my make file -
C_SOURCES = $(wildcard drivers/*.c kernel/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h)
OBJ = ${C_SOURCES:.c=.o}
all: os-image
os-image: boot/boot_sector.bin kernel.bin
cat $^ > $#
kernel.bin: kernel/kernel_start.o ${OBJ}
ld -o $# -Ttext 0x1000 $^ --oformat binary
%.o : %.c
gcc -std=c99 -Wall -pedantic -ffreestanding -c $< -o $#
%.o : %.asm
nasm $< -f elf64 -o $#
%.bin : %.asm
nasm $< -f bin -o $#
clean:
rm -fr kernel/*.o
rm -fr drivers/*.o
rm -fr boot/*.bin
rm -fr os-image *.bin *.o

With the changes suggested in other answer and comments, your problem doesn't seem to be reproducible for me. The following code works for me. I've tried to maintain how you coded it just so it makes sense to you:
#define VIDEO_MEM 0xb8000
void write_string( unsigned char colour, const char *string );
void write_string_line( unsigned char colour, const char *string, int pos );
void putChar(char character, short col, short row, unsigned char attr);
/* Place this at top of file as first code in kernel.o */
__asm__ ("call main\r\n" \
"cli\r\n" \
"hlt\r\n"
);
void main()
{
volatile unsigned char *vid = (unsigned char*) VIDEO_MEM;
int i=0;
for (i = 0; i < 2000; i++)
{
*vid = ' ';
*(vid+1) = 0x1f;
vid += 2;
}
write_string(0x1f,"The Kernel has been loaded successfully!!");
write_string_line(0x1f,"Testing Here!!",1);
putChar('Z',3,3,0xf3);
}
void write_string( unsigned char colour, const char *string ) {
volatile unsigned char *vid = (unsigned char*) VIDEO_MEM;
while(*string != 0)
{
*(vid) = *string;
*(vid+1) = colour;
++string;
vid+=2;
}
}
void write_string_line( unsigned char colour, const char *string, int pos ) {
volatile unsigned char *vid = (unsigned char*) VIDEO_MEM;
vid+=pos*160;
while(*string != 0)
{
*vid = *string;
*(vid+1) = colour;
++string;
vid+=2;
}
}
void putChar(char character, short col, short row, unsigned char attr) {
volatile unsigned char* vid_mem = (unsigned char *) VIDEO_MEM;
int offset = (row*80 + col)*2;
vid_mem += offset;
if(!attr) {
attr = 0x0f;
}
*(unsigned short int *)vid_mem = (attr<<8)+character;
/* This would do the same as line above
*vid_mem = character;
*(vid_mem+1) = attr;
*/
}
I've added the __asm__ at the beginning to make sure that code is the first to appear in the generated object file. It likely works without it. I've modified all your *vid pointers to be volatile . Since video is memory mapped IO you don't want to have the compiler potentially remove screen writes when it optimizes. Likely your code will work without volatile, but it is proper to add it here to avoid potential problems.
When run BOCHS this code produces this screen output:
If you use the code provided here and it doesn't work that would suggest the issue you are having is likely related to the a code you write in your bootloader that read the disk, enabled A20, set the GDT, entered protected mode, and then called into your C code. It is also possible problems could occur depending on how you compile and link your kernel.
Likely Cause of Undefined Behavior
After all the code and the make file were made available in EDIT 2 it became clear that one significant problem was that most of the code was compiled and linked to 64-bit objects and executables. That code won't work in 32-bit protected mode.
In the make file make these adjustments:
When compiling with GCC you need to add -m32 option
When assembling with GNU Assembler (as) targeting 32-bit objects you need to use --32
When linking with LD you need to add the -melf_i386 option
When assembling with NASM targeting 32-bit objects you need to change -f elf64 to -f elf32
A preferable option to using a 64-bit compiler and tool chain from the host environment is to create a cross compiler toolchain for i686 or i386.

This should work.
Each VGA cell is of 2 bytes long, First byte stores Character while the second byte stores the color.
Also make sure you make marked the pointer volatile. To avoid any type of unexpected changes(or optimizations) made by the compiler on that local field.
void write_string( int colour, const unsigned char *string )
{
volatile unsigned char *vid = (unsigned char*) VIDEO_MEM;
while( *string != 0 )
{
*vid++ = *string++;
*vid++ = colour;
}
}

You use *(vid) for first video character for color

Related

x86 nasm assembly - remove every nth character from string [duplicate]

This question already has answers here:
what is the difference between byte and dword in assembly and how to use them [duplicate]
(1 answer)
How to load a single byte from address in assembly
(1 answer)
Closed 3 months ago.
I am trying to implement a function in x86 nasm assembler which removes every n-th character from string. However, I am experiencing quite unexpected behaviour and don't really understand why it doesn't work.
// main.c
#include <stdio.h>
char *rem(char *s, int n);
int main(int argc, char *argv[])
{
char s[] = "abcabcabc";
int n = 3;
printf("%s\n", rem(s, n));
}
; rem.s
section .text
global rem
rem:
push ebp
mov ebp, esp
push esi
push edi
push ebx
mov eax, [ebp+8] ; read pointer
mov ecx, [ebp+8] ; write pointer
mov edx, [ebp+12] ; n
mov esi, 1 ; counter
loop:
cmp BYTE [eax], 0
jz fin
cmp edx, esi
jz remove
dont_remove:
; move current character to position
; pointed by the write pointer
mov edi, [eax]
mov [ecx], edi
inc ecx ; increase write pointer
inc eax ; increase read pointer
inc esi ; counter++
jmp loop
remove:
mov esi, 1 ; reset the counter
inc eax ; increase only read pointer
jmp loop
fin:
mov edi, [eax]
mov [ecx], edi
mov eax, [ebp+8]
pop ebx
pop edi
pop esi
pop ebp
ret
# Makefile
EXEFILE = main
OBJECTS = main.o rem.o
CCFMT = -m32
NASMFMT = -f elf32
CCOPT = -m32 -c
NASMOPT = -w+all
.c.o:
cc -g $(CCFMT) $(CCOPT) $<
.s.o:
nasm $(NASMFMT) $(NASMOPT) $<
$(EXEFILE): $(OBJECTS)
cc $(CCFMT) -o $(EXEFILE) $(OBJECTS)
clean:
rm *.o $(EXEFILE)
After running the code with command make && ./main I expected to see ababab (so that it removes all of the c's from "abcabcabc", in other way, removes every 3rd character). However it returns abacb. What is causing this issue? Thanks in advance for help

returning Assembly Function In C Code x86 Compilation on 64 bit linux

C code
#include <stdio.h>
int fibonacci(int);
int main()
{
int x = fibonacci(3);
printf("Fibonacci is : %d",x);
return 0;
}
Assembly
section .text
global fibonacci
fibonacci:
push ebp;
mov ebp, esp;
; initialize
mov dword [prev], 0x00000000;
mov dword [cur], 0x00000001;
mov byte [it], 0x01;
mov eax, dword [ebp + 8]; // n = 3
mov byte [n], al;
getfib:
xor edx,edx;
mov dl, byte [n];
cmp byte [it] , dl;
jg loopend;
mov eax,dword [prev];
add eax, dword [cur];
mov ebx, dword [cur];
mov dword [prev], ebx;
mov dword [cur] , eax;
inc byte [it];
jmp getfib;
loopend:
mov eax, dword [cur];
pop ebp;
ret;
section .bss
it resb 1
prev resd 1
cur resd 1
n resb 1
I was trying to run this assembly function in C code and on debugging , i saw that value in variable x in C code is right but there is some error coming when i use the printf function
Need Help on it
Command to compile:
nasm -f elf32 asmcode.asm -o a.o
gcc -ggdb -no-pie -m32 a.o ccode.c -o a.out
Click Below Pictures if they seem blurred
Below is debug before printf execute
Below is after printf execute
Your code does not preserve the ebx register which is a callee-preserved register. The main function apparently tries to do some rip-relative addressing to obtain the address of the format string for printf using ebx as a base register. This fails because your code overwrote ebx.
To fix this issue, make sure to save all callee-saved registers before you use them and then restore their value on return. For example, you can do
fibonacci:
push ebp
mov ebp, esp
push ebx ; <---
...
pop ebx ; <---
pop ebp
ret

How to make return in assembly?

Hi i have a very difficult question. I wrote a function in x64 assembly and then i wanted to use it in my main.c. I included the assembly function via header file.
main.c:
#include <stdio.h>
#include "myunistd.h"
#include "mystddef.h"
int main(){
mywrite(1, "Test", 4);
}
myunistd.h:
#ifndef MYUNISTD_H
#define MYUNISTD_H
struct myrusage {};
ssize_t spastwrite(int fd, const void *buf, size_t count);
#endif
myunistd.s:
global mywrite
section .text
mywrite:
mov r8, rdi ; parameters in registers only for testing
mov r9, rsi
mov r10, rdx
mov rax, 1 ; write(
mov rdi, r8 ; STDOUT_FILENO,
mov rsi, r9 ; String,
mov rdx, r10 ; sizeof String
syscall ; );
mov rax, 60 ; exit(
mov rdi, 0 ; EXIT_SUCCESS
syscall ; );
The function works but i have no return argument like in the original write syscall. Also have the warning "implicit declaration of function 'mywrite' is invalid in C99".
Also good to know for you i defined size_t as unsigned long in mystddef.h.
I dont know. Hope you can help :)
Compiled with:nasm -f elf64 myunistd.s -o myunistd.o
clang main.c myunistd.o -o exe
Thx
Instead of using the exit syscall, use ret:
global mywrite
section .text
mywrite:
mov r8, rdi ; parameters in registers only for testing
mov r9, rsi
mov r10, rdx
mov rax, 1 ; write(
mov rdi, r8 ; STDOUT_FILENO,
mov rsi, r9 ; String,
mov rdx, r10 ; sizeof String
syscall ; );
ret
In your myunistd.h, you're declaring spastwrite and not mywrite. Change this:
ssize_t spastwrite(int fd, const void *buf, size_t count);
to this:
ssize_t mywrite(int fd, const void *buf, size_t count);
and it will work.

Any attempt to put a string to the screen in Protected Mode causes reboot

I have just recently gone into Protected Mode when developing an OS from scratch. I have managed to get into C and make functions to print characters to the screen (thanks Michael Petch for helping me reach this stage). Anyway, whenever I try to make a routine that loops through a string literal and prints every character in it, well, there's a bit of a problem. QEMU just goes into a boot loop, restarts again and again, and I am never able to see my beautiful green-on-black video mode. If I move this out of a routine and print it character-by-character in the kmain() function (that part of which I have removed), everything works fine and dandy. Here's the file where I try to implement a string printing function:
vga.c -
#include <vga.h>
size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;
uint16_t *terminal_buffer;
volatile uint16_t * const VIDMEM = (volatile uint16_t *) 0xB8000;
size_t strlen(const char *s)
{
size_t len = 0;
while(s[len]) {
len++;
}
return len;
}
void terminal_init(void)
{
terminal_row = 0;
terminal_column = 0;
terminal_color = vga_entry_color(LGREEN, BLACK);
for(size_t y = 0; y < VGA_HEIGHT; y++) {
for(size_t x = 0; x < VGA_WIDTH; x++) {
const size_t index = y * VGA_WIDTH + x;
VIDMEM[index] = vga_entry(' ', terminal_color);
}
}
}
void terminal_putentryat(char c, uint8_t color, size_t x, size_t y)
{
const size_t index = y * VGA_WIDTH + x;
VIDMEM[index] = vga_entry(c, color);
}
void terminal_putchar(char c)
{
terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
if(++terminal_column == VGA_WIDTH) {
terminal_column = 0;
if(++terminal_row == VGA_HEIGHT) {
terminal_row = 0;
}
}
}
void terminal_puts(const char *s)
{
size_t n = strlen(s);
for (size_t i=0; i < n; i++) {
terminal_putchar(s[i]);
}
}
I read my kernel into memory with this bootloader code:
extern kernel_start ; External label for start of kernel
global boot_start ; Make this global to suppress linker warning
bits 16
boot_start:
xor ax, ax ; Set DS to 0. xor register to itselfzeroes register
mov ds, ax
mov ss, ax ; Stack just below bootloader SS:SP=0x0000:0x7c00
mov sp, 0x7c00
mov ah, 0x00
mov al, 0x03
int 0x10
load_kernel:
mov ah, 0x02 ; call function 0x02 of int 13h (read sectors)
mov al, 0x01 ; read one sector (512 bytes)
mov ch, 0x00 ; track 0
mov cl, 0x02 ; sector 2
mov dh, 0x00 ; head 0
; mov dl, 0x00 ; drive 0, floppy 1. Comment out DL passed to bootloader
xor bx, bx ; segment 0x0000
mov es, bx ; segments must be loaded from non immediate data
mov bx, 0x7E00 ; load the kernel right after the bootloader in memory
.readsector:
int 13h ; call int 13h
jc .readsector ; error? try again
jmp 0x0000:kernel_start ; jump to the kernel at 0x0000:0x7e00
I have an assembly stub at the start of my kernel that enters protected mode, zeroes the BSS section, issues a CLD and calls into my C code:
; These symbols are defined by the linker. We use them to zero BSS section
extern __bss_start
extern __bss_sizel
; Export kernel entry point
global kernel_start
; This is the C entry point defined in kmain.c
extern kmain ; kmain is C entry point
bits 16
section .text
kernel_start:
cli
in al, 0x92
or al, 2
out 0x92, al
lgdt[toc]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp 0x08:start32 ; The FAR JMP is simplified since our segment is 0
section .rodata
gdt32:
dd 0
dd 0
dw 0x0FFFF
dw 0
db 0
db 0x9A
db 0xCF
db 0
dw 0x0FFFF
dw 0
db 0
db 0x92
db 0xCF
db 0
gdt_end:
toc:
dw gdt_end - gdt32 - 1
dd gdt32 ; The GDT base is simplified since our segment is now 0
bits 32
section .text
start32:
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x9c000 ; Set the stack to grow down from area under BDA/Video memory
; We need to zero out the BSS section. We'll do it a DWORD at a time
cld
lea edi, [__bss_start] ; Start address of BSS
lea ecx, [__bss_sizel] ; Lenght of BSS in DWORDS
xor eax, eax ; Set to 0x00000000
rep stosd ; Do clear using string store instruction
call kmain
I have a specialized linker script that places the bootloader at 0x7c00 and the kernel at 0x7e00.
What's the problem and how can I fix it? I've made my git repo available if more information is needed.
TL;DR : You haven't read your entire kernel into memory with your bootloader in start.asm. Missing code and/or data is causing your kernel to crash with a triple fault which results in a reboot. You will need to read more sectors as your kernel grows.
I noticed that your generated lunaos.img is larger than 1024 bytes. The bootloader is 512 bytes, and the kernel after it is slightly more than 512 bytes. That means the kernel now spans multiple sectors. In your kernel.asm you load a single 512-byte sector with this code:
load_kernel:
mov ah, 0x02 ; call function 0x02 of int 13h (read sectors)
mov al, 0x18 ; read one sector (512 bytes)
mov ch, 0x00 ; track 0
mov cl, 0x02 ; sector 2
mov dh, 0x00 ; head 0
; mov dl, 0x00 ; drive 0, floppy 1. Comment out DL passed to bootloader
xor bx, bx ; segment 0x0000
mov es, bx ; segments must be loaded from non immediate data
mov bx, 0x7E00 ; load the kernel right after the bootloader in memory
.readsector:
int 13h ; call int 13h
jc .readsector ; error? try again
In particular:
mov al, 0x01 ; read one sector (512 bytes)
This is at the heart of your problem. Since you are booting as a floppy I'd recommend generating a 1.44MiB file and placing your bootloader and kernel in it with:
dd if=/dev/zero of=bin/lunaos.img bs=1024 count=1440
dd if=bin/os.bin of=bin/lunaos.img bs=512 conv=notrunc seek=0
The first command makes a 1.44MiB file filled with zeros. The second uses conv=notrunc to tell DD not truncate the file after writing. seek=0 tells DD to start writing at the first logical sector in the file. The result would be that os.bin is placed inside of a 1.44MiB image starting at logical sector 0 without truncating the original file when finished.
A properly sized disk image of a known floppy disk size makes it easier to use in some emulators.
A 1.44MiB floppy has 36 sectors per track (18 sectors per head, 2 heads per track). If you run your code on real hardware, some BIOSes may not load across a track boundary. You're likely safe reading 35 sectors with your disk read. The first sector was read by the BIOS off track 0 head 0. There are 35 more sectors on the first track. I'd amend the line above to be:
mov al, 35 ; read 35 sectors (35*512 = 17920 bytes)
This would allow your kernel to be 35*512 bytes long = 17920 bytes with minimum hassles even on real hardware. Any larger than that you will have to consider modifying your bootloader with a loop that attempts to read more than one track. To complicate matters you'd have to concern yourself that larger kernels will eventually exceed the 64k segment limit. The disk reads would probably have to be modified to use a segment (ES) that isn't 0. If your kernel gets that large your bootloader can be fixed at that time.
Debugging
Since you are in protected mode and using QEMU, I highly suggest you consider using a debugger. QEMU supports remote debugging with GDB. It's not difficult to set up and since you have generated a ELF executable of your kernel you also can use symbolic debugging.
You will want to add -Fdwarf to your NASM assembly commands right after -felf32 to enable debug information. Add the -g option to your GCC commands to enable debug information. The command below should start up your bootloader/kernel; automatically break on kmain; use os.elffor debug symbols; and display the source code and registers in the terminal.
qemu-system-i386 -fda bin/lunaos.img -S -s &
gdb bin/os.elf \
-ex 'target remote localhost:1234' \
-ex 'layout src' \
-ex 'layout regs' \
-ex 'break *kmain' \
-ex 'continue'
There are many tutorials on using GDB if you search with Google. There is a cheat sheet that describes most of the basic commands and their syntax.
If you ever find yourself in the future having troubles with Interrupts, GDT or paging I recommend using Bochs for debugging those aspects of an operating system. Although Bochs doesn't have a symbolic debugger it makes up for in being able to identify low level problems more easily than QEMU. Debugging real mode code like bootloaders is easier in Bochs given that it understands 20 bit segment:offset addressing unlike QEMU

Global variables not working in C kernel .rdata section

I don't know why global variables are not working. I think that there is a problem with the address of .rdata section. I don't know how to link my kernel to make this section visible at specific memory area.
I'm using Windows 7
Note: I dont want to use GRUB Bootloader, instead I want to use my own bootloader.
This is my files:
bootsect.asm
[org 0x7c00]
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
mov bp, 0x9000
mov sp, bp
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
mov dh, 2
mov dl, [BOOT_DRIVE]
pusha
; reading from disk requires setting specific values in all registers
; so we will overwrite our input parameters from 'dx'. Let's save it
; to the stack for later use.
push dx
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
; 0x01 is our boot sector, 0x02 is the first 'available' sector
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
; [es:bx] <- pointer to buffer where the data will be stored
; caller sets it up for us, and it is actually the standard location for int 13h
int 0x13 ; BIOS interrupt
jc $ ; if error (stored in the carry bit)
pop dx
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
jne $
popa
;call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
cli ; 1. disable interrupts
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
mov eax, cr0
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
mov cr0, eax
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
jmp $ ; Never executed
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
; the GDT starts with a null 8-byte
dd 0x0 ; 4 byte
dd 0x0 ; 4 byte
; GDT for code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code:
dw 0xffff ; segment length, bits 0-15
dw 0x0 ; segment base, bits 0-15
db 0x0 ; segment base, bits 16-23
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0 ; segment base, bits 24-31
; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
dd gdt_start ; address (32 bit)
; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
[bits 32] ; using 32-bit protected mode
; this is how constants are defined
VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f ; the color byte for each character
[bits 32]
init_pm: ; we are now using 32-bit instructions
mov ax, DATA_SEG ; 5. update the segment registers
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
;mov ax, 11c4h
mov ds, ax
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
mov esp, ebp
call KERNEL_OFFSET ; Give control to the kernel
jmp $ ; Stay here when the kernel returns control to us (if ever)
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
; padding
times 510 - ($-$$) db 0
dw 0xaa55
kernel.c
void write_string( int colour, const char *string )
{
volatile char *video = (volatile char*)0xB8000;
while( *string != 0 )
{
*video++ = *string++;
*video++ = colour;
}
}
void ClearScreen()
{
char* video = (char*) 0xB8000;
int i = 0;
while(i < 80*25)
{
*video++ = 0;
*video++ = 0;
i++;
}
}
char* str1 = "from global";
void start()
{
ClearScreen();
char str2[] = "from stack";
write_string(7, str1); //This is not working
write_string(7, str2); //This is working but I don't want that
}
kernel_entry.asm
[bits 32]
[extern _start]
call _start
jmp $
Makefile
GCC = gcc.exe
NASM = nasm.exe
LD = ld.exe
OBJCOPY = objcopy.exe
QEMU = qemu-system-i386.exe
build:
$(GCC) -ffreestanding -c kernel.c -o kernel.o
$(NASM) kernel_entry.asm -f elf -o kernel_entry.o
$(LD) -T NUL -o kernel.tmp -Ttext 0x1000 kernel_entry.o kernel.o
$(OBJCOPY) -O binary -j .text kernel.tmp kernel.bin
$(NASM) bootsect.asm -f bin -o bootsect.bin
copy /b bootsect.bin + kernel.bin os-image.bin
$(QEMU) -fda os-image.bin
clean:
del kernel.o
del kernel_entry.o
del kernel.tmp
del kernel.bin
del bootsect.bin
del os-image.bin
If you want to write your own bootloader and startup code of your application, the copy of rdata (or rodata) to your memory must be performed by your init code.
Same thing for bss section, is your startup code that has the responsability to reset to 0 the whole memory where global variables of that section are placed.

Resources