Copy Link
Add to Bookmark
Report

Assembly Programming Journal Issue 05

  

::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::. July-Sep 99
:::\_____\::::::::::. Issue 5
::::::::::::::::::::::.........................................................

A S S E M B L Y P R O G R A M M I N G J O U R N A L
http://asmjournal.freeservers.com
asmjournal@mailcity.com




T A B L E O F C O N T E N T S
----------------------------------------------------------------------
Introduction...................................................mammon_

"COM in Assembly Part II"...................................Bill.Tyler

"How to use DirectDraw in ASM"...............................X-Calibre

"Writing Boot Sectors To Disk"...........................Jan.Verhoeven

"Dumping Memory to Disk".................................Jan.Verhoeven

"Formatted Numeric Output"..............................Laura.Fairhead

"Linked Lists in ASM"..........................................mammon_

Column: Win32 Assembly Programming
"Structured Exception Handling under Win32"...........Chris.Dragan
"Child Window Controls"...................................Iczelion
"Dialog Box as Main Window"...............................Iczelion
"Standardizing Win32 Callback Procedures"............Jeremy.Gordon

Column: The Unix World
"Fire Demo ported to Linux SVGAlib".................Jan.Wagemakers

Column: Assembly Language Snippets
"Abs".................................................Chris.Dragan
"Min".................................................Chris.Dragan
"Max".................................................Chris.Dragan
"OBJECT"...................................................mammon_

Column: Issue Solution
"Binary to ASCII"....................................Jan.Verhoeven

----------------------------------------------------------------------
++++++++++++++++++Issue Challenge+++++++++++++++++
Convert a bit value to ACIII less than 10 bytes
----------------------------------------------------------------------



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::..............................................INTRODUCTION
by mammon_


I suppose I should start with the good news. A week or so ago Hiroshimator
emailed me for the nth time asking if I needed help with the journal as I have
yet to get one out on time. I relented and asked if he knew any listservers;
one hour later he had an account for APJ set up at e-groups, specifically:
http://www.egroups.com/group/apj-announce
One of the greatest obstacles to putting out these issues -- processing the
300 or so subscription requests that rack up between issues -- is now out of
the way for good.

The articles this month have somewhat of a high-level focus; with the COM and
Direct Draw by Bill Tyler and X-Caliber, respectively, as well as Chris
Dragan's classic work on exception handling and Jeremy Gordon's treatment of
windows callbacks, this issue is heavily weighed towards high-level win32
coding. Add to this Iczelion's two tutorials and my own win32-biased
linked list example, and it appears the DOS/Unix camp is losing ground.

To shore up the Unix front line, Jan Wagemakers has provided a port of last
month's fire demo to linux [GAS]. In addition, there are A86 articles by Jan
Verhoeven and a general assembly routine by Laura Fairhead to prove that not
all assembly has to be 32-bit.

And, finally, I am looking for a good 'challenge' columnist: someone to write
the monthly APJ challenges [and their solutions] so that I can start
announcing next month's challenge sooner than next month...

Now at last I can sleep ;)

_m


::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
COM in Assembly Part II
by Bill Tyler


My previous atricle described how to use COM objects in your assembly
language programs. It described only how to call COM methods, but not how to
create your own COM objects. This article will describe how to do that.

This article will describe implementing COM Objects, using MASM syntax. TASM
or NASM assemblers will not be considered, however the methods can be easily
applied to any assembler.

This article will also not describe some of the more advanced features of COM
such as reuse, threading, servers/clients, and so on. These will presented
in future articles.


COM Interfaces Review
------------------------------------------------------------------------------
An interface definition specifies the interface's methods, their return types,
the number and types of their parameters, and what the methods must do. Here
is a sample interface definition:

IInterface struct
lpVtbl dd ?
IInterface ends

IInterfaceVtbl struct
; IUnknown methods
STDMETHOD QueryInterface, :DWORD, :DWORD, :DWORD
STDMETHOD AddRef, :DWORD
STDMETHOD Release, :DWORD
; IInterface methods
STDMETHOD Method1, :DWORD
STDMETHOD Method2, :DWORD
IInterfaceVtbl ends

STDMETHOD is used to simplify the interface declaration, and is defined as:

STDMETHOD MACRO name, argl :VARARG
LOCAL @tmp_a
LOCAL @tmp_b
@tmp_a TYPEDEF PROTO argl
@tmp_b TYPEDEF PTR @tmp_a
name @tmp_b ?
ENDM

This macro is used to greatly simplify interface declarations, and so that the
MASM invoke syntax can be used. (Macro originally by Ewald :)

Access to the interface's methods occurs through a pointer. This pointer
points to a table of function pointers, called a vtable. Here is a sample
method call:

mov eax, [lpif] ; lpif is the interface pointer
mov eax, [eax] ; get the address of the vtable
invoke (IInterfaceVtbl [eax]).Method1, [lpif] ; indirect call to the function
- or -
invoke [eax][IInterfaceVtbl.Method2], [lpif] ; alternate notation

Two different styles of addressing the members are shown. Both notations
produce equivalent code, so the method used is a matter of personal
preference.

All interfaces must inherit from the IUnknown interface. This means that the
first 3 methods of the vtable must be QueryInterface, AddRef, and Release.
The purpose and implementation of these methods will be discussed later.


GUIDS
------------------------------------------------------------------------------
A GUID is a Globally Unique ID. A GUID is a 16-byte number, that is unique
to an interface. COM uses GUID's to identify different interfaces from one
another. Using this method prevents name clashing as well as version
clashing. To get a GUID, you use a generator utility that is included with
most win32 development packages.

A GUID is represented by the following structure:

GUID STRUCT
Data1 dd ?
Data2 dw ?
Data3 dw ?
Data4 db 8 dup(?)
GUID ENDS

A GUID is then defined in the data section:
MyGUID GUID <3F2504E0h, 4f89h, 11D3h, <9Ah, 0C3h, 0h, 0h, 0E8h, 2Ch, 3h, 1h>>

Once a GUID is assigned to an interface and published, no furthur changes to
the interface definition are allowed. Note, that this does mean that the
interface implementation may not change, only the definition. For changes
to the interface definition, a new GUID must be assigned.


COM Objects
------------------------------------------------------------------------------
A COM object is simply an implementation of an interface. Implementation
details are not covered by the COM standard, so we are free to implement our
objects as we choose, so long as they satisfy all the requirements of the
interface definition.

A typical object will contain pointers to the various interfaces it supports,
a reference count, and any other data that the object needs. Here is a sample
object definition, implemented as a structure:

Object struct
interface IInterface <?> ; pointer to an IInterface
nRefCount dd ? ; reference count
nValue dd ? ; private object data
Object ends

We also have to define the vtable's we are going to be using. These tables
must be static, and cannot change during run-time. Each member of the vtable
is a pointer to a method. Following is a method for defining the vtable.

@@IInterface segment dword
vtblIInterface:
dd offset IInterface@QueryInterface
dd offset IInterface@AddRef
dd offset IInterface@Release
dd offset IInterface@GetValue
dd offset IInterface@SetValue
@@IInterface ends


Reference Counting
------------------------------------------------------------------------------
COM object manage their lifetimes through reference counting. Each object
maintains a reference count that keeps track of how many instances of the
interface pointer have been created. The object is required to keep a
counter that supports 2^32 instances, meaning the reference count must be a
DWORD.

When the reference count drops to zero, the object is no longer in use, and
it destroys itself. The 2 IUnknown methods AddRef and Release handle the
reference counting for a COM object.


QueryInterface
------------------------------------------------------------------------------
The QueryInterface method is used by a COM object to determine if the object
supports a given interface, and then if supported, to get the interface
pointer. There are 3 rules to implementing the QueryInterface method:

1. Objects must have an identity - a call to QueryInterface must always
return the same pointer value.
2. The set of interfaces of an object must never change - for example, if
a call to QueryInterface with on IID succeeds once, it must succeed
always. Likewise, if it fails once, it must fail always.
3. It must be possible to successfully query an interface of an object
from any other interface.

QueryInterface returns a pointer to a specified interface on an object to
which a client currently holds an interface pointer. This function must call
the AddRef method on the pointer it returns.

Following are the QueryInterface parameters:
pif : [in] a pointer to the calling interface
riid : [in] pointer to the IID of the interface being queried
ppv : [out] pointer to the pointer of the interface that is to be set.
If the interface is not supported, the pointed to value is set to 0

QueryInterface returns the following:
S_OK if the interface is supported
E_NOINTERFACE if not supported

Here is a simple assembly implementation of QueryInterface:

IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD
; The following compares the requested IID with the available ones.
; In this case, because IInterface inherits from IUnknown, the IInterface
; interface is prefixed with the IUnknown methods, and these 2 interfaces
; share the same interface pointer.
invoke IsEqualGUID, [riid], addr IID_IInterface
or eax,eax
jnz @1
invoke IsEqualGUID, [riid], addr IID_IUnknown
or eax,eax
jnz @1
jmp @NoInterface

@1:
; GETOBJECTPOINTER is a macro that will put the object pointer into eax,
; when given the name of the object, the name of the interface, and the
; interface pointer.
GETOBJECTPOINTER Object, interface, pif

; now get the pointer to the requested interface
lea eax, (Object ptr [eax]).interface

; set *ppv with this interface pointer
mov ebx, [ppv]
mov dword ptr [ebx], eax

; increment the reference count by calling AddRef
GETOBJECTPOINTER Object, interface, pif
mov eax, (Object ptr [eax]).interface
invoke (IInterfaceVtbl ptr [eax]).AddRef, pif

; return S_OK
mov eax, S_OK
jmp return

@NoInterface:
; interface not supported, so set *ppv to zero
mov eax, [ppv]
mov dword ptr [eax], 0

; return E_NOINTERFACE
mov eax, E_NOINTERFACE

return:
ret
IInterface@QueryInterface endp


AddRef
------------------------------------------------------------------------------
The AddRef method is used to increment the reference count for an interface
of an object. It should be called for every new copy of an interface pointer
to an object.

AddRef takes no parameters, other than the interface pointer required for all
methods. AddRef should return the new reference count. However, this value
is to be used by callers only for testing purposes, as it may be unstable in
certain situations.

Following is a simple implementation of the AddRef method:

IInterface@AddRef proc pif:DWORD
GETOBJECTPOINTER Object, interface, pif
; increment the reference count
inc [(Object ptr [eax]).nRefCount]
; now return the count
mov eax, [(Object ptr [eax]).nRefCount]
ret
IInterface@AddRef endp


Release
------------------------------------------------------------------------------
Release decrements the reference count for the calling interface on a object.
If the reference count on the object is decrememnted to 0, then the object is
freed from memory. This function should be called when you no longer need to
use an interface pointer

Like AddRef, Release takes only one parameter - the interface pointer. It
also returns the current value of the reference count, which, similarly, is to
be used for testing purposess only

Here is a simple implementation of Release:

IInterface@Release proc pif:DWORD
GETOBJECTPOINTER Object, interface, pif

; decrement the reference count
dec [(Object ptr [eax]).nRefCount]

; check to see if the reference count is zero. If it is, then destroy
; the object.
mov eax, [(Object ptr [eax]).nRefCount]
or eax, eax
jnz @1

; free the object: here we have assumed the object was allocated with
; LocalAlloc and with LMEM_FIXED option
GETOBJECTPOINTER Object, interface, pif
invoke LocalFree, eax
@1:
ret
IInterface@Release endp


Creating a COM object
------------------------------------------------------------------------------
Creating an object consists basically of allocating the memory for the
object, and then initializing its data members. Typically, the vtable
pointer is initialized and the reference count is zeroed. QueryInterface
could then be called to get the interface pointer.

Other methods exist for creating objects, such as using CoCreateInstance, and
using class factories. These methods will not be discussed, and may be a
topic for a future article.


COM implementatiion sample application
------------------------------------------------------------------------------
Here follows a sample implementation and usage of a COM object. It shows how
to create the object, call its methods, then free it. It would probably be
very educational to assemble this and run it through a debugger. This and
other examples can be found at http://asm.tsx.org.


.386
.model flat,stdcall

include windows.inc
include kernel32.inc
include user32.inc

includelib kernel32.lib
includelib user32.lib
includelib uuid.lib

;-----------------------------------------------------------------------------
; Macro to simply interface declarations
; Borrowed from Ewald, http://here.is/diamond/
STDMETHOD MACRO name, argl :VARARG
LOCAL @tmp_a
LOCAL @tmp_b
@tmp_a TYPEDEF PROTO argl
@tmp_b TYPEDEF PTR @tmp_a
name @tmp_b ?
ENDM

; Macro that takes an interface pointer and returns the implementation
; pointer in eax
GETOBJECTPOINTER MACRO Object, Interface, pif
mov eax, pif
IF (Object.Interface)
sub eax, Object.Interface
ENDIF
ENDM

;-----------------------------------------------------------------------------
IInterface@QueryInterface proto :DWORD, :DWORD, :DWORD
IInterface@AddRef proto :DWORD
IInterface@Release proto :DWORD
IInterface@Get proto :DWORD
IInterface@Set proto :DWORD, :DWORD

CreateObject proto :DWORD
IsEqualGUID proto :DWORD, :DWORD

externdef IID_IUnknown:GUID

;-----------------------------------------------------------------------------
; declare the interface prototype
IInterface struct
lpVtbl dd ?
IInterface ends

IInterfaceVtbl struct
; IUnknown methods
STDMETHOD QueryInterface, pif:DWORD, riid:DWORD, ppv:DWORD
STDMETHOD AddRef, pif:DWORD
STDMETHOD Release, pif:DWORD
; IInterface methods
STDMETHOD GetValue, pif:DWORD
STDMETHOD SetValue, pif:DWORD, val:DWORD
IInterfaceVtbl ends


; declare the object structure
Object struct
; interface object
interface IInterface <?>

; object data
nRefCount dd ?
nValue dd ?
Object ends

;-----------------------------------------------------------------------------
.data
; define the vtable
@@IInterface segment dword
vtblIInterface:
dd offset IInterface@QueryInterface
dd offset IInterface@AddRef
dd offset IInterface@Release
dd offset IInterface@GetValue
dd offset IInterface@SetValue
@@IInterface ends

