software 1996 · whisky-ware · another beer

x86 Assembly Tutorial, Chapter 2 — Data Directives, Logic Gates and Conditional Jumps

🌐 Leer esta página en español →

Second chapter of the little tutorial. Distributed as whisky-ware through the Public Enemy BBS, circa 1996. The text is the original, untouched.

 ░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
 ░▒▓  Sami's dinky ASM howto  ▓▒░
 ░▒▓       Chapter Two        ▓▒░
 ░▒▓ Whisky-ware, by Sami 3;*)▓▒░
 ░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓

             -*-

 In this chapter you'll already
be programming and all that. I guess
one more little chapter will be
needed (or maybe even two)... but
right now I'm a bit busy, so make
do "training" with this one and
the previous one... If you have
doubts... you know, I'm on Public Enemy.

             -*-

( still don't know what I've got the
 warlock for) };)
░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░
░▒▓                                                                        ▓▒░
░▒▓                             ASM Tutorial                               ▓▒░
░▒▓                                                                        ▓▒░
░▒▓                           (Part II, for now)                           ▓▒░
░▒▓                                                                        ▓▒░
░▒▓   Sami, 1.996 (Manolo Buitre, to keep up some paternal anonymity)      ▓▒░
░▒▓                                                                        ▓▒░
░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░

This is the second chapter (and if it goes like the last one, in 3 chapters we'll have plowed through the whole course ;-)... I hope the last one treated you as well as summer treated me. Let's see what we left pending from last time........

01 — Getting started with hands-on stuff, skipping the theory... -;)

Alright... let's get on with programming... what would you like to do?... I can hear it already! A 3D engine?... sure... I believe that's what's called PRINT in Basic -3;)... but first a tiny bit more theory (which is actually practice, really). Before we go on, do you remember opcodes?... if you don't, go back and review them, because here I'm going a bit further:

; Compila a partir de aqui
; Segundo programa de XXXXXX

        .MODEL MediuM           ; En este caso mejor un Tiny
        .DATA                   ; Sobra (pero sigue leyendo!)
        .CODE
                mov ah,4Ch      ; Salida al dOS con "errorlevel"
                int 21h         ; (es un servicio de interrupcion)
         END

Come on, bet you know what that program up there does?... now let's move on to a *compiler* directive (not a processor instruction)... actually there are several but they all do very similar things:

Let me introduce you to DB, DW, DD, DQ and DT (although there are a few more, more or less)

Before you go cursing at the saints, I'll have you know I already did, so hush... What does DB do, you ask?... Look:

        .MODEL MediuM           ; Como tengo datos y codigo mejor el medium
        .DATA                   ; para este ejemplo no sobra, eh! 3;)

                Mi_letra  DB            'A'     ; Mete el codigo ASCII de A
                Mi_letra2 DB            65      ; (que es este)
                Entero    DW            1234h   ; definimos 16bits
                Mi_letra3 DB            41h

        .CODE

                mov al,byte ptr Mi_letra        ; Mete en al 41h
                mov bl,byte ptr Mi_letra2       ; Mete en bl 41h
                mov cl,byte ptr Mi_letra3       ; Mete en cl 41h

                mov ah,4Ch      ; Salida al dOS con "errorlevel"
                int 21h         ; (es un servicio de interrupcion)
         END

Okay dude! Don't get like that... bad example, I know... we'll play around with it a bit so you get the hang of it. First keep your little ASCII table handy, got it?... let's go on...

Let's start by analyzing the program:

- ".MODEL MediuM"
        I want a little segment for data and another for code.

- ".DATA"
        Whatever comes before ".CODE" will be my data segment,
        where I'll stash whatever data I want (up to 64 Kb)

- "Mi_letra     DB     'A'"
        Assigns a 1-byte variable holding the ASCII code
        of the letter "A" (uppercase), which is 65 in decimal
        or 41h (same thing, of course). The following assignments
        with DB work the same way. If we used DW, "we'd be defining a Word",
        that is, a 16-bit integer, with DD a double word, etc.

        Let's dig into this a bit more since it's veeeeery important:

        The first DB goes at the start of the data segment,
        the second DB in the second byte of that segment, and putting
        the DW in the third and fourth byte gives us this:

                         3   4  <== offset within the data segment
                        -------
                        34h 12h

        I haven't mixed anything up... it's just that words, double
        words and so on get stored the other way round, meaning the
        "dumbest" part goes first, and the high part of the value
        (I believe it's called MSB in English, for
        Most Significant Byte) comes after. This is something
        very much worth keeping in mind if, say, you've defined
        something with a DW and later want to read just one byte
        from that memory location.

