Intro to the 291 Protected Mode Library

ECE 291 has a nice library of useful protected mode stuff. There's not really a industry standard library for protected mode, so we wrote our own. It does very standard things, plus we have the (very short and readable) source code which you should look through to see what's going on. If you have any issues with the library (stuff doesn't work or could be made better/clearer), e-mail me at pljohnsn@uiuc.edu

The Proc and Invoke Macros

NASM has very sparse, very clean, very simple syntax.  You want to make a procedure, you don't bother with all that proc and endp crap that you have to do in MASM which makes 291 students think there is something mysterious going on.  You just give it a label, and call the label.  A call pushes the return address, and a ret pops it off and jumps to it.  That's all that's going on in MASM anyway, so why not just do it yourself, right?  The NASM way:

_main:
    mov   eax,   3
    mov   ebx,   5
    call 
_Add_Two_Things
    ; Result in eax
    ret

_Add_Two_Things; Into eax
    add   eax,   ebx
    ret

This is fine, but if you're going to do a lot of functions, it would be nice to have a standard way of passing around parameters and return values and stuff.  Hey, C has a standard way of doing this, so why don't we just use the C-style procedures you all learned about?  Not only does this make things consistent and standard, but you can call your assembly from C, and your C from assembly without a problem.  C Dictates:

This is exactly the calling format the protected mode library follows, and to enforce this consistency and make things simpler than doing all this pushing and poping and EBP offset calculation every time, there's a bunch of NASM macros so you can do stuff like this:

BITS 32 ; Tell NASM we're using 32-bit protected mode.

GLOBAL _main ; Tells the linker about the label called _main

%include "myC32.mac"  ; Include the proc / invoke MACROs

SECTION .text ; Says that this is the start of the code section.

_
main:
    mov   eax,   3
    mov   ebx,   5
    invoke _Add_Two_Things, dword eax, dword ebx
    ; Result in eax
    ret


proc
_Add_Two_Things

    %$First_Thing   arg    4    ; Both Double Word Arguments (4 bytes)
    %$Second_Thing  arg    4
   
_Add_Two_Things_arglen   equ   8  ; Total bytes of arg
                                      ; VERY important to get right.

    mov   eax, [ebp + %$First_Thing]
    add   eax, [ebp + %$Second_Thing]

    ; There is no ret, because endproc does
    ; stack stuff before doing ret itself.


endproc

Task 10:  Simple Proc / Invoke Example

For future reference, here's what the MACROs are doing:  (Because the NASM's MACRO's are hard to read)

invoke

proc

arg

endproc

Cautionendproc does a ret!  MASM's endp's did not do a ret.  The fact that they didn't perform a ret caused some people problems before.  The fact that these do perform a ret will cause some people now.  Looking at what endproc does, you can see that if you return before letting endproc do it's thing, the stack will not be at the return address, so doing the ret will jump to some random location.

Remember that these things are just MACROs.  They're not magic.  You have to understand how they work if you're going to use them.  If nothing else, assembly programming is supposed to teach you that the "Black Boxes" they talk about  in your CS classes don't exist.  (Although making your procedures modular is still good practice.) 

C-Style Procedures

One of the reasons for defining the invoke MACROs the way they are defined is so they will be compatible with C and C++.  Because of the way C pushes things onto the stack in order to call your function, when you declare a procedure using the proc MACRO, you will be able to read the arguments right off of the stack just like a compiled C program would.  For this reason, from now on, I'll give function headers in the cleaner C-style syntax.

int Add_Two_Things(int First_Thing, int Second_Thing)  means:

proc _Add_Two_Things

    %$First_Thing   arg    4    ; Both Double Word Arguments (4 bytes)
    %$Second_Thing  arg    4
    _Add_Two_Things_arglen   equ   8  ; Total bytes of arg
                                      ; VERY important to get right.

    mov   eax, [ebp + %$First_Thing]
    add   eax, [ebp + %$Second_Thing]

    ; There is no ret, because endproc does
    ; stack stuff before doing ret itself.


endproc
 

Task 11:  Set Up a Jump Table

We want our code to be nice and neat again, so for the last time I'll ask you to start over with a new skeleton and a new final.asm assembly file.  In fact, why don't you put everything you have so far into a folder called "simple stuff" to get it out of the way.

We want the program to sit around and waited for you to press a key.  Then we want it to perform some function and came back to waiting for a key.  This is best done with a jump table whose index is the scan code of the key press.

BITS 32 ; Tell NASM we're using 32-bit protected mode.

GLOBAL _main ; Tells the linker about the label called _main

%include "lib291.inc"  ; Includes everything, including proc MASCOs.

SECTION .bss ; Uninitialized Data Section

SECTION .data ; Data Section

    KeyJumpTable
    dd Just_Return ; Nothing (0)
    dd Quit ; ESC (1)
    times 55 dd Just_Return ; 1-Alt (2-56)
    dd Just_Return ; SpaceBar (57)
    dd Just_Return ; CAPSLOCK (58)
    dd String_Loop ; F1 (59)
    dd Draw_Pixels ; F2 (60)
    dd Just_Return ; F3 (61)
    dd Just_Return ; F4 (62) 
    dd Just_Return ; F5 (63)
    dd Just_Return ; F6 (64)
    dd Just_Return ; F7 (65)
    dd Just_Return ; F8 (66) 
    dd Just_Return ; F9 (67)
    dd Just_Return ; F10 (68)
    dd Just_Return ; F11 (69)
    dd Just_Return ; F12 (70) 
    dd Just_Return ; HOME (71)
    dd Just_Return ; UP (72)
    dd Just_Return ; PGUP (73)
    dd Just_Return ; Keypad Minus (74)
    dd Just_Return ; LEFT (75)
    dd Just_Return ; Keypad 5 (76)
    dd Just_Return ; RIGHT (77)
    dd Just_Return ; Keypad Plus (78)
    dd Just_Return ; END (79)
    dd Just_Return ; DOWN (80)
    dd Just_Return ; PGDN (81)
    times 175 dd Just_Return ; (82-255)

    Menu_str db 0dh, 0ah, 0dh, 0ah,  ; Two blank lines
             db "F1 - Display String Loop", 0dh, 0ah,
             db "F2 - Draw some 320x200 pixels", 0dh, 0ah
             db "ESC - Quit", 0dh, 0ah, '$'

    Loop_str db "We are in a loop until you hit a key. Weeee!", 0dh, 0ah,'$' 

SECTION .text ; Says that this is the start of the code section.

_main: 

    call _LibInit ; Initialize the library

  Just_Return:

    invoke _print_string, dword Menu_str

    mov ah, 00h ; Wait for a key to be pressed.
    int 16h
    mov ecx, 0 ; We'll use ECX as the index into the jump table.
    mov cl, ah ; Moves the Key Code into ECX

    jmp [KeyJumpTable + 4*ecx] 
      ; We used ECX as an index, and multiplied it by four.
      ; Question 11: Why do we multiply ECX by four?

  String_Loop:
    invoke _print_string, dword Loop_str
    ; Loop until a key is pressed.
    jmp Just_Return 

  Draw_Pixels:
    invoke _draw_pixels
    jmp Just_Return

  Quit: 
    call _LibExit ; Free library resources     ret

; void print_string(char* The_String)
proc _print_string
    %$The_String arg 4
    _print_string_arglen equ 4

    ; Copy string to global transfer buffer
    ; Set up global DPMI_Regs structure
    ; Call library DPMI_Int

endproc

proc _draw_pixels
    ; Draw Stuff
endproc

; The rest of the procedures from here on out...

Did you miss Question 11:  Could this be because you just blindly copied and pasted without reading it first!?  Come on!  Let's go!  You and me! Outside, right now!

Task 12:  Linking to the Library

We need to link this new final.asm to the library code. A library is just a archive of .o files. You can link the library into your final.exe as if it were just another .o file.

Here's an updated Makefile that does all that stuff:

all: final.exe

clean:
     rm -f *.o final.exe *.lst *.map

final.exe: final.o
     gcc -o final final.o v:/pmodelib/lib291.a

.asm.o:
     nasm -f coff -iv:/pmodelib/include/ $< -l $*.lst
 

Only after you get everything to build, fill in the functionality into final.asm  I want you to use the library functions to do all the stuff you did yourself before:

Implement the print_string(char* The_String)

File Handling Routines

Reading files is very important. File handling is something you definitely call library functions for, so we'll use the library file routines.  Basic operations on a file:  Open, Read, Write, Close.  Not too hard.  We won't be doing any writing, so I'll only talk about the first three.

Look at the library's filefunc.asm file.  The file defines the file handling routines as C-style, invokable procedures.  Look at the Library Function Reference:

OpenFile


int OpenFile(char *Filename, short WriteTo);
   Purpose: Opens a file for reading or writing.
   Inputs: Filename, (path)name of the file to read
           WriteTo, 1 if create & open for writing, 0 for open to read
   Outputs: DOS handle to file  (-1 if error)

ReadFile

int ReadFile(int Handle, short BufSeg, void *Buffer, unsigned int Count);
   Purpose: Reads from a file.
   Inputs: Handle, DOS handle of the file to read from
           BufSeg, selector in which Buffer resides
           Buffer, pointer (into BufSeg) of buffer to read into
           Count, number of bytes to read into buffer
   Outputs: Number of bytes actually read

CloseFile

void CloseFile(int Handle);
   Purpose: Closes an open file.
   Inputs: Handle, DOS handle of the file to close.
   Outputs: None

What is a Filehandle?  Well, a handle is something that a library or an operating system gives you which is usually pretty meaningless outside it's intended context.  The OS has a big table of which files are open and who has them open.  When your program opens a file, it puts information about the file you want into the table and returns something like an index into the table.  When you tell it "read from the file whose handle I gave you," it looks in it's table and knows where the file is on disk and how much of it you've already read, etc.

Question 11:  What interrupts are the library routines calling?  What do they do?  Give me numbers and names.

Task 13:  Read a Text File

Another little thing to add to the list off stuff you can now do in protected mode:

Make another KeyJumpTable entry for this functionality which just calls a procedure for printing out the file.  When you write and call routines, be very careful about the length of the arguments because the macros do no type checking and you will corrupt your stack!

Make it, run it, and trace through the library code once to see what it's doing.