First, thanks SO MUCH to hutch, Thomas, and especially NaN for helping get over the crux of starting with assembly. I have written my first original assembly program!

Okay, by original I don't mean it's the first time the world has ever seen this--it just prints an ASCII chart. I mean original in that I wrote all the code myself rather than modifying a tutorial. Anyway, it's pretty trivial, about 50 lines of code, but still, I'm rather pleased. :)

Comments? Thoughts? How could I make this better, smaller, faster, more compact, more readable, etc?

I'm especially concerned about the mul instruction commented out below. Masm claims that the comma is a syntax error. What's up with that?

Thank you all in advance for comments. Code attached below, sorry for it making this post so large.

-Chalain
"We all begin life naked, covered in blood and screaming. But if we live right, it doesn't have to stop there." -- Robert A. Heinlein

;=======================================================================

; File: ascii.asm
; Desc: Prints the ASCII chart as a 16x16 grid with hex labels. My
; first (original) assembly program.
; Auth: Chalain
; Date: 04/17/2002
;-----------------------------------------------------------------------
; Notes:
; Win32 Assembly *console* project. To compile and link with masm32,
; use BLDALLC.BAT, or compile by hand like so:
;
; ml /c /coff ascii.asm
; link /SUBSYSTEM:CONSOLE ascii.obj
;
;=======================================================================

;-----------------------------------------------------------------------
; DIRECTIVES
;-----------------------------------------------------------------------
.386
.model flat, stdcall
option casemap:none

;-----------------------------------------------------------------------
; INCLUDES
;-----------------------------------------------------------------------
include \masm32\include\Windows.inc
include \masm32\include\masm32.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc

includelib \masm32\lib\user32.lib
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\kernel32.lib

;-----------------------------------------------------------------------
; .DATA
;-----------------------------------------------------------------------
.data

; ascii-art caption text
AsciiArtTopBar db " ",218, 33 dup(196),191,13,10,0
AsciiArtTopCaption db " ",179
db " 0 1 2 3 4 5 6 7 8 9 A B C D E F "
db 179, 13, 10, 0
AsciiArtTopBar2 db 218,196,197, 33 dup(196),180,13,10,0
AsciiArtBottomBar db 192,196,193, 33 dup(196), 217, 13, 10, 0
AsciiArtVert db 179

ColumnFormat db "%c%X%c ",0 ; formats row headers
CharFormat db "%c ",0 ; formats the actual chars
EndColumnText db 179,13,10,0 ; | + cr + lf

;----------------------------------------------------------------------
; .DATA?
;----------------------------------------------------------------------
.data?
x dd ?
y dd ?
Ascii dd ?
Buffer db 10 dup(?)

;----------------------------------------------------------------------
; .CODE
;----------------------------------------------------------------------
.code
start:
invoke StdOut, addr AsciiArtTopBar
invoke StdOut, addr AsciiArtTopCaption
invoke StdOut, addr AsciiArtTopBar2

mov y,0
.WHILE y<16
mov x,0
invoke wsprintf, addr Buffer, addr ColumnFormat, \
dword ptr AsciiArtVert, y, dword ptr AsciiArtVert
invoke StdOut, addr Buffer
.WHILE x<16
mov eax, y
; mul eax, 16 ; WTF? This is a syntax error?
shl eax, 4 ; shl is probably faster anyway
add eax, x
mov Ascii, eax
; Don't print Ascii 0, 7-10 or 13--they'll trash our output
.IF Ascii == 0 || ( Ascii >= 7 && Ascii <= 10 ) || Ascii == 13
mov Ascii, 20h
.ENDIF
invoke wsprintf, addr Buffer, addr CharFormat, \
dword ptr Ascii
invoke StdOut, addr Buffer
inc x
.ENDW
invoke StdOut, addr EndColumnText
inc y
.ENDW

invoke StdOut, addr AsciiArtBottomBar
invoke ExitProcess, 0
end start
Posted on 2002-04-17 14:28:25 by Chalain

mov eax, y
; mul eax, 16 ; WTF? This is a syntax error?
shl eax, 4 ; shl is probably faster anyway


Heheh, its easy to think this way, as practically all the INTEL ASM commands are formatted dest, source. However, with multiply and divide, there are special *assumed* regesters that can only be used. In doing so, there is only one param, the "multiply by" and the "divide by" source.

I didn't make it all to clear i guess in my simple.asm.

Preceeding the multiply command is me setting up EAX with a number. This is the source number, mul assumes that EAX will be used for multiplying.

mov eax, 3
mov ecx, 4
mul ecx

is:

eax = 3
ecx = 4
edx:eax == eax * ecx

Also note, that the edx:eax is ment to represent that the result is 64 bits long, and distributed between the two pernament output registers. In simple, i sugessted that im hoping the input # was relatively small, doing so insured that there would not be any high order bits in edx (since eax can hold up to 4 billion as is).

I sugest you get an OP-Code reference to help you on your way, there is a nifty HelpPC 21 program i like to use (use board search to find a thread). As well on my web site, i have .hlp files that also document the asm commands (in the Help section). Click the www button below if interested.

Enjoy..
:alright:
NaN
Posted on 2002-04-17 14:42:15 by NaN
Hi Chalain, glad to see you're learning quick :)
Here are a few tips:

One sidenote: Optimisations for this code won't make it really faster, as the speed gained by it is nothing compared to the time it takes to print something to the console. I will point them out anyway, so you can learn from it, but keep this in mind.

You are now using a variable (y) for the loop, you can use a register instead to make it faster. However you will need to use a non-volatile register (esi, edi or ebx), as the others (eax, ecx, edx etc) are destroyed by the API functions. Only esi, edi and ebx (ebp and esp as well of course) are preserved.

You can't 'mul eax, 16' as the mul opcode does not allow immediate operands (a number). The signed multiply (imul) opcode does allow this, but a simple solution is to first put 16 in a register, and then multiplying by eax.
Also, mul only takes one operand. It always multiplies EAX by this operand, so you only have to specify the other operand, like this:


mul ecx
or
mul variable


In your case:


mov ecx, 16 ; put 16 in ecx
mul ecx ;mul eax by ecx


But as you already commented, use shl eax, 4, it's much faster. For divisions, you can use SHR in the same way (shr eax, 3 is divide by 8)

You do not need to put eax in Ascii first, just use eax directly:


.IF eax== 0 || ( eax>= 7 && eax<= 10 ) || eax== 13
mov eax, 20h
.ENDIF
invoke wsprintf, addr Buffer, addr CharFormat, eax


Thomas
Posted on 2002-04-17 14:50:21 by Thomas
NaN: you beat me :grin:

Here's some more on the range checking:
The .IF statement in masm can be very useful for code readability, but complex statements can be done better manually. It's also good to know how things work internally so here you go:



or eax, eax ;fast way to see if eax=0
jz _charnotok ;if zero, jump

; char is not zero here

cmp eax, 13 ;is eax 13?
ja _charok ;if above, char is okay
je _charnotok ;if equal, char is not okay

; char is less than 13 here

cmp eax, 10 ;char > 10
ja _charok ;>10 (and <13) so okay
cmp eax, 7
jb _charok ;<7 (and >0) so okay
;jmp _charnotok ;jump not necessary, fall through
_charnotok:
mov eax, " "
_charok:

(untested but should work)

Some of the optimisation gurus here have found more clever ways to do range checking, this is just the basic conversion to help you understand what's going on. masm will produce somewhat different code..

Thomas
Posted on 2002-04-17 14:57:53 by Thomas

.data
...
AsciiArtVert db 179
...

.data?
x dd ?
y dd ?
Ascii dd ?
Buffer db 10 dup(?)

.code
...
invoke wsprintf, addr Buffer, addr ColumnFormat, \
dword ptr AsciiArtVert, y, dword ptr AsciiArtVert
...
invoke wsprintf, addr Buffer, addr CharFormat, \
dword ptr Ascii

I suspect you ran into some problems here, as im wondering why you placed DWORD PTR's (its becoming clear to me your no noobie by the way, you just need to get the facts straight :) ).

I didnt mention the dd but its seems you already figured that out. However, you should also have AsciiArtVert defined as a double (instead of a byte). Then you dont need to write any DWORD PTR's, just place the variables in the param list:

invoke wsprintf, addr Buffer, addr ColumnFormat, AsciiArtVert, AsciiArtVert

