Saturday, June 16, 2012
Introduction to Assembly (Part 2)
Now we can create some fancy structures, let's make a for loop.
MOV AX, 1d
MOV BX, 2d
MOV CX, 8d ;you can use d suffix to represent decimal, it will be converted to hex 0Ah internally.
MYLOOP: ;loop begin
MUL BX
DEC CX
CMP CX, 0
JNE MYLOOP ;loop condition check
INT 20h
Can you see the for structure? Can you guess what does this code do?
C version of this code would look like
ax = 1;
bx = 2;
for(cx = 8; cx != 0; cx--)
ax = ax * bx;
ax will be 2^8 which is 0100 in hex.
Now that we know basic control structures and how to use registers, let's move on to I/O. Because I/O is about the operating system and a process can't do I/O on it's own. So we need to call System API or Interrupts in this case. Interrupts are called with the INT method. You already saw INT 20h, which indicates that our process has ended.
We will use INT 21h to get input from console, or to output something to console.
You can think of this as a function, but this function lacks two things, in assembly we can't give direct parameters to interrupts, neither they can return anything in a way you knew before. So our friends are registers as always.
By the time we call INT 21h, if there is 01h in the first 8 bits of the AX register (which is the significant part), we read a character from the standart input, and that characters ascii value is written into last 8 bits of the AX register. Else if there is 02h in the first 8 bits of the AX register, the character corresponding to the ascii value in the last 8 bits of the DX register is printed to the console.
Since sometimes we need to just use 8 bits of registers, assembly provides us with a feature which allows us to access the significant part of register A with writing AH and the least significant part with writing AL.(Ahigh and Alow) same applies for AX,BX,CX and DX registers. So if we change AH, AX changes, if we change AX, AH changes too. AX = AH (shifted to left 8 bits) + AL for all the time. Don't think you can store 3 different things in three of them. (There is no "3 of them" actually they all represent the same thing)
Let's print "Hello world!" to the screen.
MOV AH, 02h ; it is enough to set AH once.
MOV DL, 'H'
INT 21h
MOV DL, 'e'
INT 21h
MOV DL, 'l'
INT 21h
MOV DL, 'l'
INT 21h
MOV DL, 'o'
INT 21h
MOV DL, ' '
INT 21h
MOV DL, 'W'
INT 21h
MOV DL, 'o'
INT 21h
MOV DL, 'l'
INT 21h
MOV DL, 'd'
INT 21h
MOV DL, '!'
INT 21h
INT 20h
If we wanted to read characters in the middle we would have to change AH to 01h and then again to 02h to be able to output, but here we don't change the value of AH, so we don't need to set it again for each output.
Another interesting thing is we could use character literals like 'W' or 'd', this is because our compiler changes them to the corresponding ascii value, so
MOV DL, 'W'
does the same thing as
MOV DL, 057h
or
MOV DL, 87d
An ascii table assistance is needed sometimes, but we can get away from that whenever we can use character literals. It increases the readability greatly, isn't it?
How would you go about printing the entire alphabet? A while loop would be a good way to implement it. Let's make our while loop.
MOV AH, 02h
MOV DL, 'A'
PRINT:
INT 21h
INC DL
CMP DL, '[' ;This is the character comes after Z
JNE PRINT
INT 20h
This code is pretty straight forward and I know you understood it easily. We just used the techniques we learned, nothing new.
C version would be
ah = 2
dl = 'A'
do{
print;
}while(dl != '[');
Enough with outputting, let's get some input from the user.
First we will do a one char echoer.
FOREVER:
MOV AH, 01h
INT 21h ; *
MOV AH,02h
MOV DL, AL ;Because the read character is written to AL, and we need it to be in DL to be output
INT 21h
JMP FOREVER
*:We don't need to move anything to DL because that is the output itself, DL will be overwritten.
This program reads a single character from the standart input and echoes it back to the std output. It is a blocking call which means if you don't type anything INT 21h will wait there till you enter a character. To exit this program you need to CTRL+C because we didn't add a finishing condition. Let's add a finishing condition to this loop.
NOTFOREVER:
MOV AH, 01h
INT 21h
MOV AH,02h
MOV DL, AL
INT 21h
CMP 'q', AL
JNE NOTFOREVER
INT 20h
If you enter q, this program will terminate, but before terminating it will echo the 'q' too. If you consider this as a bug you can do the following:
NOTFOREVER:
MOV AH, 01h
INT 21h
MOV AH,02h
CMP 'q', AL
JE FINISH
MOV DL, AL
INT 21h
JMP NOTFOREVER
FINISH:
INT 20h
This will control the character before outputing.
Now we can make a primitive guess a number game, only numbers allowed are 0 to 9
MOV CL, 5h ; First let's forget about the "random" part and I'll explain it later.
ADD CL, 48d ; Add 48 to get the ascii value of the digit. This ease the comparison process.
MOV AH, 02h
MOV DL, 'P' ; P is our Please guess: notification
INT 21h
GUESS:
MOV AH, 01h
INT 21h ; let's read a guess
CMP AL, CL ; compare the guess with our "random" number
JE CORRECT
JG GREATER
MOV AH, 02h
MOV DL, 'L' ;L means the random number is less, input a smaller number
INT 21h
JMP GUESS
GREATER:
MOV AH, 02h
MOV DL, 'G' ;G means the random number is greater, input a bigger number
INT 21h
JMP GUESS
CORRECT:
MOV AH, 02h
MOV DL, 'C' ; C is our congratulations message
INT 21h
INT 20h
After you understand this program, there are two things lacking. One is random number, second is that the messages are only one character. Second one is because you know how to output multi-character messages from hello world, so you can translate this to a fully program with reasonable messages but I didn't want to do it because it would make the code much longer thus readability would suffer, and you may miss more important parts like control structures.
The first one is because random number generation is darn hard generally you would call a C function to give you a random data, or allocate a dynamic memory and use the garbage inside to use it as a seed. Anyway you have to design your own pseudo-random algorithm to make this work and it is way out of our scope for this tutorial. I am sorry that it isn't as easy as high-level languages, an interrupt would be nice if it put a random number in one of us registers, when we call it.
Now that we know the idea of JMPs, there is a more sophisticated version of JMP which is CALL. You can think of this as a function call, or more correctly a procedure call.
The syntax is as follows:
CALL myprocedure
myprocedure PROC
;Instructions
;Instructions
;Instructions
myprocedure ENDP
The special thing is instructions can contain the RET instruction which returns to the called place. You can do the "parameter passing" and "function return" with pointers
readchar PROC
MOV AH,01
INT 21h
RET
readchar ENDP
Now whenever you need to read a character you can do
CALL readchar
This means you can reduce two lines to a single line as well as increasing the readability of your code. If you are reading many characters in the code I would advise you to use this form. Of course you can do many more things with CALL instruction.
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment