basic concept assembly language



Index of Section 1


Ready To Start
Memory Segmentation
Code Example
The Stack
The Naming Convention
Main Page
Next Chapter
First thing you need to know is that Assembly is a great, fast language, but only if you put time and effort in learning it. You must give all or nothing. (I suggest you give all) And remember, the beginning is always borring and hard...so don't give up !
Well, I'll start with the basics, like instruction format and some simple instructions to manipulate registers. I don't know how much you know about coding, so I'll explain even the most simple stuff. Please note, that I explain 8086 assembly coding. That means NO 32-bit registers and instructions and NO protected-, real- and virtual 86 mode for now.




Ready to Start!

First of all, we'll talk about the registers and then about the instructions to manipulate (change) them. The 8086 has 14 16-bit registers, all with different usage (see below). You might not understand some of the registers purposes, but be patient, I'll explain everything later.


Segment Registers
CSCode Segment16-bit number that points to the active code-segment
DSData Segment16-bit number that points to the active data-segment
SSStack Segment16-bit number that points to the active stack-segment
ESExtra Segment16-bit number that points to the active extra-segment
Pointer Registers
IPInstruction Pointer16-bit number that points to the offset of the next instruction
SPStack Pointer16-bit number that points to the offset that the stack is using
BPBase Pointerused to pass data to and from the stack
General-Purpose Registers
AXAccumulator Registermostly used for calculations and for input/output
BXBase RegisterOnly register that can be used as an index
CXCount Registerregister used for the loop instruction
DXData Registerinput/output and used by multiply and divide
Index Registers
SISource Indexused by string operations as source
DIDestination Indexused by string operations as destination
(The general purpose registers can be "split". You have the AH and the AL register for example. AH contains the high byte of AX and AL contains the lowbyte. You also have: BH, BL, CH, CL, DL, DH So if eg. DX contains the value 1234h DH would be 12h and DL would be 34h).

And a 16-bit FLAG Register. All "flags" (see below) are stored here. The FLAGS Register consists of 9 status bits. These bits are also called flags, because they can either be SET (1) or NOT SET (0). All these flags have a name and purpose.


Flags Register
Abr.Namebit nÂșDescription
OFOverflow Flag11indicates an overflow when set
DFDirection Flag10used for string operations to check direction
IFInterrupt Flag9if set, interrupt are enabled, else disabled
TFTrap Flag8if set, CPU can work in single step mode
SFSign Flag7if set, resulting number of calculation is negative
ZFZero Flag6if set, resulting number of calculation is zero
AFAuxiliary Carry4some sort of second carry flag
PFParity Flag2indicates even or odd parity
CFCarry Flag0contains the left-most bit after calculations
Test it!
If you want to see all these register and flags, you can go to DOS and then start "debug" (just type debug) When you're in debug, just type "r" and you'll see all the registers and some abreviations for the flags. Type "q" to quit again. We won't use debug to program in this tutorial, we'll use a real assembler. I use TASM 3.2, but MASM or any other assembler works just fine too.



[Back] [Index]



Memory Segmentation

Now I've to explain something about the way the 8086 uses memory (actually about how DOS uses memory). Since the databus of the 8086 is 16-bits, it can move and store 16-bits (1 word=2 bytes) at a time. If the processor stores a "word" (16-bits) it stores the bytes in reverse order in the memory. It looks like this:
1234h (word) ---> memory 34h (byte) 12h (byte)So if the memory looks like this: 78h 56h and you get a word from memory you'll get the value 5678h. (note, I use the "h" after a number to indicate it's hexadecimal) However, if you just get a byte from memory it goes this way: memory 78h 56h -----> first byte you get 78h. Okay, pretty clear huh?
Now let's talk about segments. The 8086 divides it's memory into segments. Segments are (standard in DOS) 64 KB big and have a number. These numbers are stored in the segment registers (see above). Three main segments are the code, data and stack segment. Segments overlap each other almost completely. If you start debug again and type "d" you can see some addresses at the left of the screen. The format is like this: 4576:0100. that's a memory address. The first number is the segment number and the second number is the offset within the segment. So FFFF:FFF0 means: Segment FFFFh and FFF0h bytes from the beginning of the segment.
As I said before, segments overlap. The address 0000:0010 is EXACTLY the same address as 0001:0000. That means that segment begin at paragraph boundaries. (a paragraph=16 bytes, so the segment starts at an address divisible by 16) Now you can start calculating REAL addresses in memory. An example: 0000:0010 means: segment 0000h offset 10h Now we multiply the segment number with 16 and add the offset.
Note that the offset 10h means the value 16 in decimal(0 * 16 = 0 + 16 = 16) this is the linear address.
Next, the other address 0001:0000: (16 * 1 = 16 + 0 = 16). Same linear address! Like I told you. These are some basic things you need to know when you want to program in Assembly. Learn the registers and flags by heart and try to understand the segmentation of memory.
By The Way, this segmentation of memory is actually done by DOS at startup. On a 286 or higher, you have something called real-mode and protected-mode. This Segment explanation is based on Real-mode, in Protected-mode it's way different, but don't bother, that's real complicated stuff you don't need to know. Just assume that what I explained about segments is ALWAYS true. But remember in the back of your head, that there's more.... Trust me...... I know what I'm talking about.


[Back] [Index]



Our first program

Our first program will be a real simple one. I'll first give you the code and then I'll explain it. Here's the code, cut it out and put it in a file called FIRST.ASM.Download the source.
.model small
.stack
.data
message   db "Hello world, I'm learning Assembly !!!", "$"

.code

main   proc
   mov   ax,seg message
   mov   ds,ax

   mov   ah,09
   lea   dx,message
   int   21h

   mov   ax,4c00h
   int   21h
main   endp
end main

You can assemble this by typing: "tasm first [enter] tlink first [enter]" or something like: "masm first [enter] link first [enter] You must have an assembler and the link/tlink program. I'll explain the code now.
.model small : Lines that start with a "." are used to provide the assembler with infomation. The word(s) behind it say what kind of info. In this case it just tells the assembler the program is small and doesn't need a lot of memory. I'll get back on this later.
.stack : Another line with info. This one tells the assembler that the "stack" segment starts here. The stack is used to store temporary data. It isn't used in the program, but it must be there, because we make an .EXE file and these files MUST have a stack.
.data : indicates that the data segment starts here and that the stack segment ends there.
.code : indicates that the code segment starts there and the data segment ends there.
main proc : Code must be in procedures, just like in C or any other language. This indicates a procedure called main starts here. main endp states that the procedure is finished. Procedures MUST have a start and end. end main : tells the assembler that the program is finished. It also tells the assembler were to start the program. At the procedure called main in this case.
message db "xxxx" : DB means Define Byte and so it does. In the data-segment it defines a couple of bytes. These bytes contain the information between the brackets. "Message" is a name to indentify this byte-string. It's called an "indentifier".
mov ax, seg message : AX is a register. You use registers all the time, so that's why you had to know about them before I could explain this. MOV is an instruction that moves data. It can have a few "operands" (don't worry, I'll explain these names later) Here the operands are AX and seg message. Seg message can be seen as a number. It's the number of the segment "message" is in (The data-segment) We have to know this number, so we can load the DS register with it. Else we can't get to the bit-string in memory. We need to know WHERE the bit-string is located in memory. The number is loaded in the AX register. MOV always moves data to the operand left of the comma and from the operand right of the comma.
mov ds,ax : The MOV instruction again. Here it moves the number in the AX register (the number of the data segment) into the DS register. We have to load this DS register this way (with two instructions) Just typing: "mov ds,segment message" isn't possible.
mov ah, 09 : MOV again. This time it load the AH register with the constant value nine.
lea dx, message : LEA Load Efective Address. This intructions stores the offset within the datasegment of the bit-string message into the DX register. This offset is the second thing we need to know, when we want to know where "message" is in the memory. So now we have DS:DX. See the segment explanation above.
int 21h : This instruction causes an Interrupt. The processor calls a routine somewhere in memory. 21h tells the processor what kind of routine, in this case a DOS routine. INT's are very important and I'll explain more of them later, since they're also very, very complex. However, for now assume that it just calls a procedure from DOS. The procedure looks at the AH register to find out out what it has to do. In this example the value 9 in the AH register indicates that the procedure should write a bit-string to the screen.
mov ax, 4c00h : Load the Ax register with the constant value 4c00h
int 21h : The same INT again. But this time the AH register contains the value 4ch (AX=4c00h) and to the DOS procedure that means "exit program". The value of AL is used as an "exit-code" 00h means "No error"
That's it!!! You now fully understand this program (I hope)


[Back] [Index]


Go to DOS and type "debug first.exe". The debug screen will appear. When you are in the debugger, type "d". You see some addresses and our program.
Now type "u" you'll see a list that looks like this:

0F77:0000  B8790F          MOV AX,0F79
0F77:0003  8ED8            MOV DS,AX
0F77:0005  B409            MOV AH,09
First 0F77:0000 is the segment number and offset. B8790F is the machine code of the mov ax,0f79 instruction. B8 means "mov ax," and 790F is the number. (reversed order) Note that the instruction was: mov ax,seg message and the assembler made it mov ax,0f79 (number might be different at your computer) So that means our data is stored in the segment with the number 0F79.The other instruction lea dx,message turned into mov dx,0. So that means that the offset of the bit-string is 0 --> 0F79:0000. Let's look at that address. Type "d 0f79:0000" and YES our data is there! Look at the right of the screen and you can see the message.
Now let's calculate another address for the data. 0F79:0000 substract 2 from the segment number. That would give you 0F77 (the code segment). 0002:0000 --> 2*16+0=32. Two segments further means 32 bytes further, and that means an offset of 32.
DiagramSo at this location the data is: 0F77:0020. Check by typing "d 0f77:0020". Please note that it's the SAME data. We can see it at multiple addresses only because the segments overlap! But in the program we said the data had to be in a data-segment. Remember, the .data instruction? Well, it IS in a data-segment, the data is just stored directly behind the code, but that doesn't matter. I mean, we can address the data with a segment number and an offset of zero.
Also note, that after the int 21h instruction to end the program the data doesn't immediately start, first there some undefined bytes. (probably zero) That's because segments start at paragraph boundaries. The data-segment couldn't start at 0F77:0010 anymore, because there is code there, if there wasn't any code there, the data-segment would have been: 0F78. So the data-segment has to be 0F79 (closest match) and so, some bytes after the code and before the data just take up space. But that doesn't matter. Please remember that the assembler doesn't care how the segment are in the .ASM file. In this example we first declared the data-segment, but the assembler puts it last in memory.


[Back] [Index]



The Stack

The stack is a place where data is temporarily stored. The SS and SP registers point to that place like this: SS:SP So the SS register is the segment and the SP register contains the offset. There are a few instructions that make use of the stack. POP and PUSH are the most basic ones. PUSH can "push" a value on the stack and POP can retrieve that value from the stack. It works like this:

 MOV AX,1234H
 PUSH  AX
 MOV   AH,09
 INT   21H
 POP   AX
The final value of AX will be 1234h. First we load 1234h into AX, then we push that value to the stack. We now store 9 in AH, so AX will be 0934h and execute an INT. Then we pop the AX register. We retreive the pushed value from the stack. So AX contains 1234h again. Another example:

 MOV   AX, 1234H
MOV BX, 5678H
 PUSH  AX
 POP   BX
The final values will be:AX=1234h BX=1234h. We pushed the AX to the stack and we popped that value in BX.
As in the first program, you have to define a stack segment. It is easy done by the instruction .stack that will create a stack of 1024 bytes. Yes, there's more about the stack than just this. The stack usses a LIFO system (Last In First Out) Another example:

MOV   AX,1234H
 MOV   BX,5678H
 PUSH  AX
 PUSH  BX
 POP   AX
 POP   BX