(( I didnt compile this, and would be forced to eat my shoes if i made an error :grin: ))


Also, to introduce more *fun* stuff. To be a real Jedi of MASM, you should begin to learn (slowly) the fine are of MACRO writting. (M = Macro)ASM :grin: . They can make the code more readible and give you less typing overal.

NOTE: Macro's are NOT functions, they are just "code stamped into place". So, this means many uses will increase file size!

An Example: Lets make a MACRO function for the wsprintf/stdout pair.
PRINTF [b]MACRO[/b] StingData[b]:REQ[/b], args[b]:VARARG[/b]


IFNDEF __PRINTF_MACRO ;; Precompiler IF statement
;; If NOT DEFINED " "
__PRINTF_MACRO equ 1 ;; Then Define IT

.data? ;; Then make a special BUFFER!
;; in the .data? section. (Masm will
;; catch this in its first pass, and
;; move it out of the code section and
;; into the segment specified, so its ok!)
PF_BUFFER db 128 dup(?) ;; Define a buffer

.code ;; Tell MASM that code now resumes!
ENDIF ;; END of Precompiler IF

;; Now The MACRO can assume that PF_BUFFER is defined!
;; Lets make the code to be stamped!

IFNB args ;; PRECOMPILE THOUGHT: If args is NOT Blank

;; Then "stamp" code into place with extra args!
invoke wsprintf, [b]addr PF_BUFFER[/b], [b]addr StringData[/b], [b][u]args[/u][/b]

ELSE ;; Args IS BLANK!

invoke wsprintf, [b]addr PF_BUFFER[/b], [b]addr StringData[/b]

ENDIF ;; END OF IF

;; now Lets display the BUffer!
invoke StdOut, [b]addr PF_BUFFER[/b]

ENDM ;; END Macro!


Its uses can be (in your case):

PRINTF ColumnFormat, AsciiArtVert, AsciiArtVert
PRINTF CharFormat, Ascii


Again, im in a bit of a hurry, so i didnt compile and test it, but it should work! If not lemme know! (( BTW, the expanded Macro code will look the same as your original, except an extra buffer reserved of just this macro is also defined in your code ))

Hope this is clear to you...
Enjoy!
:alright:
NaN
Posted on 2002-04-17 15:20:32 by NaN
Here's a total different version of your program. It builds the whole table (except for the top and bottom bars) in the buffer first without any APIs and then outputs the whole buffer.


;-----------------------------------------------------------------------
; .DATA
;-----------------------------------------------------------------------
.data

; ascii-art caption text
AsciiArtTop db " ",218, 33 dup(196),191,13,10
db " ",179
db " 0 1 2 3 4 5 6 7 8 9 A B C D E F "
db 179, 13, 10
db 218,196,197, 33 dup(196),180,13,10,0
AsciiArtBottom db 192,196,193, 33 dup(196), 217, 13, 10, 0

;----------------------------------------------------------------------
; .DATA?
;----------------------------------------------------------------------
.data?
Buffer db 640 dup(?)

;----------------------------------------------------------------------
; .CODE
;----------------------------------------------------------------------
.code
start:
invoke StdOut, addr AsciiArtTop

mov edi, offset Buffer ;edi points to buffer
mov edx, 20B330B3h ;"|0| " as little endian dword = 20B330B3h
mov ebx, 0A0D20B3h ;"| ", CR,LF as little endian dword = 0A0D20B3h
mov eax, 20012000h ;0," ",1," " as little endian dword = 20012000h
;eax will hold two output ASCII codes at a time,
;seperated by spaces
mov esi, 16
_yLoop:
add edi, 4
mov ecx, 16/2 ; 16 chars, 2 chars at a time
mov [edi-4], edx ; store left part "|X| ", -4 is
; because of the 4 added to edi
_xLoop:
mov [edi], eax
add eax, 00020002h ;add 2 to both ASCII codes
add edi, 4

dec ecx ; x--
jnz _xLoop ; if decrease did not result in zero, loop x

inc dh ;increase row number digit

mov [edi], ebx ;add decoration at end of line

; check if row number digit is one greater than "9". if yes,
; it should be converted to "A".
cmp dh, "9"+1 ; If it's this value, it should be "A"
jne @F ; if not equal, just go on
;Note: @F means jump to the next @@: label,
; @B means jump to the previous @@: label
; You can have as many @@: labels as you want