; define the interface's IID
; {CF2504E0-4F89-11d3-9AC3-0000E82C0301}
IID_IInterface GUID <0cf2504e0h, 04f89h, 011d3h, <09ah, 0c3h, 00h, 00h,
0e8h, 02ch, 03h, 01h>>

;-----------------------------------------------------------------------------
.code
start:
StartProc proc
LOCAL pif:DWORD ; interface pointer

; create the object
invoke CreateObject, addr [pif]
or eax,eax
js exit

; call the SetValue method
mov eax, [pif]
mov eax, [eax]
invoke (IInterfaceVtbl ptr [eax]).SetValue, [pif], 12345h

; call the GetValue method
mov eax, [pif]
mov eax, [eax]
invoke (IInterfaceVtbl ptr [eax]).GetValue, [pif]

; release the object
mov eax, [pif]
mov eax, [eax]
invoke (IInterfaceVtbl ptr [eax]).Release, [pif]

exit:
ret
StartProc endp

;-----------------------------------------------------------------------------
IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD
invoke IsEqualGUID, [riid], addr IID_IInterface
test eax,eax
jnz @F
invoke IsEqualGUID, [riid], addr IID_IUnknown
test eax,eax
jnz @F
jmp @Error

@@:
GETOBJECTPOINTER Object, interface, pif
lea eax, (Object ptr [eax]).interface

; set *ppv
mov ebx, [ppv]
mov dword ptr [ebx], eax

; increment the reference count
GETOBJECTPOINTER Object, interface, pif
mov eax, (Object ptr [eax]).interface
invoke (IInterfaceVtbl ptr [eax]).AddRef, [pif]

; return S_OK
mov eax, S_OK
jmp return

@Error:
; error, interface not supported
mov eax, [ppv]
mov dword ptr [eax], 0
mov eax, E_NOINTERFACE

return:
ret
IInterface@QueryInterface endp


IInterface@AddRef proc pif:DWORD
GETOBJECTPOINTER Object, interface, pif
inc [(Object ptr [eax]).nRefCount]
mov eax, [(Object ptr [eax]).nRefCount]
ret
IInterface@AddRef endp


IInterface@Release proc pif:DWORD
GETOBJECTPOINTER Object, interface, pif
dec [(Object ptr [eax]).nRefCount]
mov eax, [(Object ptr [eax]).nRefCount]
or eax, eax
jnz @1
; free object
mov eax, [pif]
mov eax, [eax]
invoke LocalFree, eax
@1:
ret
IInterface@Release endp


IInterface@GetValue proc pif:DWORD
GETOBJECTPOINTER Object, interface, pif
mov eax, (Object ptr [eax]).nValue
ret
IInterface@GetValue endp


IInterface@SetValue proc uses ebx pif:DWORD, val:DWORD
GETOBJECTPOINTER Object, interface, pif
mov ebx, eax
mov eax, [val]
mov (Object ptr [ebx]).nValue, eax
ret
IInterface@SetValue endp

;-----------------------------------------------------------------------------
CreateObject proc uses ebx ecx pobj:DWORD
; set *ppv to 0
mov eax, pobj
mov dword ptr [eax], 0

; allocate object
invoke LocalAlloc, LMEM_FIXED, sizeof Object
or eax, eax
jnz @1
; alloc failed, so return
mov eax, E_OUTOFMEMORY
jmp return
@1:

mov ebx, eax
mov (Object ptr [ebx]).interface.lpVtbl, offset vtblIInterface
mov (Object ptr [ebx]).nRefCount, 0
mov (Object ptr [ebx]).nValue, 0

; Query the interface
lea ecx, (Object ptr [ebx]).interface
mov eax, (Object ptr [ebx]).interface.lpVtbl
invoke (IInterfaceVtbl ptr [eax]).QueryInterface,
ecx,
addr IID_IInterface,
[pobj]
cmp eax, S_OK
je return

; error in QueryInterface, so free memory
push eax
invoke LocalFree, ebx
pop eax

return:
ret
CreateObject endp

;-----------------------------------------------------------------------------
IsEqualGUID proc rguid1:DWORD, rguid2:DWORD
cld
mov esi, [rguid1]
mov edi, [rguid2]
mov ecx, sizeof GUID / 4
repe cmpsd
xor eax, eax
or ecx, ecx
setz al
ret
IsEqualGUID endp

end start


Conclusion
------------------------------------------------------------------------------
We have (hopefully) seen how to implement a COM object. We can see that it
is a bit messy to do, and adds quite some overhead to our programs. However,
it can also add great flexibility and power to our programs.

Remember that COM defines only interfaces, and implementation is left to the
programmer. This article presents only one possible implementation. This is
not the only method, nor is it the best one. The reader should feel free to
experiment with other methods.

Copyright (C) 1999 Bill Tyler (billasm@usa.net)



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::..........................................FEATURE.ARTICLE
How to use DirectDraw in ASM
by X-Calibre [Diamond]


Well, there has been quite a large demand for this essay, so I finally started
writing it. This essay will show you how to use C++ objects and COM interface
in Win32ASM, using DirectDraw as an example.

Well, in this part of the Win32 API, you will soon find out how important it
is to know C and C++ when you want to use an API written in these languages.
Judging from the demand for this essay, I think it will be necessary to
explain a bit of how objects work in C++. I will not go too deep, but only
show the things you need to know in Win32ASM.

What are objects really?

Actually a structure is an object of which all fields are public. We will look
at it the other way around. So the public fields in an object make up a
structure. The other fields in an object are private and are not reachable
from the outside. So they are not interesting to us.

A special thing about objects is that they can contain pointers to functions.
Normally, when using C or ASM, this would be possible, but a bit error-prone.
It can be seen as 'dirty' programming. That's why you probably haven't seen it
before.

When using C++ with a compiler, there will be no errors, as long as the
compiler does its job. So here you can use this technique with no chance of
errors, and it gives you some nice new programming options.

C++ goes even further with this 'structure of functions' idea. With
inheritance, you can also overwrite functions of the base class in the
inherited class. You can also create 'virtual' functions, which are defined in
the base class, but the actual code is only in inherited classes.

This is of course interesting for DirectX, where you want to have standard
functions, but with different code, depending on the hardware on which it is
running. So in DirectX, all functions are defined as virtual, and the base
class is inherited by hardware-specific drivers which supply hardware-specific
code. And the beauty of this is, that it's all transparent to the programmer.
The function pointers can change at runtime because of this system, so the C++
designers had to think of a way to keep the pointers to the functions
available to the program at all time.

What this all boils down to is that there is a table with pointers to the
functions. It's called the Virtual Function Table. I will call this the
vtable from now on.

So we need to get this table, in order to call functions from our object.
Lucky for you, Z-Nith has already made a C program to 'capture' the table,
and converted the resulting header file to an include file for use with MASM.
So I'll just explain how you should use this table, and you can get going
soon.

Well, actually it's quite simple. The DirectX objects are defined like this:

IDirectDraw STRUC
lpVtbl DWORD ?
IDirectDraw ENDS

IDirectDrawPalette STRUC
lpVtbl DWORD ?
IDirectDrawPalette ENDS

IDirectDrawClipper STRUC
lpVtbl DWORD ?
IDirectDrawClipper ENDS

IDirectDrawSurface STRUC
lpVtbl DWORD ?
IDirectDrawSurface ENDS

So these structs are actually just a pointer to the vtables, and don't contain
any other values. Well, this makes it all very easy for us then.
I'll give you a small example:

Say we have an IDirectDraw object called lpDD. And we want to call the
RestoreDisplayMode function.
Then we need to do 2 things:

1. Get the vtable.
2. Get the address of the function, using the vtable.

The first part is simple. All the struct contains, is the pointer to the
vtable. So we can just do this:

mov eax, [lpDD]
mov eax, [eax]

Simple, isn't it? And the next part isn't really much harder. The vtable is
put into a structure called IDirectDrawVtbl in DDRAW.INC. We now have the
address of the structure in eax. All we have to do now, is get the correct
member of that structure, to get the address of the function we want to call.
You would have guessed by now, that this will do the trick:

call [IDirectDrawVtbl.RestoreDisplayMode][eax]

That is not a bad guess...
But there's one more thing, which is very important: this function needs to be
invoked on the IDirectDraw object. We may only see the vtable in the structure,
but there are also private members inside the object. So there's more than
meets the eye here. What it comes down to is that the call needs the object
as an argument. And this will be done by stack as always. So we just need to
push lpDD before we call. The complete call will look like this:

push [lpDD]
call [IDirectDrawVtbl.RestoreDisplayMode][eax]

Simple, was it not? And calls with arguments are not much harder.
Let's set the displaymode to 320x200 in 32 bits next.
This call requires 3 arguments:

SetDisplayMode( width, height, bpp );

Well, the extra arguments work just like normal API calls: just push them onto
the stack in backward order.
So it will look like this:

push 32
push 200
push 320
mov eax, [lpDD]
push eax
mov eax, [eax]
call [IDirectDrawVtbl.SetDisplayMode][eax]

And that's all there is to it.

To make life easier, we have included some MASM macros in DDRAW.INC, for use
with the IDirectDraw and IDirectDrawSurface objects:

DDINVOKE MACRO func, this, arglist :VARARG
mov eax, [this]
mov eax, [eax]

IFB <arglist>
INVOKE [IDirectDrawVtbl. func][eax], this
ELSE
INVOKE [IDirectDrawVtbl. func][eax], this, arglist
ENDIF
ENDM

DDSINVOKE MACRO func, this, arglist :VARARG
mov eax, [this]
mov eax, [eax]

IFB <arglist>
INVOKE [IDirectDrawSurfaceVtbl. func][eax], this
ELSE
INVOKE [IDirectDrawSurfaceVtbl. func][eax], this, arglist
ENDIF
ENDM

With these macros, our 2 example calls will look as simple as this:

DDINVOKE RestoreDisplayMode, lpDD

DDINVOKE SetDisplayMode, lpDD, 320, 200, 32


Well, that's basically all there is to know about using objects, COM and
DirectX in Win32ASM. Have fun with it!

And remember:

C and C++ knowledge is power!



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Writing Boot Sectors To Disk
by Jan Verhoeven


Introduction.
-------------
In my previous article I showed how to make a private non-bootable
bootsector for 1.44 Mb floppy disks. Unfortunately, there was no way yet to
write that non-bootsector to a floppy disk....

Enter this code. It is the accompanying bootsector writer for floppy disks.
It assumes that your A: drive is the 1.44 Mb floppy disk drive and I dare
say that this will be true in the majority of cases.


The assembler used
------------------
As usual, I have written this code in A86 format. Until now, not many
aspects of the A86 extensions have been used, but, believe me, in future
articles this will be done.

A86 is particularly useful for people that make syntax errors. It will
insert the errormessages into the sourcefile so that you can easily find
them back. In the next assembler run the error messages are removed again.

To fully use this aspect of A86 programming, I made a small batchfile that
will let me choose between several options while writing the code. Below
you can see the file. After an error, I choose to go back into the editor.
When there are no errors, I might decided to do a trial run. Or to quit to
DOS.

This is all done by means of the WACHT command which waits for a keypress.
It returns (in errorlevel) the indexed position in the command tail table
of th key which was pressed.


Rapid assembly prototyping.
---------------------------
For easy processing and running sourcefiles I use a small batchfile, which
looks like:

----------- Run.Bat --------------------------------------- Start ---------
@echo off
if "%1" == "" goto leave

:start
ed %1.a86
a86 %1.a86 %2 %3 %4 %5 %6

:menu
Echo *
Echo Options:
Echo *Escape = stop
Echo * L = LIST
echo * ;-() = back to the editor
echo * space = test-run of %1.com
echo *Period = debugger-run with %1.com/sym

wacht  .\=-[]';-()/":?><{}|+_LCE

if errorlevel 27 goto start
if errorlevel 26 goto screen
if errorlevel 25 goto list
if errorlevel 4 goto start
if errorlevel 3 goto debugger
if errorlevel 2 goto execute
if errorlevel 1 exit
goto menu

:execute
%1
if errorlevel 9 echo Errorlevel = 9+
if errorlevel 8 echo Errorlevel = 8
if errorlevel 7 echo Errorlevel = 7
if errorlevel 6 echo Errorlevel = 6
if errorlevel 5 echo Errorlevel = 5
if errorlevel 4 echo Errorlevel = 4
if errorlevel 3 echo Errorlevel = 3
if errorlevel 2 echo Errorlevel = 2
if errorlevel 1 echo Errorlevel = 1
goto menu

:debugger
vgamode 3
d86 %1
goto menu

:list
list
goto menu

:screen
vgamode 3
goto menu

:leave
echo No file specified
----------- Run.Bat ---------------------------------------- End ----------

This BAT file relies heavily on my computer system. For one, I use DR-DOS 6
which means that I can use the EXIT word to get out of a Batchfile.

Also, I switch videomodes back to Mode 3 with "
Vgamode 3" and you will have
to use another command for that, like "
Mode co80" or using the utillity
that came with your videocard.

The program "
List" is Vernon Buerg's file lister which I use to track down
errors in all kinds of files.


How to write a sector to disk.
------------------------------
Globally there are three methods. The first would be to program the floppy
disk controller, but that is just downright difficult. A second approach
would be to use INT 026, the way DOS does things.

I chose for the BIOS method. For non-partitioned diskstructures this is the
easiest way. Just select track, head and side and write data to the sectors
on that disk.

The bootsector is the very first sector on a disk. For a floppy disk this
boils down to track 0, head 0 and sector 1 (sectors are counted from 1, not
from 0!).

The code is very straightforward. What it does is:

- reset disk drive controller
- open the file to transfer to the bootsector
- read file into internal buffer
- close the file
- repeat 5 times:
- try to tranfer buffer to bootsector of drive A:
- shut down and return to DOS.
- if an error occurs, the user is informed about it.

That's all there's to it.


The Source.
-----------
Below is the sourcecode for this short utillity. I have commented just
about any line I thought fit for it.

----------- Wrs.A86 --------------------------------------- Start ---------
name wrs
title WRite Sector
page 80, 120

stdout = 1 ; the "
standard" equates
lf = 10
cr = 13

DATA segment ; define the volatile data area

