The title is misleading in as much as assembler programming is not a painful surgical procedure (if done correctly) nor does it require the inhalation of intoxicating fumes to attain altered states of consciousness.
Gas is the GNU assembler used by the GCC C++ compiler (and other languages like FreeBasic) to generate executable code. While it is mainly used as part of the compilation suite, invisible to the programer. it is friendly enough to be used in its own right.
It is distributed as part of the binutils. For MS Windows this is part of the MinGW package. It is also present in the FreeBasic bin folder.
To create executables, as.exe, the assembler and ld.exe the linker is all that you need.
Here is a Hello World Message Box:
The source code is in t.asm
.intel_syntax noprefix
.section .data
caption: .asciz "GAS Greeting"
message: .asciz "Hello World!"
.align 4,0
.section .text
.balign 16
.globl _mainCRTStartup
_mainCRTStartup:
push 3 # style
push offset caption # caption
push offset message # message
push 0 # window handle
call _MessageBoxA@16 # MessageBox
ret 16
Assembling and Linking.
This can be put into a batch file like this:
go.bat
..\bin\win32\as t.asm
..\bin\win32\ld --subsystem windows a.out ..\lib\win32\libuser32.dll.a
pause
Some useful reference material for programming with GAS
Linux assemblers: A comparison of GAS and NASM
http://www.ibm.com/developerworks/library/l-gas-nasm.html
Using 'as': GNU Assembler Reference
http://sourceware.org/binutils/docs-2.17/as/index.html
alt
http://www.gnu.org/software/binutils/manual/gas-2.9.1/html_chapter/as_toc.html
Intel Architecture Software developer's manual Vol 2
http://developer.intel.com/design/pentiumii/manuals/243191.htm
Freebasic (comes with AS and LD)
http://www.freebasic.net
If you have installed Freebasic then you can extract the folder below and put it into the main directory.
It contains the source code and batchfile (and precompiled executable).
In minGW, the directory structure and lib file name conventions are slightly different so comple commands look like this:
go.bat
..\bin\as t.asm
..\bin\ld --subsystem windows a.out ..\lib\libuser32.a
pause
Another difference is the name of the entry point WinMainCRTStartup instead of mainCRTStartup:
.intel_syntax noprefix
.section .data
caption: .asciz "GAS Greeting"
message: .asciz "Hello World!"
.align 4,0
.section .text
.balign 16
.globl _WinMainCRTStartup
_WinMainCRTStartup:
.set ABC,42
push 3 # style
push offset caption # caption
push offset message # message
push 0 # window handle
call _MessageBoxA@16 # MessageBox
ret 16
The linker has innumerable command line switches for various systems but the --subsystem Windows suppresses the appearance of a console window when running Windows GUI programs.
http://sourceware.org/binutils/docs-2.17/ld/index.html
a.out is the default output of the assembler and any library files required by the executable come after this.
When Gas is used as the inline Assemble from within FreeBasic. Some ot its features are disrupted. I found that Basic's use of the single quote mark as a comment is not really compatible, and an important feature: relative labels is also partially disrupted.
These labels are an elegant solution to block-structured programming. Labels 0..9 can be used:
To jump forward to the next matching label the operand is 0f 1f 2f etc. To jmp back to the previous match the operand is 0b 1b 2b .. 9b. This is all you need to construct multiple nested loops, conditionals and case blocks with the need to invent unique labels every time.
Thanks for sharing Charles,
very interesting, please continue :)
Petr
Very interesting indeed, thanks Charles. I will soon buy one of these low cost 7" screen notebooks that are under $400 with a linux OS installed.
I hope to finally start to develop under linux while out and about and finally learn linux better. I will make use of this thread as well as Donald's linux adventure threads.
Okay, this is an intermediate step for a Windows prog which will be written in pure Gas.
Hello World
Using FreeBasic Inline Assembler
Minimal Windows SDK prog with message loop etc
' inline Assembler "Hello World"
' FreeBasic 0.18.3
' 09:30 12/02/2008
#include once "windows.bi"
'type MSG
' hwnd as HWND
' message as UINT
' wParam as WPARAM
' lParam as LPARAM
' time as DWORD
' pt as POINT
'end type
'type WNDCLASSA
' style as UINT
' lpfnWndProc as WNDPROC
' cbClsExtra as integer
' cbWndExtra as integer
' hInstance as HINSTANCE
' hIcon as HICON
' hCursor as HCURSOR
' hbrBackground as HBRUSH
' lpszMenuName as LPCSTR
' lpszClassName as LPCSTR
'end type
'type RECT
' left as LONG
' top as LONG
' right as LONG
' bottom as LONG
'end type
'type PAINTSTRUCT
' hDC AS DWORD
' fErase AS LONG
' rcPaint AS RECT
' fRestore AS LONG
' fIncUpdate AS LONG
' rgbReserved(0 TO 31) AS BYTE
'end type
declare function WndProc ( byval hWnd as HWND, _
byval wMsg as UINT, _
byval wParam as WPARAM, _
byval lParam as LPARAM ) as LRESULT
'':::::
function WinMain ( byval hInstance as HINSTANCE, _
byval hPrevInstance as HINSTANCE, _
byval szCmdLine as string, _
byval iCmdShow as integer ) as integer
dim wMsg as MSG
dim wcls as WNDCLASS
dim hWnd as HWND
function = 0
dim as any ptr p,q,r,s,t,u,v
q=@WndProc
p=@wcls
r=@"HelloWin"
s=@"Failed to register wcls"
t=@"Error"
u=@"The Hello Program"
v=@wMsg
asm
mov eax,[p] ' redundant?
mov dword ptr [eax],CS_HREDRAW
or dword ptr [eax],CS_VREDRAW
mov edx,[q]
mov [eax+4],edx
mov dword ptr [eax+8],0
mov dword ptr [eax+12],0
mov edx,[hInstance]
mov [eax+16],edx
push 32512 'IDI_APPLICATION
push 0
call LoadIcon
mov edx,eax
mov eax,[p]
mov [eax+20],edx
'
push 32512 'IDC_ARROW
push 0
call LoadCursor
mov edx,eax
mov eax,[p]
mov [eax+24],edx
'
push 0 'WHITE_BRUSH
call GetStockObject
mov edx,eax
mov eax,[p]
mov [eax+28],edx
'
mov dword ptr [eax+32],0
mov edx,[r]
mov [eax+36],edx
'
push eax
call RegisterClass
cmp eax,0
jnz Reg_ok
push 16 ' MB_ICONERROR
push [t] ' heading
push [ s ] ' message
push 0 ' hWnd or null
call MessageBox
xor eax,eax
jmp xitwm
Reg_ok:
push 0
push [hInstance]
push 0
push 0
push 480 'CW_USEDEFAULT
push 640 'CW_USEDEFAULT
push 0x80000000 'CW_USEDEFAULT
push 0x80000000 'CW_USEDEFAULT
push 0x00cf0000 'WS_OVERLAPPEDWINDOW
push [ u ]
push [r]
push 0
call CreateWindowEx
mov [hWnd],eax
push [iCmdShow]
push [hWnd]
call ShowWindow
push [hWnd]
call UpdateWindow
MessageLoop:
push 0
push 0
push 0
push [v]
call GetMessage
cmp eax,0
jz xit
push [v]
call TranslateMessage
push [v]
call DisPatchMessage
jmp MessageLoop
xit:
mov eax,[v]
mov eax,[eax+8] ' wMsg.wParam
mov [function],eax
xitwm:
end asm
end function
end WinMain( GetModuleHandle( null ), null, Command( ), SW_NORMAL )
function WndProc ( byval hWnd as HWND, _
byval wMsg as UINT, _
byval wParam as WPARAM, _
byval lParam as LPARAM ) as LRESULT
dim rct as RECT
dim pnt as PAINTSTRUCT
dim hDC as HDC
dim as any ptr p,r,s
p=@pnt
r=@rct
s=@"Hello, World!"
asm
mov eax,[wMsg]
cmp eax,WM_CREATE
jnz nx1
xor eax,eax
jmp xitwp
nx1:
cmp eax,WM_PAINT
jnz nx2
push [p]
push [hWnd]
call BeginPaint
mov [hDC],eax
push [r]
push [hWnd]
call GetClientRect
mov eax,DT_SINGLELINE ' 0x20
or eax,DT_VCENTER ' 04
or eax,DT_CENTER ' 01
push 37 'push eax
push [r]
push -1
push [ s ]
push [hDC]
call DrawText
push [p]
push [hWnd]
call EndPaint
xor eax,eax
jmp xitwp
nx2:
cmp eax,WM_KEYDOWN
jnz nx3
mov eax,[wParam]
cmp al,27
jnz cntnu
push 0
push 0
push WM_CLOSE
push [hWnd]
call PostMessage
cntnu:
xor eax,eax
jmp xitwp
nx3:
'case WM_DESTROY
cmp eax,WM_DESTROY
jnz nx4
push 0
call PostQuitMessage
xor eax,eax
jmp xitwp
nx4:
push [lparam]
push [wparam]
push [wMsg]
push [hWnd]
call DefWindowProc
'jmp xitwp
xitwp:
mov [function],eax
end asm
end function
The Original FreeBasic "Hello"
#include once "windows.bi"
declare function WinMain ( byval hInstance as HINSTANCE, _
byval hPrevInstance as HINSTANCE, _
byval szCmdLine as string, _
byval iCmdShow as integer ) as integer
end WinMain( GetModuleHandle( null ), null, Command( ), SW_NORMAL )
'':::::
function WndProc ( byval hWnd as HWND, _
byval wMsg as UINT, _
byval wParam as WPARAM, _
byval lParam as LPARAM ) as LRESULT
function = 0
select case( wMsg )
case WM_CREATE
exit function
case WM_PAINT
dim rct as RECT
dim pnt as PAINTSTRUCT
dim hDC as HDC
hDC = BeginPaint( hWnd, @pnt )
GetClientRect( hWnd, @rct )
DrawText( hDC, _
"Hello, World!", _
-1, _
@rct, _
DT_SINGLELINE or DT_CENTER or DT_VCENTER )
EndPaint( hWnd, @pnt )
exit function
case WM_KEYDOWN
if( lobyte( wParam ) = 27 ) then
PostMessage( hWnd, WM_CLOSE, 0, 0 )
end if
case WM_DESTROY
PostQuitMessage( 0 )
exit function
end select
function = DefWindowProc( hWnd, wMsg, wParam, lParam )
end function
'':::::
function WinMain ( byval hInstance as HINSTANCE, _
byval hPrevInstance as HINSTANCE, _
byval szCmdLine as string, _
byval iCmdShow as integer ) as integer
dim wMsg as MSG
dim wcls as WNDCLASS
dim hWnd as HWND
function = 0
with wcls
.style = CS_HREDRAW or CS_VREDRAW
.lpfnWndProc = @WndProc
.cbClsExtra = 0
.cbWndExtra = 0
.hInstance = hInstance
.hIcon = LoadIcon( NULL, IDI_APPLICATION )
.hCursor = LoadCursor( NULL, IDC_ARROW )
.hbrBackground = GetStockObject( WHITE_BRUSH )
.lpszMenuName = NULL
.lpszClassName = @"HelloWin"
end with
if( RegisterClass( @wcls ) = FALSE ) then
MessageBox( null, "Failed to register wcls", "Error", MB_ICONERROR )
exit function
end if
hWnd = CreateWindowEx( 0, _
@"HelloWin", _
"The Hello Program", _
WS_OVERLAPPEDWINDOW, _
CW_USEDEFAULT, _
CW_USEDEFAULT, _
CW_USEDEFAULT, _
CW_USEDEFAULT, _
NULL, _
NULL, _
hInstance, _
NULL )
ShowWindow( hWnd, iCmdShow )
UpdateWindow( hWnd )
while( GetMessage( @wMsg, NULL, 0, 0 ) <> FALSE )
TranslateMessage( @wMsg )
DispatchMessage( @wMsg )
wend
function = wMsg.wParam
end function
The Hello Progtam rewritten in standalone Gas:
You have to allocate space on the stack and manage the data structures manually. But local labels 0: 1: 2: etc work properly, making block structured programming easy.
# Hello World
# MSWindows SDK using Gas Assembler
# 05:18 15/02/2008
# Charles E V Pegge
.intel_syntax noprefix
.section .data
#========================================================#
Caption: .asciz "Test Info"
Message: .asciz "Hello World!"
Title: .asciz "The Hello Program"
ClassName: .asciz "HelloWin"
Error: .asciz "Error"
RegFail: .asciz "Unable to register"
CreateFail: .asciz "Unable to Create Window"
Buf: .fill 40,0
#========================================================#
.balign 16,0
#TYPE WNDCLASS 40 bytes
# STYLE AS DWORD
# lpfnwndproc AS DWORD
# cbClsextra AS LONG
# cbWndExtra AS LONG
# hInstance AS DWORD
# hIcon AS DWORD
# hCursor AS DWORD
# hbrBackground AS DWORD
# lpszMenuName AS ASCIIZ PTR
# lpszClassName AS ASCIIZ PTR
#END TYPE
#TYPE WNDCLASSEX
# cbSize AS DWORD
# STYLE AS DWORD
# lpfnWndProc AS LONG
# cbClsExtra AS LONG
# cbWndExtra AS LONG
# hInstance AS DWORD
# hIcon AS DWORD
# hCursor AS DWORD
# hbrBackground AS DWORD
# lpszMenuName AS ASCIIZ PTR
# lpszClassName AS ASCIIZ PTR
# hIconSm AS DWORD
#END TYPE
#typedef struct { 28 bytes
# HWND hwnd;
# UINT message;
# WPARAM wParam;
# LPARAM lParam;
# DWORD time;
# POINT pt;
#} MSG, *PMSG;
#===========================#
.section .text #
#===========================#
#===========================#
#
.globl _WinMainCRTStartup #
_WinMainCRTStartup: #
.set SW_NORMAL,1
.set SW_SHOWDEFAULT, 10
#===========================#
push SW_NORMAL # iCmdShow as integer
#---------------------------#
call _GetCommandLineA@0 #
push eax # szCmdLine as string
#---------------------------#
push 0 # hPrevInstance as HINSTANCE
#---------------------------#
push 0 #
call _GetModuleHandleA@4 #
push eax # hInstance as HINSTANCE
#---------------------------#
call WinMain #
#===========================#
push eax #
call _ExitProcess@4 #
#===========================#
.balign 16,0
#===========================#
WinMain: #
#===========================#
#
#
.set CS_VREDRAW, 1
.set CS_HREDRAW, 2
.set IDI_APPLICATION,32512
.set IDC_ARROW, 32512
.set WHITE_BRUSH, 0
.set MB_ICONERROR, 16
.set SW_NORMAL, 2
.set CW_USEDEFAULT, 0x80000000
.set WS_OVERLAPPEDWINDOW,0x00cf0000
#---------------------------#
sub esp,100 #
mov ebp,esp #
#===========================#
mov eax,CS_HREDRAW #
or eax,CS_VREDRAW #
mov [ebp],eax # 01 Style
mov dword ptr [ebp+4], offset WndProc # 02
mov dword ptr [ebp+8],0 # 03 cbClsExtra
mov dword ptr [ebp+12],0 # 04 cbWndExtra
mov eax,[ebp+104] #
mov [ebp+16],eax # 05 hInstance
#---------------------------#
push IDI_APPLICATION #
push 0 #
call _LoadIconA@8 #
mov [ebp+20],eax # 06 icon
#---------------------------#
push IDC_ARROW #
push 0 #
call _LoadCursorA@8 #
mov [ebp+24],eax # 07 cursor
#---------------------------#
push WHITE_BRUSH #
call _GetStockObject@4 #
mov [ebp+28],eax # 08 brush
#---------------------------#
mov dword ptr [ebp+32],0 # 09 lpMenuName
mov dword ptr [ebp+36],offset ClassName # 10
#===========================#
push ebp # pClass
call _RegisterClassA@4 #
#---------------------------#
cmp eax,0 #
jnz 0f #
push MB_ICONERROR #
push offset Error # heading
push offset RegFail # message
push 0 # hWnd or null
call _MessageBoxA@16 #
xor eax,eax # zero eax
jmp end_app # jmp xitwm
0: #
#===========================#
push 0 # 12 lpParam
push [ebp+104] # 11 hInstance
push 0 # 10 hMenu
push 0 # 09 hWndParent
push 480 # 08 bottom
push 640 # 07 right
push CW_USEDEFAULT # 06 top
push CW_USEDEFAULT # 05 left
push WS_OVERLAPPEDWINDOW # 04 dwStyle
push offset Title # 03 lpWindowName Title
push offset ClassName # 02 lpClassName
push 0 # 01 dwExStyle
call _CreateWindowExA@48 # 00 Create Window
mov [ebp+40],eax # window handle hWnd
#---------------------------#
cmp eax,0 #
jnz 0f #
push MB_ICONERROR # 16
push offset Error # heading
push offset CreateFail # message
push 0 # hWnd or null
call _MessageBoxA@16 #
xor eax,eax # zero eax
jmp end_app # jmp xitwm
0: #
#---------------------------#
push [ebp+116] # iCmdShow
push [ebp+40] # hWnd
call _ShowWindow@8 # show
#---------------------------#
push [ebp+40] # hWnd
call _UpdateWindow@4 # update
#===========================#
# #
# MESSAGE LOOP #
# #
#===========================#
add ebp,44 # Msg offset
0: #
#---------------------------#
push 0 #
push 0 #
push 0 #
push ebp #
call _GetMessageA@16 #
#---------------------------#
cmp eax,0 #
jz 1f #
#---------------------------#
push ebp #
call _TranslateMessage@4 #
#---------------------------#
push ebp #
call _DispatchMessageA@4 #
#---------------------------#
jmp 0b #
1: #
#===========================#
mov eax,[ebp+8] # Msg.wParam 3rd param
sub ebp,44 # original base
jmp end_app #
#===========================#
# #
# TERMINATE APP #
# #
#===========================#
end_app:
#===========================#
add ebp,100 #
add esp,100 #
ret 16 #
#===========================#
.balign 16
#'type PAINTSTRUCT 0 64
#00' hDC AS DWORD
#04' fErase AS LONG
#08' rcPaint AS RECT
#24' fRestore AS LONG
#28' fIncUpdate AS LONG
#32' rgbReserved(0 TO 31) AS BYTE
#64'end type
#'type RECT 64 16
#00' left as LONG
#04' top as LONG
#08' right as LONG
#0C' bottom as LONG
#10'end type
#' hDC 80 4
#===========================#
WndProc: #
#===========================#
push ebp #
mov ebp,esp #
mov eax,[ebp+12] #
#===========================#
cmp eax, 1 #WM_CREATE #
jnz 0f #
xor eax,eax # zero eax
jmp 9f #
#===========================#
0: #
cmp eax,2 #WM_DESTROY #
jnz 0f #
push 0 #
call _PostQuitMessage@4 #
xor eax,eax # zero
jmp 9f #
#===========================#
0: #
cmp eax,0xf #WM_PAINT #
jnz 0f #
#---------------------------#
sub esp,100
mov ebp,esp
#---------------------------#
push ebp # 04 paint struc
push [ebp+108] # 00 hWnd
call _BeginPaint@8 #
mov [ebp+80],eax # hDC
#---------------------------#
lea eax,[ebp+64] #
push eax # 04 rect pointer
#---------------------------#
push [ebp+108] # 00 hWnd
call _GetClientRect@8 #
#---------------------------#
mov eax,0x20 #DT_SINGLELINE
or eax,4 #DT_VCENTER #
or eax,1 #DT_CENTER #
push eax # 10 37
#---------------------------#
lea eax,[ebp+64] # 0C rect pointer
push eax #
#---------------------------#
push -1 # 08
#---------------------------#
push offset Message # 04 Hello World
#---------------------------#
push [ebp+80] # 00 hDC
#---------------------------#
call _DrawTextA@20 #
#---------------------------#
push ebp # 04 paint struc pointer
push [ebp+108] # 00 hWnd
call _EndPaint@8 #
#---------------------------#
add esp,100 #
mov ebp,esp #
xor eax,eax #
jmp 9f #
#===========================#
0: #
cmp eax,0x100 #WM_KEYDOWN
jnz 0f #
mov eax,[ebp+16] # wParam
cmp al,27 # esc key
jnz 1f #
push 0 #
push 0 #
push 0x10 #WM_CLOSE #
push [ebp+8] # hWnd
call _PostMessageA@16
1: #
xor eax,eax # zero
jmp 9f #
#===========================#
0:
push [ebp+20] # 0C lParam
push [ebp+16] # 08 wParam
push [ebp+12] # 04 wMsg
push [ebp+8] # 00 hWnd
call _DefWindowProcA@16 #
#===========================#
9: #
pop ebp #
ret 16 #
#===========================#
.balign 16
# .include "diagnostics.asm"
Code for error checking:
Using Message boxes to display registers, stack contents, data structures
Diagnostics.asm
#...........................#
call tester #
#...........................#
#===========================#
tester1: # dword on stack
#===========================#
push eax #
mov eax,[esp+8] #
call tester #
pop eax #
ret
#===========================#
tester2: # Error Code in eax
#===========================#
pusha #
call _GetLastError@0 #
call tester #
popa #
ret
#===========================#
tester3: # display stack (descending order)
#===========================#
push ebp #
sub esp,292 # +300 eq stack
mov ebp,esp #
pusha #
mov esi,ebp #
mov edi,ebp #
add edi,344 # 11..0 # point to stack data top
mov ebx,ebp #
add ebx,300 # boundary # point to stack data lower limit
0: # loop start
mov eax,[edi] # get data
call TextHexLong # create Hexadecimal text
mov dword ptr [esi+9],0x0a0d # crlf
add esi,10 # ready for next line
sub edi,4 # next data
cmp edi,ebx # boundary check
jge 0b # repeat for next data
#---------------------------#
call Display #
#---------------------------#
popa #
add esp,292 #
pop ebp #
ret #
#===========================#
#===========================#
tester4: # data structures (with pointer in eax)
#===========================#
push ebp #
sub esp,300 # +300 eq stack
mov ebp,esp #
pusha #
mov esi,ebp #
mov edi,eax #
mov ebx,eax #
add ebx,40 # boundary #
0: # loop start
mov eax,[edi] # get data
call TextHexLong # create Hexadecimal text
mov dword ptr [esi+9],0x0a0d # crlf
add esi,10 # ready for next line
add edi,4 # next data
cmp edi,ebx # boundary check
jl 0b # repeat for next data
#---------------------------#
call Display #
#---------------------------#
popa #
add esp,300 #
pop ebp #
ret #
#===========================#
#===========================#
Display: #
#===========================#
push 1 #
push offset Caption #
push ebp #
push 0 #
call _MessageBoxA@16 # MessageBox
ret
#===========================#
#===========================#
tester: # dword in eax
#===========================#
pusha #
push 1 #
push offset Caption # caption
call text_eax #
push offset Buf # message
push 0 # window handle
call _MessageBoxA@16 # MessageBox
popa #
ret #
#===========================#
#===========================#
text_eax: #
#===========================#
# Hexadecimal #
#---------------------------#
push esi #
mov esi,offset Buf #
call TextHexLong #
pop esi #
ret #
#===========================#
#===========================#
TextHexLong:
#===========================#
add esi,7 #
mov edx,eax #
mov ecx, 8 #
1: #
and al,0xf #
add al,0x30 #
cmp al,0x39 #
jle 0f #
add al,7 #
0: #
mov [esi],al #
dec esi #
shr edx,4 #
mov al,dl #
dec ecx #
jg 1b #
mov byte ptr [esi+9],0x20 #
ret #
#===========================#
Batch File to Assemble and Link:
..\bin\win32\as hw.asm
..\bin\win32\ld --subsystem windows -entry=_WinMainCRTStartup a.out ..\lib\win32\libkernel32.dll.a ..\lib\win32\libuser32.dll.a ..\lib\win32\libgdi32.dll.a ..\lib\win32\libshell32.dll.a
pause