; otherwise, make it "A":
mov dh, "A"
@@:

;Note, for pentium pro processors, the above AH check can be optimized
;with the new CMOV instruction:
; cmp dh, "9"+1 ;compare DH with "9"+1
; cmove dh, "A" ;if equal (cmovE), move "A" into DH

add edi, 4 ;move to next dword

dec esi ; y--
jnz _yLoop ; if decrease did not result in zero, loop y

mov byte ptr [edi], 0 ;add final null-terminator

; blank out invalid chars:
mov eax, " "
mov byte ptr [Buffer+4], al ;put space at place of the zero code
mov byte ptr [Buffer+30], al ;blank out value D
mov dword ptr [Buffer+18], eax ;blank out values 7 & 8
mov dword ptr [Buffer+22], eax ;blank out values 9 & A


invoke StdOut, addr Buffer

invoke StdOut, addr AsciiArtBottom
invoke ExitProcess, 0
end start


Thomas
Posted on 2002-04-17 16:17:51 by Thomas


I suspect you ran into some problems here, as im wondering why you placed DWORD PTR's (its becoming clear to me your no noobie by the way, you just need to get the facts straight :) ).


Hee hee! Blush, thank you. Truth be told, I pay the rent with C, C++, and Python programming. I've been programming for about 17 years, and have cursed myself roundly in the past for not being able to read disassembles of my own code. So yeah, I sort of know what I want to accomplish with assembly--for example, windows API assembly is fairly clear to me, b/c I grok the API. I just have no idea how to go about it. :)

The dword ptr thing was indeed put there because it was causing grief. In the format of "%c%X%c ", which spits out |1| , |2| , ..., |F| , it was spitting out

|0|
|1000|
|2000|
|3000|

Whee! I'm not sure entirely what the deal here was, I'd love a better explanation if someone has one. My hypothesis is that somehow AsciiArtVert was being treated as a dword parameter, but when it was popped off the stack the stack pointer was indexed one byte (the correct size of AsciiArtVert). This leaves three zeroes in memory in front of y which effectively multiplies it by 1000h in little-endian. The thing that baffles me is that the second | still came out right. *shakes head*

However, you should also have AsciiArtVert defined as a double (instead of a byte).


Right, that would have the same effect. So... somebody somewhere is expecting AsciiArtVert to be a dword. Who, and why? Is VARARGS exclusively a list of pointers (hence, dwords)? No, that doesn't make any sense because it's using the contents, not the address... *sigh* I'm rambling. Thoughts?

Also, to introduce more *fun* stuff. To be a real Jedi of MASM, you should begin to learn (slowly) the fine are of MACRO writting. (M = Macro)ASM :grin: . They can make the code more readible and give you less typing overal.
<snip>
PRINTF ColumnFormat, AsciiArtVert, AsciiArtVert
PRINTF CharFormat, Ascii


Awesome! Um, it doesn't work. :confused: I get this error message:

ascii.asm(132) : error A2051: text item required
PRINTF(18): Macro Called From
ascii.asm(132): Main Line Code

If I'm reading this correctly (I've reformatted your macro a bit), the problem is with "INFB args".

Oh, which brings me to my NEXT question. Is there a MASM manual out there, that explains the bizarre gobbledygook of the assembler itself? Like what the heck is @@: I keep seeing, etc?

Thanks!

-Chalain
IFNB Brain
; Note: this code never compiles
ENDIF
Posted on 2002-04-17 16:31:59 by Chalain
Chalain: you probably post your reply at the same time as I did. I've explained about the @@ in my code.
@@ is a special label which you can use multiple times:



mov eax, ecx
@@:
mov edx, eax
inc edx
@@:
add eax, ecx


To jump to these labels, two special jump labels are provided: @B and @F. The first jumps back (hence the B :) ) to the previous @@ in your code. The @F jumps forward to the next @@ in your code.
It's very useful for jumps over small pieces of code so you won't have to come up with unique names all the time.

The masm reference was available on bitRAKE's site but I don't know if it's still there.. there's an online version as well, do a search on this board on "MASM reference online" and you will find a post by bitRAKE with an URL to the online reference.

Thomas
Posted on 2002-04-17 16:37:20 by Thomas



;Note: @F means jump to the next @@: label,
; @B means jump to the previous @@: label
; You can have as many @@: labels as you want


Ah! Okay. Grr, must... find... manuals... :)

Thanks!

-Chalain
Ask and ye shall discover that ye have already received...
Posted on 2002-04-17 16:40:52 by Chalain

Chalain: you probably post your reply at the same time as I did.


AHHH!!! Get out of my head!!!

:grin:

-Chalain
Posted on 2002-04-17 16:42:06 by Chalain
:grin:

....Truth be told, I pay the rent with C, C++, and Python programming.....

Do you have visual C? If you have it, you can use it's nice debugger to trace through your asm code (as you typed it, not the disassembly), observing what happens to the registers and memory.
Just add /Zi and /Zd to ml command line, and /debug /debugtype:CV to the link command line. You can use int 3 (one of the rare occations you may use an interrupt in windows :) ) in your code to break, as interrupt 3 is the breakpoint interrupt. Visual C will kick in then if you choose to debug the program.

Thomas
Posted on 2002-04-17 16:46:46 by Thomas

Do you have visual C?


AWESOME! Yeah, I have VC, and I actually use DevStoo as my text editor for just about everything--I've got it macro-ified beyond belief. Heh, I've actually used this bit of C code in the past:



#define DEBUGBREAK __asm { int 03h; }


It's exactly the same thing. I use it to debug remote .DLLs and other objects that stubbornly refuse to be launched with a cheerful F5 in DevStoo. :-)

I'll try the debugging switches, that'll make life a LOT more simple. (*sniff* I used to have a geniune, honest-to-goodness legal copy of SoftICE at my last job. VERY cranky as debuggers go, but man it could do ANYTHING.)

Cheers,

-Chalain
Timeout while trying to think up a funny sig
Posted on 2002-04-17 16:56:33 by Chalain
The fix for the IFNB is:

IFNB <args>

Forgot to wrap it in quotes ( Macro's assume < and > to be string start and stop points )

As well, almost everything in API's are DWORD in length. Pointers for sure, but BYTES and WORDS are actually passed with 32bit alignments (DWORDS); the extras are simply ignored. This keeps the instruction pipes running smoother when calling API's...

Also, there are many sources of MASM guides, but an online version is at :

http://web.sau.edu/LillisKevinM/csci240/masmdocs/

I suggest to rip it down to your hard disk...

As well there is a MASM refernce posted reciently by ThoughtCriminal:

http://www.asmcommunity.net/board/index.php?topic=4739&highlight=masm+OR+guide

Get it, its very handy... As well there is a lengthy tut about STRUCT too.. may be of some interest to you...

Enjoy..
:alright:
NaN
Posted on 2002-04-17 21:56:51 by NaN

The fix for the IFNB is:

IFNB <args>


Perfect! Heh, this has been very educational. When I was originally debugging your macro, I thought the IFNDEF/ENDIF pair was misaligned--in C, the entire header file is #ifdef'ed out, so I moved the ENDIF to the end of the macro.

Once I put in the <args> reference, it worked--but the characters weren't being printed. Just the columns. Reinspection showed that only the .data chunk is what we wanted to IFDEF away, and that the macro has to be expanded anew each time it is called. You already knew this, I'm sure... but hey, another lesson learned!

Lots of thanks, Thomas and NaN... I feel like I can "play" with assembly now and see the results of my fiddling, and I'm sure it won't be long before I move on to "real" windows apps with much bolstered confidence.

Thanks tons!

-Chalain
I'm feeling very surreal today... or... AM I?!?
Posted on 2002-04-17 22:58:12 by Chalain
Chalain,

I am pleased to see that you have some code up and going, translating from your previous experience seems to be working well.

I had a comment on the LOCAL or ANONYMOUS labels @@: etc ...

They may look unusual but when you start coding algorithms, you will truly appreciate their capacity instead of endlessly inventing new label names. They are mainly for dealing with close range loop code labels where unique names are not all that intuitive but where jump back to last @@: with jmp @B or jump forward to the next @@: with jmp @F is really obvious and clear.

Keep up the good work.

Regards,

hutch@movsd.com
Posted on 2002-04-17 23:57:00 by hutch--