buffer db 512 dup (?) ; this is enough for one sector

EVEN ; make sure WORD starts at an even address
Handle dw ? ; handle number of file to write
; ----------------------
CODE segment ; start of the actualk code
; no ORG, so we start at offset 0100
jmp main ; jump forward to entry point

db 'VeRsIoN=0.2', 0
db 'CoPyRiGhT=CopyLeft 1999, Jan Verhoeven, '
db 'jverhoeven@bigfoot.com', 0
; ----------------------
filename db 'BootLoad.bin', 0 ; name of file to send to disk

Mess001 db 'Cannot open file BootLoad.bin. '
db 'Operation aborted.', cr, lf
Len001 = $ - Mess001

Mess002 db 'Something went wrong while writing to disk.', cr, lf
Len002 = $ - Mess002

Mess003 db 'The floppy disk subsytem reported an error. '
db 'Trying once more.', cr, lf
Len003 = $ - Mess003

Mess004 db cr, lf, 'Bootsector written. '
db 'Thank you for using this software.'
db cr, lf, 'This program is GNU GPL free software and you use '
db 'it at your won risk.'
db cr, lf, 'Please study the GNU '
db 'General Public License for more details.', cr, lf
Len004 = $ - Mess004
; ----------------------
Error1: mov dx, offset mess001 ; process "
cannot open file"
mov cx, len001
mov bx, stdout
mov ah, 040
int 021 ; print via DOS

mov ax, 04C01 ; exit with errorcode = 1
int 021
; ----------------------
Error2: mov dx, offset mess002 ; process "
disk error"
mov cx, len002
mov bx, stdout
mov ah, 040
int 021 ; via DOS

mov ax, 04C02 ; exit with errorcode = 2
int 021
; ----------------------
Error2a: push ax, bx, cx, dx ; process "
Disk not ready"
mov dx, offset mess003 ; point to message
mov cx, len003 ; this many bytes
mov bx, stdout ; to the console
mov ah, 040 ; do a write
int 021 ; via DOS
pop dx, cx, bx, ax ; restore state of machine
ret ; and return to caller
; ----------------------
main: mov dl, 0 ; choose drive A:
mov ah, 0 ; select funtion 0 ...
int 013 ; ... reset diskdrives

mov dx, offset filename ; point to name of file
mov ax, 03D00 ; to open
int 021 ; via DOS
jc Error1 ; if error, take action

mov [Handle], ax ; no error, => ax = handle
mov dx, offset buffer ; setup pointer, ...
mov cx, 512 ; ... byte count, ...
mov bx, ax ; ... and handle
mov ah, 03F ; to read data from file
int 021 ; via DOS

mov bx, [Handle]
mov ah, 03E
int 021 ; close this file

mov cx, 5 ; prepare for a five times LOOP
L0: push cx
mov bx, offset buffer
mov es, ds ; es:bx = buffer to read from
mov dx, 0000 ; drive A:, head 0
mov cx, 0001 ; Track 0, Sector 1
mov ax, 0301 ; Write sectors, 1 sector
int 013 ; via BIOS
jnc >L1 ; if no error, jump forward
pop cx ; Houston, we have an error!
call error2a ; inform the user
loop L0 ; and try again
jc error2 ; after five times still no go....

L1: mov dx, offset Mess004 ; Signal that we're successful
mov cx, Len004
mov bx, stdout ; to the console
mov ah, 040
int 021 ; via DOS (so it can be redirected)
mov ax, 04C00 ; mention that there were no errors
int 021 ; and return to DOS

----------- Wrs.A86 ---------------------------------------- End ----------

Have fun experimenting with bootsectors. But take care that this will NOT
work on a hard disk.


Hard disk structure.
--------------------
A hard disk uses another layout for it's structure. The very first sector
of a HDD is the MBR (Master BootRecord). It is the only sensible sector in
the first track of a normal HDD. The rest is just empty.

Each partition starts at a cylinder boundary, so the first one starts at
cylinder 1 (track 1, side 0, sector 1). The very first sector of a bootable
partition is the bootsector.

The MBR contains the partition table, indicating where partitions start and
end and whether they are bootable or not. Plus some code to interpret that
table and to find the bootsector that was selected.


If you write a floppy disk bootsector to the very first sector of a HDD,
you wipe out the MBR and hence make inaccesable all data on that disk. The
data will still be there, but the system will not be able anymore to find
or use it.

So please take care that this software is NOT used for drive (DL=) 080.

jverhoeven@bigfoot.com



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Dumping Memory To Disk
by Jan Verhoeven


This piece of code allows you to make a memory dump of any region of
conventional memory (i.e. below 1 Mb) to a diskfile.


The program itself.
-------------------
The source is documented so it speaks for itself. At points of
interest I have insterted "
break" and "restart" lines with space for
additional remarks.

So just read the source and read the remarks. This way, the text is
where the code is, and you don't need to go back and forth in the
text. I think this will easier to read than explanation afterwards.


--- Mem2File ------------------------------------------------- Start ---

name mem2file
title Send an area of memory to a diskfile.
page 80, 120

; version 1.0 : Had to be compiled for each area/filename OK: 01-01-1991
; version 1.1 : Same as above, for A86 format OK: 01-01-1999
; version 1.2 : Make it commandline driven OK: 01-02-1999
; version 1.3 : Make it reliable OK: 02-02-1999
; ----------------------
stdout = 1
tab = 9
lf = 10
cr = 13

clr MACRO ; macro called CLeaR
mov #1, 0 ; move it with zero
#EM ; and get outa here

Dum1 STRUC ; a structure definition
OffVal dw ?
SegVal dw ?
ENDS
; ----------------------
DATA segment ; this is where the volatile data lives

ByteF = $
dummy db ? ; just to fool D86....
; if this dummy variable is not here, D86 will
; reference variable "
Start" as "ByteF".
even
Start dw ?, ? ; segment:address to start
Stop dw ?, ? ; segment:address to stop
Blocks dw ? ; number of 16K chucks to save
Rest dw ? ; remaining part to save
ArgNum dw ? ; nr of bytes in this argument
OldClp dw ? ; current pointer into command line
Handle dw ?
Length dw ?, ?

FileName: ; this storage is used twice....
Argument db 80 dup (?) ; storage for next argument from command-line
Output db 16K dup (?) ; buffered output

--- Mem2File ------------------------------------------------- Break ---

An A86 enhancement: if you need 16K elements of data, just ask for it.
No need to remember that 16 Kb is 16.384 bytes. The "
K" will do.

No big deal, just a nice feature.

Also, if you need to process large binary numbers you may group them
into sub-units separated by underscores. So the number:

1000100100111101

is hard to read back. But if we insert "
_" markers like:

1000_1001_0011_1101

the grouping of bits makes them easier to understand. Not that it is a
matter of life and death, but it can come in handy once in a while.

--- Mem2File ------------------------------------------------ Restart --

Bytes = $ - ByteF ; number of volatile databytes
; ----------------------
CODE segment ; no ORG, so we start at 0100

jmp main

HexTable db '0123456789ABCDEF', 0

db 'VeRsIoN=Mem2File 1.3', 0
db 'CoPyRiGhT=CopyLeft Jan Verhoeven, jverhoeven@bigfoot.com', 0

Mess001 db 'Mem2File collects a part of conventional memory and '
db 'sends it to a file.', cr, lf, lf
db 'The syntax is:', cr, lf, lf
db tab, 'Mem2File segm1:offs1 [-] segm2:offs2 '
db '<path>file.ext.', cr, lf, lf
db 'Mem2File is GNU GPL style FREE software. ', cr, lf
db 'Please read the GNU GPL if you are in doubt.', cr, lf, lf

Mess002 db 'Mem2File was made by Jan Verhoeven, NL-5012 GH 272, '
db 'The Netherlands', cr, lf
db 'E-mail address : jverhoeven@bigfoot.com', cr, lf, lf
Len001 = $ - Mess001
Len002 = $ - Mess002

Mess004 db 7, 'Error! All numbers are expected to be hexadecimal.'
db cr, lf
Len004 = $ - Mess004
;------------------------
InitMem: mov di, ByteF ; Init volatile memory with zero's.
mov cx, Bytes ; saves a lot of strange problems.
mov al, 0
rep stosb
ret
;------------------------
--- Mem2File ------------------------------------------------- Break ---

I use volatile data to store data that does not need initialising. This
saves a lot of diskspace and it loads a lot faster. Drawback of volatile
data can be that any rubbish left there by other programs can make your
software go berzerk if you yourself forget to initialise the data.

Therefore I -always- prime the volatile data memory with zero's. Just to
have a well defined starting position.

It should not be necessary, but, on the other hand, how much overhead
and extra execution time is such an initialisation routine?

--- Mem2File ------------------------------------------------ Restart --

L0: mov b [di], 0 ; terminate argument string
mov [OldClp], si ; done, => clean up.
clc ; indicate "
No Error"
L3: pop di, si, ax ; restore registers, ...
ret ; ... and leave.

GetArg: push ax, si, di ; get next argument from command-line in ASCIIZ
format
mov si, [OldClp] ; now, where did we leave last time?
cmp si, 0 ; Have we ever used this routine?
IF E mov si, 081 ; if not, prime SI, ...
mov di, offset Argument ; ... DI and ...
mov [ArgNum], 0 ; ... nr of chars in argument.
L1: lodsb ; get byte
cmp al, ' ' ; skip over spaces, ...
je L1
cmp al, tab ; ... and tabs.
je L1
cmp al, 1 ; ONLY if AL is 0, we get a carry
jc L3 ; if CARRY, we're done

--- Mem2File ------------------------------------------------- Break ---

This construction is what I particularly like. I want to check if AL is
Zero. Normally you can code

cmp al, 0
jz L3

but L3 is the error-exit and needs the carrybit to be set as an error
flag. Normally you would enter a

stc

instruction to fullfill the specification. But that is poor programming.
It is better to let the software do this for us.

AL can have any value between 020 and 0FF, plus 00, tab, lf and cr. 01
is not an option. So the sequence

cmp al, 1 ; ONLY if AL is 0, we get a carry
jc L3 ; if CARRY, we're done

will send us to the errorexit WITH the carryflag set, all in one,
without explicitly having to set the carry flag.

--- Mem2File ------------------------------------------------ Restart --

L2: stosb ; else store char in Arguments array
inc [ArgNum] ; adjust counter
lodsb ; and get next char
cmp al, ' ' ; is it a delimiting space?
je L0
cmp al, tab ; or a tab?
je L0
cmp al, ':' ; or a colon?
je L0
cmp al, 0 ; or an end-of-line?
jne L2 ; if not, loop back,
mov si, 0FFFF ; else make SI ridiculously high, ...
stc ; ... set carry flag, ...
jmp L0 ; and get out.

--- Mem2File ------------------------------------------------- Break ---

Ok, ok, ok. I was influenced to make this function by a compiler. No, it
wasn't C. It was Modula-2.

GetArg (if necessary A86 can operate in a case sensitive mode!) extracts
the next argument from the command tail. It puts it in a seperate buffer
at address "
Arument" which can hold 80 bytes. Shoyuld be more than
enough for one word or expression.

--- Mem2File ------------------------------------------------ Restart --

;------------------------
L1: stc ; byte not in table!
pop dx ; we came here with carry set!
ret ; exit

L2: sub bx, dx ; calculate position in table
pop dx
clc ; make sure carry is cleared
ret

--- Mem2File ------------------------------------------------- Break ---

This is a typical A86 construction. The subroutine is called TableFind
and it starts in the next line and ends in the previous one!

This is done to have the local labels declared for when they are needed
in the main functionbody. All jumps are "
backward". For the CPU there's
no big influence, but for the assembler there is. No guessing about
labels.

--- Mem2File ------------------------------------------------ Restart --

TableFind: ; find AL in ASCIIZ table [BX] ...
push dx ; ... and report position
mov dx, bx ; keep value of SI
L0: cmp b [bx], 0 ; is it end of table?
je L1 ; if so, jump out
cmp al, [bx] ; compare byte with table
je L2 ; if same, jump out
inc bx ; else increment pointer
jmp L0 ; and loop back
;------------------------
MakeUpper:
cmp al, 'a' ; too low?
jb ret

--- Mem2File ------------------------------------------------- Break ---

An A86 enhancement: a conditional return instruction. All sensible CPU's
have conditional CALL and RET instructions. Not the 80x86 line. This CPU
was meant to be structured.

So you have to put a conditional jump before the call, and introduce yet
another silly labelname for the next instruction.

The "
Jcc ret" is a good way to circumvent this ommission. What it
does is the same as what, on a Z-80, would be done with a "
RET cc"
instruction.

There is one catch, however: there must be a RET instruction within
reach PRIOR to the "
Jcc Ret" (internal) macro.

If that is a problem, you could also use the line:

IF cc Ret

So either way you, the programmer, win.

--- Mem2File ------------------------------------------------ Restart --

cmp al, 'z' ; if in range, ...
ja ret
and al, not bit 5 ; ... make uppercase

--- Mem2File ------------------------------------------------- Break ---

A86 is very programmer-oriented and allows us to write down what and how
we think. So if I need to set bit 0 of register Ax, I will simply write

or ax, bit 0

Any value between 0 and 15 is valid in A86 (0 - 31 for A386) to refer to
the respective bit in the respective source.

--- Mem2File ------------------------------------------------ Restart --

ret
;------------------------
BadNumber: ; hey typo, you made a dumbo!
mov dx, offset Mess004
mov cx, Len004
mov bx, StdOut
mov ah, 040
int 021

mov ax, 04C02 ; and exit with errorcode 2
int 021
;------------------------
SyntErr: mov dx, offset Mess001
mov cx, Len001
mov bx, StdOut
mov ah, 040 ; print out "
help" screen and ...
int 021

mov ax, 04C01 ; ... exit with errorcode 1
int 021
;------------------------
L8: mov ax, dx ; Convert has result in DX, that's why.
pop dx, bx
ret

Convert: push bx, dx ; convert ASCII to Hex.
mov si, offset Argument
clr dx ; dx will contain result

--- Mem2File ------------------------------------------------- Break ---

Here the macro is invoked. It is used to load the DX register with
zero. If later you decide to change the way in which you want to clear
registers, just change the macro.

