textfiles/uploads/fasmtut.txt

332 lines
10 KiB
Plaintext

=================
FASM Tutorial
=================
Late at night...
July 22, 2010
by Jakash3
As my choice assembler, I find fasm to be an excellent and simplified program.
It is especially great for beginners and uses the well known intel style asm
syntax for Windows. For this tutorial, I will be demonstrating the creation
of console applications with fasm assembly in both 16 and 32 bit assembly
format.
Let's start with the simple 16-bit version. In 16-bit assembly, we have 2 byte
registers and we fill the 1 byte AH register with a number that represents a
sub-function to execute when calling an interrupt. Additional parameters for
that function are placed in various other registers, normally those registers
would be the general purpose registers but sometimes it may also be the source
and destination index registers (SI and DI).
Let's take a look at a hello world com file in fasm assembly:
--------------------
org 100h
mov ah,09
mov dx,msg
int 21h
mov ah,08
int 21h
int 20h
msg db "hello world!$"
--------------------
We know that all com file executable code starts at offset 0x100, so that's
what we place as the first line of code. Org 100h means origin (I think), the
specified number will be the address of where the program will start at. Also
notice that hexadecimal number values have an 'h' prefixed after the value.
After that we begin our com file where all our data and code goes in one
segment. Assembly code syntaxing looks standard,
--------------------
mov ah,09 ;Place value 9 into ah register, index of sub-function to print str
mov dx,msg ;Place address of msg variable reference into dx for str to print
int 21h ;Execute interrupt call to print the string
mov ah,08 ;Value 8 into ah, sub-function to wait for character input (pause)
int 21h ;Execute interrupt call
int 20h ;Exit com file
msg db "hello world!$" ;The $ terminated string whose address will be
;referenced with the 'msg' keyword.
--------------------
Simple enough right? But if you want more memory but still use 16 bit code,
you're going to have to break out of com files and use MZ format EXE programs.
The MZ executable format does not have code starting at 0x100 like com files,
and instead of a 64k limitation, MZ exe files now have more memory to have up
to 4 segments of 64k memory. Each of these segments have their address
referenced in the following registers:
CS = Code Segment
DS = Data Segment
ES = Extra Segment
SS = Stack Segment
To use the MZ exe format in your assembly code, just use the format keyword
specifier in fasm like in the following example:
--------------------
format MZ
entry main:start ; program entry point
stack 100h ; stack size
segment main ; main program segment
start:
mov ax,text
mov ds,ax
mov dx,hello
call extra:write_text
mov ax,4C00h
int 21h
segment text
hello db 'Hello world!',24h
segment extra
write_text:
mov ah,9
int 21h
retf
--------------------
Notice that I'm using different segments here. I specify I'm in a new segment
using the 'segment' keyword followed by the desired name of the segment. If
you take a look at Ralf Brown's Interrupt List, you see that the int 21/ah=09
function parameter for printing the string is: DS:DX -> '$'-terminated string
That means the segment pointed to by ds plus the offset found in dx, must
point to the string that's terminated with '$'. Now in com files, everything
lies in one segment so we didn't have to worry about that. But now we're
dealing with different segments so we must change the value of the ds register
so that ds (base segment address) + dx (offset address) = the address of
data that we are referencing when preparing to print a string.
So in that program, we moved the address of the text segment into ax, and
copied that value into the ds register
Since the hello message lies in a different segment, then "mov dx,hello"
operation would put the address revelevant to text segment into dx. hello is
the first data declared after declaring the text segment therefore that data
lies at an offset of 0 from text. So the segment:offset address of hello is
text:0000. From here we call a label within another segment by declaring the
full segment:offset addr to call and then print the string, return and exit.
Moving on to 32 bit fasm is where it gets a little interesting. Now in 32-bit
assembly, we do not call interrupts anymore; instead, we have to call dll
functions. We do so by pushing the arguments to the stack and then issueing
the call. In this example I will utilize the msvcrt.dll library from the C-
Language runtime.
--------------------
format PE console
entry main
include 'macro/import32.inc'
section '.data' data readable writeable
msg db "hello world!",0
p db "pause>nul",0
section '.code' code readable executable
main:
push ebp
mov ebp,esp
sub ebp,4
mov dword [esp],msg
call [printf]
mov dword [esp],p
call [system]
mov dword [esp],0
call [exit]
section '.idata' import data readable
library msvcrt,'msvcrt.dll'
import msvcrt,\
printf,'printf',\
system,'system',\
exit,'exit'
--------------------
Ok so, first we introduce the format of the executable. This is a PE format
executable console application (There's also format PE GUI). "entry main"
specifies the label at which we will start in our program, I used main.
Here we include an include file that comes with fasm. The fasm assembler
gets the path of the include file according to that inf conig file that comes
in the root directory of the fasm download. import32.inc contains macro
definitions that allow us to easily reference and import dll functions.
In other words, it allows us to use the 'library' and 'import' macros.
now for our data section, the 4th statement means that this section contains
non-executable data that we can read and write to during run-time. Inside this
section, I've declared 2 variables names msg and p, each of these variables
end with a null character.
Now for our readable and executable code section. Starting with our entrypoint
label main, I set up a stack frame and allocate 4 bytes on the stack by
subtracting 4 from the value of esp. Now in that 4 byte range I place the
address of msg in there and call printf, Next I place the address of the data
for the pause command on that same area (overwriting the previous address for
my msg data) and then I call system. Then I call exit with the dword (int)
argument 0 on the stack. Now this is all not needed, I just want to show you
how the stack and dll calls work. I personally prefer this method as it really
shows what's going on and it strives to save memory space by reusing old
blocks of memory that has been placed on the stack.
An easier method goes like this:
--------------------
push msg
call [printf]
push p
call [system]
push 0
call [exit]
--------------------
Simple: I push arguments, I call the function. It may not make much of a
difference in this application, but With each push I'm subtracting 4 from esp
and thus memory addressing could add up and become complicated on the stack.
Unless you are a l337 user like me fine tuning data on the stack, this is not
suggested. Otherwise, have fun pushing it...if you know what I mean.
Now another simple method for newbies is to use the 'invoke' macro which is
found in the 'macro/proc32.inc' include file. It's as if you're calling a
function from a high level language.
--------------------
invoke printf,msg
--------------------
invoke calls the function. The first argument is the name of the function and
the rest are the aruments for that function. An important thing to note is
that when calling dll functions, the required arguments must be pushed in
reverse order from last to first. So if I needed to call printf with 2
argument pointers: "Your name is %s",name
Then I will need to do this:
--------------------
push name
push msg
call [printf]
--------------------
However, in the case of using invoke, this macro automatically pushes the args
in reverse order for you. So you can call it normally like this:
invoke printf,msg,name
As for actually importing dlls before doing this, you declare your imports in
the '.idata' section. I used the macros library and import. Library macro
(found in import32.inc) allows me to import a dll name specifier. With each
pair of arguments for the library macro, I specify the associated name of that
dll and then the actual name of that dll.
For import, the first argument must be an associated dll name, following that,
each pair of arguments would be the name the desired specifier for a function,
followed by the actual name of that function found in the dll. Notice that I
also used backslashes to expand my one line statement on multiple lines for
easier readability.
Well that's it for this tutorial, check out these 2 samples of 32 bit asm and
their equivalent C code:
Name.asm:
--------------------
format PE console
entry main
include 'macro/import32.inc'
section '.data' data readable writeable
msg db "Enter your name: ",0
chrarr db "%s",0
msg2 db "Hello %s",0dh,0ah,0
p db "pause",0
section '.code' code readable executable
main:
push ebp
mov ebp,esp
sub esp,24
mov dword [esp],msg
call [printf]
lea eax,[ebp-12]
mov dword [esp+4],eax
mov dword [esp],chrarr
call [scanf]
mov dword [esp],msg2
call [printf]
mov dword [esp],p
call [system]
mov dword [esp],0
call [exit]
section '.idata' import data readable
library msvcrt,'msvcrt.dll'
import msvcrt,\
printf,'printf',\
scanf,'scanf',\
system,'system',\
exit,'exit'
--------------------
Name.C:
--------------------
#include "stdio.h"
#include "stdlib.h"
int main() {
char name[12];
printf("Enter your name: ");
scanf("%s",name);
printf("Hello %s\n",name);
system("pause");
return 0;
}
--------------------
Loop.asm:
--------------------
format PE console
entry main
include 'macro/import32.inc'
section '.data' data readable writeable
intl db "%d",0dh,0ah,0
p db "pause>nul",0
section '.code' code readable executable
main:
push ebp
mov ebp,esp
sub esp,12
mov dword [ebp-4],1
loopA:
mov eax,dword [ebp-4]
cmp eax,11
jge BreakA
mov eax,dword [ebp-4]
mov dword [esp+4],eax
mov dword [esp],intl
call [printf]
mov eax,dword [ebp-4]
inc eax
mov dword [ebp-4],eax
jmp loopA
BreakA:
add esp,4
mov dword [esp],p
call [system]
mov dword [esp],0
call [exit]
section '.idata' import data readable
library msvcrt,'msvcrt.dll'
import msvcrt,\
printf,'printf',\
scanf,'scanf',\
system,'system',\
exit,'exit'
--------------------
Loop.C:
--------------------
#include "stdio.h"
#include "stdlib.h"
int main() {
int i;
for (i=1;i<11;i++) {
printf("%d\n",i);
}
system("pause>nul");
return 0;
}
--------------------