- "mov al,byte ptr Mi_letra        ; Mete en al 41h"

        Well, in case I haven't hammered it home enough, that ";" is a
        comment. And then there's that "byte ptr" bit... what it does
        is say to load one byte from the memory location "Mi_letra".
        If we'd written "mov ax,word ptr Mi_letra" instead, ax would
        hold this byte plus the next one. (Simple enough, right?)...
        Well, maybe this is a silly thing to point out since if you write
        "mov al,XXX" it's already implied (because AL is 8-bit) that you
        only want to grab one byte. In fact I THINK modern compilers
        don't even need this spelled out (but since I'm running my
        ancient TASM I prefer to add it for clarity).

Where were we?... ah... right... the program's already done. As an exercise for this mini-topic... grab the Turbo Debugger and load this program (.EXE). To do that you'll need to write the .ASM with a plain text editor, then run it through TASM ("TASM mi_prog.asm") and finally link it ("TLINK mi_prog.obj"). Breakpoints in TD are set with F2, F9 runs the program to completion, and F7 lets you "trace" step by step what the little beast is doing. As tips... play around looking at memory, or your program loaded into memory (as opcodes), etc. TD (Turbo Debugger) will be one of the most useful tools you'll ever have (well, the best one), so at first you won't have a clue what it is, but once you play with it a bit you'll see how simple it is. :)

Another exercise (a bit dumb, maybe) is to grab a disassembler and produce another ASM (but starting from the executable)... this is good for finding out what those .MODEL directives and the rest actually do, plus you'll realize just how many ways there are to write the same thing in ASM.

Even though in the last chapter I lied to you saying there'd be no more theory (I think I've been lying to you the whole "way" ;)... in this one it won't "come at you" as much anymore (which doesn't mean there won't be any more, just that it'll be more "discreet")... in this chapter I've got planned for you to play a bit with the 8086, using it for simple calculations and so on. You'll use some of the more useful interrupts and the rest... so, onward :)

Let's start by printing some text to the screen:

        .MODEL MediuM           ; 64Kb de datos y 64Kb de codigo
        .DATA                   ; Segmento de datos

           Cadena       DB      "Esto esta en pantalla$"
                                ; La cadena va entre comillas, y si usas
                                ; el DOS para imprimir una cadena (cadena
                                ; es una tira de bytes), el fin de cadena
                                ; se señala con un "$".

        .CODE                   ; Segmento de codigo

           mov ax,@DATA         ; Configura el DS => @DATA es el segmento
           mov ds,ax            ; de datos que pones con la directiva ".DATA"
                                ; pero DS no es accesible directamante (como
                                ; los demas registros de segmento) y por eso
                                ; metemos el segmento en AX.

           mov dx,offset Cadena ; El servicio 9 de los servicios del DOS
           mov ah,9             ; (int 21h) imprime una cadena hasta que
           int 21h              ; encuentra un "$".. es decir... un 36.
                                ; Para indicarle que cadena quieres sacar
                                ; por la pantalla, el segmento de donde
                                ; esta la cadena se mete en DS, y el offset
                                ; en DX. Luego se llama a la interrupcion 21h.

           mov ah,4Ch           ; Acaba el programa
           int 21h

        END                     ; Fin del programa

This is the "listing" of the program above (it's generated with: TASM uu.asm /la /zi ... and it produces uu.lst):

      1  0000                   .MODEL MediuM
      2  0000                   .DATA
      4  0000  45 73 74 6F 20 65 73+  Cadena  DB  "Esto esta en pantalla$"
      7  24
     13  0016                   .CODE
     15  0000  B8 0000s         mov ax,@DATA
     16  0003  8E D8            mov ds,ax
     21  0005  BA 0000r         mov dx,offset Cadena
     22  0008  B4 09            mov ah,9
     23  000A  CD 21            int 21h
     29  000C  B4 4C            mov ah,4Ch
     30  000E  CD 21            int 21h
     32                         END

02 — Templates for your simplest programs

TO MAKE A .COM

Codigo Segment
        assume CS:Codigo, DS:Codigo

        org     100h

                                ; AQUI VA TU CODIGO Y TUS DATOS.

     Comienzo:
        mov     ax,4C00h        ; Salida al dos con errorlevel
        int     21h

Codigo EndS
       End Comienzo

Compile:   Tasm mifich.asm
Link:      Tlink/t mifich.obj

TO MAKE AN .EXE

        .MODEL medium
        .386
        .DATA

                ejemplo DB "Hola$"

        .CODE
                mov     ax,4C00h        ; Salida al dos con errorlevel
                int     21h
        .END

Compile:   Tasm mifich.asm
Link:      Tlink/3 mifich.obj

FOR .QLB FILES (for Quick Basic 4.x)

        .MODEL medium,Basic
        .386
        .DATA

                ejemplo DB "Hola$"

        .CODE
             Public MiSubrutina

                MiSubrutina Proc Far, parametro:word
                        MOV     BX,parametro
                        MOV     AX,[BX]
                        RET
                MiSubrutina EndP

        .END

Compile:        Tasm mifich.asm
Link into LIB:   Lib milib.lib +mifich.obj
Create QLB:      Link/QU milib.lib, milib.qlb,nul,bqlb45.lib

03 — Logic gates

They exist both physically and logically... they form the most basic building block of the computer, from memory chips right up to the processor itself, so you can imagine how important they are.

The processor instructions are: AND, OR, XOR and NOT

At first they'll seem completely useless, you won't remember them, and even if you do you won't know where to use them, but that just comes with practice 3;D

NOT ==> 0 - 1 / 1 - 0 — Negates: if you have a 1 it gives you a 0, and vice versa:

        .MODEL tiny
        .CODE
                mov ax,11111111b ; o lo que es lo mismo 00FFh
                not ax           ; En AL tendremos todo a 0 y en AH todo a 1
                mov ah,4Ch       ; Salida al DOS
                int 21h
        END

The best thing with logic gates is that you reach your own conclusions, since the first time I just memorized them, the second time I forgot them, the third time I drew my own conclusions, and finally I actually used them ;DDDDD. Try them out with the Turbo Debugger, and if you don't have it, well, you know... };)

OR ==> 0-0=0 / 0-1=1 / 1-0=1 / 1-1=1:

                mov al,00010001b
                or  al,00000010b

        Bit number 1 gets set to 1 without touching the other
        bits. The result, as with every ASM instruction, gets
        stored in the first operand, meaning, in this case, AL.

XOR ==> if they're equal it gives 0, if different it gives 1. That's why it's used: "xor ax,ax" to clear AX... since AX and AX are equal (well, duh...)...

AND ==> Both need to be 1 if you want a 1. That's why it's used to make bit-by-bit comparisons:

                mov al,01001001b
                and al,00000001b        ; Esto dara 1... :)

                mov al,00000000b
                and al,00000001b        ; Y esto dara 0... :)

In assembly there's also another kind of AND. What sets it apart is that TEST (that's what this new instruction is called) doesn't store the result anywhere... and since you might be wondering what the point of that is (yeah, riiiight... ..... ;DDD)... let's move on to MORE PRACTICE!!!

04 — Jumps and all that (what's called branching)

So far the handful of little programs we've made have been linear. They started at a beginning. And ended at an end. But of course... few programs can stay linear, and they need to do one thing or another (depending on certain conditions, of course).

Enough rambling, I need my afternoon snack and it's already 7:

        .MODEL medium
        .DATA

                h1       DB "Pues el bit 0 estaba puesto a 1$"
                h2       DB "Pues el bit 0 no estaba puesto a 1$"
                variable DB 00h

        .CODE

                mov ax,@data                    ; Prepara DS con @DATA
                mov ds,ax

                mov byte ptr variable,00110101b ; mete esto en "variable"
                mov al,byte ptr variable        ; en al esta 00110101b

                test al,00000001b               ; una AND con un 1
                jz   no_uno                     ; si el resultado da 0...
                                                ; ...salta a la etiqueta
                mov ah,9                        ; no_uno (Jump if Zero)
                mov dx,offset h1                ;         ^       ^ ==> JZ
                int 21h                         ; ... y si no es 0, seguir
                jmp final                       ; ... y saltar a final

              no_uno:
                mov ah,9
                mov dx,offset h2
                int 21h

              final:
                mov ah,4ch                      ; Sale al DOS
                int 21h
        END

Let's look at the explanation for this "phenomenon"... it turns out that where it says "jmp final", while that instruction is executing, the CS:IP (you'll finally know what that is by now)... well, let's say it's 6000:0020 (made up, of course). "Final" is a symbol, a label..."something" that marks a memory location (which is the one for "mov ah,4ch")... so if we tell it to jump to Final (jmp final)... it'll jump to the memory location where "mov ah,4ch" sits... there are three kinds of jumps...

short: allows a *** RELATIVE *** jump (from wherever
       you happen to be at that moment) of 8 bits... meaning,
       between -128 and +127. Normally you don't have to worry
       about the jump type, since the compiler usually sets it
       through the directives. It takes up the JMP opcode + 1 byte.

near : an intra-segment jump, within the same code
       segment. So it only modifies IP. It therefore only
       takes up one more byte than the previous one.

