Chapter 1
1.0 Introduction
DexOS (formerly: Dex4U) is a 32-bit protected mode, single-tasking operating system implemented entirely in x86 assembly language1. This operating system shares similar traits to Microsoft's Disk Operating System (MS-DOS), and includes access to VESA-compliant2 graphics adapters.
1.1 Prerequisites
It is assumed that you have a sound knowledge of assembly language or programming constructs in general, and that you are comfortable using Flat Assembler3 [FASM]. All sourcecode presented in this tutorial is in FASM-dialect assembly language.
The software requirements for this guide is as follows:
- DexOS on a floppy diskette or CD
- 'DexFunctions.inc'
- Flat Assembler
- A host operating system such as Microsoft Windows or Linux
Chapter 2
2.0 Overview
DexOS is a single-tasking operating system with a flat, contiguous memory space. This guide shall take you through the basics of implementing a simple application that will fulfill the following requirements:
- Be able to read/write files located on the current drive (except CD-ROM media);
- Be able to gather input from the user;
- Be able to write data to the terminal (80x50 character text mode).
Now that the introductions have concluded, lets move onto the basics.
2.1 Generalized Application Structure
The general structure of an application consists of an application header, then generally, the initialized data, code, and uninitialized data sections. Below is a general representation of the layout of a typical application (do not assemble):
; Binary Header
use32
org 0x400000
jmp initialize
db 'DEX2'
; Initialized Data 'Section'
data1 db 'Hi!',0
data2 db 13,0
; Code 'Section'
initialize:
; Code goes here
ret
; Uninitialized Data 'Section'
data3 db ?
data4: times 32 db ?
; DexOS API Lookup Table
include "DexFunctions.inc"
2.2 Application Header
We use 32bit code for applications (DexOS is a 32-bit Operating System), therefore we instruct the assembler to use 32-bit code. We organize the binary to load into memory at 200000h, as this is where the operating system loads the binary and all offsets (CS, DS, ES etc) use this value. The next instruction defines the point that the actual code 'section' starts. The final element is a 4-byte string that indicates that this binary is in fact a DexOS binary.
The Initialized Data section: although not entirely necessary, it is good programming practice to put all initialized data here (executing data is not nice!). Note that all strings to be printed to screen should have a ASCII NUL (0) character appended to signify the end of the string.
The Code section: all application code should be situated here. The 'ret' instruction indicates the application returning to the OS Shell.
The Uninitialized Data section: the last section and generally is so because this is where you will load files from disk, put strings, tables (including DexFunctions.inc) and so forth.
As a side note, DexOS binaries use a flat executable model (because it has a flat memory space), so do not include directives that format output binaries to MZ, PE, COFF or ELF executables. The final binary must have a '.dex' extension for it be recognized as a DexOS binary.
2.3 Full Source Listing
Below is a listing of the application we will dissect. Cut and Paste the code section below into a plain ASCII file named "my_app.asm" (or download it here), assemble it using FASM, test it, then proceed to the sourcecode breakdown. As this application opens files, you will require something to read. Any ASCII text file is fine.
To assemble using FASM:
"fasm my_app.asm my_app.dex"
my_app.asm:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; my_app.asm - Learning Application ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
macro newline
{
mov esi,newline_string
call [PrintString]
}
;----------------------------------------------------;
; Application Header. ;
;----------------------------------------------------;
use32
org 0x400000
jmp initialise
db 'DEX2'
;----------------------------------------------------;
; Initialised Data. ;
;----------------------------------------------------;
name_string db 'Filename: ',0
wait_string db 'Press any key to continue',0
string_1 db 'You have ',0
string_2 db ' MB of Total Memory!',0
string_3 db ' MB of Extended Memory!',0
string_4 db ' Conventional Memory!',0
newline_string db 13,0
buffer_offset dd 0
;----------------------------------------------------;
; Code Section. ;
;----------------------------------------------------;
initialise:
mov ax,0x18 ; Setup the segment registers
mov ds,ax
mov es,ax
mov edi,Functions ; This is the interrupt we use
mov ax,0x0A00 ; to load 'DexFunction.inc' call-
int 0x40 ; table of functions.
getuserinput:
mov esi,name_string ; Get the address of the string
call [PrintString] ; then call the system functions
call [GetUserInput] ; Get input from the user
newline ; use the newline macro
xor ebx,ebx
@@:
cmp ebx,4095 ; This section initialises the
je openfile ; data buffer to 0.
mov [filebuffer+ebx],dword 0 ; That way we can use a print
inc ebx ; string call.
jmp @b
openfile:
mov esi,edi ; See the relevant breakdown
mov edi,filebuffer
call [FloppyfileLoad]
mov ax,18
call [SetDelay]
jc quit_app
dumpbuffer:
mov esi,filebuffer ; See relevant breakdown
call [PrintString]
newline
mov esi,wait_string
call [PrintString]
call [WaitForKeyPress]
newline
newline
getmemory:
call [ExtendedMemory]
imul eax,1024 ; Convert to megabytes
mov [extend_mem],eax ; Then put in memory
call [ConvenMemorySize]
imul eax,1024
printconv_memory:
mov esi,string_1
call [PrintString]
call [WriteHex32] ; Eax already has the conv.
mov esi,string_4
call [PrintString]
newline
printext_memory:
mov esi,string_1
call [PrintString]
mov eax,[extend_mem]
call [WriteHex32]
mov esi,string_3
call [PrintString]
newline
printtotal_memory:
mov esi,string_1
call [PrintString]
mov eax,ebx
call [WriteHex32]
mov esi,string_2
call [PrintString]
newline
quit_app:
newline
mov esi,wait_string
call [PrintString]
call [WaitForKeyPress]
ret ; End application
;----------------------------------------------------;
; Uninitialised Data. ;
;----------------------------------------------------;
filebuffer: times 4096 db ? ; 4kb Buffer
extend_mem dd ?
include "DexFunctions.inc" ; Call-table include file
2.4 Source Code Breakdown
This is a pretty simple application. and here is the general flow of operation:
- Gets a filename from the user;
- Reads a file of that filename into a 4 kilobyte buffer;
- Dumps the buffer to screen;
- Gets the Conventional, Extended and Total Memory and prints the hexadecimal values to screen.
Now, there are a few inefficiencies and rough edges, but optimizations can be performed after learning the basics. I have used address labels to label code stubs. Hopefully you are proficient in using macros, as the macro 'newline' simply does that, prints a newline. The application header should also look familiar, as this was covered previously under section ??.
The 'Initialized Data' section of our application is straightforward, and is
also discussed previously in section ??. So with that covered, let's look at the code in 'initialize':
mov AX,0x18
mov DS,AX
mov ES,AX
mov EDI,Functions
mov AX,0x0A00
int 0x40
Firstly, we initialize segment registers DS and ES with 18h. Then load EDI and ax with the function call table pointer and A00h respectively. This sets up the application to use the function call table, of which we use heavily.
Next, we have to get a string from the user to use as a filename. Here we immediately use the string in the next stublet, but you could alternately copy the string into an uninitialised data buffer, using the value of cx to determine the amount of elements needed for such a buffer. We also print a 0 terminated string for prompting.
mov ESI,name_string
call [PrintString]
call [GetUserInput]
newline
As you can see, function calls are simply the case of loading the required registers with required data, the calling the actual name of the function.
xor EBX,EBX
@@: cmp EBX,4095
je openfile
mov [filebuffer+EBX],dword 0
inc EBX
jmp @b
This section is fairly simple, as all of the soon-to-be-used file buffer is zeroed. See the Flat Assembler documentation for more information about reserving data.
mov ESI,EDI
mov EDI,filebuffer
call [FloppyfileLoad]
mov AX,18
call [SetDelay]
jc quit_app
'openfile' simply does that. The only point to be made is that on error, the carry flag is set. If this checked while the file is still reading off the drive, it will jump to 'quit_app'. Therefore we have to add a delay before we decide what to do. This is done with 'SetDelay'. See 'DexFunctions.inc' for more information.
mov ESI,filebuffer
call [PrintString]
newline
mov ESI,wait_string
call [PrintString]
call [WaitForKeyPress]
newline
newline
We have already covered these functions. Basically, it's all about loading registers with values, then calling the appropriate function. We'll jump to 'quit_app'.
newline
mov ESI,wait_string
call [PrintString]
call [WaitForKeyPress]
ret
What's to be said about 'quit_app'? Well, as soon as 'ret' is called, the application will return to the CLI. So be weary about calling your own functions then returning from them.
2.5 Conclusion
As mentioned before, an application consists of calls to functions, then moving the data around, and calling more functions. Simple. This is just a simple application and is by no means definitive of what DexOS is capable of. Please investigate 'DexFunctions.inc' for more information on the necessary registers for different calls.
Chapter 3
3.0 References
3.1 Updates and Change Log
Date | Change Description |
---|---|
2005-12-27 | Version 1 Released |
2009-05-23 | Tutorial updated for referencing, readability, grammar and spelling |
2009-05-25 | Binary headers modified to reflect new offsets/ID String |