MS Windows x64 Application Binary Interface macros.
This library contains macros for some basic OS interactions: retrieving environment and command-line arguments, standard I/O, program termination, invocation of 64bit MS Windows Application Binary Interface as specified in [WinABI64].
Programming interface macros represent elegant way of interaction with operating system using only one single statement. Otherwise we would have to push or load appropriate registers with argument values in the right order, store caller-save registers, manually arrange stack alignment, call the imported function and finally restore the stack.
Most macro names in this 64bit librarywinabi.htmare identical with macros from 32bit library winapi.htm and other API libraries. If you really need to include both libraries in one common source file, definitions of those macros should be forgotten before inclusion of the other library with%DROPMACRO GetArg,GetArgCount,GetEnv,StdInput,StdOutput,TerminateProgram
or with%DROPMACRO *
in order to prevent warning W2512 Overwriting macro "!1S" previously defined at !2@.
winabi HEAD INCLUDEHEAD1 winansi.htm ; Make sure that %WinANSI is available before WinABI invocation.
Macroinstruction WinABI invokes Function exported from MS Windows 64bit by [WindowsAPI] in the FastCall calling convention compatible with [WinABI64].
Ambiguous functions, which have both ANSI and WIDE variant, may be specified
with or without explicit suffix A or W.
Function may also be provided as a GPR with address of the function (pointer to its thunk in [.idata]).
The macro is similar to FastCall's Invoke with two differences:
Lib=
.WinABI functions require Direction Flag be zero on input and they do not change its value.
64bit Windows functions do not keep the original contents of flags, RCX, RDX, R8..R11,XMM0..XMM5,
but when this macro WinABI is invoked with keyword Fastmode=No
(default), it preserves all XMML and GP registers except for RAX and XMM0.
According to FastCall convention, arguments are pushed backwards on stack as QWORDs,
starting from the last to the fifth. The first four parameters are loaded to RCX, RDX, R8, R9
and not pushed on stack. Nevertheless, room for the first four arguments is always reserved on stack
(so called shadow space) even when the invoked function has less than four arguments.
When the Function expects argument in floating-point format instead of integer, pointer or immediate,
the first four arguments are loaded to XMM0, XMM1, XMM2, XMM3 rather than to GPR.
Stack pointer will be OWORD aligned before the CALL instruction is performed.
Invoked function does not remove arguments from stack, it terminates with a simple near RET. Epilogue of macro WinABI restores RSP to its original value.
XMM15
,RBX
,FS
or GS
,0
or -11
,GMEM_FIXED
,="Hello, world!"
or MyCallback:
.[RBP+32]
or [MyTable+RSI]
or [=Q 22.5]#SD
.#SS
or #SD
(case insensitive),
which signalizes that it represents floating-point value in Scalar Single or
Scalar Double precision format, and that it should be therefore passed to the Function in XMM register instead of GPR.
EUROASM UNICODE=
.
kernel32.dll, or
IMPORT Function, Lib=user32.dll
, orobjlib\winapi.libcreated by the script dll2lib.htm.
WinABI MessageBox,RDX,R8,R8,MB_OK
.
XMM0, XMM1, XMM2, XMM3
when they are floating-point values, and to
RCX, RDX, R8, R9
in all other cases, so the invokation is faster.
Registers RCX, RDX, R8, R9, XMM0..XMM3 cannot be used as macro arguments,
because they are being overwritten in prologue. Or they should be only used in the exact ABI-specified order, e.g.
WinABI Function, RCX, XMM1, R8, XMM3
.
Similary, registers RCX, RDX, R8, R9 cannot be used instead of Function name in fast mode. Use other GPR, e.g.
IMPORT WriteConsoleW,Lib=kernel32.dll
LEA RDI, [WriteConsoleW]
WinABI Fastmode=Yes, RDI, RBX, Message, SIZE# Message, WrittenChars, 0
WinABI CreateFileA, FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
| | ; Fast Version.
|00000000: | WinABI CreateFileA, FileName, GENERIC_READ, FILE_SHARE_READ, 0, \
|00000000: | OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0, Fastmode=Yes
|00000000:54 + PUSH RSP ; Store original stack pointer value (equilibrum).
|00000001:40F6C408 + TEST SPL,1000b ; Test RSP OWORD alignment at run-time.
|00000005:7506 + JNZ .WinABI1:
|00000007:54 + PUSH RSP ; Store and update 2nd copy of original RSP (equilibrum).
|00000008:4883042408 + ADDQ [RSP],8 ; Those two instructions aren't executed if RSP was properly aligned.
|0000000D: + .WinABI1:
|0000000D:6A00 + PUSHQ 0 ; Push 7th argument.
|0000000F:6880000000 + PUSHQ FILE_ATTRIBUTE_NORMAL ; Push 6th argument.
|00000014:6A03 + PUSHQ OPEN_EXISTING ; Push 5th argument.
|00000016:41B900000000 + MOV R9,0 ; Load 4th argument.
|0000001C:41B801000000 + MOV R8,FILE_SHARE_READ ; Load 3rd argument.
|00000022:BA00000080 + MOV RDX,GENERIC_READ ; Load 2nd argument.
|00000027:488D0D(00000000) + LEA RCX,[FileName] ; Load 1st argument.
|0000002E:4883EC20 + SUB RSP,4*8 ; Make room for shadow space in fast mode. RSP is OWORD-aligned.
|00000032:E8(00000000) + CALL CreateFileA ; Call the imported function.
|00000037:488D642438 + LEA RSP,[RSP+7*8] ; Discard transferred arguments, keep RFlags.
|0000003C:5C + POP RSP ; Restore RSP to equilibrum from 1st or 2nd copy.
|0000003D: | ; WinABI in fast mode occupies 61 bytes of code.
|00000000: | ; Robust version.
|00000000: | WinABI CreateFileA, FileName, GENERIC_READ, FILE_SHARE_READ, 0, \
|00000000: | OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0, Fastmode=No
|00000000:6A00 + PUSHQ 0 ; Push 7th argument.
|00000002:6880000000 + PUSHQ FILE_ATTRIBUTE_NORMAL ; Push 6th argument
|00000007:6A03 + PUSHQ OPEN_EXISTING ; Push 5th argument
|00000009:6A00 + PUSHQ 0 ; Push 4th argument
|0000000B:6A01 + PUSHQ FILE_SHARE_READ ; Push 3rd argument
|0000000D:6800000080 + PUSHQ GENERIC_READ ; Push 2nd argument
|00000012:50 + PUSH RAX ; Alloc 1st argument on stack.
|00000013:488D05(00000000) + LEA RAX,[FileName] ; Load 1st argument.
|0000001A:48870424 + XCHG RAX,[RSP] ; Transfer the pointer without clobbering RAX.
|0000001E:6A07 + PUSHQ 7 ; Push the number of arguments.
|00000020:488D05(00000000) + LEA RAX,[CreateFileA] ; Load the function address (pointer to its thunk in [.idata]).
|00000027:E805000000 + CALL WinABI@RT ; Call the runtime with function in RAX.
|0000002C:488D642440 + LEA RSP,[RSP+8*8] ; Restore stack to equilibrum, preserving RFlags.
|00000031: | ; WinABI in robust mode occupies 49 bytes of code (plus 190 bytes of runtime code, once per program).
MOV reg,value
(5 bytes).
64bit pointers are transferred by the help of temporary register with
LEA reg,[address]
using RIP-relative addressing (7 bytes) resolvable at link time.PUSH value ; POP reg
(2+1 bytes).Selection of the mode depends on optimisation criterion:
Example of Windows function invocation | Number of args |
WinABI Fastmode=Yes | WinABI Fastmode=No |
---|---|---|---|
GetCursor | 0 | 28 | 19 |
SetArcDirection, [hDC], AD_CLOCKWISE | 2 | 43 | 36 |
MessageBox, NULL, Text, Caption, MB_OK | 4 | 53 | 47 |
AngleArc, [hDC], 100, 120, R9, [StartAngle]#SS, XMM14#SS | 6 | 74 | 65 |
CreateFile, FileName, GENERIC_READ, FILE_SHARE_READ, 0, \
| 7 | 61 | 49 |
Robust variant of WinABI is often shorter, but it emits 190 additional bytes of runtime procedure (only once in a program), so it is profitable when we have more than cca twenty Windows invocations in the program. Robust variant may also spare some push/pops because it doesn't change any scratch registers.
When you want to switch on the Fastmode for all WinABI invokations, you don't have to append,Fastmode=Yes
to every invokation ofWinABI
orInvoke
, if you set preprocessing variable %Fastmode in the beginning of your program:%Fastmode %SETB On
.
WinABI %MACRO Function, Arg1, Arg2,,, Fastmode=%Fastmode, Unicode=%^UNICODE, Lib= %Fast %SETB %Fastmode %Robust %SETB ! %Fast Fa %IF %Fast ; Align stack in fast mode only. PUSH RSP ; Store original stack pointer value (equilibrum). TEST SPL,1000b ; Test stack OWORD alignment at run-time. FaEv %IF %# & 1b || %# <= 5 ;>If the number of Function arguments is 0,1,2,3,4,6,8,10,,(even), JZ .WinABI%.: ; store 2nd copy of equilibrum when RSP is OWORD-unaligned. %ELSE FaEv ; If the number of arguments is 5,7,9,11,,, (odd), JNZ .WinABI%.: ; store 2nd copy of equilibrum when RSP is OWORD-aligned. %ENDIF FaEv PUSH RSP ; Store and update 2nd copy of original RSP (equilibrum). ADDQ [RSP],8 ; Those two instructions aren't executed if RSP was properly aligned. .WinABI%.: %ENDIF Fa %GPR %SET RCX,RDX,R8,R9 ; Enumerate registers for transfer of integer|pointer values. %SIMD %SET XMM0,XMM1,XMM2,XMM3 ; Enumerate registers for transfer of floating-point values. %ArgNr %SETA %# ; Number of macro ordinals, i.e. number of Function arguments + 1. Arg %WHILE %ArgNr > 1 %Arg %SET %*{%ArgNr} ; Transfer all Function arguments, start with the last one. %ArgNr %SETA %ArgNr-1 ; %ArgNr is now the ordinal Nr of Function argument (,,3,2,1). %suffix %SET Q ; %suffix of MOV will be Q, SS or SD (MOVQ, MOVSS or MOVSD). %IF '%Arg[%&-2..%&-1]'=='#S' ; If suffix #SS or #SD is present in argument notation, %suffix %SET %Arg[%&-1..%&] ; let %suffix be SS or SD %Arg %SET %Arg[1..%&-3] ; and remove it from the argument. %ENDIF ; %Arg may be GPR,SIMD,imm@abs,ptr@rel,[mem@abs],[mem@rel]. Rb %IF %ArgNr>4 || %Robust ; Transfer %Arg via stack. RbSc %IF TYPE#(SEGMENT#(%Arg))='N'; %Arg is not relocatable (scalar). RbScRg %IF TYPE#(%Arg)='R' ; It can be GPR,SIMD,imm@abs,[mem@abs]. RbScRgXm %IF REGTYPE#(%Arg)='X' ; %Arg is a GP or SIMD register. SUB RSP,8 MOV%suffix [RSP],%Arg ; %Arg is a SIMD register. %ELSE RbScRgXm PUSHQ %Arg ; %Arg is a GP register. %ENDIF RbScRgXm %ELSE RbScRg ; %Arg it not a register. PUSHQ %Arg ; %Arg is scalar immediate or [mem], e.g. 1 or [RBP+16]. %ENDIF RbScRg %ELSE RbSc ; %Arg is relocatable (vector), e.g. Symbol or [Symbol+RSI]. RbVeM %IF '%Arg[1]' === '[' ; Argument is passed by value, via a temporary GPR. PUSH RAX ; Original contents of the borrowed RAX must be kept. LEA RAX,%Arg ; Use relative addressing frame for relocatable %Arg. MOV RAX,[RAX] ; Dereference the argument value. XCHG RAX,[RSP] ; Transfer the value. %ELSE RbVeM ; Relocatable argument is passed by reference, e.g. Symbol. PUSH RAX ; Original contents of the borrowed RAX must be kept. LEA RAX,[%Arg] ; Use relative addressing frame for relocatable %Arg. XCHG RAX,[RSP] ; Transfer the pointer. %ENDIF RbVeM %ENDIF RbSc %ELSE Rb ; Fastmode=Yes and %ArgNr=4,3,2,1. Transfer via registers. FaSc %IF TYPE#(SEGMENT#(%Arg))='N'; %Arg is not relocatable (scalar). FaScRg %IF TYPE#(%Arg)='R' ; It can be GPR,SIMD,imm@abs,[mem@abs]. FaScRgXm %IF REGTYPE#(%Arg)='X' ; %Arg is a GP or SIMD register. %IF "%SIMD{%ArgNr}" !== "%Arg" ; %Arg is XMM. Skip when it's already there. MOV%suffix %SIMD{%ArgNr},%Arg ; Copy SIMD %Arg to other SIMD (XMM0..XMM3). %ENDIF %ELSE FaScRgXm ; %Arg is GPR. %IF "%GPR{%ArgNr}" !== "%Arg" ; Skip when it's already there. MOV %GPR{%ArgNr},%Arg ; Copy GPR to other GPR (R9,R8,RDX,RCX). %ENDIF %ENDIF FaScRgXm %ELSE FaScRg ; %Arg is scalar immediate or [mem], e.g. 1 or [RBP+16]. FaScIm %IF '%suffix' === 'Q' MOV %GPR{%ArgNr},%Arg ; %Arg is integer value. %ELSE FaScIm MOV%suffix %SIMD{%ArgNr},%Arg ; %Arg is FP value, e g. [RSI]. %ENDIF FaScIm %ENDIF FaScRg %ELSE FaSc ; %Arg is relocatable (vector), e.g. Symbol or [Symbol+RSI]. FaVeM %IF '%Arg[1]' === '[' ; Argument is passed by value, via this GPR. LEA %GPR{%ArgNr},%Arg MOV %GPR{%ArgNr},[%GPR{%ArgNr}] ; Dereference the argument value, transfer the value. %ELSE FaVeM ; Argument is passed by reference, e.g. Symbol. LEA %GPR{%ArgNr},[%Arg] ; Transfer the pointer. %ENDIF FaVeM %ENDIF FaSc %ENDIF Rb %ENDWHILE Arg ; All arguments are loaded|pushed. %sfx %SET ; A or W Function suffix. Empty by default. reg %IF REGTYPE# %Function = 'Q' ; Function was specified as 64bit GPR. %IF "%Function"=="RCX"||"%Function"=="RDX"||"%Function"=="R8"||"%Function"=="R9" %ERROR ID=5956,'%0 function cannot be supplied in scratch register %Function when Fastmode=Yes.' %EXITMACRO Invoke %ENDIF %ELSE reg ; Function was specified by name. fn %FOR %WinANSI found %IF '%fn' === '%Function' %IF %Unicode %sfx %SETC 'W' %ELSE %sfx %SETC 'A' %ENDIF %EXITFOR fn ; No need for further examination of the list. %ENDIF found %ENDFOR fn ; %sfx is now A or W or empty. IMPORT %Function%sfx, Lib=%Lib %ENDIF reg Fa %IF %Fast SUB RSP,4*8 ; Make room for shadow space in fast mode. RSP is OWORD-aligned. CALL %Function%sfx ; Call the function in fast mode. %IF %# > 5 LEA RSP,[RSP+8*(%#-1)] ; Discard transferred arguments, keep RFlags. %ELSE LEA RSP,[RSP+8*4] ; Discard transferred arguments, keep RFlags. %ENDIF POP RSP ; Restore RSP to equilibrum from 1st or 2nd copy. %ELSE Fa ; In robust mode use helper runtime procedure WinABI@RT. PUSHQ %#-1 ; Push the number of Function arguments. RbRg %IF REGTYPE# %Function = 'Q' ; Function was specified as 64bit GPR. %IF '%Function' !== 'RAX' ; Skip if it's already in RAX. MOV RAX,%Function %ENDIF %ELSE RbRg LEA RAX,[%Function%sfx] ; RVA of the Function (pointer to its thunk in [.idata]). %ENDIF RbRg CALL WinABI@RT ; Arguments+their number are on stack. Function is in RAX. LEA RSP,[RSP + 8 * %#] ; Restore stack to equilibrum, preserving RFlags. WinABI@RT: PROC1 ; Macro emits the runtime subroutine, which is expanded only once in program. PUSH RSI,RDI,R12 ; Calee-save registers used by WinABI@RT procedure. PUSH RCX,RDX,R8,R9,R10,R11 ; Robust version preserves scratch GP registers. MOVQ RDX,XMM1 MOVQ R8,XMM2 MOVQ R9,XMM3 MOVQ R10,XMM4 MOVQ R11,XMM5 PUSH RDX,R8,R9,R10,R11 ; Robust version preserves scratch SIMD registers. MOV ECX,[RSP+15*8] ; Number of Function arguments. LEA RSI,[RSP+16*8] ; Pointer to %Arg1 in WinABI@RT stack frame. CMP ECX,4 ; Number of arguments is 0|1|2|3|4|5|6... JAE .AtLeast4: MOV CL,4 ; Number is saturated to 4|4|4|4|4|5|6... .AtLeast4:MOV R12,RSP ; Save (perhaps unaligned) RSP to callee-preserved register. ; Align stack pointer as dictated by ABI specification. MOV EDX,ECX ; Saturated number of operands (4,5,6,,,). SHL EDX,3 ; RDX bit 3 is set if saturated number of arguments is odd (5,7,9,,). XOR EDX,ESP ; RDX will be 8 when number of arguments is even and RSP is OWORD unaligned, AND EDX,0000_1000b ; or when it's odd and RSP is already OWORD aligned. Otherwise RDX=0. SUB RSP,RDX ; Align RSP to OWORD. ; Copy ECX arguments from RSI to the callee's shadow space. MOV EDX,ECX SHL EDX,3 SUB RSP,RDX ; Alloc Function frame (shadow space + possible other arguments). MOV RDI,RSP ; RSP is OWORD aligned at this moment. REP MOVSQ ; Copy all pushed arguments. ; Load first four arguments to GP registers. MOVQ RCX,[RSP+00] MOVQ RDX,[RSP+08] MOVQ R8, [RSP+16] MOVQ R9, [RSP+24] ; Copy first four arguments to SIMD registers (for the case they were floating-point). MOVQ XMM0,RCX MOVQ XMM1,RDX MOVQ XMM2,R8 MOVQ XMM3,R9 CALL RAX ; Call the Function in robust mode. MOV RSP,R12 ; Discard Function frame plus possible stack-alignment stuff. POP R11,R10,R9,R8,RDX MOVQ XMM5,R11 MOVQ XMM4,R10 MOVQ XMM3,R9 MOVQ XMM2,R8 MOVQ XMM1,RDX ; Restore SIMD scratch registers. POP R11,R10,R9,R8,RDX,RCX ; Restore GP scratch registers. POP R12,RDI,RSI ; Restore used callee-save registers. RET ENDP1 WinABI@RT: %ENDIF Fa %ENDMACRO WinABI
Macro GetArg retrieves ArgNumber-th parameter provided on command line.
Parameters on the command line may be separated with unquoted
white spaces or commas. Single apostrophe cannot be used as quote.
Macro returns the executable name itself when ArgNumber is 0.
It is taken verbatim from the console window or, if launched
from Explorer, it may be expanded to a full pathname.
The returned argument is not zero terminated and it is not writable. Make a copy in local memory if you need to modify it. Quotes surrounding the argument are returned, too.
Value of EUROASM UNICODE=
option specifies whether the returned string will be ANSI or WIDE.
GetArg %MACRO ArgNumber, Unicode=%^UNICODE PUSHQ %ArgNumber AorW %IF %Unicode CALL GetArgWin64W@RT ; WIDE variant. GetArgWin64W@RT:: PROC1 PUSHQ RAX,RDX,RSI,RDI,R8,R9,R10,R11 WinABI GetCommandLineW, Fastmode=Yes MOV RSI,RAX ; RSI = parser pointer. MOV RDI,RAX ; RDI = end of current Arg. MOV R8,RAX ; R8 = begin of current Arg. XOR ECX,ECX ; RCX = current Arg ordinal. MOV R10,[RSP+72] ; R10 = requested Arg ordinal. MOV [RSP+40],RCX ; %ReturnRSI. DEC RCX CMP R10,RCX ; Requested ordinal is -1 when GetArg is invoked from GetArgCount. ADC R10,0 ; Correction for the 0-th string on cmd-line. XOR EAX,EAX CMP EAX,ESI STC JZ .90: ; If GetCommandLine returned FALSE. REPNE SCASW LEA RDX,[RDI-2] ; RDX = end of command line. XOR ECX,ECX ; RCX = current Arg ordinal. .10:MOV RDI,RSI ; RDI = brutto end of current argument. CMP RCX,R10 JAE .50: ; Jump if requested ordinal was just found. .20:CMP RSI,RDX JNB .50: ; If no more arguments available. LODSW CMP AX,' ' JBE .20: SUB RSI,2 INC RCX MOV R8,RSI ; R8 is brutto beginning of RCX-th argument. MOV RDI,RSI .30:CMP RSI,RDX JNB .10: LODSW CMP AX,'"' JNE .40: .35:CMP RSI,RDX ; Inside quotes look for ending quote. CMC JC .90: ; Syntax error - unpaired quotes. LODSW CMP AX,'"' JNE .35: JMP .30: .40:CMP AX,',' JE .10: CMP AX,' ' JA .30: .45:CMP RSI,RDX JNB .10: LODSW CMP AX,' ' JNA .45: CMP AX,',' JE .10: SUB RSI,2 JMP .10: .50:CMP R10,-1 ; Test if invoked from GetArgCount with ArgNr= -1. JNE .60: DEC RCX ; Omit the 0-th argument (the executable itself). JMP .90: .60:CMP RCX,R10 MOV RCX,0 STC JNE .90: ; Return with CF and RCX=0 when the requested Arg is not provided. MOV RSI,R8 ; Brutto RCX-th argument found at RSI..RDI. Trim spaces and commas. .65:CMP RSI,RDI JNB .75: LODSW CMP AX,' ' JBE .65: CMP AX,',' JE .65: SUB RSI,2 .70:SUB RDI,2 MOV AX,[RDI] CMP AX,' ' JBE .70: CMP AX,',' JE .70: ADD RDI,2 ; Netto argument is now at RSI..RDI. .75 MOV [RSP+40],RSI ; %ReturnRSI. SUB RDI,RSI JC .80: MOV RCX,RDI .80:CLC .90:POPQ R11,R10,R9,R8,RDI,RSI,RDX,RAX RET 1*8 ENDP1 GetArgWin64W@RT %ELSE AorW CALL GetArgWin64A@RT GetArgWin64A@RT:: PROC1 ; Stdcalled with %Param1=ArgNumber. PUSHQ RAX,RDX,RSI,RDI,R8,R9,R10,R11 WinABI GetCommandLineA, Fastmode=Yes MOV RSI,RAX ; RSI = parser pointer. MOV RDI,RAX ; RDI = end of current Arg. MOV R8,RAX ; R8 = begin of current Arg. XOR ECX,ECX ; RCX = current Arg ordinal. MOV R10,[RSP+72] ; R10 = requested Arg ordinal. MOV [RSP+40],RCX ; %ReturnRSI. DEC RCX CMP R10,RCX ; Requested ordinal is -1 when GetArg is invoked from GetArgCount. ADC R10,0 ; Correction for the 0-th string on cmd-line. XOR EAX,EAX CMP EAX,ESI STC JZ .90: ; If GetCommandLine returned FALSE. REPNE SCASB LEA RDX,[RDI-1] ; RDX = end of command line. XOR ECX,ECX ; RCX = current Arg ordinal. .10:MOV RDI,RSI ; RDI = brutto end of current argument. CMP RCX,R10 JAE .50: ; Jump if requested ordinal was just found. .20:CMP RSI,RDX JNB .50: ; If no more arguments available. LODSB CMP AL,' ' JBE .20: DEC RSI INC RCX MOV R8,RSI ; R8 is brutto beginning of RCX-th argument. MOV RDI,RSI .30:CMP RSI,RDX JNB .10: LODSB CMP AL,'"' JNE .40: .35:CMP RSI,RDX ; Inside quotes look for ending quote. CMC JC .90: ; Syntax error - unpaired quotes. LODSB CMP AL,'"' JNE .35: JMP .30: .40:CMP AL,',' JE .10: CMP AL,' ' JA .30: .45:CMP RSI,RDX JNB .10: LODSB CMP AL,' ' JNA .45: CMP AL,',' JE .10: DEC RSI JMP .10: .50:CMP R10,-1 ; Test if invoked from GetArgCount with ArgNr= -1. JNE .60: DEC RCX ; Omit the 0-th argument (the executable itself). JMP .90: .60:CMP RCX,R10 MOV RCX,0 STC JNE .90: ; Return with CF and RCX=0 when the requested Arg is not provided. MOV RSI,R8 ; Brutto RCX-th argument found at RSI..RDI. Trim spaces and commas. .65:CMP RSI,RDI JNB .75: LODSB CMP AL,' ' JBE .65: CMP AL,',' JE .65: DEC RSI .70:DEC RDI MOV AL,[RDI] CMP AL,' ' JBE .70: CMP AL,',' JE .70: INC RDI ; Netto argument is now at RSI..RDI. .75 MOV [RSP+40],RSI ; %ReturnRSI. SUB RDI,RSI JC .80: MOV RCX,RDI .80:CLC .90:POPQ R11,R10,R9,R8,RDI,RSI,RDX,RAX RET 8 ENDP1 GetArgWin64A@RT %ENDIF AorW %ENDMACRO GetArg
GetArgCount %MACRO GetArg -1, Unicode=%^UNICODE %ENDMACRO GetArgCount
Macro PutArg retrieves ArgNumber-th argument from command-line and copies its content to the memory specified by BufPtr, BufSize,, including the NUL character at its end.
SIZE# %BufPtr
will be used instead.
EUROASM UNICODE=
.
When the argument value expects e.g. not more than 10 characters andUNICODE=Yes
, you should reserve BufSize at least 22 bytes (10+1 WIDE characters).
In €ASM you can define the output buffer asOutBuffer D (11 >> %^UNICODE) * BYTE
, which will assemble asOutBuffer D 11 * BYTE
whenUNICODE=0
and asOutBuffer D (11 << 1) * BYTE
whenUNICODE= -1
, always reserving room for 10+1 characters.
PutArg %MACRO ArgNumber, BufPtr, BufSize, Unicode=%^UNICODE sized? %IF %# = 2 mem? %IF %^PASS > 1 && TYPE#(SEGMENT# %BufPtr) != 'A' %ERROR ID=5814, 'Please specify the size of output buffer.' %EXITMACRO PutArg %ELSE mem? ; BufPtr is specified as a memory variable with size. %PutArgSize %SETA SIZE# %BufPtr %ENDIF mem? %ELSE sized? ; BufSize is explicitly specified. %PutArgSize %SET %BufSize %ENDIF sized? PUSH RDX,RSI,RDI %IF "%BufPtr[1]"==="[" MOV RDI,%BufPtr %ELSE %IF "%BufPtr" !== "RDI" LEA RDI,[%BufPtr] %ENDIF %ENDIF XOR EDX,EDX %IF %Unicode MOV [RDI],DL %ELSE MOV [RDI],DX %ENDIF GetArg %ArgNumber, Unicode=%^Unicode JC PutArgY%.: MOV EDX,%PutArgSize INC ECX %IF %Unicode INC ECX %ENDIF CMP EDX,ECX JAE PutArgX%.: MOV ECX,EDX REP MOVSB JMP PutArgY%.: PutArgX%.:DEC ECX %IF %Unicode DEC ECX MOV EDX,ECX REP MOVSB MOV [EDI],CX %ELSE MOV EDX,ECX REP MOVSB MOV [EDI],CL %ENDIF PutArgY%.:MOV ECX,EDX POP RDI,RSI,RDX %ENDMACRO PutArg
Macro PutEnv retrieves environment-variable with zero-terminated name EnvName$ at run-time, and copies its content to the memory specified by BufPtr, BufSize,, including the NUL character at its end.
SIZE# %BufPtr
will be used instead.
EUROASM UNICODE=
.
When the environment value expects e.g. not more than 10 characters andUNICODE=Yes
, you should reserve BufSize at least 22 bytes (10+1 WIDE characters).
In €ASM you can define the output buffer asOutBuffer D (11 >> %^UNICODE) * BYTE
, which will assemble asOutBuffer D 11 * BYTE
whenUNICODE=0
and asOutBuffer D (11 << 1) * BYTE
whenUNICODE= -1
, always reserving room for 10+1 characters.
PutEnv %MACRO EnvName$, BufPtr, BufSize, IgnoreCase=Yes, Unicode=%^UNICODE sized? %IF %# = 2 mem? %IF %^PASS > 1 && TYPE#(SEGMENT# %BufPtr) != 'A' %ERROR ID=5814, 'Please specify the size of output buffer.' %EXITMACRO PutEnv %ELSE mem? ; BufPtr is specified as a memory variable with size. %PutEnvSize %SETA SIZE# %BufPtr %ENDIF mem? %ELSE sized? ; BufSize is explicitly specified. %PutEnvSize %SET %BufSize %ENDIF sized? %PutEnvCase %SETB %IgnoreCase PUSHQ %PutEnvCase, %PutEnvSize, %BufPtr, %EnvName$ ansi? %IF %Unicode CALL PutEnvWin64W@RT:: PutEnvWin64W@RT::PROC1 XOR ECX,ECX PUSH RAX,RBX,RCX,RDX,RBP,RSI,RDI,R8 MOV RBP,RSP XOR EAX,EAX MOV RDX,[RBP+10*8] ; BufPtr. MOV RDI,[RSP+9*8] ; EnvName$. MOV [RDX],AX ; Prepare for the case when EnvName$ not found. NOT RCX MOV RSI,RDI REPNE SCASW NOT RCX MOV EDX,ECX ; RDX=size of EnvName$ in unichars including the terminating NUL. LEA EDI,[2*RCX+7] AND EDI,-8 ; Round RDI up to DWORD. SUB RSP,RDI ; Room for lowercase EnvName$. MOV RDI,RSP .10: LODSW CMP AX,'A' JB .20: CMP AX,'Z' JA .20: OR AL,'x'^'X' ; Convert character to lowercase. .20: STOSW DEC ECX JNZ .10: WinABI GetEnvironmentStringsW,Fastmode=No MOV R8,RAX ; Environment block. MOV RBX,RAX .30: MOV RDI,RBX XOR ECX,ECX XOR EAX,EAX NOT RCX MOV RSI,RDI CMP [RDI],AX ; Test the end of environment block. JZ .90: ; Return with RCX=0, CF=0, ZF=1. REPNE SCASW MOV RBX,RDI NOT RCX ; RSI..RDI is ASCIIZ string EnvName=EnvVal, brutto size in unichars=RCX. MOV RDI,RSI MOV AX,'=' REPNE SCASW JNE .30: SUB RDI,RSI SHR EDI,1 CMP EDI,EDX JNE .30: LEA ECX,[EDI-1] ; RCX is unichar-size of netto string EnvName. TESTB [RBP+12*8],1 ; PutEnvCase. JNZ .40: MOV RDI,[RBP+9*8] ; EnvName$ - case sensitive search. REP CMPSW JNE .30: JMP .70: .40: MOV RDI,RSP ; Case insensitive search. .50: LODSW CMP AX,'A' JB .60: CMP AX,'Z' JA .60: OR AL,'x'^'X' ; Convert character to lowercase. .60: CMP AX,[RDI] JNE .30: ADD RDI,2 DEC ECX JNZ .50: .70: ADD RSI,2 ; EnvName was found. RSI is now ASCIIZ EnvVal. MOV RDI,RSI XOR ECX,ECX XOR EAX,EAX NOT RCX REPNE SCASW SUB RDI,RSI ; EDI= required-by-value brutto size of EnvVal in bytes. SUB EDI,2 MOV [RBP+5*8],RDI ; Return RCX=netto size of EnvVal in bytes. ADD RDI,2 MOV RCX,[RBP+11*8] ; BufSize. EnvVal allocated-by-user brutto size in bytes. CMP ECX,EDI XCHG ECX,EDI JAE .80: XCHG EDI,ECX .80: MOV RDI,[RBP+10*8] ; BufPtr. REP MOVSB .90: PUSHFQ WinABI FreeEnvironmentStringsW, R8, Fastmode=No POPFQ MOV RSP,RBP POP R8,RDI,RSI,RBP,RDX,RCX,RBX,RAX RET 4*8 ENDP1 PutEnvWin64W@RT:: %ELSE ansi? CALL PutEnvWin64A@RT:: PutEnvWin64A@RT::PROC1 XOR ECX,ECX PUSH RAX,RBX,RCX,RDX,RBP,RSI,RDI,R8 MOV RBP,RSP XOR EAX,EAX MOV RDX,[RBP+10*8] ; BufPtr. MOV RDI,[RSP+9*8] ; EnvName$. MOV [RDX],AL ; Prepare for the case when EnvName$ not found. NOT RCX MOV RSI,RDI REPNE SCASB NOT RCX MOV EDX,ECX ; RDX=size of EnvName$ in bytes including the terminating NUL. LEA EDI,[2*RCX+7] AND EDI,-8 ; Round RDI up to QWORD. SUB RSP,RDI ; Room for lowercase EnvName$. MOV RDI,RSP .10: LODSB CMP AL,'A' JB .20: CMP AL,'Z' JA .20: OR AL,'x'^'X' ; Convert character to lowercase. .20: STOSB DEC ECX JNZ .10: WinABI GetEnvironmentStringsA,Fastmode=No MOV R8,RAX ; Environment block. MOV RBX,RAX .30: MOV RDI,RBX XOR ECX,ECX XOR EAX,EAX NOT RCX MOV RSI,RDI CMP [RDI],AL ; Test the end of environment block. JZ .90: ; Return with RCX=0, CF=0, ZF=1. REPNE SCASB MOV RBX,RDI NOT RCX ; RSI..RDI is ASCIIZ string EnvName=EnvVal, brutto size in bytes=RCX. MOV RDI,RSI MOV AL,'=' REPNE SCASB JNE .30: SUB RDI,RSI CMP EDI,EDX JNE .30: LEA ECX,[EDI-1] ; RCX is the size of netto string EnvName$. TESTB [RBP+12*8],1 ; PutEnvCase. JNZ .40: MOV RDI,[RBP+9*8] ; EnvName$ - case sensitive search. REP CMPSB JNE .30: JMP .70: .40: MOV RDI,RSP ; Case insensitive search. .50: LODSB CMP AL,'A' JB .60: CMP AL,'Z' JA .60: OR AL,'x'^'X' ; Convert character to lowercase. .60: CMP AL,[RDI] JNE .30: INC RDI DEC ECX JNZ .50: .70: INC RSI ; EnvName was found. RSI is now ASCIIZ EnvVal. MOV RDI,RSI XOR ECX,ECX XOR EAX,EAX NOT RCX REPNE SCASB SUB RDI,RSI ; RDI= required-by-value brutto size of EnvVal in bytes. DEC RDI MOV [RBP+5*8],RDI ; Return RCX=netto size of EnvVal in bytes. INC RDI MOV RCX,[RBP+11*8] ; BufSize. EnvVal allocated-by-user brutto size in bytes. CMP ECX,EDI XCHG ECX,EDI JAE .80: XCHG EDI,ECX .80: MOV RDI,[RBP+10*8] ; BufPtr. REP MOVSB .90: PUSHFQ WinABI FreeEnvironmentStringsA, R8, Fastmode=No POPFQ MOV RSP,RBP POP R8,RDI,RSI,RBP,RDX,RCX,RBX,RAX RET 4*8 ENDP1 PutEnvWin64A@RT:: %ENDIF ansi? %ENDMACRO PutEnv
Macro StdOutput writes one or more concatenated strings to the standard output or to other equipment specified with the Handle identifier.
Strings are either zero-terminated, or the keyword Size= must specify its size in bytes. The terminating NULL character is never written.
If keyword Eol=Yes, macro writes CR+LF after all strings.
One of four possible runtime subprocedures is selected to emit, depending on the chosen ANSI/WIDE and File/Console options.
Unicode= %^UNICODE is boolean specification whether the Strings are in WIDE (UTF-16) encoding. By default (if omitted) it copies the global optionOutput of WriteFile (default) is redirectable, but it writes WIDE string as is; in OEM console are the UTF-16 encoded characters displayed as interlaced.
Output produced by WriteConsole (when Console=Yes) cannot be redirected by command-line operator > but it accepts WIDE Unicode strings and displays the text in TrueType console properly, including non-English characters.When you want to use the Console (nondefault) mode in all StdOutput and StdInput invokations, you don't have to append
,Console=Yes
to every invokation ofStdOutput
andStdInput
if you set preprocessing %variable in the beginning of your program:%StdConsole %SETB Yes
.
EUROASM Unicode=
.StdOutput %MACRO String1,String2,,,Size=-1, Handle=-11, Eol=No, Console=%StdConsole, Unicode=%^UNICODE IMPORT WriteFile,WriteConsoleA,WriteConsoleW C %IF %Console U %IF %Unicode %StdOutputFlags %SETA 2 + 1 %ELSE U %StdOutputFlags %SETA 2 + 0 %ENDIF U %ELSE C W %IF %Unicode %StdOutputFlags %SETA 0 + 1 %ELSE W %StdOutputFlags %SETA 0 + 0 %ENDIF W %ENDIF C ArgNr %FOR 1..%#, STEP=1 ; Call the runtime for each String. PUSHQ %StdOutputFlags S %IF TYPE#(SEGMENT# %1) = 'N' ; String is specified as a scalar, e.g. RSI. PUSHQ %1 %ELSE S ; String is specified as a symbolic address, e.g. Msg or ="Msg". PUSH RAX LEA RAX,[%1] XCHG RAX,[RSP] %ENDIF S PUSHQ %Size, %Handle CALL StdOutputWin64@RT %SHIFT 1 ; The next string to output. %ENDFOR ArgNr Eol %IF %Eol U %IF %Unicode PUSHQ %StdOutputFlags + 4, 0, 4, %Handle %ELSE U PUSHQ %StdOutputFlags + 4, 0, 2, %Handle %ENDIF U CALL StdOutputWin64@RT %ENDIF Eol StdOutputWin64@RT:: PROC1 ; Invoked in stdcall convention with parameters Handle,Size,Addr,Flags. PUSHQ RAX,RCX,RDX,RDI,R8,R9,R10,R11,0,0 %StdOutputWritten %SET RSP+0 ; QWORD memory for characters-written number. %StdOutputEol$ %SET RSP+8 ; Local string CR+LF. %StdOutputHandle %SET RSP+88 %StdOutputSize %SET RSP+96 %StdOutputString %SET RSP+104 %StdOutputFlags %SET RSP+112 MOV RCX,[%StdOutputHandle] WinABI GetStdHandle,RCX,Fastmode=Yes CMP RAX,-1 STC JZ .90: ; Abort with CF when INVALID_HANDLE_VALUE (-1). MOV R8,RAX ; StdOutput handle. MOV RCX,[%StdOutputSize] MOV RDI,[%StdOutputString] MOV RDX,[%StdOutputFlags] ; Bits 0=Unicode, 1=Console, 2=Eol. MOV R11,WriteFile:: TEST DL,2 ; Select File or Console mode. JZ .10: MOV R11,WriteConsoleA:: TEST DL,1 ; Select ANSI or WIDE variant. JZ .10: MOV R11,WriteConsoleW:: .10:TEST DL,4 ; Test if the string should be Eol. JZ .20: LEA RDI,[%StdOutputEol$] ; EOL is requested instead of string. MOVD [RDI],0x000A_000D ; WIDE EOL. TEST DL,1 JNZ .20: MOVW [RDI],0x0A0D ; ANSI EOL. .20:XOR EAX,EAX ; RDI,RCX is ASCIIZ string. MOV R10,RDI TEST DL,1 ; ASCII | WIDE. JZ .40: SHR ECX,1 ; Convert size to length in characters. REPNE SCASW ; Find the WIDE zero terminator. JNE .50: SUB RDI,2 ; Omit the zero terminator. JMP .50: .40:REPNE SCASB ; Find the ANSI zero terminator. JNE .50: DEC RDI .50:SUB RDI,R10 ; RDI is now the string size in bytes. AND DL,3 XOR DL,3 JNZ .60: SHR EDI,1 ; RDI is now string size in WIDE characters for Console. MOV RDX,RSP .60:MOV RCX,R8 ; File/Console handle. MOV R9,RSP ; Characters-written variable. MOV R8,RDI ; String length in characters. MOV RDX,R10 ; Pointer to string. WinABI R11,RCX,RDX,R8,R9,RAX,Fastmode=Yes CMP [%StdOutputWritten],RDI ; Set CF if not all characters were written. .90:POP RAX,RAX,R11,R10,R9,R8,RDI,RDX,RCX,RAX RET 4*8 ENDP1 StdOutputWin64@RT:: %ENDMACRO StdOutput
Macro StdInput reads a line of text terminated with CR from standard input device (usually the keyboard) specified by the Handle identifier.
Input of ReadFile (default) is redirectable, but it does not distinguish WIDE or ANSI characters. ReadConsole mode can only be used with a console input buffer handle (no redirection by command-line operators < or | is working) but it interprets WIDE characters properly.
StdInput %MACRO Buffer, Size=, Handle=-10, Console=No, Unicode=%^UNICODE IMPORT ReadFile,ReadConsoleW,ReadConsoleA C %IF %Console U %IF %Unicode %StdInputFlags %SETA 2 + 1 %ELSE U %StdInputFlags %SETA 2 + 0 %ENDIF %ELSE C ; ReadFile variant. Ignores Unicode. W %IF %Unicode %StdInputFlags %SETA 0 + 1 %ELSE W %StdInputFlags %SETA 0 + 0 %ENDIF W %ENDIF C PUSHQ %StdInputFlags B %IF TYPE#(SEGMENT# %Buffer) = 'N' ; Buffer is specified as a scalar, e.g. RSI. PUSHQ %Buffer %ELSE B ; Buffer is specified as a symbolic address, e.g. Answer or [Answer+RDI]. M %IF '%Buffer[1]' === '[' PUSH RAX LEA RAX,%Buffer MOV RAX,[RAX] XCHG RAX,[RSP] %ELSE M PUSH RAX LEA RAX,[%Buffer] XCHG RAX,[RSP] %ENDIF M %ENDIF B S %IF '%Size' === '' PUSHQ SIZE# %Buffer %ELSE S PUSHQ %Size %ENDIF S PUSHQ %Handle CALL StdInputWin64@RT StdInputWin64@RT:: PROC1 PUSHQ RAX,RDX,R8,R9,R10,R11,0 %ReturnedRCX %SET RSP+0 %StdInputHandle %SET RSP+64 %StdInputSize %SET RSP+72 %StdInputBuffer %SET RSP+80 %StdInputFlags %SET RSP+88 MOV RCX,[%StdInputHandle] WinABI GetStdHandle,RCX,Fastmode=Yes CMP RAX,-1 STC JZ .90: ; Abort with CF when INVALID_HANDLE_VALUE (-1). MOV RCX,RAX MOV RDX,[%StdInputFlags] ; Bits 0=Unicode, 1=Console. MOV R8,[%StdInputSize] MOV R11,ReadFile TEST DL,2 JZ .20: .10:MOV R11,ReadConsoleA TEST DL,1 JZ .20: MOV R11,ReadConsoleW SHR R8,1 .20:CMP DL,3 MOV RDX,[%StdInputBuffer] MOV R9,RSP ; %ReturnedECX. PUSHFQ ; Remember in ZF if Unicode&&Console. WinABI R11,RCX,RDX,R8,R9,0,Fastmode=Yes POPFQ CLC JNE .90: SALQ [RSP],1 ; Convert read character to bytes when Unicode&&Console. .90:POPQ RCX,R11,R10,R9,R8,RDX,RAX RET 4*8 ENDP1 StdInputWin64@RT:: %ENDMACRO StdInput
Errorlevel=
, this value may also be specified as an ordinal operand.
TerminateProgram %MACRO Errorlevel=0 %IF %#=1 ; If ordinal provided. MOV RCX,%1 %ELSE ; If keyword provided. MOV RCX,%Errorlevel %ENDIF SUB RSP,4*8 IMPORT ExitProcess, Lib="kernel32.dll" JMP ExitProcess %ENDMACRO TerminateProgram
ENDHEAD winabi