In LST files (the assembler listings) the expansions are controlled by
means of the +L switch. If you issue the option "
+L35" macro's will not
be expanded in the listings file.

--- Mem2File ------------------------------------------------ Restart --

L1: lodsb ; get first character
cmp al, 0 ; end of string?
je L8
call MakeUpper ; if not, make uppercase
mov bx, offset HexTable
call TableFind ; and lookup in table
jc BadNumber
shl dx, 4 ; multiply DX by 16

--- Mem2File ------------------------------------------------- Break ---

This is another A86 goody. I coded a "
SHL DX, 4" instruction, although
I do not know what the target processor will be.

No problem with A86. It will find out with which CPU you are assembling
and use that. If your CPU supports this function, it is implemented as
such. If it doesn't this instruction is expanded as a macro into the
following:

shl dx, 1
shl dx, 1
shl dx, 1
shl dx, 1

More code in the executable, but it makes programming easier.

If on a modern CPU, you can force A86 to act as if the CPU were a
vintage 88 with the commandline switch +P65.

--- Mem2File ------------------------------------------------ Restart --

or dl, bl ; bx = index into table
jmp L1 ; repeat until done
;------------------------
Credits: mov dx, offset Mess002
mov cx, Len002
mov bx, stdout
mov ah, 040
int 021 ; print some egotripping data
ret
;------------------------

main: call InitMem ; prime volatile data
mov al, [080] ; get tail length
cbw ; make 16 bits long
mov si, 081 ; point to start of tail
add si, ax ; point to end of tail
mov [si], ah ; make commandtail ASCIIZ
call GetArg ; get argument from command tail
jc SyntErr ; if error, get out
call Convert ; convert text to hex
mov [Start.SegVal], ax ; store it
call GetArg ; etcetera
jc SyntErr
call Convert
mov [Start.OffVal], ax

L0: call GetArg
jc SyntErr
cmp b [Argument], '-' ; single '-' character?
je L0 ; if so, ignore it
call Convert
mov [Stop.SegVal], ax
call GetArg
jc SyntErr
call Convert
mov [Stop.OffVal], ax

call GetArg
IF C jmp SyntErr

--- Mem2File ------------------------------------------------- Break ---

This is one of the A86 enhancements. This IF construct prevents that you
have to make up all kinds of ridiculous labelnames like jmp_001F in a
construct as follows:

call GetArg
jnc jmp_01F
jmp SyntErr
jmp_01F: ...

The IF construct (not to be confused with the "
#IF" construct which is
for conditional assemblies) enables you to just make a fast jump by
stating the reverse condition in the IF statement and acting further
like a high level language:

IF C jmp SyntErr

Neat, isn't it?

--- Mem2File ------------------------------------------------ Restart --

mov si, offset Argument
add si, [ArgNum]
mov b [si], 0 ; make it ASCIIZ

mov dx, offset FileName ; same as Argument buffer....
mov cx, 0
mov ah, 03C
int 021 ; create the file
IF C jmp SyntErr

--- Mem2File ------------------------------------------------- Break ---

See how powerful the IF construct can be? It is a very convenient way to
circumvent the foolish conditional instructions of the x86 architecture.

--- Mem2File ------------------------------------------------ Restart --

mov [Handle], ax

mov ax, [Stop.SegVal]
mov dx, 0 ; prime DX
mov cx, 4
L0: shl ax, 1
rcl dx, 1
loop L0 ; shift upper 4 bits of address into DX
add ax, [Stop.OffVal]
adc dx, 0 ; now, dx:ax = linear address to stop at
mov [Stop.SegVal], dx
mov [Stop.OffVal], ax ; store linear address STOP

mov ax, [Start.SegVal]
mov dx, 0 ; prime DX
mov cx, 4
L0: shl ax, 1
rcl dx, 1
loop L0 ; shift upper 4 bits of address into DX
add ax, [Start.OffVal]
adc dx, 0 ; now, dx:ax = linear address to start
from
mov [Start.SegVal], dx
mov [Start.OffVal], ax ; store linear address START

cmp dx, [Stop.SegVal] ; start > stop?
ja >L1 ; fix it!
jb >L2

--- Mem2File ------------------------------------------------- Break ---

A86 likes to have as much as possible labels declared before they are
referenced. That's why many times there is code "
before" the subroutine
name is declared.

For local labels (i.e. labels that consist of 1 letter and the rest
decimal digits) it is a MUST that they are defined before being
referenced.

If, for some reason, you do not want to put a label backwards in memory,
you can forward reference a local label by prefixing it with a "
>" sign.
A86 now knows that the local label still has to come. Not a luxury since
many A86 programmers can do with 4 or 5 local labels in over 2000 lines
of code.... Especially L0 is always very well available.

--- Mem2File ------------------------------------------------ Restart --

cmp ax, [Stop.OffVal] ; start > stop?
jbe >L2 ; if not, OK
L1: push [Stop.SegVal]
push [Stop.OffVal] ; swap start and stop addresses
mov [Stop.SegVal], dx
mov [Stop.OffVal], ax
pop [Start.OffVal]
pop [Start.SegVal]

L2: mov dx, [Stop.SegVal]
mov ax, [Stop.OffVal]
add ax, 1
adc dx, 0 ; limits are INCLUSIVE
sub ax, [Start.OffVal]
sbb dx, [Start.SegVal] ; dx:ax = bytes to move

shl ax, 1
rcl dx, 1
shl ax, 1
rcl dx, 1 ; dx = nr of 16 Kb blocks to move
mov [Blocks], dx ; store it
shr ax, 2 ; ax = remainder to move
mov [Rest], ax ; save it

mov ax, [Start.SegVal] ; end of linear addressing, we're going
to DOS!
mov cl, 12
shl ax, cl
mov [Start.SegVal], ax

mov es, ds ; use es to refer to data
mov bx, [Handle]
lds dx, d [Start]

--- Mem2File ------------------------------------------------- Break ---

Normally this instruction would require a secretary with 100 letters per
minute typing rate:

lds dx, dword ptr [Start]

But the "
ptr" argument is always the same, so it is only there to please
the assembler and humiliate the programmer: entering data that nobody
needs.

Therefore A86 only needs the first letter of such prose. In our case:
the "
dword ptr" is abbreviated to a "d". "Byte ptr" is a "b". "Word Ptr"
is a "
w". Simple as that.

So if your coding skills outweigh your typing speed, you should consider
switching to the superior assembler. :)

--- Mem2File ------------------------------------------------ Restart --

es cmp [Blocks], 0 ; if less than 16 Kb, skip this one.

--- Mem2File ------------------------------------------------- Break ---

For people who still remember that "
Seg ES" is a legal instruction
(used for a segment override) this might bring back memories.

A86 allows the user to put the segmentation override before the actual
instruction. This way, the operand field looks neater. And it is also
the way in which D86 shows segmentation overrides.

--- Mem2File ------------------------------------------------ Restart --

je >S0
mov cx, 16K
L0: mov ah, 040
int 021
mov ax, ds ; store ds into ax
add dx, 04000 ; next buffer to load data from
IF C add ax, 01000 ; if carry, inc ds
mov ds, ax ; ds:dx now ready for next bufferfull of data

es dec [Blocks]
jnz L0

S0: es cmp [Rest], 0
je >L1
mov ah, 040
es mov cx, [Rest]
int 021
L1: mov ds, es

mov bx, [Handle]
mov ah, 03E
int 021 ; close file

call Credits ; show my ego
mov ax, 04C00
int 021 ; exit to DOS

--- Mem2File -------------------------------------------------- End ----

That's it.

jverhoeven@bigfoot.com



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Formatted Numeric Output
by Laura Fairhead


Here I am going to present you with a very useful routine for numeric
output. I have been using it myself for sometime and now I think it is
almost perfect.

It consists of 2 basic API's. The first (nuconvs), you call when you
want to change the parameters of the main routine (nuconv). You simply call
it with one DWORD in EAX, this specifies the following:

EAX = SSFFPPRR (hexadecimal value of course)
SS size size of the datum you will be calling the main routine with,
only 3 values are valid:
0=byte
1=word
2=dword
FF field size of a field in which to right-justify the number,
if this is = 0 then there is no right-justification you
only get the number
PP pad the ASCII value of the character to use to right-justify
the number in the field of output
RR radix the radix to output the number in

Once you've set the control parameters you can call the main routine
(nuconv) freely to do the work. You call the main routine with ES:DI set
to where the output is to be stored, and the value to be output in AL or
AX or EAX (depending on what data size you set).

I use the word 'output' here which might conjure up images of the screen,
but in fact what we are doing here is writing all the ASCII to memory.
This is much more powerful than incorporating all that OS/application
specific nonsense, and it really doesn't cost much overhead at all (in
fact this is the way C does it and even though I **HATE** C, here it is
right on the mark;)

Here is the code:

================START OF CODE==============================================
;
;nuconvs- set control parameters for 'nuconv'
;
; !! this must be called at least once before calling nuconv
;
;entry: EAX=SSFFPPRR (hex digits)
;
; where: SS=data size (0=byte,1=word,2=dword)
; FF=field size (0=none)
; PP=pad char
; RR=radix (2-16)
;
; !! these parameters must be set correctly by the application
; !! they are not validated in anyway and invalid parameters
; !! will cause undefined operation
;
;exit: (all registers preserved)
;

nuconvs PROC NEAR
MOV DWORD PTR CS:[nuradix],EAX
RET
nuconvs ENDP

;
;control parameters
;
; !! these absolutely must be in the below order due to the way the above
; routine works
;

nuradix DB ? ;output radix
nupad DB ? ;pad character
nufld DB ? ;field size
nudsiz DB ? ;data size

;
;nuconv- output value in accumalator -> ES:DI
;
; !! see 'nuconvs' header for more information
;
;entry: AL|AX|EAX=value to output
; ES:DI=address to write output data
;
; size of accumulator that is used depends on what the current data
; size is ( as specified by a previous call to 'nuconvs' )
;
;
;exit: DI=updated to offset of last character + 1
;
; (all other registers preserved)
;

nuconv PROC NEAR
;
;all registers are going to be preserved
;
PUSH DS
PUSH EAX
PUSH EBX
PUSH CX
PUSH EDX
;
;save some CS: overrides
;
PUSH CS
POP DS
;
;initialise
;
; set EBX =radix
; CX =fieldsize
;
; also we zero pad out the datum passed so it fills EAX
;
XOR EBX,EBX

CMP BL,BYTE PTR

  
DS:[nudsiz]
JNP SHORT ko1
JS SHORT ko0
MOV AH,0
ko0: DEC BX
AND EAX,EBX
INC BX
ko1:

MOV BL,BYTE PTR DS:[nuradix]
MOV CH,0
MOV CL,BYTE PTR DS:[nufld]
;
;calculate digits and push to stack
;
; EAX is divided and modulus taken which is the standard way,
; loop exits when it reaches 0 or the field size is hit
; notice that if CX=0 on entry to this then the field size
; will be effectively unbounded
;
nulop0: XOR EDX,EDX
DIV EBX
PUSH DX
AND EAX,EAX
LOOPNZ nulop0
;
;'output' the field padding
;
; the number of padding characters is normally the value
; now in CX (ie: fieldsize - digits ). however no pad chars
; should be output if field size = 0. i think the check here
; for this is nice and tight (read the code...)
;
MOV BX,CX
NEG BX
JNS SHORT ko
MOV AL,BYTE PTR DS:[nupad]
REP STOSB
ko:
;
;'output' all the digits
;
; CX is set to the number of digits on the stack we have to output
; ie: fieldsize - ( fieldsize - digits )
;
MOV CH,0
MOV CL,BYTE PTR DS:[nufld]
ADD CX,BX
;
; now we pop off those #CX digits translating into ASCII using a nice
; variation of the traditional speed method
;
MOV BX,OFFSET nudat

