News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

console input/output with no macros

Started by koalapauljeff, May 01, 2014, 11:40:38 PM

Previous topic - Next topic

koalapauljeff

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?

GoneFishing

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.

dedndave

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

dedndave

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

hutch--

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.

dedndave

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

jj2007

There is another cheap trick: Step through your macro-infested executable with Olly 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?  ;)

koalapauljeff

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?

jj2007

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

dedndave

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

koalapauljeff

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.

jj2007

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 dword


One byte shorter, and faster:
cmp byte ptr [edx+eax], ah
... but it works only for strings with less than 255 characters ;-)

dedndave

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

Antariy

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

jj2007

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: