Why TASM is lame !

by Lucifer48 [Phrozen Crew]


History :

revision 3 (04/13/02) : added a new subsection : Proc syntax. Also added a little remark in the Use MACROS ! subsection.
revision 2 (08/10/01) : i have rewritten this paper, and added some (minors) corrections.
revision 1 (10/04/00) : added TABLE keyword, and other stuff that i don't remember.
revision 0 (07/??/00) : initial version.

Contents :

I. Introduction
II. How to crash TASM
1) .If statement
III. Parsing problems / bugs
1) Struct (part 1/2)
2) Struct (part 2/2)
3) CMP
4) LEA
5) No data
6) No comment after a \
7) Proc syntax
VI. Remarks & advices
1) Don't use extrn !
2) Don't use prototypes !
3) Organize your work
4) Correct expressions
5) Preprocessor
6) Use MACROS !
6) Predefined symbols
7) Adding new section
8) Float basic quantities
9) Use Size !
10) The STRUCT keyword
11) The UNION keyword
12) The TABLE keyword
13) Separate compilation
14) Lib etc.
V. Conclusion


Introduction

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 ;)


How to crash TASM
.If statement

;just forget to put the right member ;)
.if eax>	;.if ax>= ...

Parsing problems / bugs
Struct (part 1/2)

.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).

Struct (part 2/2)

.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

cmp  123h, dword ptr [esi]	;is not accepted (Illegal immediate)
cmp  dword ptr [esi], 123h	;is ok (left member: register or address)
LEA

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]).

No data

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 !

No comment after a \

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
Proc syntax

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 !]

Remarks & advices
Don't use extrn !

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>
Don't use prototypes !

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.

Organize your work

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\Lib
Consequently 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.inc
This is the way i compile (my make.bat, i don't say that it is the best way ! It's just my way !) :
Notes : If you don't like brc32. You can use any another .rc compiler (gorc is a good one). Unless you have a big project, a simple .bat is ok. A makefile is more adapted to linux/unix rather than windows (we can't compare shell to command.com). Go download cygwin, it's great !

Remark : You can also use the keyword includelib (put it inside you .asm file, like with masm) and don't specify any lib in your makefile (or make.bat).
Example (put it on the top of your asm source) : includelib import32.lib.
Example of compilation : c:\tasm32\bin\tlink32 -Tpe -aa -c -x -V4.0 myprog1.obj,myprog1.exe,,,,myprog1.res.
Correct expressions

+	: 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
Preprocessor

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.
Use MACROS !

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.

Predefined symbols

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)
Adding new section

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.
Float basic quantities

IEEE Hex Format:
dd	0.0	;DWORD (4 bytes)
dq	0.0	;QWORD (8 bytes)
dt	0.0	;TBYTE (10 bytes)
Use Size !

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
The STRUCT keyword

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.

The UNION keyword

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
The TABLE keyword

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
Separate compilation

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
Lib etc.

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 ?


Conclusion

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 !



(c) Lucifer48. All rights reserved & reversed