The values: AX=5678h BX=1234h First the value 1234h was pushed after that the value 5678h was pushed to the stack. Acording to LIFO 5678h comes of first, so AX will pop that value and BX will pop the next.
How does the stack look in memory? Well, it "grows" downwards in memory. When you push a word (2 bytes) for example, the word will be stored at SS:SP and SP will be decreased to times. So in the beginning SP points to the top of the stack and (if you don't pay attention) it can grow so big downwards in memory that it overwrites the source code. Major system crash is the result.

[Back] [Index]



Names

There are some names you need to know. Well, you don't HAVE to know them, but it's handy if you do. I'll use these names from now on, so better learn them.

IndentifiersAn identifier is a name you aply to items in your program. the two types of indetifiers are "name", wich refers to the address of a data item, and "label", wich refers to the address of an instruction. The same rules aply to names and labels.
StatementsA program is made of a set of statements, there are two types of statements, "instructions" such as MOV and LEA, and "directives" wich tell the assembler to perform a specific action, like ".model small"

Here's the general format of a statement:
indentifier - operation - operand(s) - comment
The identifier is the name as explained above.
The operation is an instruction like MOV.
The operands provide information for the Operation to act on. Like MOV (operation) AX,BX (operands).
The comment is a line of text you can add as a comment, everything the assembler sees after a ";" is ignored.
So a complete instruction looks like this:


MOVINSTRUCTION:      MOV   AX,BX           ;this is a MOV instruction
The label and the comment are optional. In fact I allready explained directives , but, okay, I'll do it again. Directives provide the assembler with information on how to assemble a .ASM file. .MODEL SMALL, or .CODE are, for example, directives.


[Back] [Index]


And so we have come to the end of Section 1 of this tutorial. If you fully understand this stuff (registersflagssegmentsstacknames, etc.) you may, from now on, call yourself a "Level 0 Assembly Coder". Congratulations!
In Part 2 I'll explain some more instructions and I'll explain how to address data yourself.
(MOV BYTE PTR ES:[DI],AL)
I'll also explain the Interrupts and interrupt table.



Assembly Tutorial

Chapter 2

Written by MAD


Index of Chapter 2

Adressing Memory
Video Memory: Principles
Video Memory: Practice
Main page
Next Chapter
Previous Chapter
Hello everybody, I hope you read the first part of this tutorial, because I assume you understand all that. I don't think I'll have to explain again, but okay... This is an assembly tutorial, designed for you to learn assembly fast. I'll only discuss the usefull things and the basics. I explain 8086 coding, so NO 32-bit registers or protected mode. I just explain everything like the computer has a 16-bit data bus. In higher level tutorials I will explain 80386 coding, so stay tuned. Now let's get started!

Addressing Memory

In chapter 1 we used the instruction LEA to check where data is stored and we used INT 21h Function 09 to display this data. However, this INT is rather slow, and speed is the limit. So now I'll discuss a way to address the data directly and store it into the video memory. Let's take a look at the following program. Since the programs are becomming bigger, I decided to upload the source code myself. I'll continue to do this in the future, so that means no more cutting and pasting. Thesource code can be found here. I suggest you print the source and have it with you when you read this tutorial.
There's a lot of new stuff in this program! I'll start explaining...BTW, I only explain the new things.
.stack 10 : Is like .stack only now the stack will be 10 instead of 1024 bytes long.
dw 0 : Define Word (like db, only a word this time)
dd : Define Double word (like dw, only 2 words this time)
db 3 dup ("!") : Defines 3 bytes with the value "!". Same as: db "!!!"
mov es,[video_segment] : Important one. If you type something between square brackets [] in assembly, it means you don't want the location of the identifier but the contents. So ES will be loaded with the value 0B800h (note the zero before the B, if you don't do this, the assembler thinks it's an identifier)
mov [value_one],"H" : About the same as above. The byte value_one will be loaded with the value "H" (note, if you type ASCII between "" the assembler will generate the ASCII number. Eg. space bar is 20h, so " "=20h)
mov byte ptr [value_two],"e" : "Byte ptr" lets the assembler know you want to store a byte. This must be done, because value_two was declared as a word. (DW) If we would have declared it a byte (DB), like value_one, you could have ommited the "byte ptr".
mov byte ptr [value_two + 1],"l" : Same as above, except for the +1. The +1 means the assembler has to add one to the address of value_two. This way, an "l" is stored in the second byte of the word value_two (still get it?)
mov word ptr [value_three + 2],2020h : Word ptr means that we want to store a word. (this way the bytes will be stored in reverse order, see part 1) The +2 indicates that we want to store the value in the second word of the dword value_three.
inc byte ptr [di] : First we loaded the address of value_four into DI. INC adds one to a value. But, not DI is increased. Since DI is in brackets [DI] the contents of the memory location DI points to (value_four) will be increased. Byte ptr again indicates that the value is a byte.
If you don't add a "ptr" where one IS needed, the assembler (TASM) wil give an "argument needs type override" error. You don't need a "ptr" everywhere, if the assembler can know what type you want to store, you don't need a "type override". For example "mov es,[video_segment]". Since ES is a word and video_segment was declared as a word also, you don't need a type override. Just see for yourself, you can just leave them everywhere, and see at wich lines the assembler gives an error. In the memory from value_one to value_five now stands: Hello everybody!!!

The Video Memory

Principles

The part that actually writes the string to the screen is between commented lines. But before I talk about that, I want to explain how the video memory works. The video memory is a memory region that's 64K big. In 80x25 Text mode only 4000 bytes are used (80*25*2). The memory region starts at segment 0B800h. If you start debug and type "d 0b800:0" you can see it. It looks like this:
B800:0000 20 07 20 07 20 07 20 07
B800:0008 20 07 20 07 20 07 20 07
etc.
It will only look like this, when the first line on the screen is blank. The first byte (B800:0000) is the first character on the screen. The second byte is the atribute of the character. An atribute is a value that indicates how the character looks, eg. what colour. Below is a list of all possible atributes and their means.

Attributes:

Atribute = background colour * 16 + foreground colour
Add 8 with the foreground for a bright version.
Add 8 with the background for a blinking version.

0 = Black
1 = Blue
2 = Green
3 = Cyaan
4 = Red
5 = Magenta
6 = Brown
7 = White 
So 07h means: white text on a black background. Now suppose we want blinking, bright red text on a blue background.
Red=4 Bright red=4+8=12=0Ch. Blue=1*16=16+8=24=18h ==> 18h + 0Ch = 24h

The Video Memory

Practice

Now you understand how the video memory works, let's take a look how the program actually writes to it.xor di,di : eXclusive OR, a logical instruction. It works this way:


XOR01
001
110
So if we XOR a number with the same number, it will be zero. (1 XOR 1 = 0, 364 XOR 364 = 0). We could have typed: mov di,0
cld : CLear Direction flag, sets the direction flag to 0. (see part 1)
lodsb : LOaD StringByte, loads a byte from DS:SI (Source Index) into the AL register. Almost the same as: mov byte ptr al,ds:[si]. However, SI is increased, or decreased, depending on the direction flag. If set to 0 SI is increased, otherwise SI is decreased.
stosw : STOre StringWord, stores a word from the AX register on the memory location ES:DI (Destination Index) Almost the same as: mov word ptr es:[di],ax. DI is increased or decreased like with "lodsb", only DI is increased by two (word).
loop print_loop : The number in CX indicates how many times to loop. So in this case, it will loop 19 times between print_loop and the loop instruction.

Now, try to figure out what this little part of the code does yourself. I'll explain it below, but please, try it yourself first. You'll learn from that. With programming you always have to try and figure out things. Also, don't be afraid to change the code, just do crazy stuff and try! So what if the system crashes!
Fist we load DI with 0, we do this because we want stosw to store the data at an offset zero. Then we load SI with the offset of the message we want to display. After that we load CX with 19, because the message has a length of 19 bytes and so we have to loop 19 times. We load AH with 07, this is the attribute of the characters we want to display. (07=white on black) Now we clear the direction flag, so that SI and DI are increased and not decreased. After all this initialization the actual loop starts here. First it loads AL with the value on DS:SI. AH was 07, so AX will be 07xxh. Now we store AX at ES:DI (the video memory) Because we store a word, it is stored in reverse order (see part 1) So the the first two bytes of the video memory look like this: xx 07. DI and SI were increased, so we just loop 19 times to store all characters this way. Simple huh?
Okay, that's how the program works. I hope you learned something... Writing this message to the screen can, of course, be done a lot easier. The first part of the code was just to show you how addressing memory works. Also, with this little text, you better use INT 21h Function 09. But if you want to clear the screen (writing 2000 spaces), you better do it this way.

Index of Chapter 3

Interrupts Can We Live Without Them?
Internal Hardware Interrupts
External Hardware Interrupts
Software Interrupts
Interrupt Table and TSR basics
How INT's are processed
Closing words
Next Chapter
Previous Chapter
Example Source used:

Not available
Hello coder! Yup, you got it...the thirth part of this awesome tutorial.;) What are we going to discus? The Interrupt table!!! Yeah! I hope you read the previous parts, because I assume you know that stuff. Well, let's cut the crap, and get on with the new stuff!


Interrupts - Can we live without them?

Yeah, Yeah, Yeah...(like Pup says to Pacmeyer in PQ: SWAT;)) I know...the answer is simple: We CAN NOT! Altough some weird coders (including me;)) think they can bypass every single interrupt and address all hardware manually, don't let them foul you! Don't feel embarresed when you use int's.;) I feel kinda funny today...:) But apparently I'm not...okay, back to serious mode.
When the CPU detects an interrupt signal, it stops the current activity and jumps to a special routine, called an interrupt handler. This handler then detects why the interrupt occured and takes the appropiate action. When the handler is finished executing this action, it jumps back to the interrupted process. Several levels or "types" of interrupts are supported, ranging from 0 to 255. Each type has a reserved memory location, called an interruptvector. The interruptvector points to the appropiate interrupt handler. When two or more interrupts occure at the same time, the CPU uses a priority system. The CPU can also disable interrupts during crucial parts in a program. It can be done per level, but usually all interrupts are disabled. When the CPU executes an interrupt handler, all interrupts with the same or lower priority are disabled until the handler has finished. This means that even an interrupt handler can be interrupted by an interrupt with a higher priority level. The 256 priority levels supported by the Intel 80x86-processors can be split into three categories:
1) Internal hardware-interrupts
2) External hardware-interrupts
3) Software-interrupts

I'll explain these three types below.


Internal Hardware-Interrupts

Internal hardware-interrupts are the result of certain situations that occure during the execution of a program, eg. divide by zero. The interrupt levels attached to each situation are stored in hardware and can not be changed.

Level00h
01h
02h
03h
04h
05h
06h
07h
08h
09h
0Ah
0Bh
0Ch
0Dh
0Eh
0Fh
10h
11h-1Fh
SituationDivide by zero
Single Step
NMI
Breakpoint
Overflow
BOUND limit passed
Invalid opcode
Processor extension not available
Double exception
Segment limit passed
Invalid task status segment
Segment not present
Stacksegment limit passed
General protection error
Page fault
Reserved
Math coprocesst error
Reserved
8086/88X
X
X
X
X
-
-
-
-
-
-
-
-
-
-
-
-
-
80286X
X
X
X
X
X
X
X
-
X
X
X
X
X
X
-
-
X
80386X
X
X
X
X
X
X
X
-
X
X
X
X
X
X
X
-
X



External Hardware-Interrupts

External hardware-interrupts are produced by controlers of external devices or coprocessors and are linked to the processor pin for Non Maskable Interrupts (NMI) or to the pin for Maskable Interrupts (INTR). The NMI line is usually reserved for interrupts that occure because of fatal errors like a parity error or a power distortion. Interrupts from external devices can also be linked to the processor via the Intel 8259A Programmable Interrupt Controller (PIC). The CPU uses a group of I/O ports to control the PIC and the PIC puts its signals on the INTR pin. The PIC makes it possible to enable or disable interrupts and to change the priority levels under supervision of a program. One single PIC can handle eight interrupt levels, but several 8259A PIC's can be coupled to support as much levels as needed. 80286 and 80386 machines use two PIC's, wich can together configure 16 interrupt levels. Ever wondered what that IRQ level of your soundcard means? IRQ stands for Interrupt ReqQuest level, and as you might've guessed, you have 16 IRQ's. The keyboard for example uses IRQ 1 and the clock IRQ 0. This means that the clock has a higher priority than the keyboard, so if you type something the clock won't stop. The instructions STI and CLI can be used to enable/disable interrupts on the INTR pin, this has no effect on NMI interrupts.


Software Interrupts

Software interrupts are the result of an INT instruction in an executed program. This can be seen as a programmer triggered event that immediately stops execution of the program and passes execution over to the INT handler. The INT handler is usually part of the operating system and will determine the action that should be taken (eg. output to screen, execute file etc.) An example is INT 21h, wich is the DOS service interrupt. When the handler is called it will read the value stored in AH (sometimes even AL) and jumps to the right routine.


Interrupt Table and TSR basics

I mentioned before that each interrupt level has a reserved memory location, called an interrupt vector. All these vectors (or pointers) are stored in a table, the interrupt table. This table lies at linear address 0, or with 64KB segments, at 0000:0000. Each vector is 2 words long (4 bytes). The high word contains the offset and the low word the segment of the INT handler. Since there are 256 levels and each vector is 4 bytes long, the table contains 1024 bytes (400h). The INT number is multiplied by 4 to fetch the address from the table. A very useful property of this system is that we can change the vectors to point to another routine. This is in fact what a TSR (Terminate and Stay Resident) does. Suppose we want to make our own "put string" (INT 21h AH=09 DS:DX=offset string) routine. We could do this by making a procedure in our program and call it when needed, but that's not what we want. We want every other program writing to the screen this way use our routine. This can easily be achieved by changing the INT 21h vector to point to our routine. We should write the routine so, that it checks if AH=09 and if it's not it uses the old vector. However there is one problem, as soon as the program to set this up is finished the memory can be used again and will probably be overwritten by the next program. This results in a jump into the middle of the overwriting program when INT 21h is executed and will crash the system. We must find some way to "lock" the memory. This can be achieved by using INT 27h (Terminate and stay resident). When we end a program with INT 27h it will reserve the memory the program runs in.


How INT's are processed

In fact most of this information can be get from the rest of the text, but I wanted to make it a bit more clear. When the CPU registeres an INT it will push the FLAGS register to the stack and it will also push the CS and IP registers. After that the CPU disables the interrupt system. Then it gets the 8-bit value the interrupting device sends and multiplies this by 4 to get the offset in the interrupt table. From this offset it gets the address of the INT handler and carries over execution to this handler. The handler usually enables the interrupt system immediately, to allow interrupts with higher priority. Some devices also need a signal that the interrupt has been acknowledged. When the handler is finished it must signal the 8259A PIC with an EOI (End Of Interrupt). This can be done by sending 20h to port 20h. Then the handler executes an IRET instruction, wich is the same as a RET instruction with the only difference that it pops the FLAGS register too.

Assembly Tutorial

Chapter 4

Written by MAD


Index of Chapter 4

Looping
Conditional Jumps
Output of a Binary number
Output of a Decimal number
Mathematical instructions
A short word about VGA
Closing words
Next Chapter
Previous Chapter
Example Source used:

Binary output
Decimal output
Basic VGA
Hi there! I think that everybody knows how this tutorial works by now, so I'm not going to explain that again. I recieved some comments/suggestions, but not enough! Please mail me suggestions of what to explain in the next tutorials, because I think it's no use when I explain something nobody cares about...
Okay, what is Chapter 4 about? I'm going to discuss Looping, conditional instructions and some ways to output and input text and numbers. We'll use the Flags a lot, so I hope you remembered them. I use these (Pascal/C etc.) expresions to indicate conditions. Now let's get going!
!=Does not equal
==Equals
<Smaller
>Bigger
=<Smaller or equal
>=Bigger or equal


Looping

There are several ways to loop, but first I'll talk about the most easy way to do it. (I think I explained it in Chapter 2) You can easily loop with the, you guessed it, LOOP instruction. This instruction uses one operand, the memory location to loop to. It also uses the CX register as a counter. Loop simply decreases CX and checks if CX != 0, if so, a Jump to the specified memory location is issued. Example:
MOV CX,100
_LABEL:
INC AX
LOOP _LABEL 

This will increase AX 100 times. There are two other types of Loops: LOOPZ / LOOPNZSometimes these instruction are also called: LOOPE / LOOPNE 
LOOPZ works like LOOP except that it only loops when the zero flag is set, LOOPNZ only loops when the zero flag is NOT set. Now before I can give an example you need to know how to compare. The CMP instruction is used for this. It compares the two operands given and sets/clears the appropriate flags. After a CMP conditional instructions can be used to act on the result of the compare. For example jump to a special routine when two registers have the same value. Example:
MOV CX,10
_CMPLOOP:
DEC AX
CMP AX,3
LOOPNE CMPLOOP

This code might look like you'll never use it, but in some programs it can be very useful. It decreases AX ten times, but when AX == 3 it stops. Note I used LOOPNE, but LOOPNZ is the same. Now let's look at the CMP a little closer. In fact it does SUB AX,3 but doesn't store the result in AX, just alters the flags. So if AX == 3 the result of the SUB will be 0 and the Zero flag will be set. For the CPU equal is the same as zero (with conditionals) and it will always set the zero flag when the result of a mathematical operation is zero. So if we wanted to stop when AX == 0, there's no need to do a CMP. Just DEC AX and LOOPNZ. I suggest that you don't use LOOP(N)Z, nor LOOP. These instructions are slow, and decrease performance. Instead of LOOP you better use the combination: DEC CX / JNZ cmploop

Conditional Jumps

A conditional jump works like a normal jump, with the only difference that it jumps on a specific condition. First I'll give the most used conditional jumps below, then I'll explain them.
JZJump if Zero
JEJump if Equal
JLJump if Less
JGJump if Greater
JBJump if Below
JAJump if Above
JCJump if Carry
JOJump if Overflow
JPJump if Parity
JSJump if Sign

A lot of these conditional jumps can be combined. This way over 60 (!) different jumps can be made. Usually the "N" can be placed after a jump to reverse the effect. An "E" can be placed to let equal count too. Example:
JLE - Jump if Less or Equal, same as JNG (Jump if Not Greater)
Another thing is the difference between JG/JA and JL/JB Jump if Less and Jump if Greater are used for signed numbers, the other for unsigned numbers. Most of the time you'll use JL/JG.
A lot of instructions set the flags according to the result of the instruction. All conditionals jumps can be used to test the flags, so if an Overflow occurs a JO can be used. But not only instructions set flags (In fact this is not true, since the only things that can be executed ARE instructions) also procedures and INTs set flags. For example an INT that reads from disk returns a SET carry flag to indicate failure. Most routines/INTs use a SET carry flag for failure. We can test this flag with JC/JNC. The carry is set in more cases, eg. a bit overflow like this:
SHR SI,1
JNC @wordvalue
CALL Write_byte
@wordvalue:
CALL Write_word 

This will test if SI is odd, if so it first writes a byte, else immediately a word. This code is used a lot in 3D polygon fillers. Why is the carry flag set? Suppose SI = 0141h = 0000000101000001b. Now the SHR shifts all bits one place to the right and fills the start with 0. So this is what we get: 0000000010100000b. The least significant bit if "pushed" out of SI into the carry.

Output of a Binary number

Now lets talk how to convert and output a hexadecimal number into binary ASCII. What we want is output a string of 0 and 1 to the screen. The ASCII number of "0" is 48 and "1" is 49. I already made the source and it can be found here. As always I suggest you print it and have it with you when you read any further.
What's the thought behind the Source? We're going to use the SHR instruction the same way as we did before. If the least significant bit is a 1 this will set the carry. We use a JC to test if we need to output a "1" or a "0". We'll continue to shift like this 16 times (16bits=word). I might be a good eductation if you try to make the source yourself with this help. Or first read it and then try to reproduce it yourself without looking.
Well, the code itself isn't that hard. I think I didn't use any new instructions or directives. This code can be written differently with the use of ADC - ADd with Carry, but I won't explain that here. I used INT 21h subfunction 02 to output to the screen. The ASCII code is stored in DL.

Output of a Decimal number

Outputting a decimal number is a _bit_ harder, since hexadecimal has a base of 16 and decimal a base of 10. Binary had base 2 and 16 can be divided by 2 so this is easy, however 16 can not be divided by 10 so we can't do this bitwise. We'll have to find another way. It can be done by dividing the hexadecimal number by 10 and output the remainder to the screen. Again, code can be found here. first a short word about the DIV instruction. This instruction has two operands. But only one can be defined at the command line. The first operand (the dividend) is DX:AX. This means it's a 32bit number, DX the high 16bits and AX the low. The second operand is the divisor and is (in this case) a 16bit register. The instruction returns AX as quotient and DX as remainder. Another thing to take note of is that when we convert a number like this (with the DIV) we'll first get the "low numbers". (Eg. 10h, the first remainder we get is 6) To solve this we make smart use of the stack, we first push all values and later pop them. (stack uses LIFO remember?) We need to keep a counter of the pushed values, because if we pop or push to much, the RET instruction will go wrong.
I'll leave the output of a hexadecimal number to you. A little hint, in ASCII the "0" is 48, the "9" is 57, but the A is 65. So you can't just add the number to 48, you'll have to check and if the number is higher than 9 (ABCDEF) you first have to add 7.

Mathematical instructions

We've already seen the DIV instruction, but there are much more mathematical instructions. They form the basis of Assembly porgramming. Below I'll explain:ADD, SUB, MUL, DIV, INC, DEC, SHL, SAR.
ADD - Does what it says, it adds two numbers. The first operand is the register/mem that will be added to, the second is the register/mem that will be added. The instruction must be in the form of ADD reg/mem,reg/mem Only thing not allowed is two times mem (ADD [var1],[var2]).
SUB - Works the same as ADD, only substracts. Again the second operand will be substracted from the first. Form is SUB reg/mem,reg/mem. Two times mem is not allowed.
MUL - Unsigned Multiply. Multiplies two numbers, the first defined in a register, the second defined as AX. Here are the possible combinations (note, I haven't discussed 32bit regs yet, but I do give them here)

SizeOperand 1Operand 2ProductExample
8-bitAL8-bit reg/memAXMUL BL
16-bitAX16-bit reg/memDX:AXMUL BX
32-bitAL32-bit reg/memEDX:EAXMUL EBX


DIV - Unsigned Divide. We've already seen this insruction, so I only give the table with the possible combinations.

SizeOperand 1Operand 2QuotientRemainderExample
8-bitAX8-bit reg/memALAHDIV BL
16-bitDX:AX16-bit reg/memAXDXDIV CX
32-bitEDX:EAX32-bit reg/memEAXEDXDIV ECX


INC - Increase. Adds one to reg/mem, this is faster than ADD reg/mem,1 and should always be used instead. The form is INC reg/mem.
DEC - Decrease. Substracts one from reg/mem, this is faster than SUB reg/mem,1 and should always be used instead. The form is DEC reg/mem.
SHL - SHift Left. This will shift a reg/mem a number of bits to the left. It is the opposite of SHR. The number of bits to shift can either be an immediate (a constant value) or the CL register. The form is SHL reg/mem,imm/CL
SAR - Shift Arthimetic Right. This instruction works almost exactly like SHR, the only difference is that SAR will not always fill the "leftmost" bits with 0. If the number is negative (highest bit is set) it will fill it with 1 bits. So if we would SHR an 8-bit number, -5, 2 bits, the result is 00111110b. If we use SAR the result is11111110.

A short word about VGA

We now have been working in text mode some time, and I can imagine you want to make some graphics too. I won't explain much of this, because I think that's another tutorial. If you're really interested in VGA coding I suggest you read Denthor's VGA tutorials. However these tutorials are mainly in Pascal.
Okay, how do we get into a different video mode? The most easy way to do this is by using INT 10h of BIOS. Note this is NOT an MS-DOS function. This funtion reads AX and sets that video mode. There are a lot of video modes, but we'll only use mode 13h and mode 03h. Mode 13h is 320x200 256 colours, mode 03h is standard 80x25 text mode (16 colours). This way we get into and out of mode 13h:

MOV AX,13H
INT 10H
----------
MOV AX,03H
INT 10H 

Now how do we put a pixel on the screen? The screen memory is located at A000:0000 and works linearly. Byte one is the upper left pixel, byte 320 the upper right pixel. Each byte values matches the colour of the pixel. Technicaly it matches a colour palette entry, the palette can be changed. All pixel colours change immediately with the palette change, no need to refresh the screen. Let's take advantage of this in the next Source. Download it here. First, how DO we change the palette? We can do it with a few BIOS functions (INTs), but we wont. I'll teach you a way to do it directly (that's ASM, doing everything directly). Do you remember the string instructions? (MOVSB, MOVSW etc.) If not I suggest you look back at Chapter 1. There is another string instruction that doesn't move from mem/reg to mem, we can move from mem/reg to an I/O PORT. The I/O port in question is numbered 3C8h. First output the palette entry you wish to change, and after that the new values. The palette is set up as a 768 byte array. First byte is Red, second Green, third Blue. (256 colours * 3 = 768) The string instruction is OUTSB Let's look at the Set_Palette procedure.

Set_Palette PROC
mov cx,768
mov dx,3c8h
xor al,al
out dx,al
inc dx
rep outsb
ret
Set_Palette ENDP 

First we make CX 768, because we want to set all colours, we load DX with the I/O port number. Then we make AX 0 because we want to start at the first colour in the palette, we output this to the port. DX needs to be increased to write the actuall RGB values. Finally we output all the colours wich DS:SI points to. Well, this should be a short word, and it's getting long already, so just look at the Source. If you don't understand it, please be patient or read Denthor's tutorials. (or you can always drop me an Email)