For those who are interested, here is a simple GAS example.
.intel_syntax noprefix
.global _start
.data
.extern _MessageBoxA@16
.extern _ExitProcess@4
.set MessageBox,_MessageBoxA@16 /* .set = EQU */
.set ExitProcess,_ExitProcess@4
message:
.ascii "Hello world!" /* define NULL terminated string */
.byte 0
caption:
.ascii "Message box"
.byte 0
.text
_start:
pushd 0 /* push DOUBLE word */
push OFFSET caption
push OFFSET message
pushd 0
call MessageBox
pushd 0
call ExitProcess
Building the project : ( I am using the GNU tools coming with FreeBASIC package )
C:\Freebasic\bin\win32\as -o %1.o %1.asm
C:\Freebasic\bin\win32\ld -e_start -subsystem windows -Lc:\masm32\lib -o %1.exe %1.o --library=user32 --library=kernel32 -s
[attachment deleted by admin]
Vortex,
Thanks for the demo, I wonder if there is another way to get hold of "AS" ? I remember climbing through the GNU site and could not find it.
Hi Hutch,
All the information what I found to use the GNU assembler as.exe :
Manual for as.exe :
http://www.gnu.org/software/binutils/manual/gas-2.9.1/html_mono/as.html#SEC2
A nice CHM documentation :
ftp://ftp.berlios.de/pub/htmlhelp/as-2.15.chm
Well, at least it supports Intel syntax now, it didn't used to. I couldn't stand looking at gas code before, this is better.
Hi Greg,
Yes, you are right. One feels really very uncomfortable with the AT&T syntax.
The only reason to use gas is if you are on a linux machine, and FASM does the job better.
Looks like a nice low level tool. With thanks to Vortex for the original example, here is a quick play with GAS using the Intel syntax.
/* -------------------------------------------------------------- */
.intel_syntax noprefix
.global _entry_point
.macro msgbox arg1,arg2,arg3,arg4
push \arg4 /* push DOUBLE word */
push OFFSET \arg3
push OFFSET \arg2
push \arg1
call MessageBox
.endm
.macro quit
push 0
call ExitProcess
.endm
.data
.extern _MessageBoxA@16
.extern _ExitProcess@4
.set MessageBox,_MessageBoxA@16
.set ExitProcess,_ExitProcess@4
.text
// ----------------------------------------
// howdy, I am a leading C++ commented text
// ----------------------------------------
message: .ascii "Its a GAS\x00" /* ZERO terminated string */
caption: .ascii "Hey man !\x00"
/* -------------------------------------------------------------- */
_entry_point:
.data
buffer: .ascii " "
.text
push ebx
mov ecx, OFFSET message
mov edx, OFFSET buffer
xor ebx, ebx
call szcopy
pop ebx
msgbox 0,buffer,caption,0
quit
/* -------------------------------------------------------------- */
szcopy:
mov al, BYTE PTR [ecx+ebx]
mov BYTE PTR [edx+ebx], al
add ebx, 1
test al, al
jnz szcopy
ret
/* -------------------------------------------------------------- */
I had a play with it too. It's not that bad. Once you get the hang of it, it works pretty well.
.intel_syntax noprefix
.arch pentium
.global _start
GetStdHandle = _GetStdHandle@4
lstrlen = _lstrlenA@4
WriteConsole = _WriteConsoleA@20
ExitProcess = _ExitProcess@4
NULL = 0
STD_OUTPUT_HANDLE = -11
.data
szTitle: .asciz "\nGNU Assembler Example\n\n"
szMsg: .asciz " Hello from GAS!\n\n"
.bss
hStdOut: .long 0
dwNumWritten: .long 0
dwNumToWrite: .long 0
// szBuffer: .space 255, 0
.text
_start:
push STD_OUTPUT_HANDLE
call GetStdHandle
mov hStdOut, eax
push OFFSET szTitle
call lstrlen
mov dwNumToWrite, eax
push NULL
push OFFSET dwNumWritten
push dwNumToWrite
push OFFSET szTitle
push hStdOut
call WriteConsole
push OFFSET szMsg
call lstrlen
mov dwNumToWrite, eax
push NULL
push OFFSET dwNumWritten
push dwNumToWrite
push OFFSET szMsg
push hStdOut
call WriteConsole
push 0
call ExitProcess
.end
Has some useful directives as well.
/* -------------------------------------------------------------- */
.intel_syntax noprefix
.global _entry_point
.macro msgbox arg1,arg2,arg3,arg4
push \arg4 /* push DOUBLE word */
push OFFSET \arg3
push OFFSET \arg2
push \arg1
call MessageBox
.endm
.macro exit
push 0
call ExitProcess
.endm
.data
.extern _MessageBoxA@16
.extern _ExitProcess@4
.set MessageBox,_MessageBoxA@16
.set ExitProcess,_ExitProcess@4
.set MB_OK, 0x00
.set MB_OKCANCEL, 0x01
.set MB_ABORTRETRYIGNORE, 0x02
.set MB_YESNOCANCEL, 0x03
.set MB_YESNO, 0x04
.set MB_RETRYCANCEL, 0x05
.set MB_ICONHAND, 0x10
.set MB_ICONQUESTION, 0x20
.set MB_ICONEXCLAMATION, 0x30
.set MB_ICONASTERISK, 0x40
.text
// ----------------------------------------
// howdy, I am a leading C++ commented text
// ----------------------------------------
message: .ascii "Its a GAS\nIts a GAS\x00" /* ZERO terminated string */
caption: .ascii "Hey man !\x00"
/* -------------------------------------------------------------- */
_entry_point:
.data
buffer: .ascii " "
.text
push OFFSET buffer
push OFFSET message
call szcopy
msgbox 0,buffer,caption,MB_OK | MB_ICONASTERISK
exit
/* -------------------------------------------------------------- */
.align 8
szcopy:
mov ecx, [esp+4]
mov edx, [esp+8]
push ebx
xor ebx, ebx
cpy:
.rept 3
mov al, [ecx+ebx]
mov [edx+ebx], al
add ebx, 0x01
test al, al
jz cpout
.endr
mov al, [ecx+ebx]
mov [edx+ebx], al
add ebx, 0x01
test al, al
jnz cpy
cpout:
pop ebx
ret 8
/* -------------------------------------------------------------- */
It also does address substitution with .long data types.
// -----------------------
// zero terminated strings
// -----------------------
message: .ascii "Its a GAS\nIts a GAS\x00" /* ZERO terminated string */
caption: .ascii "Hey man !\x00"
// -----------------------------------
// pointers to zero terminated strings
// -----------------------------------
pmsg: .long message
pttl: .long caption
On the fly .DATA seems to work fine as well.
.data
buffer: .fill 128 /* 128 byte buffer */
pbuf: .long buffer /* pointer to buffer */
.text
Here is a near complete set of include files for AS.
You need to use double backslashes in the paths as follows.
.include "h:\\gas\\include\\user32.in"
.include "h:\\gas\\include\\kernel32.in"
[attachment deleted by admin]
QuoteThe only reason to use gas is if you are on a linux machine, and FASM does the job better.
Well, it is the assembler for GCC so it's probably pretty good. I started tinkering with it because I run FreeBSD but got sidetracked with some C stuff. There is a whole tutorial on AS on the FreeBSD sites developers site and the Whizkid has one, too. (They were both written by himi). What little I did shows me that it is no more difficult using as/gas than switching between any two similar languages.
An example using msvcrt.dll.
.intel_syntax noprefix
.arch pentium
.global _start
GetStdHandle = _GetStdHandle@4
lstrlen = _lstrlenA@4
WriteConsole = _WriteConsoleA@20
ExitProcess = _ExitProcess@4
sprintf = _sprintf
_getch = __getch
NULL = 0
STD_OUTPUT_HANDLE = -11
.macro exit rv
push \rv
call ExitProcess
.endm
.macro printsz szStr
push STD_OUTPUT_HANDLE
call GetStdHandle
mov hStdOut, eax
push OFFSET \szStr
call lstrlen
mov dwNumToWrite, eax
push NULL
push OFFSET dwNumWritten
push dwNumToWrite
push OFFSET \szStr
push hStdOut
call WriteConsole
.endm
.macro getPi Pi
finit
fldpi
fstp QWORD PTR \Pi
.endm
.data
szTitle: .asciz "\nGNU Assembler Example\n\n"
szMsg: .asciz " It's a GAS!\n\n"
szFmt: .asciz " Pi = %.12lf\n\n"
szPAK: .asciz "Press any key to exit..."
szLf: .asciz "\n"
.bss
hStdOut: .long 0
dwNumWritten: .long 0
dwNumToWrite: .long 0
dblPi: .double 0.0
szBuffer: .space 32, 0
.text
_start:
printsz szTitle
printsz szMsg
getPi dblPi
push [dblPi+4]
push [dblPi+0]
push OFFSET szFmt
push OFFSET szBuffer
call sprintf
add esp, 4 * 4 /*_cdecl*/
printsz szBuffer
printsz szPAK
call _getch
cmp eax, 0
je again
cmp eax, 0xE0
je again
jmp done
again:
call _getch
done:
printsz szLf
exit 0
.end
Hutch, Greg,
Thanks for the examples :U
Hello world example linked with PoLink to reduce the size of the final executable to 1536 bytes.
[attachment deleted by admin]
I have a working complete set of macros that make the API functions more or less high level function in the following form. I just did not know if anyone was interested in them. I have also ripped a set of equates from the masm32 windows.inc that seem to be working OK. All I am having trouble with at the moment is a convenient way to handle structures. If anyone is interested I will post a set somewhere so they are available.
.extern _ActivateKeyboardLayout@8
.macro ActivateKeyboardLayout arg1 arg2
push \arg2
push \arg1
call _ActivateKeyboardLayout@8
.endm
.extern _AdjustWindowRect@12
.macro AdjustWindowRect arg1 arg2 arg3
push \arg3
push \arg2
push \arg1
call _AdjustWindowRect@12
.endm
.extern _AdjustWindowRectEx@16
.macro AdjustWindowRectEx arg1 arg2 arg3 arg4
push \arg4
push \arg3
push \arg2
push \arg1
call _AdjustWindowRectEx@16
.endm
Hutch,
I'm interested. GAS is a pretty darn good assembler. I had always avoided it before, mainly because of the AT&T syntax. Now that it supports Intel syntax, I like it. Another tool for the toolkit.
Is there a way to handle varargs in the macros?
FWIW I spent several hours today trying to create an invoke macro. I could find no good, clean method of using a variable number of arguments. Any macro call with more arguments than parameters would trigger "Error: too many positional arguments". When I switched to more parameters than arguments, I could find no direct method of determining which parameters were blank. What I am doing for now is passing the number of arguments and using that to determine which of the other arguments to process. Another problem was that an argument of the form "offset arg" would expand to two arguments. I was able to fix this by specifying the arguments as offset(arg).
.macro invoke func,argcnt,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10
.if \argcnt > 9
push \a10
.endif
.if \argcnt > 8
push \a9
.endif
.if \argcnt > 7
push \a8
.endif
.if \argcnt > 6
push \a7
.endif
.if \argcnt > 5
push \a6
.endif
.if \argcnt > 4
push \a5
.endif
.if \argcnt > 3
push \a4
.endif
.if \argcnt > 2
push \a3
.endif
.if \argcnt > 1
push \a2
.endif
.if \argcnt > 0
push \a1
.endif
call \func
.endm
invoke MessageBox,4,0,offset(message),offset(buffer),MB_OK | MB_ICONASTERISK
I think a better method would be to append the number of arguments to the function name, perhaps just the way it is in the include files, for example _MessageBoxA@16, but I don't see where GAS provides any method of extracting the number from the name.
Sorry to be a bit slow but I have been doing a lot of checking and experimenting with the include file layout. The currect batch are macros in the form suggested above that work as a form of type checking as you at least get some response if the argument count is incorrect. You can either use the macro which is just automated push/call or you can directly use CALL on the undecorated name.
You can do a passable emulation of a structure in the .data section with the following.
.align 8
// WNDCLASSEX structure
wc_cbSize: .long 48
wc_style: .long CS_BYTEALIGNWINDOW | CS_BYTEALIGNCLIENT
wc_lpfnWndProc: .long WndProc
wc_cbClsExtra: .long 0
wc_cbWndExtra: .long 0
wc_hInstance: .long 0
wc_hIcon: .long 0
wc_hCursor: .long 0
wc_hbrBackground: .long 0
wc_lpszMenuName: .long 0
wc_lpszClassName: .long szclassname
wc_hIconSm: .long 0
Michael solved the problem of using OFFSET with the macros with the brackets.
There is a big file winequ.gi (gas include) that has a lot of equates in it but the file is rough in that it does not properly handle joined styles. I fixed the WS_OVERLAPPEDWINDOW one for one of the examples but the file needs to be done a lot better than it currently is.
One example is a resource less window that seems to work OK. I have included the two batch files I have been using to build examples and I have so far continued to use the masm32 API import libraries. The MASM32 library is also used.
http://www.masmforum.com/private/gas.zip
Thanks Hutch :U
A bit slow? You either have very sore fingers now, or you somehow automated the creation of all this.
Say, would anyone happen to know how to write a simple command line program under Linux using GAS?
Hi Hutch,
Very nice work :U
I guess you have a special tool to convert the master windows.inc file to GAS syntax.
MichaelW,
I came up with something almost identical to what you did for variable arguments. I was hoping there was a better way.
offset(arg) :U
Hutch,
Thanks.
MichaelW, thanks for your invoke macro :U
Based on the macro, I added a feature to support GAS to my tool external function scanner :
// Original example by Hutch
.intel_syntax noprefix
.global _start
.macro szByteCopy arg1,arg2
push \arg2
push \arg1
call szcopy
.endm
.macro m2m arg1,arg2
push \arg2
pop \arg1
.endm
.include "invoke.gi"
.include "msgbox.imp"
.set MB_ICONASTERISK, 0x40
.set MB_OK, 0x0
// -----------------------
// zero terminated strings
// -----------------------
.data
message: .ascii "Its a GAS Its a GAS Its a GAS Its a GAS\n"
.ascii "Its a GAS Its a GAS Its a GAS Its a GAS\n"
.ascii "Its a GAS Its a GAS Its a GAS Its a GAS\x00"
caption: .ascii "Hey man !\x00"
// -----------------------------------
// pointers to zero terminated strings
// -----------------------------------
pmsg: .long message
pttl: .long caption
.text
/* ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««« */
_start:
.bss
buffer: .fill 128 /* 128 byte buffer */
.data
pbuf: .long buffer /* pointer to buffer */
.text
invoke GetCL,2,1,pbuf
cmp eax, 1
jne nocl
invoke MessageBox,4,0,pbuf,pttl,MB_OK | MB_ICONASTERISK
jmp quit
nocl:
.data
nomsg: .ascii "No command line argument was entered\x00"
nottl: .ascii "Sorry ....\x00"
pnm: .long nomsg
pnt: .long nottl
.text
invoke MessageBox,4,0,pnm,pnt,MB_OK | MB_ICONASTERISK
quit:
invoke ExitProcess,1,0
/* ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««« */
.align 8
szcopy:
mov ecx, [esp+4]
mov edx, [esp+8]
push ebx
xor ebx, ebx
cpy:
.rept 3
mov al, [ecx+ebx]
mov [edx+ebx], al
add ebx, 0x01
test al, al
jz cpout
.endr
mov al, [ecx+ebx]
mov [edx+ebx], al
add ebx, 0x01
test al, al
jnz cpy
cpout:
pop ebx
ret 8
/* ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««« */
Building the demo :
scan Msgbox.as -ga
as -o Msgbox.obj Msgbox.as
ld -e_start -subsystem windows -L\masm32\lib -o Msgbox.exe Msgbox.obj --library=masm32 --library=user32 --library=kernel32 -s
The output of scan for external functions :
.extern _ExitProcess@4
.set ExitProcess,_ExitProcess@4
.extern _MessageBoxA@16
.set MessageBox,_MessageBoxA@16
.extern _GetCL@8
.set GetCL,_GetCL@8
[attachment deleted by admin]
I have just finished a much better equate file for AS. This one properly handles the joined ORRED styles.
So far I have not found a way to pass quoted strings to a macro, anyone know if it can be done ?
[attachment deleted by admin]
Hutch,
So far I have not found a way to pass quoted strings to a macro, anyone know if it can be done ?
.ascii "Its a GAS Its a GAS Its a GAS Its a GAS\x00"
You mean something like this?
.ascii ""its a GAS" "Its a GAS" "Its a GAS "Its a GAS"\x00"
Wouldn't this be a problem with the .ascii directive in the GAS compiler?
You would have to make your own routine to keep the " " symbols.
Your table parser rountine useing ascii characters would probably be able to handle this type of conversion as a template. Then you could simplify with a small set symbols or single symbol if you just wanted it to handle the "". Otherwise, I don't know of routines although my knowledge is limited.
Hope this helps give ideas.
I found ".altmacro" buried in the documentation,
.altmacro
.macro sztext uname,quoted_text
.data
uname&: .string quoted_text
.text
.endm
Ok, here is my super simple GAS example as it doesnt have any code really, just calls exit.
There is a make to simplify assembling and linking.
I'll extend the example to include a DLL and call a function in it.
Does anyone know where I can find the 'invoke' macro used in the other examples
below that works with GAS?
Note: Im using cygwin under WinXP to build my examples.
Rgs, James.
simple.s
.intel_syntax noprefix
.arch pentium
.global _start
.data
.extern _ExitProcess@4
.text
_start:
xor eax, eax
pushd 0
call _ExitProcess@4
Makefile (note that there are tabs in the Makefile)
SRC = simple.s
OBJ = simple.o
BIN = simple
LIBS = kernel32
all: $(OBJ) $(BIN)
clean:
rm -f $(OBJ)
rm -f $(BIN)
.s.o:
as -g -o $@ $<
simple: simple.o
ld -e_start -o simple simple.o -l$(LIBS)
Here is a dialog box example with GAS :
.intel_syntax noprefix
.global _start
.include "invoke.gi"
.include "Dlgbox.gi"
.data
DlgName:
.asciz "MyDialog"
.bss
hInstance:
.long 0
hCursor:
.long 0
.text
_start:
invoke GetModuleHandle,1,0
mov hInstance,eax
invoke DialogBoxParam,5,hInstance,OFFSET(DlgName),0,OFFSET(DlgProc),0
invoke ExitProcess,1,eax
// DlgProc PROC hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
DlgProc:
push ebp
mov ebp,esp
cmp dword ptr [ebp+uMsg], WM_CLOSE
jne initdlg
invoke EndDialog,2,[ebp+hWnd],0
jmp true
initdlg:
cmp dword ptr [ebp+uMsg], WM_INITDIALOG
jne setcursor
invoke LoadCursor,2,0,IDC_CROSS
mov hCursor,eax
jmp true
setcursor:
cmp dword ptr [ebp+uMsg], WM_SETCURSOR
jne false
invoke SetCursor,1,hCursor
jmp true
false:
xor eax,eax
pop ebp
ret 16
true:
mov eax,1
pop ebp
ret 16
[attachment deleted by admin]