nulop1: POP AX
XLAT
STOSB
LOOP nulop1
;
; restore all registers and exit (in case it wasn't obvious!)
;
POP EDX
POP CX
POP EBX
POP EAX
POP DS
RET
;
nudat DB "0123456789ABCDEF"
;
nuconv ENDP

==================END OF CODE==============================================



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Linked Lists in ASM
by mammon_


Assembly language is notorious for being low-level; to wit, it lacks many of
the features in higher-level languages which make programming easier. In the
course of my work in the visasm project I have put quite a bit of time into
working on exactly which higher language features are important and which, in a
nutshell, are swill.

One of the areas in which assembly language is lacking is the use of dynamic
structures. Pointer manipulation in asm is simple and clear for up to one
level of redirection; further redirection causes the code to quickly become a
confusion of register juggling and indirect addressing. As a result,
implementing even a simple linked list in assembly language can be tedious
enough to make one rewrite the project in C.

In this article I have undertaken an implementation of a linked list in NASM;
the implementation is generic enough to support more complex data structures,
and should port to other assemblers with few changes.

To begin with, one must define the memory allocation routines for use in the
application; I have chosen Win32 for convenience. The routines defined below
are for local heap allocation and for the Win32 console interface to allow the
use of STDOUT on the console.
;=========================================================Win32 API Definitions
STD_INPUT_HANDLE EQU -10 ;nStdHandle types
STD_OUTPUT_HANDLE EQU -11
STD_ERROR_HANDLE EQU -12

EXTERN AllocConsole ;BOOL AllocConsole()
EXTERN GetStdHandle ;HANDLE GetStdHandle( nStdHandle )
EXTERN WriteConsoleA ;HANDLE hConsole, lpBuffer, Num2Write, lpWritten,NULL
EXTERN ExitProcess ;UINT ExitCode
EXTERN GetProcessHeap ;
EXTERN HeapAlloc ;HANDLE hHeap,DWORD dwFlags, DWORD dwBytes:ret ptr
EXTERN HeapFree ;HANDLE hHeap,DWORD dwFlags, LPVOID lpMem
EXTERN HeapReAlloc ;HANDLE hHeap,DWORD dwFlags,LPVOID lpMem,DWORD dwBytes
EXTERN HeapDestroy ;HANDLE hHeap
%define HEAP_NO_SERIALIZE 0x00000001
%define HEAP_GROWABLE 0x00000002
%define HEAP_GENERATE_EXCEPTIONS 0x00000004
%define HEAP_ZERO_MEMORY 0x00000008
%define HEAP_REALLOC_IN_PLACE_ONLY 0x00000010
%define HEAP_TAIL_CHECKING_ENABLED 0x00000020
%define HEAP_FREE_CHECKING_ENABLED 0x00000040
%define HEAP_DISABLE_COALESCE_ON_FREE 0x00000080
%define HEAP_CREATE_ALIGN_16 0x00010000
%define HEAP_CREATE_ENABLE_TRACING 0x00020000
%define HEAP_MAXIMUM_TAG 0x0FFF
%define HEAP_PSEUDO_TAG_FLAG 0x8000
%define HEAP_TAG_SHIFT 16
;===========================================================End API Definitions


In addition, it is useful to define a few common routines for use later:
;==============================================================Utility Routines
[section data class=DATA use32] ;set up the segments early
%macro STRING 2+
%1: db %2
.end:
%define %1.length %1.end - %1
%endmacro
[section code class=CODE use32]

GetConsole:
;GetConsole()
[section data]
hConsole DD 0
[section code]
call AllocConsole
push dword STD_OUTPUT_HANDLE
call GetStdHandle
mov [hConsole], eax
xor eax, eax
ret

puts:
;puts( ptrString, NumBytes )
[section data]
NumWrote DD 0
[section code]
%define _ptrString ebp + 8
%define _strlen ebp + 12
push ebp
mov ebp,esp
push eax
push dword 0
push dword NumWrote
mov eax, [ _strlen ]
push dword eax
mov eax, [ _ptrString ]
push dword eax
push dword [hConsole]
call WriteConsoleA
pop eax
mov esp, ebp
pop ebp
ret 8
;==========================================================End Utility Routines
The STRING macro is particular interesting; it allows one to define a string
in the data segment as
STRING label, 'contents of string',0Dh,0Ah
while defining the constant label.length as the total length of the string.
This will come in handy during the many calls to puts, which is used to write
to the Win32 console. Puts has the syntax
puts( lpString, strLength )
and returns the result of WriteConsole, a BOOL value. GetConsole is a routine
provided to move the Win32 console allocation code out of the main program; it
takes no parameters and defines the hConsole handle.

The linked list implementation has been designed to be extendable; the routine
names are prefaced with underscores to avoid filling up the namespace of the
linked list application, and the routines themselves are generic enough to be
called from higher-level Stack, Queue, and List implementations. The Linked
List interface is as follows:
ptrHead _create_list( hHeap, NodeSize )
void _delete_list( hHeap, ptrHead)
ptrNode _add_node( hHeap, ptrPrev, NodeSize )
void _delete_node( hHeap, ptrPrev, ptrNode )
void _set_node_data( ptrNode, NodeOffset, data )
DWORD data _get_node_data( ptrNode, NodeOffset )
The names of the routines should make their intent apparent; note however that
NodeSize is assumed to be the size of a LISTSTRUCT structure.
;====================================================Linked List Implementation
[section data]
;Define .next as offset Zero for use in generic functions
struc _llist
.next: resd 1 ;this is basically a constant
endstruc

;Macro to ensure that .next is always at offset zero in user-defined lists
%macro LISTSTRUCT 1
struc %1
.next: resd 1
%endmacro
%macro END_LISTSTRUCT 0
endstruc
%endmacro

[section code]
;Note that these assume an LISTSTRUCT base type
_create_list:
; ptrHead_create_list( hHeap, NodeSize )
%define _hHeap ebp + 8
%define _ListSize ebp + 12
ENTER 0 , 0
push dword [_ListSize] ;size of LISTSTRUCT
push dword HEAP_ZERO_MEMORY ;FLAG for HeapAlloc
push dword [_hHeap] ;Heap being used
call HeapAlloc
test eax, eax
jz .Error ;Alloc failed!
mov [eax + _llist.next], dword 0 ;.next pointer = NULL
.Exit: LEAVE ;eax = ptrHead
ret 8
.Error: xor eax, eax ;error = return NULL
jmp .Exit

_delete_list:
; _delete_list( hHeap, ptrHead)
%define _hHeap ebp + 8
%define _ptrHead ebp + 12
ENTER 0, 0
push eax
push ebx ;save registers
mov eax, [_ptrHead] ;eax = addr of list head node
.DelNode:
mov ebx, [eax + _llist.next] ;ebx = [eax].next
push eax ;free addr in eax
push dword 0 ;FLAG
push dword [_hHeap] ;local heap
call HeapFree
test ebx, ebx ;is [eax].next == NULL?
jz .Exit ;if yes then done
mov eax, ebx ;loop until done
jmp .DelNode
.Exit: pop ebx
pop eax
LEAVE
ret 8

_add_node:
; ptrNode _add_node( hHeap, ptrPrev, NodeSize )
%define _hHeap ebp + 8
%define _ptrPrev ebp + 12
%define _ListSize ebp + 16
ENTER 0, 0
push edx ;HeapAlloc kills edx!!
push ebx
push ecx ;save registers
mov ebx, [_ptrPrev] ;ebx = node to add after
push dword [_ListSize] ;size of node
push dword HEAP_ZERO_MEMORY ;FLAG
push dword [_hHeap] ;local heap
call HeapAlloc
test eax, eax
jz .Error ;alloc failed!
mov ecx, eax ;note -- eax = ptrNew
add ecx, _llist.next ;ecx = ptrNew.next
mov [ecx], ebx ;ptrNew.next = ptrPrev.next
add ebx, _llist.next ;note -- ebx = ptrPrev
mov [ebx], eax ;ptrPrev.next = ptrNew
.Exit: pop ecx
pop ebx
pop edx
LEAVE
ret 12
.Error: xor eax, eax ;return NULL on failure
jmp .Exit

_delete_node:
; _delete_node( hHeap, ptrPrev, ptrNode )
%define _hHeap ebp + 8
%define _ptrPrev ebp + 12
%define _ptrNode ebp + 16
ENTER 0, 0
push ebx
mov eax, [_ptrNode + _llist.next] ;eax = ptrNode.next
mov ebx, [_ptrPrev] ;
mov [ebx + _llist.next], eax ;ptrPrev.next = ptrNode.next
push dword [_ptrNode] ;free ptrNode
push dword 0 ;FLAG
push dword [_hHeap] ;local heap
call HeapFree
pop ebx
LEAVE
ret 12

_set_node_data:
; _set_node_data( ptrNode, NodeOffset, data )
%define _ptrNode ebp + 8
%define _off ebp + 12
%define _data ebp + 16
ENTER 0, 0
push eax
push ebx
mov eax, [_ptrNode] ;eax = ptrNode
add eax, [ _off ] ;eax = ptrNode.offset
mov ebx, [_data] ;ebd = data
mov [eax], ebx ;ptrNode.offset = data
pop ebx
pop eax
LEAVE
ret 12

_get_node_data:
; DWORD data _get_node_data( ptrNode, NodeOffset )
%define _ptrNode ebp + 8
%define _off ebp + 12
ENTER 0, 0
mov eax, [_ptrNode] ;eax = ptrNode
add eax, [_off] ;eax = ptrNode.offset
mov eax, [eax] ;return [ptrNode.offset]
LEAVE
ret 8
;===============================================================End Linked List
The LISTSTRUCT structure is perhaps the most crucial part of this implemen-
tation. In NASM, a structure is simply a starting address with local labels
defined as constants which equal the offset of the local label from the start
of the structure. Thus, in the structure
struc MyStruc
.MyVar resd 1
.MyVar2 resd 1
.MyVar3 resd 1
.MyByte resb 1
endstruc
the constant MyStruc.MyVar has a value of 0 [0 bytes from the start of the
structure], MyStruc.MyVar2 has a value of 4, MyStruc.MyVar3 has a value of 8,
MyStruc.MyByte has a value of 12, and MyStruc_size [defined as the offset of
the "endstruc" directive] has a value of 13. Note that in NASM, the name of a
structure instance determines the address in memory of the instance [i.e., it
is a simple code label], while the constants defined in the structure
definition allow access to offsets from that address.

What this means is that structures in NASM can be defined and never instant-
iated, allowing the convenient use of the structure constants for dynamic
memory structures such as classes and linked list nodes. The above code uses
the LISTSTRUCT macro to force all linked list nodes to have a ".next" member;
this also allows the use of the constant "_llist.next" in the linked list
routines to avoid having to pass the offset of the ".next" member for a node.

The implementation routines should be pretty straight forward. _create_list
allocates memory from the local heap of the size of one list node [determined
by the parameter NodeSize passed to _create_list] and returns the address of
the allocated memory; since this node is assumed to be the list "head", the
.next member is set to NULL. _delete_list is passed the address of the head
node of the list; it saves the address in the .next member of the node and
then frees the memory allocated to the node, repeating this with each .next
link until the .next member is NULL [indicating an end of list].

_add_node is used to insert a node into an existing list; it is passed the
address of the node after which the new node is to be inserted. The .next
member of this node is moved into the .next member of the new node, and
replaced with the address of the new node. Thus, if before insertion the list
had the structure
.next [Node1] --> .next [Node2] --> .next [NULL]
.data NULL .data Node1 .data Node2
then it would have the following structure after insertion following Node1:
.next [Node1] --> .next [NewNode] --> .next [Node2] --> .next [NULL]
.data NULL .data Node1 .data NewNode .data Node2
_del_node does the opposite of _add_node; it moves the .next member of the
node to be deleted into the .next member of the preceding node, then frees the
specified node.

Note that both _del_node and _add_node are designed to be as generic as
possible and make no assumptions regarding the linked list structure; thus in
a double linked list of the format
struc DLLIST
.next
.prev
.data
endstruc
one could front-end the Delete function as follows:
DelNode:
push dword eax ;eax = Node to delete
push dword [eax + DLLIST.prev]
push hHeap
call _del_node
ret 8
The other linked list routines can be provided with similar front-ends to take
care of common heap handles, list sizes, and member assignments.

Both the _set_node_data and the _get_node_data routines are basic pointer
manipulations added for code clarity. Each could be rewritten inline; for
example, the _get_node_data routine can be implemented as
add ebx, offset
mov eax, [ebx]
assuming ebx holds the node to be accessed and "offset" is the offset [or
constant] of the node member to be accessed.

Below is a simple program which makes a four-node linked list of the format
.next Node1 --> .next Node2 --> .next Node3 --> .next NULL
.prev NULL <-- .prev Head <-- .prev Node1 <-- .prev Node2
.data NULL .data 'node1' .data 'node2' .data 'node3'
Note the use of the NewNode routine, which provides a front-end to _add_node
which sets the .prev member for the new node. One brief caveat, the example
does not delete the list, as the Win32 heap is deallocated on program
termination; neither is there any substantial error checking in the sample.

;=======================================================Linked List Application
[section data]
hHeap dd 0
ptrHead dd 0
STRING strData1, 'node 1',0Dh,0Ah
STRING strData2, 'node 2',0Dh,0Ah
STRING strData3, 'node 3',0Dh,0Ah
STRING strStart, 'Creating List',0Dh,0Ah
STRING strDone, 'Finished!',0Dh,0AH,'Printing Data...',0Dh,0Ah
STRING strErr, 'Error!',0Dh, 0AH

LISTSTRUCT llist
.prev resd 0
.data resd 0
END_LISTSTRUCT

[section code]
Error:
push dword strErr.length
push dword strErr
call puts
jmp Exit

..start:
call GetProcessHeap
mov [hHeap], eax
call GetConsole

push dword strStart.length
push dword strStart
call puts

CreateList:
push dword llist_size
push dword [hHeap]
call _create_list
test eax, eax
jz Error
mov [ptrHead], eax
push dword 0
push dword llist.data
push eax
call _set_node_data ;set ptrHead.data to NULL
push dword 0
push dword llist.prev
push eax
call _set_node_data ;set ptrHead.prev to NULL

call NewNode ;create Node1
test eax, eax
jz ListDone
push dword strData1
push dword llist.data
push eax
call _set_node_data ;set Node1.data to 'node1'

call NewNode ;create Node2
test eax, eax
jz ListDone
push dword strData2
push dword llist.data
push eax
call _set_node_data ;set Node2.data to 'node2'

call NewNode ;create Node3
test eax, eax
jz ListDone
push dword strData3
push dword llist.data
push eax
call _set_node_data ;set Node3.data to 'node3'

ListDone:
push dword strDone.length
push dword strDone
call puts

mov ebx, [ptrHead]
PrintList:
push dword _llist.next
push ebx
call _get_node_data ;could have been mov eax,[ebx]

test eax, eax ;if ptrCurrent.next == NULL exit
jz Exit ; [end of list]
mov ebx, eax ;save ptrNode
push dword strData1.length ;push length for call to puts
push dword llist.data
push ebx
call _get_node_data ;get ptrNode.data
push dword eax ;push string for call to puts
call puts
jmp PrintList ;loop

Exit:
push dword 0
call ExitProcess

NewNode:
ENTER 0, 0
push edx
mov edx, eax ;save previous node
push dword llist_size
push dword eax
push dword [hHeap]
call _add_node
test eax, eax
jz .Done
push dword eax
push dword llist.next
push dword edx
call _set_node_data ;set ptrPrev.next to ptrNew
push edx
push dword llist.prev
push eax
call _set_node_data ;set ptrNew.prev to ptrPrev
push dword 0
push dword llist.next
push eax
call _set_node_data ;set PtrNew.next to NULL
.Done pop edx
LEAVE ;eax is still set to ptrNew
ret
;==========================================================================EOF
As mentioned earlier, this is a generic implementation of dynamic structures
designed with linked lists in mind. The macros and routines may be included in
a header file such as llist.h and used to automate the creation of dynamic
memory structures in future projects. In addition, further macros and routines
can be added to provide specific implementations of Single Linked Lists,
Double Linked Lists, Circular Lists, Stacks, Queues, and Deques.



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Structured Exception Handling under Win32
by Chris Dragan


Structured Exception Handling is a powerful feature of all Win32 platforms
that allows a program to recover from any critical errors like BOUND, divide
overflow, page missing or general protection fault. It is documented only for
C-level usage (try-except/finally syntax), and no documentation for low level
languages exists. Therefore I will try to show how to use it.

The starting point for Structured Exception Handling, SEH, is the Thread
Info Block. TIB, as almost all the other structures, is described in winnt.h
file that comes with PlatformSDK.

struc NT_TIB
ExceptionList dd ? ; Used by SEH
StackBase dd ? ; Used by functions to check for
StackLimit dd ? ; stack overflow
SubSystemTib dd ? ; ?
FiberDataOrVersion dd ? ; ?
ArbitraryUserPointer dd ? ; ?
Self dd ? ; Linear address of the TIB
ends

TIB is accessible at address fs:0. NT_TIB.Self contains linear address of TIB,
base of FS segment.

When an exception occurs, the system uses (dword)fs:0, NT_TIB.ExceptionList
to find an exception handler and execute it. The exception list entry is very
simple:

struc E_L_ENTRY
Next dd ? ; Points to next entry in the list
ExceptionHandler dd ? ; User callback - exception hook
Optional db X dup (?) ; Exception Handler data
EntryTerminator dd -1 ; Optional
ends

C compilers usually keep some additional information in E_L_ENTRY.Optional
field of varying size and usually terminated with (dword)-1. Both .Optional
and .EntryTerminator fields are not required.

Before calling an exception handler, the exception manager pushes
ExceptionRecord and ContextRecord onto the stack. These structures identify an
exception and processor state before it. The exception manager adds also its
own entry to the exception list.

Exception handler is in fact a typical callback. It is not however
installed by any API function, but appended in E_L_ENTRY into the exception
list.

EXCEPTION_DISPOSITION __cdecl _except_handler (
struct _EXCEPTION_RECORD *ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT *ContextRecord,
void * DispatcherContext
);

The exception handler uses C-style calling convention, it does not release
arguments while returning. The most important parameters are ExceptionRecord
and ContextRecord, described at the end of this text, that point to the pushed
corresponding structures. I do not have yet any idea what is the purpose of
EstablisherFrame and DispatcherContext.

struc EXCEPTION_RECORD
ExceptionCode dd ? ; See at the end of this text
ExceptionFlags dd ?
ExceptionRecord dd ? ; ?
ExceptionAddress dd ? ; Linear address of faulty instruction
NumberParameters dd ? ; Corresponds to the field below
ExceptionInformation dd 15 dup (?) ; ?
ends

Exception flags are:

EXCEPTION_NONCONTINUABLE = 1
EXCEPTION_UNWINDING = 2
EXCEPTION_UNWINDING_FOR_EXIT = 4

The exception handler has two possible ways of proceeding. It can return to
the exception manager, or it can unwind the stack and continue the program. In
the first case it has to return one of the following values:

enum EXCEPTION_DISPOSITION \
ExceptionContinueExecution = 0,\
ExceptionContinueSearch = 1,\
ExceptionNestedException = 2,\
ExceptionCollidedUnwind = 3

The value of zero forces the exception manager to continue the program
at saved in context cs:eip, which may be altered by the exception handler. The
value of 1 causes the exception manager to call another exception handler in
the exception list. Values 2 and 3 inform the exception manager that an error
occured - an exception-in-exception happened, or the handler wanted to unwind
the stack during another handler of higher instance was doing this already.
The other case can be determined if one of .ExceptionFlags is
EXCEPTION_UNWINDING or EXCEPTION_UNWINDING_FOR_EXIT.

While appending a new exception handler to the exception list, a common
practice is to push new E_L_ENTRY onto the stack. This way unwinding the stack
can be done simply by skipping the exception manager's entry and restoring the
stack pointer.

Here is an example of exception handling.

----Start-of-file-------------------------------------------------------------

ideal
p686n
model flat, stdcall

O equ <offset>

struc EXCEPTION_RECORD
ExceptionCode dd ?
ExceptionFlags dd ?
ExceptionRecord dd ?
ExceptionAddress dd ?
NumberParameters dd ?
ExceptionInformation dd 15 dup (?)
ends

procdesc wsprintfA c :dword, :dword, :dword:?
procdesc MessageBoxA :dword, :dword, :dword, :dword
procdesc ExitProcess :dword

udataseg

ExCode dd ?
szCode db 12 dup (?)

dataseg

szWindowTitle db 'Exception code', 0
szFormat db '%0X', 0

codeseg

proc main
; Install exception handler
push O ExceptionHandler
push [dword fs:0] ; E_L_ENTRY.Next
mov [fs:0], esp ; Append new E_L_ENTRY

; Cause Invalid Opcode exception
ud2

; Display exception code and quit
_Continue: call wsprintfA, O szCode, O szFormat, [ExCode]
call MessageBoxA, 0, O szCode, O szWindowTitle, 0
call ExitProcess, 0
endp

proc ExceptionHandler c ExceptionRecord, EF, ContextRecord, DC
; Save exception code
mov eax, [ExceptionRecord]
mov ecx, [(EXCEPTION_RECORD eax).ExceptionCode]
mov [ExCode], ecx

; Unwind the stack
mov eax, [fs:0] ; Exception Manager's entry
mov esp, [eax] ; Our entry
pop [dword fs:0] ; Restore fs:0
add esp, 4 ; Skip ExHandler address
jmp _Continue
endp

end main

----End-of-file---------------------------------------------------------------

The above source should be compiled with TASM 5.0r or later like this:
tasm32 /ml except.asm
tlink32 /x /Tpe /aa /c /V4.0 except.obj,,, LIBPATH\import32.lib

And here are other important constants and structures, all defined in
winnt.h PlatformSDK file.

Exception codes:
----------------

STATUS_SEGMENT_NOTIFICATION = 040000005h
STATUS_GUARD_PAGE_VIOLATION = 080000001h
STATUS_DATATYPE_MISALIGNMENT = 080000002h
STATUS_BREAKPOINT = 080000003h
STATUS_SINGLE_STEP = 080000004h
STATUS_ACCESS_VIOLATION = 0C0000005h
STATUS_IN_PAGE_ERROR = 0C0000006h
STATUS_INVALID_HANDLE = 0C0000008h
STATUS_NO_MEMORY = 0C0000017h
STATUS_ILLEGAL_INSTRUCTION = 0C000001Dh
STATUS_NONCONTINUABLE_EXCEPTION = 0C0000025h
STATUS_INVALID_DISPOSITION = 0C0000026h
STATUS_ARRAY_BOUNDS_EXCEEDED = 0C000008Ch
STATUS_FLOAT_DENORMAL_OPERAND = 0C000008Dh
STATUS_FLOAT_DIVIDE_BY_ZERO = 0C000008Eh
STATUS_FLOAT_INEXACT_RESULT = 0C000008Fh
STATUS_FLOAT_INVALID_OPERATION = 0C0000090h
STATUS_FLOAT_OVERFLOW = 0C0000091h
STATUS_FLOAT_STACK_CHECK = 0C0000092h
STATUS_FLOAT_UNDERFLOW = 0C0000093h
STATUS_INTEGER_DIVIDE_BY_ZERO = 0C0000094h
STATUS_INTEGER_OVERFLOW = 0C0000095h
STATUS_PRIVILEGED_INSTRUCTION = 0C0000096h
STATUS_STACK_OVERFLOW = 0C00000FDh
STATUS_CONTROL_C_EXIT = 0C000013Ah
STATUS_FLOAT_MULTIPLE_FAULTS = 0C00002B4h
STATUS_FLOAT_MULTIPLE_TRAPS = 0C00002B5h
STATUS_ILLEGAL_VLM_REFERENCE = 0C00002C0h

Context flags:
--------------

CONTEXT_i386 = 000010000h
CONTEXT_i486 = 000010000h

CONTEXT_CONTROL = (CONTEXT_i386 or 1) ; SS:ESP, CS:EIP, EFLAGS, EBP
CONTEXT_INTEGER = (CONTEXT_i386 or 2) ; EAX, EBX,..., ESI, EDI
CONTEXT_SEGMENTS = (CONTEXT_i386 or 4) ; DS, ES, FS, GS
CONTEXT_FLOATING_POINT = (CONTEXT_i386 or 8) ; 387 state
CONTEXT_DEBUG_REGISTERS = (CONTEXT_i386 or 16); DB 0-3,6,7
CONTEXT_EXTENDED_REGISTERS = (CONTEXT_i386 or 32); cpu specific extensions

CONTEXT_FULL = (CONTEXT_CONTROL or CONTEXT_INTEGER or\
CONTEXT_SEGMENTS)

Context structure:
------------------

struc CONTEXT
ContextFlags dd ? ; CONTEXT_??? flags

Dr0 dd ? ; Debug registers
Dr1 dd ?
Dr2 dd ?
Dr3 dd ?
Dr6 dd ?
Dr7 dd ?

ControlWord dd ? ; FPU context
StatusWord dd ?
TagWord dd ?
ErrorOffset dd ?
ErrorSelector dd ?
DataOffset dd ?
DataSelector dd ?
RegisterArea dt 8 dup (?)
Cr0NpxState dd ?

SegGs dd ? ; Segment registers
SegFs dd ?
SegEs dd ?
SegDs dd ?

Edi dd ? ; Integer registers
Esi dd ?
Ebx dd ?
Edx dd ?
Ecx dd ?
Eax dd ?

Ebp dd ? ; Control registers
Eip dd ?
SegCs dd ?
EFlags dd ?
Esp dd ?
SegSs dd ?

ExtendedRegisters db 512 dup (?)
ends

Additional word
---------------
This article was posted on comp.lang.asm.x86.
Especially thanks to Michael Tippach for pointing out some exception flags.
My web page is at http://ams.ampr.org/cdragan/



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Child Window Controls
by Iczelion


In this tutorial, we will explore child window controls which are very
important input and output devices of our programs.

Theory
------
Windows provides several predefined window classes which we can readily use
in our own programs. Most of the time we use them as components of a dialog
box so they're usually called child window controls. The child window
controls process their own mouse and keyboard messages and notify the
parent window when their states have changed. They relieve the burden from
programmers enormously so you should use them as much as possible. In this
tutorial, I put them on a normal window just to demonstrate how you can
create and use them but in reality you should put them in a dialog box.
Examples of predefined window classes are button, listbox, checkbox, radio
button,edit etc.

In order to use a child window control, you must create it with
CreateWindow or CreateWindowEx. Note that you don't have to register the
window class since it's registered for you by Windows. The class name
parameter MUST be the predefined class name. Say, if you want to create a
button, you must specify "button" as the class name in CreateWindowEx. The
other parameters you must fill in are the parent window handle and the
control ID. The control ID must be unique among the controls. The control
ID is the ID of that control. You use it to differentiate between the
controls.

After the control was created, it will send messages notifying the parent
window when its state has changed. Normally, you create the child windows
during WM_CREATE message of the parent window. The child window sends
WM_COMMAND messages to the parent window with its control ID in the low
word of wParam, the notification code in the high word of wParam, and its
window handle in lParam. Each child window control has different
notification codes, refer to your Win32 API reference for more information.


The parent window can send commands to the child windows too, by calling
SendMessage function. SendMessage function sends the specified message with
accompanying values in wParam and lParam to the window specified by the
window handle. It's an extremely useful function since it can send messages
to any window provided you know its window handle.
So, after creating the child windows, the parent window must process
WM_COMMAND messages to be able to receive notification codes from the child
windows.

Application
-----------
We will create a window which contains an edit control and a pushbutton.
When you click the button, a message box will appear showing the text you
typed in the edit box. There is also a menu with 4 menu items:

1. Say Hello -- Put a text string into the edit box
2. Clear Edit Box -- Clear the content of the edit box
3. Get Text -- Display a message box with the text in the edit box
4. Exit -- Close the program.

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?) ; buffer to store the text
retrieved from the edit box

.const
ButtonID equ 1 ; The control ID of the
button control
EditID equ 2 ; The control ID of the
edit control
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3
IDM_EXIT equ 4

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc
hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \
ADDR AppName, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT,\
300,200,NULL,NULL, hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50,35,200,25,hWnd,8,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analysis:

Let's analyze the program.

.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, \
ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or
ES_LEFT\
or ES_AUTOHSCROLL,\
50,35,200,25,hWnd,EditID,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,\
ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax

We create the controls during processing of WM_CREATE message. We call
CreateWindowEx with an extra window style, WS_EX_CLIENTEDGE, which makes
the client area look sunken. The name of each control is a predefined one,
"edit" for edit control, "button" for button control. Next we specify the
child window's styles. Each control has extra styles in addition to the
normal window styles. For example, the button styles are prefixed with
"BS_" for "button style", edit styles are prefixed with "ES_" for "edit
style". You have to look these styles up in a Win32 API reference. Note
that you put a control ID in place of the menu handle. This doesn't cause
any harm since a child window control cannot have a menu.

After creating each control, we keep its handle in a variable for future
use.

SetFocus is called to give input focus to the edit box so the user can type
the text into it immediately.

Now comes the really exciting part. Every child window control sends
notification to its parent window with WM_COMMAND.

.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0

Recall that a menu also sends WM_COMMAND messages to notify the window
about its state too. How can you differentiate between WM_COMMAND messages
originated from a menu or a control? Below is the answer

Low word of wParam High word of wParam lParam
Menu Menu ID 0 0
Control Control ID Notification code Child Window Handle

You can see that you should check lParam. If it's zero, the current
WM_COMMAND message is from a menu. You cannot use wParam to differentiate
between a menu and a control since the menu ID and control ID may be
identical and the notification code may be zero.

.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK


You can put a text string into an edit box by calling SetWindowText. You
clear the content of an edit box by calling SetWindowText with NULL.
SetWindowText is a general purpose API function. You can use SetWindowText
to change the caption of a window or the text on a button.
To get the text in an edit box, you use GetWindowText.

.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF

The above code snippet deals with the condition when the user presses the
button. First, it checks the low word of wParam to see if the control ID
matches that of the button. If it is, it checks the high word of wParam to
see if it is the notification code BN_CLICKED which is sent when the button
is clicked.

The interesting part is after it's certain that the notification code is
BN_CLICKED. We want to get the text from the edit box and display it in a
message box. We can duplicate the code in the IDM_GETTEXT section above but
it doesn't make sense. If we can somehow send a WM_COMMAND message with the
low word of wParam containing the value IDM_GETTEXT to our own window
procedure, we can avoid code duplication and simplify our program.

SendMessage function is the answer. This function sends any message to any
window with any wParam and lParam we want. So instead of duplicating the
code, we call SendMessage with the parent window handle, WM_COMMAND,
IDM_GETTEXT, and 0. This has identical effect to selecting "Get Text" menu
item from the menu. The window procedure doesn't perceive any difference
between the two.

You should use this technique as much as possible to make your code more
organized.

Last but not least, do not forget the TranslateMessage function in the
message loop. Since you must type in some text into the edit box, your
program must translate raw keyboard input into readable text. If you omit
this function, you will not be able to type anything into your edit box.



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Dialog Box as Main Window
by Iczelion


Now comes the really interesting part about GUI, the dialog box. In this
tutorial (and the next), we will learn how to use a dialog box as our main
window.

Theory
------
If you play with the examples in the previous tutorial long enough, you 'll
find out that you cannot change input focus from one child window control
to another with Tab key. The only way you can do that is by clicking the
control you want it to gain input focus. This situation is rather
cumbersome. Another thing you might notice is that I changed the background
color of the parent window to gray instead of normal white as in previous
examples. This is done so that the color of the child window controls can
blend seamlessly with the color of the client area of the parent window.
There is a way to get around this problem but it's not easy. You have to
subclass all child window controls in your parent window.

The reason why such inconvenience exists is that child window controls are
originally designed to work with a dialog box, not a normal window. The
default color of child window controls such as a button is gray because the
client area of a dialog box is normally gray so they blend into each other
without any sweat on the programmer's part.

Before we get deep into the detail, we should know first what a dialog box
is. A dialog box is nothing more than a normal window which is designed to
work with child window controls. Windows also provides internal "dialog box
manager" which is responsible for most of the keyboard logic such as
shifting input focus when the user presses Tab, pressing the default
pushbutton if Enter key is pressed, etc so programmers can deal with higher
level tasks. Dialog boxes are primarily used as input/output devices. As
such a dialog box can be considered as an input/output "black box" meaning
that you don't have to know how a dialog box works internally in order to
be able to use it, you only have to know how to interact with it. That's a
principle of object oriented programming (OOP) called information hiding.
If the black box is *perfectly* designed, the user can make use of it
without any knowledge on how it operates. The catch is that the black box
must be perfect, that's hard to achieve in the real world. Win32 API is
also designed as a black box too.

Well, it seems we stray from our path. Let's get back to our subject.
Dialog boxes are designed to reduce workload of a programmer. Normally if
you put child window controls on a normal window, you have to subclass them
and write keyboard logic yourself. But if you put them on a dialog box, it
will handle the logic for you. You only have to know how to get the user
input from the dialog box or how to send commands to it.

A dialog box is defined as a resource much the same way as a menu. You
write a dialog box template describing the characteristics of the dialog
box and its controls and then compile the resource script with a resource
editor.

Note that all resources are put together in the same resource script file.
You can use any text editor to write a dialog box template but I don't
recommend it. You should use a resource editor to do the job visually since
arranging child window controls on a dialog box is hard to do manually.
Several excellent resource editors are available. Most of the major
compiler suites include their own resource editors. You can use them to
create a resource script for your program and then cut out irrelevant lines
such as those related to MFC.

There are two main types of dialog box: modal and modeless. A modeless
dialog box lets you change input focus to other window. The example is the
Find dialog of MS Word. There are two subtypes of modal dialog box:
application modal and system modal. An application modal dialog box doesn't
let you change input focus to other window in the same application but you
can change the input focus to the window of OTHER application. A system
modal dialog box doesn't allow you to change input focus to any other
window until you respond to it first.

A modeless dialog box is created by calling CreateDialogParam API function.
A modal dialog box is created by calling DialogBoxParam. The only
distinction between an application modal dialog box and a system modal one
is the DS_SYSMODAL style. If you include DS_SYSMODAL style in a dialog box
template, that dialog box will be a system modal one.

You can communicate with any child window control on a dialog box by using
SendDlgItemMessage function. Its syntax is like this:


SendDlgItemMessage proto hwndDlg:DWORD,\
idControl:DWORD,\
uMsg:DWORD,\
wParam:DWORD,\
lParam:DWORD


This API call is immensely useful for interacting with a child window
control. For example, if you want to get the text from an edit control, you
can do this:

call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR
text_buffer

In order to know which message to send, you should consult your Win32 API
reference.

Windows also provides several control-specific API functions to get and set
data quickly, for example, GetDlgItemText, CheckDlgButton etc. These
control-specific functions are provided for programmer's convenience so he
doesn't have to look up the meanings of wParam and lParam for each message.
Normally, you should use control-specific API calls when they're available
since they make source code maintenance easier. Resort to
SendDlgItemMessage only if no control-specific API calls are available.
The Windows dialog box manager sends some messages to a specialized
callback function called a dialog box procedure which has the following
format:

DlgProc proto hDlg:DWORD ,\
iMsg:DWORD ,\
wParam:DWORD ,\
lParam:DWORD

The dialog box procedure is very similar to a window procedure except for
the type of return value which is TRUE/FALSE instead of LRESULT. The
internal dialog box manager inside Windows IS the true window procedure for
the dialog box. It calls our dialog box procedure with some messages that
it received. So the general rule of thumb is that: if our dialog box
procedure processes a message,it MUST return TRUE in eax and if it does not
process the message, it must return FALSE in eax. Note that a dialog box
procedure doesn't pass the messages it does not process to the
DefWindowProc call since it's not a real window procedure.


There are two distinct uses of a dialog box. You can use it as the main
window of your application or use it as an input device. We 'll examine the
first approach in this tutorial.

"Using a dialog box as main window" can be interpreted in two different
senses.

1. You can use the dialog box template as a class template which you
register with RegisterClassEx call. In this case, the dialog box
behaves like a "normal" window: it receives messages via a window
procedure referred to by lpfnWndProc member of the window class, not
via a dialog box procedure. The benefit of this approach is that you
don't have to create child window controls yourself, Windows creates
them for you when the dialog box is created. Also Windows handles the
keyboard logic for you such as Tab order etc. Plus you can specify the
cursor and icon of your window in the window class structure.
Your program just creates the dialog box without creating any parent
window. This approach makes a message loop unnecessary since the
messages are sent directly to the dialog box procedure. You don't even
have to register a window class!

This tutorial is going to be a long one. I'll present the first approach
followed by the second.

Application
-----------

------------------------------------------------------------------------
dialog.asm
------------------------------------------------------------------------

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "DLGCLASS",0
MenuName db "MyMenu",0
DlgName db "MyDialog",0
AppName db "Our First Dialog Box",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)

.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc
hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hDlg:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,DLGWINDOWEXTRA
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
mov hDlg,eax
invoke ShowWindow, hDlg,SW_SHOWNORMAL
invoke UpdateWindow, hDlg
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
------------------------------------------------------------------------
Dialog.rc
------------------------------------------------------------------------

#include "resource.h"

#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002

#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003


MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our First Dialog Box"
CLASS "DLGCLASS"
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13, WS_GROUP
END


MyMenu MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END

Analysis
--------

Let's analyze this first example.
This example shows how to register a dialog template as a window class and
create a "window" from that class. It simplifies your program since you
don't have to create the child window controls yourself.
Let's first analyze the dialog template.

MyDialog DIALOG 10, 10, 205, 60

Declare the name of a dialog, in this case, "MyDialog" followed by the
keyword "DIALOG". The following four numbers are: x, y , width, and height
of the dialog box in dialog box units (not the same as pixels).

STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK

Declare the styles of the dialog box.

CAPTION "Our First Dialog Box"

This is the text that will appear in the dialog box's title bar.

CLASS "DLGCLASS"

This line is crucial. It's this CLASS keyword that allows us to use the
dialog box template as a window class. Following the keyword is the name of
the "window class"

BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END

The above block defines the child window controls in the dialog box.
They're defined between BEGIN and END keywords. Generally the syntax is as
follows:

control-type "text" ,controlID, x, y, width, height [,styles]

control-types are resource compiler's constants so you have to consult the
manual.

Now we go to the assembly source code. The interesting part is in the
window class structure:

mov wc.cbWndExtra,DLGWINDOWEXTRA
mov wc.lpszClassName,OFFSET ClassName

Normally, this member is left NULL, but if we want to register a dialog box
template as a window class, we must set this member to the value
DLGWINDOWEXTRA. Note that the name of the class must be identical to the
one following the CLASS keyword in the dialog box template. The remaining
members are initialized as usual. After you fill the window class
structure, register it with RegisterClassEx. Seems familiar? This is the
same routine you have to do in order to register a normal window class.

invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL

After registering the "window class", we create our dialog box. In this
example, I create it as a modeless dialog box with CreateDialogParam
function. This function takes 5 parameters but you only have to fill in the
first two: the instance handle and the pointer to the name of the dialog
box template. Note that the 2nd parameter is not a pointer to the class
name.

At this point, the dialog box and its child window controls are created by
Windows. Your window procedure will receive WM_CREATE message as usual.

invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax

After the dialog box is created, I want to set the input focus to the edit
control. If I put these codes in WM_CREATE section, GetDlgItem call will
fail since at that time, the child window controls are not created yet. The
only way you can do this is to call it after the dialog box and all its
child window controls are created. So I put these two lines after the
UpdateWindow call. GetDlgItem function gets the control ID and returns the
associated control's window handle. This is how you can get a window handle
if you know its control ID.

invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF

The program enters the message loop and before we translate and dispatch
messages, we call IsDialogMessage function to let the dialog box manager
handles the keyboard logic of our dialog box for us. If this function
returns TRUE , it means the message is intended for the dialog box and is
processed by the dialog box manager. Note another difference from the
previous tutorial. When the window procedure wants to get the text from the
edit control, it calls GetDlgItemText function instead of GetWindowText.
GetDlgItemText accepts a control ID instead of a window handle. That makes
the call easier in the case you use a dialog box.
------------------------------------------------------------------------

Now let's go to the second approach to using a dialog box as a main window.
In the next example, I 'll create an application modal dialog box. You'll
not find a message loop or a window procedure because they're not
necessary!
------------------------------------------------------------------------
dialog.asm (part 2)
------------------------------------------------------------------------

.386
.model flat,stdcall
option casemap:none

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
DlgName db "MyDialog",0
AppName db "Our Second Dialog Box",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)

.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002


.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL

invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSEIF ax==IDM_EXIT
invoke EndDialog, hWnd,NULL
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.if dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc endp
end start
------------------------------------------------------------------------
dialog.rc (part 2)
------------------------------------------------------------------------

#include "resource.h"

#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002

#define IDR_MENU1 3003

#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003


MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our Second Dialog Box"
MENU IDR_MENU1
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END


IDR_MENU1 MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END

------------------------------------------------------------------------

The analysis follows:

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

We declare the function prototype for DlgProc so we can refer to it with
addr operator in the line below:

invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL

The above line calls DialogBoxParam function which takes 5 parameters: the
instance handle, the name of the dialog box template, the parent window
handle, the address of the dialog box procedure, and the dialog-specific
data. DialogBoxParam creates a modal dialog box. It will not return until
the dialog box is destroyed.

.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0

The dialog box procedure looks like a window procedure except that it
doesn't receive WM_CREATE message. The first message it receives is
WM_INITDIALOG. Normally you can put the initialization code here. Note that
you must return the value TRUE in eax if you process the message.
The internal dialog box manager doesn't send our dialog box procedure the
WM_DESTROY message by default when WM_CLOSE is sent to our dialog box. So
if we want to react when the user presses the close button on our dialog
box, we must process WM_CLOSE message. In our example, we send WM_COMMAND
message with the value IDM_EXIT in wParam. This has the same effect as when
the user selects Exit menu item. EndDialog is called in response to
IDM_EXIT.

The processing of WM_COMMAND messages remains the same.
When you want to destroy the dialog box, the only way is to call EndDialog
function. Do not try DestroyWindow! EndDialog doesn't destroy the dialog
box immediately. It only sets a flag for the internal dialog box manager
and continues to execute the next instructions.

Now let's examine the resource file. The notable change is that instead of
using a text string as menu name we use a value, IDR_MENU1. This is
necessary if you want to attach a menu to a dialog box created with
DialogBoxParam. Note that in the dialog box template, you have to add the
keyword MENU followed by the menu resource ID.

A difference between the two examples in this tutorial that you can readily
observe is the lack of an icon in the latter example. However, you can set
the icon by sending the message WM_SETICON to the dialog box during
WM_INITDIALOG.



::/ \::::::.
:&#

  
47;___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Standardizing Win32 Callback Procedures
by Jeremy Gordon


This short article describes my preferred method for coding CALLBACK procedures
in a large assembler program for Windows 32. First I describe what Win32
callback procedures are, and then get down to some code.

At run time the Win32 system will call your program on a regular and frequent
basis. The procedures you supply for the system to call are called CALLBACK
procedures. Here are examples of when these are used:-
1. To manage a window you created. In this case the system will send many
messages to the Window Procedure for the window. The Window Procedure is
the code label you provide when you register your window class (by calling
RegisterClass). For example the message WM_SIZE is sent by the system
when the window is resized.
2. To inform the owner of a child window of events in the child window. For
example WM_PARENTNOTIFY (with a notify code) is sent to the Window
Procedure of the owner of a window when the child window is being created
or destroyed, or if the user clicks a mouse button while the cursor is
over the child window.
3. To inform the owner of a common control of events in the control. For
example if you create a button owned by your window the Window Procedure
for that window receives BN_CLICKED messages if the button is clicked.
4. Messages sent to a dialog you have created. These are messages relating
to the creation of the dialog and of the various controls. The dialog
procedure is informed of events in the controls.
5. If you "Superclass" or "Subclass" a common control, you receive messages
for that common control like a hook procedure but your window procedure
has the responsibility of passing them on to the control.
6. If you create "Hook" procedures you can intercept messages about to be sent
to other windows. The system will call your hook procedure and will pass
the message on only when your hook procedure returns.
7. You can ask the system to provide your program with information to be sent
to a CALLBACK procedure. Examples are EnumWindows (enumerate all top-level
windows) or EnumFonts (enumerate all available fonts).

