Application Programming Structure Tutorial

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:

  1. DexOS on a floppy diskette or CD
  2. 'DexFunctions.inc'
  3. Flat Assembler
  4. 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:

  1. Gets a filename from the user;
  2. Reads a file of that filename into a 4 kilobyte buffer;
  3. Dumps the buffer to screen;
  4. 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

1. DexOS, C. Bamford, Team DexOS. Homepage
2. VESA, Wikimedia Foundation, Inc. Wikipedia Entry
3. Flat Assembler, T. Grysztar. Homepage

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
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License