The "Go" tools
The GoAsm manual
by Jeremy Gordon -
This file is intended for those interested in 32 bit assembler programming, in particular for Windows.
All programs make extensive use of the stack at run-time. If you program in a high level language you may not realise that the compiler will be making use of the stack at all. But as an assembler programmer you will be fully aware of this since the stack is one of the main tools at your disposal. By using it directly, you can realise its full potential in your programs. Although you can program in assembler without knowing anything about the stack, it is helpful to know and understand something about it.
In Part 1 you will find information which you ought to understand if you are going to do any serious assembler programming.
Features and advantages of the stack
Basically the stack is an area of dwords (32 bit data areas) in memory at run-time which your application can use to store data temporarily. It has certain features and real advantages over other types of memory storage (data sections and run-time memory areas). They are:-
Practical uses for the stackcontentsThe stack can be used to:-
The stack pointer: ESP registercontentsThe register ESP (literally "extended stack pointer") holds the top of the stack. This is the point where the instructions which use the stack (PUSH, POP, CALL and RET) actually use the stack. More about this later.
The register EBP (literally "extended base pointer") is traditionally moved by the programmer to a particular place on the stack at any one time, so that data on the stack can be read from and written to using base index addressing. For example in the instruction MOV EAX,[EBP+8h] the register EBP is used as an index to an area of the stack and this instruction will move a dword from 8 bytes further down the stack into the register EAX. The traditional use of EBP for this purpose comes from the days of 16-bit processing. Under 16-bits the register was just "BP" holding 16-bits of data and unlike other registers unless a segment override was used it always addressed the stack in memory. Now under 32 bits, segments have been abolished and each program runs in its own 4GB of address space. EBP therefore can address the whole of this address space and is not limited to addressing the stack unless an override is used. It can therefore now be used as a general purpose register, but because of this history it is still used to address particular areas of the stack mainly to access parameters passed to function and callback routines and to address local data.
PUSHing data onto, and POPing data from, the stackcontentsThe stack can be visualised as a caterer's plate dispenser. This works on a "last in first out" basis. The last plate pushed on the dispenser using the PUSH instruction will be the first one removed using the POP instruction. The stack pointer in ESP always points to this top plate.
Lets look at this in a more traditional form. Suppose the value of ESP is 64FE3Ch and you have the following instructions in your source code:-
PUSH 2 PUSH [hWnd] PUSH ADDR STRINGThen after these three instructions ESP would be 64FE30h (that is 12 bytes or 3 dwords less in value) and the stack would look like this:-
Note that each PUSH instruction decreases the value of ESP
by 4 bytes.
Now lets try some POPping. Using the same values on the stack, lets use the following instructions:-
POP EAX POP EBX POP ECXThen after these three instructions the stack would look like this:-
The first thing to note here is that after these three instructions ESP
is back at 64FE3Ch. This means that ESP has been restored to equilibrium.
This is an important concept, see below.
Preserving register values in functionscontentsPrograms written in assembler are fast because they make use of the registers as much as possible. This often means however, that the contents of the registers at any one time have to be preserved for later use. So for example, suppose a file handle is in EDI, and after carrying out some calculations using EDI you will eventually need to close the file handle, then you might preserve it like this:-
PUSH EDI ;save file handle CALL CALCULATE ;make some calculations (uses EDI) POP EDI ;restore file handle CALL CLOSE_FILEHANDLE ;close the file handle contained in EDIAs an alternative you could also preserve EDI within the CALCULATE procedure itself for example:-
CALL CALCULATE ;make some calculations (saves EDI) CALL CLOSE_FILEHANDLE ;close the file handle contained in EDIand the CALCULATE function would be:-
CALCULATE: PUSH EDI ;save file handle . . ;some code using EDI . POP EDI ;restore file handle RETAnother reason why a register may need to be preserved is if a particular function is called externally (by another function in the same program, from another program or by the system). In most cases you would ensure that the EBP, EBX, EDI and ESI are preserved. This is certainly a requirement of a "C" program if it calls a routine written in assembler, and it is also a requirement of callback procedures called by Windows itself. An example of such a callback procedure is a window procedure which is used by the system to pass messages to a window in your application. In such circumstances you would ensure these registers are preserved by using for example:-
PUSH EBP,EBX,EDI,ESI . . ;your code goes here . POP ESI,EDI,EBX,EBPOf course if your code does not change these registers anyway you could leave out some of the PUSHes and POPs, but it may possibly be regarded as good practice anyway to include them in case you add to your code and forget to ensure that these registers are preserved. Note how the POPs are all in the reverse order: this is because of the "last in, first out" nature of the stack. Note also in the code above, I have put the registers in alphabetical order. This helps to keep track of the PUSHes and it is easy to check that the POPs are in reverse alphabetical order too.
If you prefer, in GoAsm you can use the USES statement which will preserve and restore the registers automatically for you.
Preserving data in memorycontentsJust as you can preserve the value in a register using the stack, you can use it to preserve data in memory. Suppose for example you have carefully calculated the number of widgets and want to write details of the widgets both to the screen and also to a file. You might use the following code:-
PUSH [NOOF_WIDGETS] ;keep number of widgets L2: CALL REPORT_WIDGET ;write details of the widget to the screen DEC D[NOOF_WIDGETS] ;count down the number of widgets JNZ L2 ;continue with next while not yet zero POP [NOOF_WIDGETS] ;restore the number of widgets CALL WRITETO_FILE ;and make a similar report to the file
Move data around without using registerscontentsSuppose you want to move the number of widgets to another memory label. You could use:-
MOV EAX,[NOOF_WIDGETS] MOV [COPYOF_NOOF_WIDGETS],EAXBut equally effective would be:-
PUSH [NOOF_WIDGETS] POP [COPYOF_NOOF_WIDGETS]Since this makes no use of the EAX register, that register would not lose its value and could therefore be used elsewhere.
Reverse data ordercontentsYou can take advantage of the "last in, first out" nature of the stack to reverse the order of data. One handy use for this is in writing to the screen a value in decimal. Here is an example (where EAX holds the value to write to the screen in decimal and EDI holds the position in memory of a buffer which will hold the string):-
XOR EDX,EDX ;zero edx XOR ECX,ECX ;zero ecx (used as a counter) MOV EBX,10 ;ebx always holds the value 10 L2: DIV EBX ;div edx:eax by 10 = quotient in eax, remainder in edx PUSH EDX ;keep result on the stack INC ECX ;count how many are done XOR EDX,EDX ;zero edx CMP EAX,EDX ;see if any more to do JNZ L2 ;yes L3: ;now reverse the order of the digits POP EAX ;get next back from the stack ADD AL,48 ;convert to ascii number STOSB ;write ascii number to buffer LOOP L3 ;continue while ecx is not zeroLets just follow this code for a moment. Suppose the value in EAX is 123 decimal. The first divide by ten puts 12 in EAX and 3 in EDX. 3 is put on the stack. The second divide by ten puts 1 in EAX and 2 in EDX. 2 is put on the stack. The third divide puts zero in EAX and 1 in EDX. 1 is put on the stack. The result of CMP EAX,EDX is then zero and the code drops to the label L3. ECX is now 3 because this has counted the number of digits done. Each is now stripped off the stack in turn. So 48 is added to the values 1, 2 and 3 to make 49, 50 and 51 respectively. These are written to the buffer and are the ascii characters "1", "2", and "3" ready to be written to the screen later on.
How CALL and RET use the stackcontentsThe CALL instruction is used a lot in programming. It is used to divert execution to a particular procedure (or "function"). When the procedure is finished, execution continues at just after the call. Calling procedures helps to keep your source code clear and obvious, for example:-
MOV EAX,EDX CALL CALCULATE_ASSETS MOV [ASSETS],EAX ;keep result of the call in memoryHere no doubt a lot of hard work is done by the CALCULATE_ASSETS procedure, but there is no need to worry about how it works when looking at this excerpt from the source script.
Using calls also helps to keep your code modular. The CALCULATE_ASSETS procedure above, may also be usable in other programs. If you like you can regard it as an "object". Basically this is object orientated programming.
So how does the processor know where to continue processing after the call? Well it inserts the return address on the stack!
Lets look at the stack when this happens. Suppose the value of ESP is 64FE3Ch again and you have the following instructions in your source code:-
401020: MOV EAX,EDX 401022: CALL CALCULATE_ASSETS 401027: MOV [ASSETS],EAX ;keep result of the call in memoryI have added here the address of execution on the left hand side to illustrate what happens. After the first instruction of course ESP is still 64FE3Ch and the stack has not changed, because a MOV instruction does not affect the stack in any way. But when the next instruction CALL CALCULATE_ASSETS is executed the processor PUSHes on the stack the return address 401027h. Now in the CALCULATE_ASSETS procedure there is a RET instruction (return to caller) for example:-
CALCULATE_ASSETS: ;various code here RET ;return to callerThe RET instruction causes in effect, a POP into EIP in other words whatever is at [ESP] is given to EIP (the instruction pointer) and then ESP is increased by 4 bytes.
So lets look at the stack before, during and after these instructions:-
Note how after the call, ESP is restored to equilibrium.
Importance of stack equilibriumcontentsWe have seen how a procedure can be called and the return execution address is kept on the stack. Now procedures often call other procedures, which often call others and so on. So you might have, for example:-
CALCULATE_ASSETS: CALL CALCULATE_FIXEDASSETS RET ;return to callerand
CALCULATE_FIXEDASSETS: ;various code CALL GET_COSTVALUES CALL ADJUSTFOR_DEPRECIATION ADD ESP,4 ;make esp out of equilibrium RETHere each part of the task is divided into various components. Now suppose the procedure CALCULATE_FIXEDASSETS adds 4 to ESP by mistake. If this happens, when the RET instruction is executed the instruction pointer EIP will be loaded with the wrong value and the program will crash.
While a procedure is running it is often the case that ESP will have been moved (for example when making space on the stack for, but it is vital to ensure that the equilibrium of the stack is restored when a procedure is about to end. When are sent on the stack this can be done with the RET xx instruction or by adjusting ESP to suit the number of parameters.
Stack equilibrium is also important to return to Windows in a minimalist application. The simplest possible Windows application which does nothing is as follows:-
START: RETWhere START is the entry point of the application. In fact, normally Windows calls your application from Kernel32.dll, and so a simple RET ends the program quite happily.
Using the stack to pass parameterscontentsThe Windows APIs expect to receive parameters on the stack. So when you call an API you will be PUSHing the parameters on the stack so that they can be retrieved by the API. So in this code:-
PUSH 1,[hButton] CALL EnableWindow ;enable buttonYou push on the stack the value 1 (ENABLE flag) first, followed by the handle to the window you want to enable. Windows uses the stdcall "C" convention for its APIs so on return from the API the stack will be back to equilibrium. The convention also means that the EBP,EBX,ESI and EDI are always restored to their previous values by the API.
Another aspect of the convention is that parameters are always pushed from right to left. The specifications for the API EnableWindow are given in the Windows Software Development Kit as:-
WINAPI EnableWindow( HWND hWnd, BOOL bEnable);In translating this to assembler, you need to read from the right hand side first. This may be made slightly easier if you use the INVOKE instruction instead of CALL, since the parameters are now in the order in which they appear in the SDK:-
INVOKE EnableWindow, [hButton], 1That's all you need to know about passing parameters on the stack for the moment, but it is covered in more detail in understanding the stack (part 2).
Copyright © Jeremy Gordon 2002-2003