Hi all, I'm working on my final project for an assembly language class and could use some help. All semester we have been coding in MIPS (using PCSPIM), and now the assignment is to write and assemble a real program using MASM. I've got my program completely written and it works perfectly -- runs in the command line, prompts the user for input, then does some math, then outputs the results. The problem is I've used MASM32's built-in macros throughout my code, and the instructor just clarified that we are not to use any kind of macro code.
So, now I'm struggling with figuring out how to execute a command like invoke StdOut ,addr Prompt1
without any kind of macro. In MIPS I know it would look something like:
li $v0, 4
la $a0, Prompt1
syscall
But I can't find a syscall equivalent in 32-bit MASM (I thought I had found the answer when I stumbled across interrupts but those appear to be DOS only). All of the tutorials and examples that I can find on MASM end up using macros like StdOut and Print, which are perfectly fine in any realistic scenario but unfortunately I'm not allowed to use them in this assignment. The textbook we've been using is entirely focused on MIPS and we've been given no other resources for the class. I've tried reading StdOut.asm in the masm32\m32lib folder, which apparently calls 3 other macros GetStdHandle, StrLen, and WriteFile. I can find StrLen in the same directory, but I can't find the other two to know what they do.
So does anyone out there know of a good resource that might show MASM 32-bit code without any macros that I can learn from, or some way to dig deep enough into the rabbit hole of "invokes" that I can translate the existing macros into assembly-level code?
Hi,
GetStdHandle and WriteFile are windows API functions . You can find their descriptions on msdn . Perhaps it's the best way for you as far as you're allowed to use windows system calls.
StdOut is not a macro, it's a masm32 library PROC
i wrote a version that is UNICODE aware, and preserves the console mode across the call
;***********************************************************************************************
awConOut PROC USES EBX lpBuffer:LPVOID,nChars:DWORD
;UNICODE aware Console Output - DednDave, 4-2013
; Returns: EAX = number of characters written
;--------------------------------------------
LOCAL dwOrigModeOut :DWORD
LOCAL nNumberOfCharsWritten :DWORD
;--------------------------------------------
INVOKE GetStdHandle,STD_OUTPUT_HANDLE
xchg eax,ebx
INVOKE GetConsoleMode,ebx,addr dwOrigModeOut
mov edx,ENABLE_PROCESSED_OUTPUT or ENABLE_WRAP_AT_EOL_OUTPUT
.if edx!=dwOrigModeOut
INVOKE SetConsoleMode,ebx,edx
.endif
xor ecx,ecx
mov nNumberOfCharsWritten,ecx
INVOKE WriteConsole,ebx,lpBuffer,nChars,addr nNumberOfCharsWritten,ecx
mov edx,dwOrigModeOut
.if edx!=(ENABLE_PROCESSED_OUTPUT or ENABLE_WRAP_AT_EOL_OUTPUT)
INVOKE SetConsoleMode,ebx,edx
.endif
mov eax,nNumberOfCharsWritten
ret
awConOut ENDP
;***********************************************************************************************
used with an StrLength routine, you can get the same functionality as StdOut
here is a little program that works as either ANSI or UNICODE
comment out the __UNICODE__ EQU line to build as ANSI
it includes console input and AnyKey functions
it does use a macro, but only for defining strings in both ANSI and UNICODE
you can easily eliminate the macro by defining the type you want, or by using TCHAR's
The basic are you cannot do console IO without using Windows system functions, commonly called the Windows API functions. It is the only way you can interact with the operating system. You can bypass the built in macro INVOKE by using the normal PUSH / CALL notation which is no big deal to do but you will still have to call Windows API functions. The library procedures in MASM32 are reasonably straight forward and if you are not allowed to use them you can basically copy them and build them into your application. For the API calls you will still need to libraries from MASM32 so that the linker can find the IMPORT data.
ahhh - i had forgotten that INVOKE was a macro - lol
INVOKE StdOut,offset szString
push offset szString
CALL StdOut
for functions with more than one argument, the rightmost arguments are pushed first
There is another cheap trick: Step through your macro-infested executable with Olly (http://www.ollydbg.de/version2.html) in order to understand what the "true" code is. Example:
include \masm32\include\masm32rt.inc
.code
start: inkey "Hello World"
exit
end start
Translates to:
push offset ??0019 ; ASCII "Hello World"
call StdOut
call wait_key
push offset ??001F ; ASCII CrLf
call StdOut
push 0
call ExitProcess ; jmp to kernel32.ExitProcess
By hitting F7 in Olly, you can dig inside StdOut to see this:
push ebp
mov ebp, esp
add esp, -0C
push -0B
call GetStdHandle ; jmp to kernel32.GetStdHandle
mov [ebp-4], eax
push dword ptr [ebp+8]
call StrLen
mov [ebp-C], eax
push 0
lea eax, [ebp-8]
push eax
push dword ptr [ebp-C]
push dword ptr [ebp+8]
push dword ptr [ebp-4]
call WriteFile ; jmp to kernel32.WriteFile
mov eax, [ebp-8]
leave
retn 4
But watch out, even StrLen is a Masm32 function, and it is pretty complex:
StrLen:
mov eax, [esp+4]
lea edx, [eax+3]
push ebp
push edi
mov ebp, 80808080
mov edi, [eax]
add eax, 4
lea ecx, [edi+FEFEFEFF]
not edi
and ecx, edi
and ecx, ebp
jnz short 004010EA
mov edi, [eax]
add eax, 4
lea ecx, [edi+FEFEFEFF]
not edi
and ecx, edi
and ecx, ebp
jnz short 004010EA
mov edi, [eax]
add eax, 4
lea ecx, [edi+FEFEFEFF]
not edi
and ecx, edi
and ecx, ebp
jnz short 004010EA
mov edi, [eax]
add eax, 4
lea ecx, [edi+FEFEFEFF]
not edi
and ecx, edi
and ecx, ebp
je short 0040109E
test ecx, 8080
jnz short 004010F8
shr ecx, 10
add eax, 2
shl cl, 1
sbb eax, edx
pop edi
pop ebp
retn 4
Maybe you could ask your instructor where exactly he wants to draw the line? ;)
Thanks everyone for your help. I didn't realize that some of the commands were calling Windows APIs; I thought anything with an "invoke" was a macro, and I was at a loss as to what was left when you took away all those. From what hutch said, I believe API calls should be okay, since they appear to be the Windows-equivalent of a syscall. I'll use pushing to the stack to avoid using "invoke" though.
To avoid StrLen, I'm just manually giving the WriteConsole function the string length (all of my prompt strings are the same length). I won't know ahead of time the length of the algebra results to be displayed, but I'm going to have to loop through those anyway to convert the integers into ASCII strings (manually replacing dwtoa) so I think I can use a counter while doing that.
I did try using a disassembler on my current code but I got totally lost. I didn't know about stepping through with Olly though; I was just opening the file and having it do the whole thing at once. That's definitely good to know, thank you jj.
My last challenge once I have all the in/out completed is going to be converting a couple short .if statements at the tail end of my code. From context in the striplf function, I've gathered this can be done with cmp, jne, and je; similar to slt, bne, and beq in MIPS. Hopefully that's not too far off base?
Quote from: koalapauljeff on May 02, 2014, 02:33:30 AMI won't know ahead of time the length of the algebra results to be displayed, but I'm going to have to loop through those anyway to convert the integers into ASCII strings (manually replacing dwtoa) so I think I can use a counter while doing that.
There are many ways to get the len of a string - attached a sixpack ;-)
Have a look at
xz:, it's fairly simple and fast, too.
Re Olly: For a start, you only need three keys:
- F7 for single step
- F8 for single step but not "diving into" a call
- F9 to run until it hits an
int 3 (or the end, or an exception)
xx: len=67 bytes, code size=22 bytes for xx
201 ms
xy: len=67 bytes, code size=20 bytes for xy
201 ms
xz: len=67 bytes, code size=17 bytes for xz
100 ms
M32 len=67 bytes, code size=??
60 ms
CRT len=67 bytes, code size=??
55 ms
MB len=67 bytes, code size=??
22 ms
if you look at the little program i posted earlier, it has a simple length routine
it supports ANSI or UNICODE, and uses REP SCAS
so - not fast, but simple code
the faster routines are a bit more complex - require more code
but, how fast do you want to display console text ?
it's one of those cases where speed isn't all that critical
Thanks Dave, I did look at that program you posted, but I'm not sure I can use scasb. Is that part of the Windows API as well? I was just going to write a simple loop (can't use the if or while syntax) using dwtoa as a guide. And you're right, I'm really not concerned with speed for this program, mostly just need accurate and working non-macro code.
Also appreciate the sixpack jj; I don't think I can use .Repeat but xz would work great outside of this assignment.
Quote from: koalapauljeff on May 02, 2014, 05:16:45 AMI don't think I can use .Repeat but xz would work great outside of this assignment.
.Repeat ... .Until is not a macro, it's built-in "high level syntax"
Here the same without .Repeat .. .Until:
xz_s:
mov edx, [esp+4] ; get pushed source (note [esp+0] is the return address)
or eax, -1
@@:
inc eax
cmp byte ptr [edx+eax], 0
jne @B
retn 4
And the super short version, 14 bytes:
xz_s:
pop eax ; return address
pop edx ; get pushed source
push eax ; put return address back
or eax, -1
@@:
inc eax
cmp byte ptr [edx+eax], 0
jne @B
retn 0 ; zero because we took away one dwordOne byte shorter, and faster:
cmp byte ptr [edx+eax], ah
... but it works only for strings with less than 255 characters ;-)
SCAS is an intel instruction - part of a group often called "string instructions"
REP is an intel instruction prefix - a shortcut of either REPZ or REPNZ (aka REPE, REPNE),
which are only used with string instructions
raw assembly language :t
A bit modified, 12 bytes but maybe slower
xz_s:
pop eax ; return address
pop edx ; get pushed source
push eax ; put return address back
@@:
cmp byte ptr [edx+eax], 1
inc eax
jnc @B
dec eax
retn 0 ; zero because we took away one dword
Quote from: Antariy on May 02, 2014, 08:44:30 AM
A bit modified, 12 bytes but maybe slower
The idea is cute (inc leaves the carry flag intact...), but there are some problems:
Intel(R) Celeron(R) M CPU 420 @ 1.60GHz (MMX, SSE, SSE2, SSE3)
xx: len=67 bytes, code size=22 bytes for xx
207 ms
xy: len=67 bytes, code size=21 bytes for xy
207 ms
xz: len=67 bytes, code size=14 bytes for xz
100 ms
xf: len=67 bytes, code size=15 bytes for xf
99 ms
Alex 1= 67 bytes, code size=14 bytes for xa1
99 ms
Alex 2= 67 bytes, code size=14 bytes for xa2
463 ms
M32 len=67 bytes, code size=??
60 ms
CRT len=67 bytes, code size=??
74 ms
MB len=67 bytes, code size=??
22 ms
DaveLen=67 bytes, code size=28 bytes for awGetLen
208 ms
Here are the two variants:
align 16
xa1_s:
pop eax ; return address
pop edx ; get pushed source
push eax ; put return address back
or eax, -1 ; care for null$, too
@@:
inc eax
cmp byte ptr [edx+eax], 1 ; only zero sets the carry flag!
jnc @B
retn 0 ; zero because we took away one dword
xa1_endp:
align 16
xa2_s:
pop eax ; return address
pop edx ; get pushed source
push eax ; put return address back
xor eax, eax
@@:
cmp byte ptr [edx+eax], 1 ; only zero sets the carry flag!
inc eax ; 463 ms - stall?
jnc @B
dec eax
retn 0 ; zero because we took away one dword
xa2_endp:
Quote from: jj2007 on May 02, 2014, 09:00:40 AM
Quote from: Antariy on May 02, 2014, 08:44:30 AM
A bit modified, 12 bytes but maybe slower
The idea is cute (inc leaves the carry flag intact...), but there are some problems:
Also forget to zero eax in the first post :biggrin:
Yes, I think the slowdown is because partial changement of the flags. With consideration of zeroing eax, the tweak with jnc is not useful, since the code is the same size as you posted first :t Interesting how behaves partial reg changement on the other machines :biggrin:
Intel(R) Celeron(R) CPU 2.13GHz (MMX, SSE, SSE2, SSE3)
xx: len=67 bytes, code size=22 bytes for xx
204 ms
xy: len=67 bytes, code size=21 bytes for xy
192 ms
xz: len=67 bytes, code size=14 bytes for xz
128 ms
xf: len=67 bytes, code size=15 bytes for xf
125 ms
Alex 1= 67 bytes, code size=14 bytes for xa1
132 ms
Alex 2= 67 bytes, code size=14 bytes for xa2
116 ms
M32 len=67 bytes, code size=??
99 ms
CRT len=67 bytes, code size=??
61 ms
MB len=67 bytes, code size=??
30 ms
DaveLen=67 bytes, code size=28 bytes for awGetLen
234 ms
:biggrin:
22 bytes. Still, give the guy a break, I doubt he was looking for a string length benchmark, just some simple code to get the job done.
mov eax, [esp+4]
sub eax, 1
lbl0:
add eax, 1
cmp BYTE PTR [eax], 0
jne lbl0
sub eax, [esp+4]
ret 4
Just wanted to update that I've finished my project and managed to reduce my includes to:
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
which I think is the bare minimum for interacting with the console. Thanks so much for all the help; while I was never super concerned with making it run quickly, seeing all the different examples that were posted helped me understand more opcodes and loop structures.
Thanks all! :greenclp: