Why TASM is lame !
by Lucifer48 [Phrozen Crew]
History :
Contents :
MASM or NASM users, go away !
Well ! I wanted to write something about TASM. I have never been able to find any doc/info about it; so i needed some time to use it correctly. Lots of people says that TASM users are lamers (MASM rulez dixit ...), is it true :P Sure, MASM is regularly updated, that's a good point, the linker is really powerful (yes, tlink32 is poor). But for my need, i'm happy with tasm (i CALL, i don't INVOKE). Well, i'm sure that some people use MASM because they are not able to make their resources without using Visual Studio. Guys criticize TASM because they never used it ! Yes, TASM support MMX, MACROS, .data?, DirectX and more .. I only use MASM to build my VXD. I should also try NASM one day ;)
.data
NICESTRUCT1 struct
champ1 dd 0
champ2 dd 0
NICESTRUCT1 ends
NICESTRUCT2 struct
champ2 dd 0
champ3 dd 0
NICESTRUCT2 ends
dummy1 NICESTRUCT1 <1,2>
It will fail at compilation: Symbol already defined elsewhere: champ2
That's a real problem. A field's name must be unique. Lots of windows struct has always common fields (like cbSize), the only solution is to rename the fields; examples: nid_cbSize, msg_cbSize, ti_cbSize, tme_cbSize, rbbi_cbSize (for NOTIFYICONDATA, MSGBOXPARAMS, TOOLINFO, TRACKMOUSEEVENT and REBARBANDINFO structure). Dont forget that uppercases and lowercases are different for TASM (/ml).
.data
PRETTYSTRUCT struct
field1 dw 0
field2 dd 0
PRETTYSTRUCT ends
OTHERSTRUCT struct
field3 db 0
field4 dd 0
field5 dd 0
OTHERSTRUCT ends
dummy1 PRETTYSTRUCT <-1, -2>
.code
mov eax, dummy1.field5 ;dword ptr [dummy1+5] : will give eax=FF
That's the direct consequence the above problem (see part1).
cmp 123h, dword ptr [esi] ;is not accepted (Illegal immediate)
cmp dword ptr [esi], 123h ;is ok (left member: register or address)
lea eax, [1-ecx] ;TASM will understand: lea eax, [ecx-1]
No error ! Remark: you can also try with many other instructions (push dword ptr [2-eax]).
That's very well known too. If you declare an emply .data or no data at all. There will be a problem with the import. Example:
.data .code main: int 3 call ExitProcess, NULL end main
Result : No .idata and no .reloc section !
You just cannot insert a comment after a \. Example :
mov eax, \
ecx
The above example is perfectly allowed, but this one is incorrect :
mov eax, \
;comment ... (you will get two errors : Need address or regisrer and Illegal instruction)
ecx
names : Don't put include dashes ("-") in a proc name, it won't work.
toto-toto proc
mov eax, 6
ret
toto-toto endp ;replace by an underscore ("_") !
uses keyword : To push registers, separate them by a space, not a coma !!!
toto_toto proc uses esi edx, mystring : dword
mov eax, 6
mov ecx, mystring
ret
toto_toto endp ;[advice : registers to save in a wndproc : ebx esi edi]
If you put a coma after esi you won't get any compilation error, so beware !
local keyword : For creating local variables (located on the stack).
toto_toto proc uses esi edx, mystring : dword
local i : dword
mov i, 0
mov eax, 9
ret
toto_toto endp ;[beware to buffer overflow !]
It is usually used to include used api. But, what a waste to time to write each time at the top of your source your extrn stuff. That's boring ! Example (Virogen's PE Shrinker v0.14):
extrn ExitProcess:PROC extrn CreateFileA:PROC extrn CloseHandle:PROC extrn ReadFile:PROC extrn WriteFile:PROC extrn SetFilePointer:PROC extrn MapViewOfFile:PROC extrn CreateFileMappingA:PROC extrn UnmapViewOfFile:PROC extrn SetEndOfFile:PROC extrn SetFilePointer:PROC ...
The problem with extrn is that it won't be ignored if the api is not used. Used or not, your api will be written in the PE's import. The magic solution is to replace extrn with global. If the function (api) is used, (of course), it will be included in the import, otherwise i will be ignored ! Now you can make you own "mywin.inc" full of "global" and constant :) A little piece of my "monwin32.inc" :
global _wsprintfA : PROC ;"wsprintfA" (don't pop the args) global BeginPaint : PROC global BeginPath : PROC global BitBlt : PROC global CallWindowProcA : PROC global CheckDlgButton : PROC ;see BST_CHECKED, BST_INDETERMINATE, BST_UNCHECKED ...
Of course, don't hesitate to add some equates, to make your life easier :
CreateFile equ <CreateFileA> CreateWindowEx equ <CreateWindowExA>
How to convince you.. ;) We are working on a low level language. By using prototype, you are losing some flexibility. This a little example :
MessageBoxA PROCDESC WINAPI :DWORD,:DWORD,:DWORD,:DWORD
Remark : MessageBoxA PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD is also accepted.
.code
;you have too way to call the api.
;first (the number of arg is checked) :
call MessageBoxA, hwnd, offset sayhello, NULL, MB_OK
;second (the number of arg is not checked) :
push MB_OK
push NULL
push offset sayhello
push hwnd
call MessageBoxA
This first case is the problem ! You won't be able to do that:
.code
.if (eax!=0)
push MB_ICONEXCLAMATION
.else
push MB_ICONHAND
.endif
call MessageBoxA, hwnd, offset sayhello, NULL
You will obtain a Too few arguments to procedure error. That's on this point you can lose flexibility. So i prefer to declare each happy as :PROC (the number of args aren't checked but i think it's better).
Remark : In a same way, i don't feel necessary to TYPEDEF everything. Example (short extract of windows.inc):
BOOL TYPEDEF DWORD LPDWORD TYPEDEF PTR DWORD WPARAM TYPEDEF UINT LPARAM TYPEDEF DWORD HANDLE TYPEDEF DWORD HWND TYPEDEF DWORD HGLOBAL TYPEDEF DWORD HGDIOBJ TYPEDEF DWORD HACCEL TYPEDEF DWORD HBITMAP TYPEDEF DWORD HBRUSH TYPEDEF DWORD HDC TYPEDEF DWORD HFONT TYPEDEF DWORD HICON TYPEDEF DWORD HMENU TYPEDEF DWORD HINSTANCE TYPEDEF DWORD HRGN TYPEDEF DWORD HRSRC TYPEDEF DWORD HCURSOR TYPEDEF DWORD COLORREF TYPEDEF DWORD ...
I really don't think it necessary (you can use it in some special case [like DirectX], don't focus too much on typedef). Beyond that, just think it's 32 bit. I do asm to be free ! For people loving typed stuff : code in C (LPARAM, WPARAM, HWND ; MAKEINTRESOURCE, (LPARAM)TEXT("48"), ..). That's my view. Just properly comment your asm source, there won't be any problem.
This is my tasm directory:
C:\TASM32 \Bin ;tasm32.exe, tasm.cfg, tlink32.exe, tlink32.cfg, brc32.exe, brcc32.exe, ... \Include ;your include containing api and constant (monwin32.inc for me). \Lib ;import32.lib (902k, contain fundamental DLL), but you can add other specific .lib like masm. \Projects ;or another name, your projects will be located there. My tasm.cfg contain: -ic:\tasm32\Include My tlink32.cfg contain: -Lc:\tasm32\LibConsequently you won't need to declare many stuff, i simple line is enough !
MASM: include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib ... TASM: include mywin32.incThis is the way i compile (my make.bat, i don't say that it is the best way ! It's just my way !) :
@echo off
c:\tasm32\bin\brc32 -r myprog1.rc
c:\tasm32\bin\tasm32 -ml -m5 -q myprog1.asm
c:\tasm32\bin\tlink32 -Tpe -aa -c -x -V4.0 myprog1.obj,myprog1.exe,,import32,,myprog1.res
del myprog1.obj
del myprog1.res
Remark: Extensions are not indispensable (c:\tasm32\bin\tlink32 -Tpe -aa -c -x -V4.0 myprog1,myprog1,,import32,,myprog1).
@echo off
c:\tasm32\bin\tasm32 -ml -m5 -q myprog2.asm
c:\tasm32\bin\tlink32 -Tpe -aa -c -x myprog2.obj,myprog2.exe,,import32
del myprog2.obj
Remark: You can forget the output name myprog2.exe. (c:\tasm32\bin\tlink32 -Tpe -aa -x -c myprog2.obj,,,import32).
@echo off
c:\tasm32\bin\tasm32 -ml -m5 -q myprog3.asm
c:\tasm32\bin\tlink32 -Tpd -aa -c -x myprog3.obj,myprog3.dll,,import32,myprog3.def
del myprog3.obj
+ : plus
- : minus
* : multiplication
/ : integer division
MOD : modulus
SHL : shifts x bits on the left
SHR : shifts x bits on the right
OR : bit or
AND : bit and
XOR : bit xor
NOR : bit nor (a nor b = not(a or b) )
NAND : bit nand (a nand b = not(a and b) )
&& : conditionnal and (example: .if (eax<1 && ecx>5) )
|| : conditionnal or
^ : conditionnal xor
EQ : (a EG b) gives 0 (if a!=b) and -1 (if a==b)
GT : (a GT b) gives 0 (if a<=b) and -1 (if a>b)
LT : (a LT b) gives 0 (if a>=b) and -1 (if a<b)
Note : 4 bases are available :mov eax, 11100b ;2 mov eax, 34o ;8 mov eax, 28d ;10 mov eax, 1Ch ;16
Like in C, you can make conditonnal definitions. Example :
IFDEF WHATYOUWANT ;IFNDEF is available too !
;...
ENDIF
Or, with the ELSE statement:
IFDEF WHATYOUWANT
;...
ELSE
;...
ENDIF
To define, just add this line inside your source (if you don't want to define it, comment the line) :
WHATYOUWANT = 1 ;1 or any other number (except 0 ?), equ works too.
Example 1 :
REPEAT 10
nop ;statements
ENDM
will be assembled: nop nop nop nop nop nop nop nop nop nop.
Example 2 :
FOR i, <1,2,3,4,5,6,7,8,9,10>
mov eax, i
ENDM
will be assembled: mov eax,1 mov eax,2 mov eax,3 .. mov eax,8 mov eax,9 mov eax, 10d
Remark : Apparently, it's impossible to put imbricated macros. Example :
FOR i, <1,2,3,4,5,6,7,8,9,10>
ROUND eax, ecx, ...
ENDM
or
FOR i, <1,2,3,4,5,6,7,8,9,10>
.if (eax > 46)
mov ...
.endif
ENDM
To be completed.
A short example :
.data
today db "Last compiled on : ", ??date, " / ", ??time, 0
today_size dd ($ - offset today - 1)
file db ??filename, 0
.code
mov ecx, offset today ;"Last compiled on : 08-10-01 / 16:09:50"
mov eax, today_size ;26h = 38d
mov edx, @Cpu ;90Dh for ".386p / .model flat, stdCALL"
mov ecx, offset file ;"testtasm " (yes 3 spaces after the name)
That's quite simple to add a new segment :
.new SEGMENT 'NAMEINPE' ;if you don't specify any name, the name of the section will be 8 null chars.
ENDS
Note: The section will have C0000040 as characteristics.IEEE Hex Format: dd 0.0 ;DWORD (4 bytes) dq 0.0 ;QWORD (8 bytes) dt 0.0 ;TBYTE (10 bytes)
For MASM it is the sizeof keyword. For TASM, it's Size.
mov wc.cbSize, Size WNDCLASSEX
instead of :
WNDCLASSEX_SIZE equ 4*12
mov wc.cbSize, WNDCLASSEX_SIZE
This is a little quizz :) Euh where is the quizz ?
.data
TTEST struct
member dd 1
ends
x1 TTEST ?
x2 TTEST <?>
x3 TTEST <9>
x4 TTEST {}
x5 TTEST <>
.code
mov eax, x1.member ;0
mov eax, x2.member ;0
mov eax, x3.member ;9
mov eax, x4.member ;1
mov eax, x5.member ;1
Remark : That's very important to not affect values to struct members (use ? not 0). Otherwise you
won't be able to declare a struct in the .data? segment and you will get an error in union structs too. Example :
...
HELLO struct
f1 dd 0 ;should be "f1 dd ?"
f2 dd 0 ;should be "f2 dd ?"
HELLO ends ;you can simply put ends, but it's cleaner to put the name (same thing for procs)
.data?
aftertime HELLO <?>
You will obtain a warning : Data or code written to uninitialized segment.
Like in C... A quick example :
.data
SUPERHELLO struct
hello dd ?
union
bonjour dd ?
salut dd ?
ends
hiya dd ?
SUPERHELLO ends
iii SUPERHELLO <1,<2>,3> ;a common error is to put : "iii SUPERHELLO <1,2,3>"
;tasm reply: Need angle brackets for structure fill
It's something i have discovered recently in an old borland asm source. This simple example will show you the way :
.data
MYCOOLSTRUCT TABLE {
VIRTUAL field1 : dword
VIRTUAL field2 : dword
VIRTUAL field3 : word
VIRTUAL field4 : byte:16
VIRTUAL field5 : byte = 32h
}
abcd MYCOOLSTRUCT { field4 = "no order !", field2 = -2, field3 = 5, field1 = 1 }
.code
mov eax, abcd.field2 ;eax=-2
mov ecx, offset abcd.field4 ;(offset abcd + 10d)
movzx edx, abcd.field5 ;edx=32h
Even if you can include .asm file, it's sometimes better to really share your work. This is a simple example.
First file (favorite.asm) :
.data
my_favorite_number dd 36157800d
public my_favorite_number
.code
my_favorite_func proc ;compute: eax!
public my_favorite_func
push ebx ;(only ebx is modified)
mov ebx,eax
test ebx,ebx
jnz recursif
mov eax,1
pop ebx
ret
recursif:
mov eax,ebx
dec eax
call my_favorite_func
imul ebx
pop ebx
ret
my_favorite_func endp
end ;don't forget this END
Second and main file (program.asm):
.data
Externdef my_favorite_number : DWORD
Externdef my_favorite_func : PROC
.code
main:
;int 3
mov eax, my_favorite_number
and eax, 0Fh
call my_favorite_func
;...
call ExitProcess, 0
end main
How to compile (my make.bat):
@echo off
c:\tasm32\bin\tasm32 -ml -m5 -q favorite.asm
c:\tasm32\bin\tasm32 -ml -m5 -q program.asm
c:\tasm32\bin\tlink32 -Tpe -aa -c -x -V4.0 program.obj+favorite.obj,Result.exe,,import32
I never succeeded in building a tasm lib. With LIB (provided with MASM) you can buid a Coff lib. But of course you won't be able to use (link) it with tasm. The only result i got using coff2omf is files full of 00. With the /IGNORE:4033 given to Link (masm), you are able to link obj files but there will be a big problem with import32.lib. However it is possible to compile with tasm and link with masm, EliCZ worked on that way (look for EliASM.zip, and read it!). But the lib problem is quite troublesome (see my direct input example - coff/obj problems). Unfortunately i don't know what did Borland, i'm sure all libs are available, but i don't have them, ilink can maybe be used with tasm ; i haven't Delphi or C++builder to check, so i can't be really objective. It's sure that my import32.lib and my tlink.exe are weak.. Damn.. should i use MASM ?
PS: I have heard that microsoft (which provided borland's directx .lib to borland) doesn't want to support directx8 lib (borland format) anymore.. it is true ? Microsoft doesn't sell enough copies of Visual Studio ?
Yes, TASM is far to be perfect ! I just remind you, that all of that has been tested with TASM v5.2 under Windows 98. I hope now, you will be able to use TASM in a better way ; as a direct consequence, you will code faster and better (the better you will know TASM, the best you will code, trust me!). Please, report me any mistake and other remarks at: lucifer48@yahoo.com. Thanks.
NEW !!! Tasm 5.3 is available on tE's site ! Thanks to him :) [however tlink has not been updated]
Greetings: ID and PC members, french dudes and you !