In cases 1 to 5 above, just before the system calls the CALLBACK procedure,
it PUSHES 4 dwords on the stack (ie. 4 "parameters"). Traditionally the
names given to these parameters are:-
hWnd = handle of window being called
uMsg = message number
wParam = a parameter sent with the message
lParam = another parameter sent with the message.

The number of parameters sent to hook procedures and emumeration
callbacks varies - see the Window SDK.

Since your Window (or Dialog) procedure will need to react in a certain
way depending on the message being sent, your code will need to divert
execution to the correct place for a particular message.

"C" programmers have the advantage of being able to code this simply,
using "switch" and "case".

Assembler programmers use various techniques. Perhaps the worst if there are
a lot of messages to handle is the chain of compares, eg. (in A386 format):-
MOV EAX,[EBP+0Ch] ;get message number
CMP EAX,1h ;see if WM_CREATE
JNZ >L2 ;no
XOR EAX,EAX ;ensure eax is zero on exit
JMP >L32 ;finish
L2:
CMP EAX,116h ;see if WM_INITMENU
JNZ >L4 ;no
CALL INITIALISE_MENU
JMP >L30 ;correct exit code
L4:
CMP EAX,47h ;see if WM_WINDOWPOSCHANGED
JNZ >L8
and so on ........

To avoid these long chains, assembler programmers have developed various
techniques. You will have seen many of these in sample code around Win32
assembler web sites and in the asm journal, using conditional jumps, macros
or table scans. I do not wish to compare these various methods, merely to put
forward my own current favourite, which I believe has these advantages:-
1. It works on all assemblers
2. It is modular, ie. the code for each window can be concentrated in a
particular part of your source code
3. It is easy to follow from the source code what message causes what result
4. The same function can easily be called from within different window
procedures

My method results in a very simple Window Procedure as follows (A386 format):-

WndProc:
MOV EDX,OFFSET MAINMESSAGES
CALL GENERAL_WNDPROC
RET 10h

where the messages and functions (specific to this particular window
procedure) are set out in a table such as this:-

;----------------------------------------------------------
DATA SEGMENT FLAT ;assembler to put following in data section
;--------------------------- WNDPROC message functions
MAINMESSAGES DD ENDOF_MAINMESSAGES-$ ;=number to be done
DD 312h,HOTKEY,116h,INITMENU,117h,INITMENUPOPUP,11Fh,MENUSELECT
DD 1h,CREATE,2h,DESTROY, 410h,OWN410,411h,OWN411
DD 231h,ENTERSIZEMOVE,47h,WINDOWPOSCHANGED,24h,GETMINMAXINFO
DD 1Ah,SETTINGCHANGE,214h,SIZING,46h,WINDOWPOSCHANGING
DD 2Bh,DRAWITEM,0Fh,PAINT,113h,TIMER,111h,COMMAND
DD 104h,SYSKEYDOWN,100h,KEYDOWN,112h,SYSCOMMAND
DD 201h,LBUTTONDOWN,202h,LBUTTONUP,115h,SCROLLMESS
DD 204h,RBUTTONDOWNUP,205h,RBUTTONDOWNUP
DD 200h,MOUSEMOVE,0A0h,NCMOUSEMOVE,20h,SETCURSORM
DD 4Eh,NOTIFY,210h,PARENTNOTIFY,86h,NCACTIVATE,6h,ACTIVATE
DD 1Ch,ACTIVATEAPP
ENDOF_MAINMESSAGES: ;label used to work out how many messages
;----------------------------------------------------------
_TEXT SEGMENT FLAT ;assembler to put following in code section
;----------------------------------------------------------

and where each of the functions here are procedures, for example:-

CREATE:
XOR EAX,EAX ;ensure zero and nc return
RET

and where GENERAL_WINDPROC is as follows:-

GENERAL_WNDPROC:
PUSH EBP
MOV EBP,[ESP+10h] ;get uMsg in ebp
MOV ECX,[EDX] ;get number of messages to do * 8 (+4)
SHR ECX,3 ;get number of messages to do
ADD EDX,4 ;jump over size dword
L33:
DEC ECX
JS >L46 ;s=message not found
CMP [EDX+ECX*8],EBP ;see if its the correct message
JNZ L33 ;no
MOV EBP,ESP
PUSH ESP,EBX,EDI,ESI ;save registers as required by Windows
ADD EBP,4 ;allow for the extra call to here
;now [EBP+8]=hWnd,[EBP+0Ch]=uMsg,[EBP+10h]=wParam,[EBP+14h]=lParam
CALL [EDX+ECX*8+4] ;call correct procedure for the message
POP ESI,EDI,EBX,ESP
JNC >L48 ;nc=don't call DefWindowProc eax=exit code
L46:
PUSH [ESP+18h],[ESP+18h],[ESP+18h],[ESP+18h] ;ESP changes on push
CALL DefWindowProcA
L48:
POP EBP
RET


NOTES:
-------------------------------------------------------------------------------
1. Instead of giving the actual message value, you can, of course, give
the name of an EQUATE. For example
WM_CREATE EQU 1h
enables you to use WM_CREATE,CREATE instead of 1h,CREATE if you wish.
2. It is tempting to keep the message table in the CODE SECTION. This is
perfectly possible because the only difference to the Win32 system between
the code section and the data section is that the code section area of
memory is marked read only, whereas the data section is read/write.
However, you may well get some loss of performance if you do this because
most processors will read data more quickly from the data section.
I performed some tests on this and found that having the table in the code
section rather than the data section could slow the code considerably:-
486 processor - 22% to 36% slower
Pentium processor - 94% to 161% slower
AMD-K6-3D processor - 78% to 193% slower
(but Pentium Pro - from 7% faster to 9% slower)
(and Pentium II - from 29% faster to 5% slower)
These tests were carried out on a table of 60 messages and the range of
results is because tests were carried out varying the number of scans
required before a find and also testing a no-find.
3. The procedure names must not be the names of API imports to avoid
confusion! For example change SETCURSOR slightly to avoid confusion
with the API SetCursor.
4. If a function returns c (carry flag set) the window procedure will call
DefWindowProc. An nc return (carry flag not set) will merely return to
the system with the return code in eax. (Some messages must be dealt with
in this way).
5. You can send a parameter of your own to GENERAL_WNDPROC using EAX.
This is useful if you wish to identify a particular window.
For example:-
SpecialWndProc:
MOV EAX,OFFSET hSpecialWnd
MOV EDX,OFFSET SPECIALWND_MESSAGES
CALL GENERAL_WNDPROC
RET 10h
6. The ADD EBP,4 just before the call to the function is to ensure that
EBP points to the parameters the stack in the same way as if the window
procedure had been entered normally. This is intended to ensure that
the function will be compatible if called by an ordinary window procedure
written in assembler, for example:-
WndProc:
PUSH EBP
MOV EBP,ESP
;now [EBP+8]=hWnd,[EBP+0Ch]=uMsg,[EBP+10h]=wParam,[EBP+14h]=lParam
7. A standardized procedure for dealing with messages to a dialog procedure
can also be created in the same way, except that it should return TRUE
(eax=1) if the message is processed and FALSE (eax=0) if it is not, without
calling DefWindowProc. The same coding method can be applied to hooks and
to enumerator CALLBACKS although these will vary.

jorgon@compuserve.com
http://ourworld.compuserve.com/homepages/jorgon



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::............................................THE.UNIX.WORLD
Fire Demo ported to Linux SVGAlib
by Jan Wagemakers


In APJ4 there was a little nice fire demo written in DOS assembly language.
I have ported this program to Linux assembly language. It is written in the
AT&T-syntax (GNU assembler) and makes use of SVGAlib.

My main goal of porting this program to Linux was to show that it can be
done. So, I have not optimized this program. For example, things like 'call
ioperm' can also be done by making use of int 0x80; quite possibly making use
of int 0x80 will make the program smaller. More information about int 0x80 is
available at Konstantin Boldyshev's webpage [http://lightning.voshod.com/asm].

With SVGALib you can access the screen memory directly, just like you
would write to A000:0000 in a DOS asm-program.

I like to thank 'paranoya' for his explanation about how to make use of
SVGAlib. Anyway, enough blablabla, here is the source ;-)

# fire.s : fire.asm of apj 4 ported to Linux/SVGAlib ==========================
# gcc -o fire fire.s -lvga
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp

call vga_init # Init vga
pushl $5
call vga_setmode # set mode to 5 = 320x200x256
addl $4,%esp
pushl $0
call vga_setpage # Point to page 0 (There is only 1 page)
addl $4,%esp

pushl $0x3c8 # Get IOpermission, starting from 3c8h
pushl $2 # to 3c9h
pushl $1 # Turn On value
call ioperm
addl $12,%esp

pushl $0x60 # Get IOpermission, for 60h : keyboard
pushl $1
Pushl $1
call ioperm
addl $12,%esp

inb $0x60,%al # Read current value of keyboard
movb %al,key

movw $0x3c8,%dx
movw $0,%ax
outb %al,%dx
incw %dx
lus:
outb %al,%dx
outb %al,%dx
outb %al,%dx
incw %ax
jnz lus

movl graph_mem,%ebx

Mainloop:
movl $1280,%esi # mov si,1280 ;
movl $0x5d00,%ecx # mov ch,5dh ; y-pos, the less the faster demo
pushl %esi # push si
pushl %ecx # push cx
Sloop:
movb (%ebx,%esi),%al # lodsb
incl %esi #


addb (%ebx,%esi),%al # al,[si] ; pick color and
addb 320(%ebx,%esi),%al # add al,[si+320] ; pick one more and
shrb $2,%al # shr al,2

movb %al,-960(%ebx,%esi) # mov [si-960],al ; put color

loop Sloop

popl %edi # pop di
popl %ecx # pop cx

Randoml:
mulw 1(%ebx,%edi) # mul word ptr [di+1] ; 'random' routine.
incw %ax
movw %ax,(%ebx,%edi) #stosw
incl %edi
incl %edi
loop Randoml

inb $0x60,%al
cmpb key,%al
jz Mainloop

pushl $0
call exit
addl $4,%esp

movl %ebp,%esp
popl %ebp
ret

.data
key:
.byte 0
# =============================================================================



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................ASSEMBLY.LANGUAGE.SNIPPETS
Abs
by Chris Dragan

;Summary: Calculates absolute value of a signed integer in eax.
;Compatibility: 386+
;Notes: 9 bytes, 4 clocks (P5), destroys ecx
mov ecx, eax ; Duplicate value
shr ecx, 31 ; Fill ecx with its sign
xor eax, ecx ; Do 'not eax' if negative
sub eax, ecx ; Do 'inc eax' if negative

; For comparison, the standard way (2-8 clocks on P5 and 1-17 on P6):
; or eax, eax
; js @@1
; neg eax
;@@1:

Min
by Chris Dragan

;Summary: eax = min (eax, ecx) (both eax and ecx unsigned)
;Compatibility: 386+
;Notes: 8 bytes, 4 clocks (P5), destroys ecx and edx
sub ecx, eax ; ecx = n2 - n1
sbb edx, edx ; edx = (n1 > n2) ? -1 : 0
and ecx, edx ; ecx = (n1 > n2) ? (n2 - n1) : 0
add eax, ecx ; eax += (n1 > n2) ? (n2 - n1) : 0
; Standard cmp/jbe/mov takes 2-8 clocks on P5 and 1-17 on P6

Max
by Chris Dragan

;Summary: eax = max (eax, ecx) (both eax and ecx unsigned)
;Compatibility: 386+
;Notes: 9 bytes, 5 clocks (P5), destroys ecx and edx
sub ecx, eax ; ecx = n2 - n1
cmc ; cf = n1 <= n2
sbb edx, edx ; edx = (n1 > n2) ? 0 : -1
and ecx, edx ; ecx = (n1 > n2) ? 0 : (n2 - n1)
add eax, ecx ; eax += (n1 > n2) ? 0 : (n2 - n1)
; Standard cmp/jae/mov takes 2-8 clocks on P5 and 1-17 on P6

OBJECT
by mammon_
;Summary: Primitive for defining dynamic objects
;Compatibility: NASM
;Notes: The basic building block for classes in NASM; part of
; an ongoing project of mine. Note that .this can be
; filled with the instance pointer, and additional
; routines such as .%1 [constructor] and .~ can be added.
%macro OBJECT 1
struc %1
.this: resd 1
%endmacro
%macro END_OBJECT 0
endstruc
%endmacro

;_Sample:________________________________________________________________
;OBJECT MSGBOX
; .hWnd: resd 1
; .lpText: resd 1
; .lpCapt: resd 1
; .uInt: resd 1
; .show: resd 1
;END_OBJECT
;;MyMBox is a pointer to a location in memory or in an istruc; its members
;;are filled in an init routine ['new'] with "show" being "DD _show"
;_show: ;MSGBOX class display routine
; push dword [MyMbox + MSGBOX.uInt]
; push dword [MyMbox + MSGBOX.lpCapt]
; push dword [MyMbox + MSGBOX.lpText]
; push dword [MyMbox + MSGBOX.hWnd]
; call MessageBoxA
; ret
;..start:
; call [MyMbox + MSGBOX.show]
; ret



::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................ISSUE.CHALLENGE
Binary-to-ASCII
by Jan Verhoeven


The Challenge
-------------
Write a routine to convert the value of a bit to ASCII in under 10 bytes, with
no conditional jumps.

The Solution
------------
Load the number into the AX register and shift through the bits. If a bit is
cleared [0], you want to print a "0" character; if a bit is set [1], you want
to print a "1".

Prime the BL register with the ASCII character "0"; if the next bit in AX is
set, carry will be set after the SHL and BL will thus be incremented to an
ASCII "1". The key, as you will see, is the ADC [AddWithCarry] instruction:

L0: B330 MOV BL,30 ; try with al = ZERO
D1E0 SHL AX,1 ; ... but if bit = set, ...
80D300 ADC BL,00 ; ... make it a ONE,

7 bytes all told; with a loop and mov instruction for storing each value in
BL to the location of your choice, you will have a full-fledged binary-to-
ascii converter in a handful of bytes.




::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::.......................................................FIN

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT