Small model DOS .exe compiled and linked by OpenWatcom crashes - c

I'm trying to create a small DOS .exe program. I wrote the entry point in NASM assembly
; st.nasm
global _small_code_
global _printmsg_
extern _main0_
segment code
_small_code_:
..start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, stacktop
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
segment data
hello_msg: db 'Hello, World!', 13, 10, '$'
segment stack stack
resb 1024
stacktop:
Please note that I'm not sure how the ..start: code and the segments should look like, I copy-pasted that part from somewhere.
I wrote the main program in C:
/* prog.c */
void _printmsg(const char *msg);
int add(int a, int b) {
return a + b * 2;
}
void other() {
_printmsg("Hello!\r\n$"); /*CRASH*/
/*_printmsg(0);*/ /*OK*/
}
int _main0() {
return 5;
}
I compile it with this:
$ nasm -f obj -o st.obj st.nasm
$ owcc -bdos -mcmodel=s -fno-stack-check -Os -s -march=i86 -o prog.exe prog.c st.obj
The resulting prog.exe is:
$ xxd prog.exe
00000000: 4d5a 8b00 0100 0200 0300 4000 ffff 0100 MZ........#.....
00000010: 4b04 0000 0a00 0100 2000 0000 0000 0000 K....... .......
00000020: 0b00 0100 1000 0100 0000 0000 0000 0000 ................
00000030: d1e2 01d0 c3b8 0000 e934 00b8 0500 c300 .........4......
00000040: 4865 6c6c 6f21 0d0a 2400 b801 008e d8b8 Hello!..$.......
00000050: 0100 8ed0 bc4b 04b4 09ba 3b00 cd21 e8da .....K....;..!..
00000060: ff89 c281 c23b 00b4 09cd 21b4 4ccd 21c3 .....;....!.L.!.
00000070: 5292 b409 ba3b 00cd 215a c348 656c 6c6f R....;..!Z.Hello
00000080: 2c20 576f 726c 6421 0d0a 24 , World!..$
Disassembly of prog.exe:
$ ndisasm -e 0x20 -b 16 prog.exe
00000000 0B00 or ax,[bx+si]
00000002 0100 add [bx+si],ax
00000004 1000 adc [bx+si],al
00000006 0100 add [bx+si],ax
00000008 0000 add [bx+si],al
0000000A 0000 add [bx+si],al
0000000C 0000 add [bx+si],al
0000000E 0000 add [bx+si],al
00000010 D1E2 shl dx,1
00000012 01D0 add ax,dx
00000014 C3 ret
00000015 B80000 mov ax,0x0
00000018 E93400 jmp 0x4f
0000001B B80500 mov ax,0x5
0000001E C3 ret
0000001F 004865 add [bx+si+0x65],cl
00000022 6C insb
00000023 6C insb
00000024 6F outsw
00000025 210D and [di],cx
00000027 0A24 or ah,[si]
00000029 00B80100 add [bx+si+0x1],bh
0000002D 8ED8 mov ds,ax
0000002F B80100 mov ax,0x1
00000032 8ED0 mov ss,ax
00000034 BC4B04 mov sp,0x44b
00000037 B409 mov ah,0x9
00000039 BA3B00 mov dx,0x3b
0000003C CD21 int 0x21
0000003E E8DAFF call 0x1b
00000041 89C2 mov dx,ax
00000043 81C23B00 add dx,0x3b
00000047 B409 mov ah,0x9
00000049 CD21 int 0x21
0000004B B44C mov ah,0x4c
0000004D CD21 int 0x21
0000004F C3 ret
00000050 52 push dx
00000051 92 xchg ax,dx
00000052 B409 mov ah,0x9
00000054 BA3B00 mov dx,0x3b
00000057 CD21 int 0x21
00000059 5A pop dx
0000005A C3 ret
0000005B 48 dec ax
0000005C 656C gs insb
0000005E 6C insb
0000005F 6F outsw
00000060 2C20 sub al,0x20
00000062 57 push di
00000063 6F outsw
00000064 726C jc 0xd2
00000066 64210D and [fs:di],cx
00000069 0A24 or ah,[si]
prog.exe puts DOSBox to an infinite loop. Oddly enough, if I remove the string literal from the C source file (in the other function, which isn't even called), it successfully returns. What's wrong in the assembly file?
Please note that this is the first time I'm using OpenWatcom, and this is the first time I build a DOS .exe file.
I don't want to write a main function, because that would cause the OpenWatcom libc to be linked to the output executable, making it unnecessarily large.

The primary problem is in how you define the code segment. The Watcom C/C++ compiler when using the SMALL memory model requires the code segment to be called _TEXT with a class of CODE. This mismatch between the assembly code and the C code leads to the code segment being in different physical segments and the call _main0_ jumping to the wrong place in memory causing exceptions to be thrown and the program hanging or crashing.
You can also get the Watcom linker to generate the required STACK in the DOS EXE by creating a segment called _STACK with attribute STACK and class STACK. If you create the stack segment this way, you won't need to initialize SS:SP at the beginning of your program.
The other sections that Watcom uses in the SMALL memory model are:
_DATA segment with a class of DATA for read/write data
CONST segment with a class of DATA for string literals (that aren't expected to be modified)
CONST2 segment with a class of DATA for other read only data
_BSS segment with class of BSS for uninitialized data.
Watcom expects that the segments CONST, CONST2, _DATA and _BSS to all be in the same group called DGROUP. All the data in the same group can be referenced by the name of the group. When you set up DGROUP in the way Watcom expects then all you have to do is initialize DS to the DGROUP segment and not the individual segments within the group.
The special label that starts with .. acts as the DOS entry point where execution should start. Thus ..start is used to generate an entry point in the DOS EXE header telling the program loader where to start executing when the program is loaded into memory.
A revised version of your assembly code could have looked like this:
; st.nasm
; DGROUP in watcom C/C++ for small model is:
GROUP DGROUP CONST CONST2 _DATA _BSS
global _small_code_
global _printmsg_
extern _main0_
; Code Segment (16-bit code)
segment _TEXT use16 class=CODE
_small_code_:
; .. denotes the label to be used as the DOS entry point
..start:
mov ax, DGROUP
mov ds, ax
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
; Read only string literals here
segment CONST class=DATA
hello_msg: db 'Hello, World!', 13, 10, '$'
; Other read only data here
segment CONST2 class=DATA
; Read/Write data here
segment _DATA class=DATA
; Uninitialized data segment
segment _BSS class=BSS
; Stack segment 1k in size
segment _STACK STACK class=STACK
resb 1024
This code assumes that SS != DS, however it would have to be compiled with OWCC's option -Wc,-zu that passes the -zu to WCC (Watcom Compiler). -zu modifies code generation so that:
-zu SS != DGROUP (i.e., do not assume stack is in data segment)
If you wish to set SS==DS==DGROUP there are a number of ways to do it. One option I may suggest is putting _STACK in DGROUP with all the other program data. You would need a label after resb 1024 like stack_top: so you can load that offset into SP at startup after you set SS to the same value as DS. This change would result in assembly code that looks like:
; st.nasm
; DGROUP in watcom C/C++ for small model is:
GROUP DGROUP CONST CONST2 _DATA _BSS _STACK
; _STACK has been added to DGROUP so we can set SS==DS==DGROUP
global _small_code_
global _printmsg_
extern _main0_
; Code Segment (16-bit code)
segment _TEXT use16 class=CODE
_small_code_:
; .. denotes the label to be used as the DOS entry point
..start:
mov ax, DGROUP
mov ds, ax
mov ss, ax ; Set stack SS:SP to DGROUP:stack_top
mov sp, stack_top
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
; Read/Write data here
segment _DATA class=DATA
; Read only string literals here
segment CONST class=DATA
hello_msg: db 'Hello, World!', 13, 10, '$'
; Other read only data here
segment CONST2 class=DATA
; Uninitialized data segment
segment _BSS class=BSS
; Stack segment 1k in size
segment _STACK STACK class=STACK
resb 1024
stack_top:

Related

My bootloader works, but doesn't load my kernel into memory

I've looked more into creating my own bootloader, rather than using grub. I soon came up with this: It takes care of switching to 32bit pm, it loads my kernel from the disk & it jumps to it to execute it.
I'm catting my kernel & my bootloader like this: cat boot.bin kernel > img.bin
I'm assembling my bootloader like this: nasm -f bin boot.s -o boot.bin
i686-elf-ld -o kernel -Ttext=0x1000 kernel_entry.bin kernel.bin --oformat binary
I'm compiling my kernel like this: i686-elf-gcc *.o -Ttext=0x1000 -o kernel.bin -ffreestanding -O2 -nostdlib -lgcc
(*.o are all compiled C files which I compile like this: i686-elf-gcc -c file.c -o file.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra
[org 0x7c00]
[bits 16]
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00
jmp 0:skip ; far jump
skip:
; load kernel
mov bx, 0x1000
mov dh, 17 ; reading 20 sectors should be enough ._.
mov dl, [BOOT_DRIVE]
call dsk_load
call load_kernel
dsk_load:
mov [SECTORS], dh
mov ch, 0x00 ; C = 0
mov dh, 0x00 ; H = 0
mov cl, 0x02 ; S = 2
next_group:
mov di, 5 ; retry 5 times
again:
mov ah, 0x02
mov al, [SECTORS]
int 0x13
jc maybe_retry
sub [SECTORS], al ; set remaining sectors
jz done
mov cl, 0x01 ; read sector 1
xor dh, 1 ; next head
jnz next_group
inc ch ; next cylinder
jmp next_group
maybe_retry:
mov ah, 0x00 ; reset drive
int 0x13
dec di
jnz again
jmp dsk_err ; we've tried too many times, give up
dsk_err:
mov bx, BOOTLOADER_SIG
call print
mov bx, DISK_READ_FAIL
call print
jmp $
done:
ret
; print string
print:
; print loop
print_loop:
mov ah, 0x0e
mov al, [bx] ; load current character
cmp al, 0
je print_return ; return when finished
int 0x10 ; print character
inc bx ; next character
jmp print_loop
print_return:
ret
load_kernel:
; If all that went well, we can switch to protected mode
cli
lgdt [gdt_descriptor]
mov eax, cr0
or eax, 0x1
mov cr0 , eax
jmp CODE_SEG:init_32_pm ; make a far jump
[bits 32]
init_32_pm:
set_up_stack:
mov esp, stack_end
mov ax, DATA_SEG
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
jmp 0x1000 ; jump to kernel_entry.s
; our beloved gdt
gdt_start:
gdt_null: ; null descriptor
dd 0x0
dd 0x0
gdt_code: ; code segment descriptor
dw 0xffff ; limit (bits 0-15)
dw 0x0 ; base (bits 0-15)
db 0x0 ; base (bits 16-23)
db 10011010b ; 1st flags, type flags
db 11001111b ; 2nd flags, Limit (bits 16-19)
db 0x0 ; base (bits 24 - 31)
gdt_data: ; data segment descriptor
dw 0xffff ; limit (bits 0-15)
dw 0x0 ; base (bits 0-15)
db 0x0 ; base (bits 16 -23)
db 10010010b ; 1st flags, type flags
db 11001111b ; 2nd flags, Limit (bits 16-19)
db 0x0 ; base (bits 24 - 31)
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; gdt size
dd gdt_start ; gdt start address
; some handy constants
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
BOOT_DRIVE db 0
SECTORS db 0
BOOTLOADER_SIG db "------ bootloader ------", 0x0d, 0xa, 0
DISK_READ_FAIL db "An error occurred while loading the kernel! Please restart your computer.", 0x0d, 0xa, 0
times 510-($-$$) db 0
dw 0xaa55
section .bss
stack_begin:
resb 4096 ; 4kib stack
stack_end:
; 9 sectors
The code that resides over at 0x1000 is this:
; kernel_entry.s
[bits 32]
[extern kmain]
call kmain
jmp $
times 510-($-$$) db 0
dw 0xaa55
; 1 sectors
My bootloader doesn't crash but it does not load my kernel, which should print some things to the screen.
This is the kmain function:
void kmain(void)
{
/* Initialize terminal */
tty_init();
tty_puts("Hello kernel!", VGA_COLOR_LIGHT_CYAN);
}
Assume the tty functions are working, since they were doing just fine when testing with grub instead of my own bootloader. Does anyone know what's going on? (Testing in bochs shows no errors)

C kernel variable data is a sequence of random bytes in long mode x86

I am trying to write a simple OS just for fun and somewhat practice. I've worked in the real mode before but I decided to move on and try playing with protected mode to have an opportunity to use C rather than plain assembly. I have copied bootloader code that seemed to work and it seems to me that basically all it does is just goes into long mode and of course starts the kernel. So it works fine until you declare a variable in the code, because if you do, QEMU will output what I believe is a chunk of memory translated to ASCII.
So the bootloader code itself:
[org 0x7c00]
KERNEL_ADDRESS equ 0x100000
cli
lgdt [gdt_descriptor]
;Switch to PM
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp 0x8:init_pm
[bits 32]
init_pm :
mov ax, 0x10
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
call build_page_tables
;Enable PAE
mov eax, cr4
or eax, 1 << 5
mov cr4, eax
;# Optional : Enable global-page mechanism by setting CR0.PGE bit to 1
mov eax, cr4
or eax, 1 << 7
mov cr4, eax
;Load CR3 with PML4 base address
;NB: in some examples online, the address is not offseted as it seems to
;be in the proc datasheet (if you were wondering about this strange thing).
mov eax, 0x1000
mov cr3, eax
;Set LME bit in EFER register (address 0xC0000080)
mov ecx, 0xC0000080 ;operand of 'rdmsr' and 'wrmsr'
rdmsr ;read before pr ne pas écraser le contenu
or eax, 1 << 8 ;eax : operand de wrmsr
wrmsr
;Enable paging by setting CR0.PG bit to 1
mov eax, cr0
or eax, (1 << 31)
mov cr0, eax
;Load 64-bit GDT
lgdt [gdt64_descriptor]
;Jump to code segment in 64-bit GDT
jmp 0x8:init_lm
[bits 64]
init_lm:
mov ax, 0x10
mov fs, ax ;other segments are ignored
mov gs, ax
mov rbp, 0x90000 ;set up stack
mov rsp, rbp
;Load kernel from disk
xor ebx, ebx ;upper 2 bytes above bh in ebx is for cylinder = 0x0
mov bl, 0x2 ;read from 2nd sectors
mov bh, 0x0 ;head
mov ch, 2 ;read 2 sectors
mov rdi, KERNEL_ADDRESS
call ata_chs_read
jmp KERNEL_ADDRESS
jmp $
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[bits 16]
;; http://wiki.osdev.org/ATA_in_x86_RealMode_%28BIOS%29
;load_loader:
;;!! il faut rester sur le meme segment, ie <0x10000 (=2**16)
;mov bx, LOADER_OFFSET
;mov dh, 1 ;load 1 sector (max allowed by BIOS is 128)
;mov dl, 0x80 ;drive number
;mov ah, 0x02 ;read function
;mov al, dh
;mov ch, 0x00 ;cylinder
;mov dh, 0x00 ;head
;; !! Sector is 1-based, and not 0-based
;mov cl, 0x02 ;1st sector to read
;int 0x13
;ret
[bits 32]
build_page_tables:
;PML4 starts at 0x1000
;il faut laisser la place pour tte la page PML4/PDP/PD ie. 0x1000
;PML4 # 0x1000
mov eax, 0x2000 ;PDP base address
or eax, 0b11 ;P and R/W bits
mov ebx, 0x1000 ;MPL4 base address
mov [ebx], eax
;PDP # 0x2000; maps 64Go
mov eax, 0x3000 ;PD base address
mov ebx, 0x2000 ;PDP physical address
mov ecx, 64 ;64 PDP
build_PDP:
or eax, 0b11
mov [ebx], eax
add ebx, 0x8
add eax, 0x1000 ;next PD page base address
loop build_PDP
;PD # 0x3000 (ends at 0x4000, fits below 0x7c00)
; 1 entry maps a 2MB page, the 1st starts at 0x0
mov eax, 0x0 ;1st page physical base address
mov ebx, 0x3000 ;PD physical base address
mov ecx, 512
build_PD:
or eax, 0b10000011 ;P + R/W + PS (bit for 2MB page)
mov [ebx], eax
add ebx, 0x8
add eax, 0x200000 ;next 2MB physical page
loop build_PD
;(tables end at 0x4000 => fits before Bios boot sector at 0x7c00)
ret
;=============================================================================
; ATA read sectors (CHS mode)
; Max head index is 15, giving 16 possible heads
; Max cylinder index can be a very large number (up to 65535)
; Sector is usually always 1-63, sector 0 reserved, max 255 sectors/track
; If using 63 sectors/track, max disk size = 31.5GB
; If using 255 sectors/track, max disk size = 127.5GB
; See OSDev forum links in bottom of [http://wiki.osdev.org/ATA]
;
; #param EBX The CHS values; 2 bytes, 1 byte (BH), 1 byte (BL) accordingly
; #param CH The number of sectors to read
; #param RDI The address of buffer to put data obtained from disk
;
; #return None
;=============================================================================
[bits 64]
ata_chs_read: pushfq
push rax
push rbx
push rcx
push rdx
push rdi
mov rdx,1f6h ;port to send drive & head numbers
mov al,bh ;head index in BH
and al,00001111b ;head is only 4 bits long
or al,10100000b ;default 1010b in high nibble
out dx,al
mov rdx,1f2h ;Sector count port
mov al,ch ;Read CH sectors
out dx,al
mov rdx,1f3h ;Sector number port
mov al,bl ;BL is sector index
out dx,al
mov rdx,1f4h ;Cylinder low port
mov eax,ebx ;byte 2 in ebx, just above BH
mov cl,16
shr eax,cl ;shift down to AL
out dx,al
mov rdx,1f5h ;Cylinder high port
mov eax,ebx ;byte 3 in ebx, just above byte 2
mov cl,24
shr eax,cl ;shift down to AL
out dx,al
mov rdx,1f7h ;Command port
mov al,20h ;Read with retry.
out dx,al
.still_going: in al,dx
test al,8 ;the sector buffer requires servicing.
jz .still_going ;until the sector buffer is ready.
mov rax,512/2 ;to read 256 words = 1 sector
xor bx,bx
mov bl,ch ;read CH sectors
mul bx
mov rcx,rax ;RCX is counter for INSW
mov rdx,1f0h ;Data port, in and out
rep insw ;in to [RDI]
pop rdi
pop rdx
pop rcx
pop rbx
pop rax
popfq
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 16]
GDT:
;null :
dd 0x0
dd 0x0
;code :
dw 0xffff ;Limit
dw 0x0 ;Base
db 0x0 ;Base
db 10011010b ;1st flag, Type flag
db 11001111b ;2nd flag, Limit
db 0x0 ;Base
;data :
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_descriptor :
dw $ - GDT - 1 ;16-bit size
dd GDT ;32-bit start address
[bits 32]
;see manual 2, §4.8: most fields are ignored in long mode
GDT64:
;null;
dq 0x0
;code
dd 0x0
db 0x0
db 0b10011000
db 0b00100000
db 0x0
;data
dd 0x0
db 0x0
db 0b10010000
db 0b00000000
db 0x0
gdt64_descriptor :
dw $ - GDT64 - 1 ;16-bit size
dd GDT64 ;32-bit start address
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 16]
times 510 -($-$$) db 0
dw 0xaa55
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I've played around with the code a little bit and simplified it to this state:
void putc(uchar_t __char, uint8_t pos) {
uint16_t* vidmemory = (uint16_t*) 0xB8000;
vidmemory[pos] = (uint16_t)__char | (uint16_t)0x0F << 8;
}
const char* hw = "Hello, World!";
void kmain() {
for (uint8_t i = 0; i < 14; ++i) {
putc(hw[i], i);
}
for (;;);
return;
}
I had the same problem with the real mode after loading up the kernel, because I forgot to set the Data Segment, but as far as I am concerned segments are almost obsolete in the long mode.
Update
After debugging I, as was expected, found out that values of hw were some random bytes from memory. It looks like this:
(gdb) x/14bc hw
0x0 <putc>: 83 &apos;S&apos; -1 &apos;\377&apos; 0 &apos;\000&apos; -16 &apos;\360&apos; 83 &apos;S&apos; -1 &apos;\377&apos; 0 &apos;\000&apos; -16 &apos;\360&apos;
0x8 <putc+8>: -61 &apos;\303&apos; -30 &apos;\342&apos; 0 &apos;\000&apos; -16 &apos;\360&apos; 83 &apos;S&apos; -1 &apos;\377&apos;
I build my image with this script, which also uses "loader.s", but essentially all it does is just calls the kernel itself:
loader.s:
[bits 64]
extern kmain
global _start
_start:
call kmain
jmp $
And the build.sh:
#!/bin/bash
nasm -f bin bootload.s -o boot.bin
nasm -f elf64 loader.s -o loader.o
cc -m64 -masm=intel -c kernel.c -ffreestanding -Wall -Wextra -g -O2
ld -Ttext 0x100000 -o kernel.elf loader.o kernel.o -e kmain
objcopy -R .note -R .comment -S -O binary kernel.elf kernel.bin
dd if=/dev/zero of=image.bin bs=512 count=2880
dd if=boot.bin of=image.bin conv=notrunc
dd if=kernel.bin of=image.bin conv=notrunc bs=512 seek=1
There are probably other problems with my code, but for now I believe it's the matter of the data segment, which I haven't set.
Screenshot of QEMU

kernel.c:(.text+0x2d): undefined reference to `memset'

I am writing a custom kernel, but I am getting an error when linking. I have taken the code and broken it down, but still no luck. Here's my code:
kernel.c:
#include <string.h>
// define our structure
typedef struct __attribute__ ((packed)) {
unsigned short di, si, bp, sp, bx, dx, cx, ax;
unsigned short gs, fs, es, ds, eflags;
} regs16_t;
// tell compiler our int32 function is external
extern void int32(unsigned char intnum, regs16_t *regs);
// int32 test
void int32_test()
{
int y;
regs16_t regs;
// switch to 320x200x256 graphics mode
regs.ax = 0x0013;
int32(0x10, &regs);
// full screen with blue color (1)
memset((char *)0xA0000, 1, (320*200));
// draw horizontal line from 100,80 to 100,240 in multiple colors
for(y = 0; y < 200; y++)
memset((char *)0xA0000 + (y*320+80), y, 160);
// wait for key
regs.ax = 0x0000;
int32(0x16, &regs);
// switch to 80x25x16 text mode
regs.ax = 0x0003;
int32(0x10, &regs);
}
kernel.asm:
[bits 32]
global int32, _int32
struc regs16_t
.di resw 1
.si resw 1
.bp resw 1
.sp resw 1
.bx resw 1
.dx resw 1
.cx resw 1
.ax resw 1
.gs resw 1
.fs resw 1
.es resw 1
.ds resw 1
.ef resw 1
endstruc
%define INT32_BASE 0x7C00
%define REBASE(x) (((x) - reloc) + INT32_BASE)
%define GDTENTRY(x) ((x) << 3)
%define CODE32 GDTENTRY(1)
%define DATA32 GDTENTRY(2)
%define CODE16 GDTENTRY(3)
%define DATA16 GDTENTRY(4)
%define STACK16 (INT32_BASE - regs16_t_size)
extern int32_test
global start
section .text
align 4
dd 0x1BADB002
dd 0x00
dd - (0x1BADB002 + 0x00)
int32: use32
_int32:
cli
pusha
mov esi, reloc
mov edi, INT32_BASE
mov ecx, (int32_end - reloc)
cld
rep movsb
jmp INT32_BASE
reloc: use32
mov [REBASE(stack32_ptr)], esp
sidt [REBASE(idt32_ptr)]
sgdt [REBASE(gdt32_ptr)]
lgdt [REBASE(gdt16_ptr)]
lea esi, [esp+0x24]
lodsd
mov [REBASE(ib)], al
mov esi, [esi]
mov edi, STACK16
mov ecx, regs16_t_size
mov esp, edi
rep movsb
jmp word CODE16:REBASE(p_mode16)
p_mode16: use16
mov ax, DATA16
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, ~0x01
mov cr0, eax
jmp word 0x0000:REBASE(r_mode16)
r_mode16: use16
xor ax, ax
mov ds, ax
mov ss, ax
lidt [REBASE(idt16_ptr)]
mov bx, 0x0870
call resetpic
popa
pop gs
pop fs
pop es
pop ds
sti
db 0xCD
ib: db 0x00
cli
xor sp, sp
mov ss, sp
mov sp, INT32_BASE
pushf
push ds
push es
push fs
push gs
pusha
mov bx, 0x2028
call resetpic
mov eax, cr0
inc eax
mov cr0, eax
jmp dword CODE32:REBASE(p_mode32)
p_mode32: use32
mov ax, DATA32
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
lgdt [REBASE(gdt32_ptr)]
lidt [REBASE(idt32_ptr)]
mov esp, [REBASE(stack32_ptr)]
mov esi, STACK16
lea edi, [esp+0x28]
mov edi, [edi]
mov ecx, regs16_t_size
cld
rep movsb
popa
sti
ret
resetpic:
push ax
mov al, 0x11
out 0x20, al
out 0xA0, al
mov al, bh
out 0x21, al
mov al, bl
out 0xA1, al
mov al, 0x04
out 0x21, al
shr al, 1
out 0xA1, al
shr al, 1
out 0x21, al
out 0xA1, al
pop ax
ret
stack32_ptr:
dd 0x00000000
idt32_ptr:
dw 0x0000
dd 0x00000000
gdt32_ptr:
dw 0x0000
dd 0x00000000
idt16_ptr:
dw 0x03FF
dd 0x00000000
gdt16_base:
.null:
dd 0x00000000
dd 0x00000000
.code32:
dw 0xFFFF
dw 0x0000
db 0x00
db 0x9A
db 0xCF
db 0x00
.data32:
dw 0xFFFF
dw 0x0000
db 0x00
db 0x92
db 0xCF
db 0x00
.code16:
dw 0xFFFF
dw 0x0000
db 0x00
db 0x9A
db 0x0F
db 0x00
.data16:
dw 0xFFFF
dw 0x0000
db 0x00
db 0x92
db 0x0F
db 0x00
gdt16_ptr:
dw gdt16_ptr - gdt16_base - 1
dd gdt16_base
int32_end:
start:
call int32_test
hlt
link.ld:
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 0x100000;
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
}
Command Line & Output:
# gcc -fno-stack-protector -m32 -c kernel.c -o kc.o
# nasm -f elf32 kernel.asm -o kasm.o
# ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o
kc.o: In function `int32_test':
kernel.c:(.text+0x2d): undefined reference to `memset'
kernel.c:(.text+0x5c): undefined reference to `memset'
A little bit of background:
I am working on a custom OS for a custom project that I call AI Glasses. I am currently building a bootloader that will show a logo and some loading indicator. I am thinking that my indicator will be sort of like on Ubuntu. Anyways, I need to be able to go into a VGA mode and display stuff on the screen, while the rest of my (future) code can boot it up fully.
My question:
How do I import memset's lib into my code so that ld can fully link my code.
Most library functions, including memset, are not available in kernel modules.
You'll need to create your own implementation. It should be as simple as a single for loop. Since you're also writing some assembly, you may want to try writing it that way.
The issue here is that linker doesn't find memset function in the object files you provide to ld. Neither kasm.o nor kc.o seem to have this function. In fact this is part of standard C library. So, you need to link standard C library, libc.so or libc.a. If you don't want to use standard C library in your kernel, you will need to provide your own implementation of memset

Automatic code insertion in tasm

I am trying to write a simple C startup to produce DOS COM file.
I have not yet written argc, argv implementations.
As a test, I'm trying to put dummy argv[0], and argc=1.
But I get 2 unexpected lines as in the dump :
;---------------------------------------------------------------
Turbo Debugger Log
CPU 80486
cs:0100B85902 mov ax,0259
cs:0103 50 push ax
cs:0104 BF5002 mov di,0250
cs:0107 FF35 push word ptr [di]
cs:0109 90 nop ; << Unexpected 1
cs:010A 0E push cs ; << Unexpected 2
cs:010B E83C00 call 014A
cs:010E E80000 call 0111
cs:0111 B44C mov ah,4C
cs:0113 CD21 int 21
cs:0115 55 push bp
cs:0116 8BEC mov bp,sp
cs:0118 8B4604 mov ax,[bp+04]
cs:011B 50 push ax
cs:011C B40E mov ah,0E
cs:011E CD10 int 10
cs:0120 58 pop ax
cs:0121 5D pop bp
cs:0122 C3 ret
cs:0123 50 push ax
cs:0124 55 push bp
cs:0125 8BEC mov bp,sp
cs:0127 C746026900 mov word ptr [bp+02],0069
cs:012C 5D pop bp
cs:012D E8E5FF call 0115
cs:0130 59 pop cx
cs:0131 C3 ret
cs:0132 55 push bp
cs:0133 8BEC mov bp,sp
cs:0135 56 push si
cs:0136 33F6 xor si,si
cs:0138 EB01 jmp 013B
cs:013A 46 inc si
cs:013B 8B5E04 mov bx,[bp+04]
Terminated, exit code 7
Stack :
5B7C:0028 FFFF
5B7C:0026 FFFF
5B7C:0024 FFFF
5B7C:0022 FFFF
5B7C:0020 FFFF
5B7C:001E FFFF
5B7C:001C FFFF
5B7C:001A FFFF
5B7C:0018 FFFF
5B7C:0016 0DEE
5B7C:0014 1C80
5B7C:0012 0280
5B7C:0010 2225
5B7C:000E 01AE
5B7C:000C 2225
5B7C:000A 01E4
5B7C:0008 F01D
5B7C:0006 FEF0
5B7C:0004 9A00
5B7C:0002 9FFF
5B7C:0000 20CD
5B7C:FFFE 0000
5B7C:FFFC 0259 ; << argv
5B7C:FFFA 0001 ; << argc
5B7C:FFF8 5B7C ; << Unexpected CS
5B7C:FFF6 0111
5B7C:FFF4 3206
5B7C:FFF2 5B7C
5B7C:FFF0 0115
5B7C:FFEE 3206
;---------------------------------------------------------------
I could not understand why these lines are added by the assembler.
Could segment-padding be the cause? Also, could there be a stack-frame 'like' construct?
Please enlighten me on this.
Here's the source :
;---------------------------------------------------------------
_TEXT SEGMENT WORD PUBLIC USE16 'CODE'
_TEXT ENDS
_DATA SEGMENT WORD PUBLIC USE16 'DATA'
_DATA ENDS
DGROUP GROUP _DATA , _TEXT
_TEXT SEGMENT
assume cs: _TEXT, ds: _DATA, ss: nothing, es: nothing
ORG 0100h
;----------------------------------
; Program Starts Here...
;----------------------------------
start:
; Push arguments in reverse order...
;====================================
mov ax, offset DGROUP:argv
push ax
mov di, offset DGROUP:argc
push word ptr [di]
call far ptr _main
call near ptr __terminate
;----------------------------------
;----------------------------------
; Function Declarations...
;----------------------------------
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
__terminate proc DIST
PUBLIC __terminate
mov ah, 4ch
int 21h
__terminate endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;----------------------------------
_TEXT ends
_DATA SEGMENT
argc dw 1
arg0 db 'NEW_C0', 0h
argv dw DGROUP:arg0
_DATA ENDS
extrn _main
end start
;---------------------------------------------------------------
From a document I found titled "Turbo Assembler 2.0 New Features":
The default condition is SMART enabled. When SMART is enabled, a qualifying
FAR jump will be replaced by a NEAR or a SHORT jump. Also, when SMART is
enabled, a qualifying FAR call will be replaced by a PUSH CS instruction
and a NEAR call.
So TASM, being smart at recognizing that _main and your startup code are in the same segment (you are producing a COM file, after all), takes the liberty to replace:
call far ptr _main
by the shorter and faster near call:
push cs
call _main
The push is necessary because TASM must assume that main itself was compiled with a far return; it expects a 32-bit return address on the stack.
You should ask yourself whether you actually want/need this far call in a COM file.
As for the nop, I have no idea. It could be word padding in an effort to make the far return more efficient, but I find Jester's explanation (reserve space for a true far call) very plausible too.

QEMU crashes while method execution

I wrote really simple a C kernel and a bootloader. I'm loading the kernel in the QEMU like this:
qemu-system-i386 kernel.image
When I load the kernel and switch from real mode to protected mode (that step success completed) and main method start execute other method like this
void foo(int a, int b, int c, int d, int e) {return;}. But it execution leads to crash QEMU. However if I rewrite foo method for void foo(int a, int b, int c, int d) {return;} - everything will be ok. Here is the error information:
(qemu) qemu: fatal: Trying to execute code outside RAM or ROM at 0xf4000010
EAX=ffffffff EBX=00001000 ECX=00000000 EDX=00000000
ESI=00000000 EDI=00000000 EBP=ae00008f ESP=00008fcd
EIP=f4000010 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00007c75 00000017
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=00008fc5 CCO=INCL
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
My bootloader.S
KERNEL_OFFSET equ 0x1000
[org 0x7c00]
[bits 16]
mov bp, 0x8000
mov sp, bp
mov bx, bootstring
call print
mov [BOOT_DRIVE], dl
mov dh, 3h
mov bx, KERNEL_OFFSET
call load_kernel
call enable_A20
call init_protected
loop:
jmp loop
bootstring: db "Loading...", 0
BOOT_DRIVE: db 0
print:
mov ah, 0xE
mov al, [bx]
cmp al, 0
jz print_done
int 0x10
inc bx
jmp print
print_done:
ret
load_kernel:
push dx
mov ah, 0x02
mov al, dh
mov ch, 0
mov cl, 2
mov dh, 0
int 0x13
jc load_error
pop dx
cmp al, dh
jne load_error
ret
load_error:
mov bx, load_err_msg
call print
jmp $
enable_A20:
cli
call a20wait
mov al, 0xAD
out 0x64, al
call a20wait
mov al, 0xD0
out 0x64, al
call a20wait2
in al, 0x60
push eax
call a20wait
mov al, 0xD1
out 0x64, al
call a20wait
pop eax
or al, 2
out 0x60, al
call a20wait
mov al, 0xAE
out 0x64, al
call a20wait
sti
ret
a20wait:
in al, 0x64
test al, 2
jnz a20wait
ret
a20wait2:
in al, 0x64
test al, 1
jz a20wait2
ret
load_err_msg: db "Cannot load from disk", 0
gdt:
gdt_null:
dd 0
dd 0
gdt_cs:
dw 0xffff
dw 0
db 0
db 10011010b
db 11001111b
db 0
gdt_ds:
dw 0xffff
dw 0
db 0
db 10010010b
db 11001111b
db 0
gdt_end:
gdt_desc:
dw gdt_end - gdt - 1
dd gdt
CODE_SEG equ gdt_cs - gdt
DATA_SEG equ gdt_ds - gdt
[bits 16]
init_protected:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ax, 0x9000
mov ss, ax
mov sp, 0xFFFF
lgdt [gdt_desc]
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp CODE_SEG:start_protected
[bits 32]
start_protected:
mov ax, DATA_SEG
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x9000
call KERNEL_OFFSET
times 510 -( $ - $$ ) db 0
dw 0xAA55
Where I'm wrong?
The symptoms suggest that your stack memory is not set up correctly -- it looks like things go wrong when there are enough arguments to the function that some of them have to go on the stack rather than all being in registers. The message from QEMU is telling you that your guest tried to execute from a physical address with no RAM in it. You should be able to debug what exactly has gone wrong using the debug logging options (-d in_asm,exec,cpu,int -D qemu.log) to see what the CPU actually did to get to a point where it tried to execute from an invalid address.
Note that although the QEMU error message looks a bit like "QEMU crashed" it always indicates a guest OS bug.

Resources