far  : an inter-segment jump... between segments.. it modifies
       CS:IP, so it takes up 2 more bytes than the previous one.

It's important to know which jump type to use, since the "farther" it is, the slower the jump, and the more memory it takes up (the instruction, of course).

05 — Special aside: the flags ;)

The flags are yet another 16-bit register...... yeeees.... anoootheeer one 3:). ... but this one is special... you can't write "mov ax,flags" or anything like that. Here are the flags (let's hope I don't mess this up too much ;):

       15                                                0 ==> bit number
        *  *  *  * | *  *  *  * | *  *  *  * | *  *  *  *
                     OF DF IF TF  SF ZF    AF     PF    CF

Reeeeelax.... it's nothing!!!

If you're reading this for the first time, don't worry if you don't get any of it, this aside is here for reference, not for explanation ;)... the explanation works muuuuch better in practice than in theory. Each of these bits is there to signal one thing or another (that's why they're bits). Here's the list... if a bit is set to 1:

CF: "carry flag" ==> the carry when adding
OF: "OverFlow"   ==> when a value overflows and doesn't fit in 8 or 16 bits
ZF: "Zero Flag"  ==> (this is the one JZ uses)... 1 if a comparison
                     is true.
SF: "Sign Flag?" ==> blah, blah, blah
AF: "?"          ==> the same as carry but when you're working in BCD
                     (you'll see what that is later, for now just keep
                       taking small hops ;)
DF: "Direction flag" ==> tells you whether strings are stored going
                     up or down. (think about it)

IF: whether interrupts are enabled (or not, of course)
TF: the "trapping" flag... for running programs "step by step"... the
    Turbo Debugger uses this little devil, for example, when you're
    hitting F7 and F8.

There's more in processors above the 8086, buuuut.... Samy has just as much info on PCs beyond the 8086 as you do... so once we move past the 8086 you'll see how my "knowledge" ("although my mommy says I don't have any") starts degrading 3;(

06 — Jumps and all that (what's called branching) ][

And since there's no two without a one (and I'll bet you anything I'll have to do a third one too... and I'm sure I'll win, obviously X-D)... here come MORE JUMPS!!! So, since earlier I only showed you the unconditional jump, the one that jumps no matter what (jmp), and JZ (if the result is 0)... you must've been getting suspicious, but here come some more:

JA      : jump if above => jumps if bigger
JAE     : jump if above or equal => if bigger or equal
JB      : (ah, gooootcha) jump if below => if smaller
JBE     : jump if below or equal => or smaller or equal
JE      : jump if equal => jumps if equal

And now the opposites of these:

JNA     : if NOT bigger
JNB     : if not smaller

... well, all negated, just with an N right after the J ;)

We'll keeeeep going... there are looots more CX-DDDDDDDDDD, but first let's train a bit with these since they're the simplest and most used:

        .MODEL medium
        .DATA

                h1 DB "es igual a 1$"
                h2 DB "No es igual a 1$"

        .CODE

                mov ax,@data                    ; Prepara DS con @DATA
                mov ds,ax

                mov al,1
                cmp al,1                ; compara al con 1
                jne no_uno              ; y SEGUIDAMENTE... si no es igual
                                        ; salta a no_uno

                                        ; INCISO: "CMP" es una instruccion
                                        ; del procesador, no es el test
                                        ; de antes (que tambien es del proce-
                                        ; sador). Y casi siempre lleva el
                                        ; Jxxx detras.

                mov ah,9
                mov dx,offset h1
                int 21h
                jmp final

              no_uno:
                mov ah,9
                mov dx,offset h2
                int 21h

              final:
                mov ah,4ch                      ; Sale al DOS
                int 21h
        END

Sorry for not adding comments but it's the same as the previous one :) The rest of these jump types, you know the drill, go practice them 3:D

Next week we'll carry on with other kinds of jumps, subroutines and the rest. We'll create our first interrupt, etc. But I'm leaving you homework (X-DDDDDDDDDD).... try out everything we've covered so far, no fear at all because you can't break anything (as long as you don't touch INT 13h, the disk one)... second: finally go get the interrupt list, it's on Public Enemy (53-18-07)... the actual Ralf Brown one. Try a few things out with it, you learn a lot that way, and then grab yourselves the processor's instruction list (and if you can't find it, just ask me for it). And I don't think I'm forgetting anything else... take care and happy (with a wink, of course) weekend 3:*DDDD...... FAFAN, TREAT YOURSELF TO SOMETHING!!!

PS: Whisky-ware document, share-whisky, or whatever you want to call it }:*D~~