Programming 版 (精华区)
发信人: zhaowei (还珠格格), 信区: Programming
标 题: 汇编杂志第三期
发信站: 紫 丁 香 (Tue Jun 1 19:13:56 1999), 转信
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::. Feb/March 98
:::\_____\::::::::::. Issue 3
::::::::::::::::::::::.........................................................
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_
"An Introduction to SPARC assembly"............................+Spath.
"Extending NASM"...............................................mammon_
Column: Win32 Assembly Programming
"NASM specific Win32 coding".......................Tamas Kaproncai
"More about Text".........................................Iczelion
"Keyboard Input"..........................................Iczelion
Column: The C standard library in Assembly
"C string functions: introduction, _strlen".................Xbios2
"C string functions: _strcpy"...............................Xbios2
Column: The Unix World
"X-Windows in Assembly Language: Part II"..................mammon_
Column: Virtual Machines
"An Intro to the Java Virtual Machine"............Cynical Pinnacle
Column: Assembly Language Snippets
"NumFactors"..........................................Troy Benoist
Column: Issue Solution
"6-byte Solution"..........................................mammon_
----------------------------------------------------------------------
++++++++++++++++++++++++Issue Challenge+++++++++++++++++++++
Write a routine for converting ASCII hex to binary in 6 bytes
----------------------------------------------------------------------
____________________________________________________________________________
___ .___ __) (__ _____ ______ ```
._____| \____\ ___/__._) /._) _ (_. \\
| | _\ |_ \ | \/ | |CE ,
.=|_____|___)\___|(_______|______| |===============[ Introduction ]===.
'================================| :=================================='
: . by mammon_
The first thing that you will notice about this issue --well, that it is late--
will probably be the section headers designed by iCE. I had to add a top/upper
left border to them [the horizontal and slanted lines] in order to make them
standout when scrolling though a 100K file such as this one, but other than
they are all his: comments, etc welcome.
I don't have much to say about this issue: I went overboard with the NASM stuff
this month as I have been doing a lot of 'research' work in that area recently;
my articles have been supplemented with Tamas Kaproncai's Win32 NASM pointers.
Iczelion and XBios2 have both produced --as usual-- 2 quality articles this
month, Iczelion's based on his win32 asm tutorial 'the MASM way', and XBios2
once again continuing to replace C with assembler.
+Spath. has produced an excellent article on SPARC assembly language; I was
hoping to debut the 'other CPU' scene with a MIPS article I had planned but it
looks like +Spath has beat me to it.
On a similar note, I mentioned on the Message Board wanting to start a Virtual
Machines column. Cynical Pinnacle has started the column off this month with an
article on programming the Java VM in its native 'assembly language'; in
subsequent issues I and perhaps others will be adding articles here as well.
A final note, I have not come up with a challenge for the next issue; anyone
with good ideas is welcome to post one to the Message Board or to the APJ
email address.
Enjoy the mag!
_m
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
An introduction to SPARC assembly
by +Spath.
The goal of this article is to introduce SPARC v8 architecture and SPARC
assembly ; I hope it can also constitute a good introduction to RISC
philosophy.
What is SPARC ?
-----------------
The principles of RISC (Reduced Instructions Set Computer) are born in the
early 80's in two universities (Berkeley and Stanford) ; its philosophy is
the quest for simplicity and CPU speed. SPARC (Scalable Processor ARChitecture)
is a 32 bits RISC architecture created by Sun in 1987. It's an open
architecture, so that any manufaturer can make SPARC processors (like Philips,
VLSI, T.I., Fujitsu... already did). Its key features are :
- a load/store architecture : this means that only registers can be used
in data manipulation operations, and not memory locations. Memory is
organised in a linear address space of 2^32 bits which use "big-endian"
organisation (the MSB is stored first) ; a word is 32 bits wide (a 16 bits
data is a halfword).
- a large number of registers : from 2 to 32 sets of 24 general purpose
registers are available ; these 24 registers are local registers %l[0-7],
in registers %i[0-7] and out registers %o[0-7], all working in an
overlapping windows mechanism that will be explained later. The SPARC
architecture also provides 8 global registers %g[0-7], 32 registers for
floating-point operations (%f[0-31]) and some specific registers (%pc, %sp,
%psr, %y,...).
- a small set of simple instructions : to avoid translation from machine code
to microcode, SPARC instructions are directly implemented in hardware, and
therefore are very basic (mainly load/store, logical, arithmetic, branching).
All instructions are 4 bytes long, and most of them use 3 registers (source1,
source2, destination in that order). Assemblers also provide a set of
synthetic instructions, which are more "coder friendly", but does not really
exist for the processor (and therefore must be carefully used). These
synthetic instructions have most of the time less operands, so that the
corresponding real instructions often use %g0, a read-only register stuck
at 0 ; here are some aliases :
synthetic instruction | real opcode
nop <=> sethi 0, %g0
ret <=> jmpl %i7+8, %g0
mov reg_or_imm, reg <=> or %g0, reg_or_imm, reg
cmp reg, reg_or_imm <=> subcc reg, reg_or_imm, %g0
Enough with theory, let's see some code.
SPARC assembly basics
-----------------------
Let's start with an in-season "hello world" style program : '!' is used
for single line comments, /* .. */ is used for multiple lines comments).
!8<------------------------------------------------------------------------
/* FILE : hello.s */
.section ".rodata" ! read-only initialised datas
.MyText: ! define our string label
.asciz "Happy new year %i \n"! define a null-terminated string
.Year:
.word 1999 ! define a word constant
.section ".text" ! read-only object code (instructions)
.global main ! Make function name globally visible
main:
save %sp, -112, %sp ! allocate space for stack
sethi %hi(.MyText), %o1 ! load higher part of string offset
or %o1, %lo(.MyText), %o0 ! add lower part of offset
set (.Year), %l1 ! get year address
ld [%l1], %o1 ! load year into %o1
call printf ! print the string
nop ! do nothing (BDS)
ret ! Return to caller
restore ! Restore register windows (BDS)
Endmain: ! Tell the linker how big the
.size main,(.-main) ! procedure is ("." is current address).
!8<-------------------------------------------------------------------------
Every procedure must save some memory space for itself ; this stack space
will be used to store the out and local registers and all the datas needed by
the procedure (the minimal space is 64 bytes for %o and %l registers). The
stack grows from higher to lower addresses, so that allocating a stack space
is implemented by substracting a value from the current stack pointer ; the
previous stack pointer is called the frame pointer (%fp).
Registers %o0 - %o5 are used to pass the first six parameters to a procedure,
because the current stack pointer (%sp) is stored in %o6 and the calling
program counter (%pc, used to calculate the return address) is stored in %o7.
If a procedure has more than six parameters, the remaining parameters are
passed using the stack space (eg for a caller's stack space of 92 bytes, the
child procedure can get the seventh parameter at [%fp+92]).
As I said before, all instructions are 32 bits long, so that you must use
two steps (with sethi and or) to load a 32 bits data. Note that %hi refers
to the most significant 22 bits and %lo refers to the least significant 10
bits of a register.
Like most RISC machines, a SPARC processor uses a branch delay slot (BDS) to
optimize pipeline efficiency : this means that by default, the instruction
following a branching is executed regardless of whether or not the branch is
taken. So the coder must move (when possible) an instruction from before the
branch to after the branch. Another possibility is to use the 'nop' instruction
or to add the ',a' suffix to the branch instruction, which annul the next
operation.
Calling and branching
-----------------------
Let's take another example to better illustrate the calling process : this
is a recursive implementation of the Fibonacci numbers, which are defined as :
fib(N) = fib(N-1) + fib(N-2)
fib(0) = fib(1) = 1
!8<--------------------------------------------------------------------------
/* FILE: fib.s */
.section ".rodata" ! read-only initialised datas (constants)
.align 8 ! datas must be double-words aligned
.MyText: ! define our string label
.asciz "Fib(%i) = %i \n" ! define a null-terminated string
! ------- FIB : handles F(0) and F(1) --------
.section ".text" ! read-only object code (instructions)
.align 4 ! code must be word-aligned (4 bytes)
.global fib ! Make function name globally visible
fib:
save %sp, -112, %sp ! save stack space
mov %i0, %o0 ! 1st parameter may be needed for calling
cmp %o0, 1 ! asked for F(0) of F(1) ?
ble F1orF0 ! yes : take the branch
mov 1, %i0 ! return value = 1 (BDS)
call fibcall !
nop ! do nothing (BDS)
mov %o0, %i0 ! return value = fibcall return value
F1orF0:
ret ! Return to caller
restore ! Restore register windows (BDS)
Endfib:
.size fib,(.-fib)
! ----- FIBCALL : calls F(N-1) and F(N-2) -----
.global fibcall ! Make function name globally visible
fibcall:
save %sp,-112,%sp ! save stack space
mov %i0,%l0 ! save N in %l0
call fib ! call F(N-1)
sub %l0,1,%o0 ! compute N-1 (BDS)
mov %o0,%i0 ! save result in %i0
call fib ! call F(N-2)
sub %l0,2,%o0 ! compute N-2 (BDS)
ret ! Return to caller
restore %i0,%o0,%o0 ! return F(N-1) + F(N-2) (BDS)
Endfibcall:
.size fibcall,(.-fibcall)
!-------- MAIN --------------------------------
.global main ! Make function name globally visible
main:
save %sp,-112,%sp ! save stack space
call fibcall ! calculate fib number 7
mov 7,%o0 ! (BDS)
mov %o0,%o2 ! result is second parameter
sethi %hi(.MyText),%o0 ! load higher part of string offset
or %o0,%lo(.MyText),%o0 ! add lower part of offset
call printf
mov 7,%o1 ! number is the first parameter (BDS)
ret ! Return to caller
restore ! Restore register windows (BDS)
Endmain:
.size main,(.-main)
!8<--------------------------------------------------------------------------
All procedures share the global registers (%g[0-7]), the remaining registers
%l[0-7], %i[0-7], %o[0-7] constitute the register window. When a procedure
starts execution, it allocates 16 registers (input and local), the output
registers are overlapped with the subroutine's input registers. Here's what
happens if procedure A calls procedure B which calls procedure C :
proc A in | local | out |
| |
proc B | in | local | out |
| |
proc C | in | local | out
As you see, for each procedure, the parameters passed to and received from a
subroutine are stored in %o registers. The same way, the parameters taken from
and passed to the calling procedure are stored in %i registers. The current
window pointer (CWP) identifies the current register window : it is stored in
the least significant 5 bits of %psr, and is modified by the 'save' and
'restore' commands.
The condition code register (another part of %psr) contains four flags : Z
(zero), N (negative), C (carry), and V (overflow) ; contrary to x86 assembly,
these bits are not updated by standard arithmetic operations, but by special
instructions (with 'cc' suffix, like 'cmp' which is in fact a 'subcc').
For instance, you have these equivalences :
SPARC x86
subcc r1, r2, r1 <=> sub r1, r2 ; keep result and flags
subcc r1, r2, %g0 <=> cmp r1, r2 ; discard result, keep flags
sub r1, 0, r2 <=> mov r2, r1 ; keep result, discard flags
As last example, here's a simple anti-cracking method : a checksum on
our own code :
!8<-----------------------------------------------------------------------
/* FILE: cksum.s */
.section ".data" ! read-only initialised datas (constants)
.align 8 ! datas must be double-words aligned
.CRCError:
.asciz "Wrong CRC !! \n" !
.section ".text" ! read-only object code (instructions)
.align 4 ! code must be word-aligned (4 bytes)
cksum:
save %sp,-64,%sp ! save minimal stack space
mov %i0, %l0 ! %l0 is the base address
sub %i1, %i0, %l1 ! %l1 is the decreasing index
mov %g0, %l2 ! %l2 is the running sum
loop:
ld [%l0+%l1], %o0 ! fetch the next element
add %l2, %o0, %l2 ! add it to the running sum
subcc %l1, 4, %l1 ! one fewer element
bge,a loop ! if %o0 >= 0 get next element
! (delay slot result is annulled)
mov %l2, %o0 ! store the result in sum (BDS)
ret ! Return to caller
restore ! Restore register windows (BDS)
endcksum: ! Tell the linker how big the
.size cksum,(.-cksum) ! procedure is.
!-------------------- MAIN -------------------------
.global main
main:
save %sp,-64,%sp ! allocate space for stack
set main, %o0 ! start address for cksum
sethi %hi(EndOfCRCZone),%o1 ! high part of end address
call cksum ! calculate cksum
or %o1,%lo(EndOfCRCZone),%o1 ! low part of end address (BDS)
EndOfCRCZone:
set 0x10954, %o1 ! load precalculated checksum
cmp %o0, %o1 ! is checksum correct ?
be End ! yes : exit
nop ! do nothing (BDS)
Error: ! no : display message
sethi %hi(.CRCError ),%o0 ! load higher part of string offset
call printf ! print the string
or %o0,%lo(.CRCError),%o0 ! add lower part of string offset (BDS)
End:
ret ! Return to caller
restore ! Restore register windows (BDS)
EndMain: ! Tell the linker how big the
.size main,(.-main) ! procedure is.
!8<-----------------------------------------------------------------------
Tools and references
----------------------
Here are the tools I use when I play with SPARC assembly ; some are SunOS
specific tools, some are multi-platforms ones. The code you read here has
been tested on various Sun workstations (using SPARC and UltraSPARC
processors). With very little modifications, it also worked on ISEM, a SPARC
emulator for Linux (see below).
- assembling : I use gcc for that job, which itself uses as and ld (assembler
and linker) to create ELF executables. CC and cc also work well ; these 3
compilers can also be used to generate ASM source code from C source code
with the "-S" option, which is IMHO a great method to learn assembly on
a new platform.
- debugging : I use adb, which is very basic but also very powerful, but gdb
and dbx may also work.
- reversing : all the previous tools are useful ; I also use a disassembler
(SunOS dis), but some exist for other platforms (see Bruce Ediger's
homepage).
If you plan to give a try to SPARC assembly, here are some links :
http://www.cs.unm.edu/~maccabe/classes/341/labman/labman.html
ISEM (Instructional Sparc EMulator) homepage.
http://www.csn.net/~bediger/
Bruce Ediger Homepage
http://www.cs.earlham.edu/~mutioke/cs63/
a good introduction to SPARC with plenty of links.
http://www.sics.se/~psm/sparcstack.html
a very good overview of SPARC stack and registers.
Final Words
------------
If you use a SPARC based machine, give a try to assembly, it's quite fun.
If not, remember that the best you know your processor, the best you
can code ASM.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Extending NASM
by mammon_
Programmers transitioning to NASM from a commercial assembler such as MASM or
TASM immediately notice the lack of any high-level language structures -- the
assembly syntax accepted by NASM is only slightly more sophisticated than what
you would find in a debugger. While this has its good side --smaller code size,
nothing hidden from the programmer-- it does make coding a bit more tedious.
For this reason NASM comes with a preprocessor that is both simple and powerful;
by writing NASM macros, the high-level functionality of other assemblers can be
emulated rather easily. As thw following macros will demonstrate, most of the
high-level asm features in commercial assemblers really do not do anything very
elaborate; they simply are more convenient for the programmer.
The macros that I will detail below provide some basic C and ASM constructs for
use in NASM. I have made the complete file available at
http://www.eccentrica.org/Mammon/macros.asm
The macro file can be included in a .asm file with the NASM directive
%INCLUDE "macros.asm"
Comments on the usage of each macro are included in the file.
Macro Basics
------------
The fundamenal structure of a NASM macro is
%macro {macroname} {# parameters}
%endmacro
The actual code resides on the line between the %macro and %endmacro tags; this
code will be inserted into your program wherever NASM finds {macroname}. Thus
you could create a macro to push the contents of each register such as:
%macro SAVE_REGS 0
push eax
push ebx
push ecx
push edx
%endmacro
Once you have defined this macro, you can use it in your code like:
SAVE_REGS
call ReadFile
...which the preprocessor will expand to
push eax
push ebx
push ecx
push edx
call ReadFile
before assembling. It should be noted that all preprocessing takes place in a
single stage immediately before compiling starts; to preview what the pre-
processor will send to the assembler, you can invoke nasm with the -e option.
The %macro tag requires that you declare the number of paramters that will be
passed to the macro. This can be a single number or a range, with a few quirks:
%macro LilMac 0 ; takes 0 arguments
%macro LilMac 5 ; takes 5 arguments
%macro LilMac 0-3 ; takes 0-3 arguments
%macro LilMac 1-* ; takes 1 to unlimited arguments
%macro LilMac 1-2+ ; takes 1-2 arguments
%macro LilMac 1-3 0, "OK" ; takes 1-3 arguments, 2-3 default to 0 & "OK"
The last three examples bear some explanation. The "-*" operator in the %macro
tag specifies that the macro can handle any number of parameters; in other
words, there is no maximum number, and the minimum is whatever number is to the
left of the "-*" operator. The "+" operator means that any additional arguments
will be appended to the last argument instead of causing an error, so that:
LilMac 0, OK, This argument is one too many
will result in argument 1 being 0 and argument 2 being "OK, This argument is
one too many." Note that this is a good way to pass commas as part of an argu-
ment (normally they are only separators). Providing defualt arguments after the
number of arguments allows a macro to be called with fewer arguments than it
expects.
%macro SAVE_VARS 1-4 ecx, ebx, eax
will fill a missing 4th argument with eax, 3rd with ebx, and 2nd with ecx. Note
that you have to provide defaults starting with the last argument and working
backwards.
The parameters to the macro are available as %1 for the first argument, %2 for
the second, and so on, with %0 containing a count of all the arguments. There
is an equivalent to the DOS "SHIFT" command called %rotate which will rotate
the parameters to either the left or to the right depending on whether a
positive or negative value was supplied:
Before: %1 %2 %3 %4 Before: %1 %2 %3 %4 Before: %1 %2 %3 %4
%rotate 1 %rotate -1 %rotate 2
After: %4 %1 %2 %3 After: %2 %3 %4 %1 After: %3 %4 %1 %2
So that rotating by 1 will put the value at %1 into %4, and rotating by -1 will
put the value of %1 into %2.
High-Level Calls
----------------
Perhaps the buggest complaint about NASM is its primitive call syntax. In MASM
and TASM, the parameters to a call may be appended to the call itself:
call MessageBox, hOwner, lpszText, lpszTitle, fuStyle
where in NASM the parameters must be pushed onto the stack prior to the call:
push fuStyle
push lpszTitle
push lpszText
push hOwner
call MessageBox
Using NASM's "-*" macro feature along with the %rep directive make a high-level
call easy to replicate:
%macro call 2-*
%define _func %1
%rep &0-1
%rotate 1
push %1
%endrep
call _func
%endmacro
The %define directive simply defines the variable _func [underscores should
prefix variable names in macros so you do not mistakenly use the same name
later in the program] as %1, the name of the function to call. The %rep and
%endrep directives enclose the instructions to be repeated, and %rep takes as a
parameter the number of repetitions [in this case set to the number of macro
parameters minus 1]. Thus, the above macro cycles through the arguments to call
and pushes them last-argument first [C syntax] before making the call.
Overloading an existing instruction such as call will cause warnings at compile
time [remember, the preprocessor thinks you are doing a recursive macro invoke]
so usually you will want to name the macro "c_call" or something similar. The
following macros provide facilities for C, Pascal, fastcall, and stdcall call
syntaxes.
;==============================================================-High-Level Call
; ccall FuncName, param1, param2, param 3... ;Pascal: 1st-1st, no clean
; pcall FuncName, param1, param2, param 3... ;C: Last-1st, stack cleanup
; stdcall FuncName, param1, param2, param 3... ;StdCall: last-1st, no clean
; fastcall FuncName, param1, param2, param 3... ;FastCall: registers/stack
%macro pcall 2-*
%define _j %1
%rep %0-1
%rotate -1
push %1
%endrep
call _j
%endmacro
%macro ccall 2-*
%define _j %1
%assign __params %0-1
%rep %0-1
%rotate -1
push %1
%endrep
call _j
%assign __params __params * 4
add esp, __params
%endmacro
%macro stdcall 2-*
%define _j %1
%rep %0-1
%rotate -1
push %1
%endrep
call _j
%endmacro
%macro fastcall 2-*
%define _j %1
%assign __pnum 1
%rep %0-4
%rotate -1
%if __pnum = 1
mov eax, %1
%elif __pnum = 2
mov edx, %1
%elif __pnum = 3
mov ebx, %1
%else
push %1
%endif
%assign __pnum __pnum+1
%endrep
call _j
%endmacro
;==========================================================================-END
Switch-Case Blocks
------------------
One of the most awkward C constructs to code in assembly is the SWITCH-CASE
block. It is also rather difficult to re-create as a macro due to variable
number and length of CASE statements.
NASM's preprocessor has a context stack which allows you to create a set of
local variables and addresses which is specific to a particular invocation of a
macro. Thus it becomes possible to refer to labels which will be created in a
future macro by giving them context-dependent names:
%macro MacPart1 0
%push mac ;create a context called "mac"
jmp %$loc ;jump to context-specific label "loc"
%endmacro
%macro MacPart2 0
%ifctx mac ;if we are in context 'mac'
%$loc: ;define label 'loc'
xor eax, eax ;code at this label...
ret
%endif ;end the if block
%pop ;destroy the 'mac' context
%endmacro
As you can see, the context is created and named with a %push directive, and
destroyed with a $pop directive. NASM has a number of preprocessor conditional
IF/ELSE statements; in the above example, the %ifctx [if current context equals]
directive is used to determine if a 'mac' context has been created [Note that
the 'base' NASM conditionals include %if, %elif, %else, and %endif; these carry
over to the %ifctx directive, such that there is available %ifctx, %ifnctx,
%elifctx, %elifnctx, %else, and %endif; all %if directives must be closed with
an %endif directive]. Finally, %$ is used to prefix the name of a context-
specific variable or label. Non-context-specific local labels use the %% prefix:
%macro LOOP_XOR
%%loop:
pop eax
xor eax, ebx
test eax, eax
jnz %%loop
%endmacro
The SWITCH-CASE macro that follows uses the syntax:
SWITCH Variable
CASE Int
BREAK
CASE Int
BREAK
DEFAULT
ENDSWITCH
Which could be implemented as follows:
card db 0 ;card_variable
Jack EQU 11
Queen EQU 12
King EQU 13
...
SWITCH card
CASE Jack
add edx, Jack
BREAK
CASE Queen
add edx, Queen
BREAK
CASE King
add edx, King
BREAK
DEFAULT
add d, [card]
ENDSWITCH
Note that SWITCH moves the variable into eax and CASE moves the value into ebx.
;===========================================================-SWITCH-CASE Blocks
%macro SWITCH 1
%push switch
%assign __curr 1
mov eax, %1
jmp %$loc(__curr)
%endmacro
%macro CASE 1
%ifctx switch
%$loc(__curr):
%assign __curr __curr+1
mov ebx, %1
cmp eax, ebx
jne %$loc(__curr)
%endif
%endmacro
%macro DEFAULT 0
%ifctx switch
%$loc(__curr):
%endif
%endmacro
%macro BREAK 0
jmp %$endswitch
%endmacro
%macro ENDSWITCH 0
%ifctx switch
%$endswitch:
%pop
%endif
%endmacro
;==========================================================================-END
If-Then Blocks
--------------
While the preprocessor provides support for if-then directives, it is a slight
bit of work to cause that to generate the equivalent assembly language 'if'
code [ the preprocessor 'if' is resolved before compile time, not at run time].
Using macros, you can create if-then blocks with the following structure:
IF Value, Cond, Value
;if code here
ELSIF Value, Cond, Value
;else-if code here
ELSE
;else code here
ENDIF
An example being:
IF [Passwd], e, [GoodVal] ;e == equals or je
jmp Registered
ELSE
jmp FormatHardDrive
ENDIF
The trickiest part about this macro sequence is the 'Cond' parameter. NASM
allows condition codes [the 'cc' in 'jcc' that you findin opcode refs] to be
passed to macros; these condition codes are simply the 'jcc' with the 'j' cut
off -- 'jnz' becomes 'nz', 'jne' becomes 'ne', 'je' becomes 'e', and so on.
The reason for this is that the condition code is appended to a 'j' later in
the macro:
%macro Jumper %1 %2 %3 ;JUMPER Reg1, cc, Reg2
cmp %1, %3
j%+2 Gotcha
jmp error
%endmacro
The above code appends %2 to the 'j' with the directive j%+2. Note that if you
use j%- instead of j%+, NASM will insert the *inverse* condition code, so that
jz becomes jnz, etc. For example, calling the macro
%macro Jumper2 %1
j%-1 JmpHandler
%endmacro
with the invocation 'Jumper2 nz' would assemble the code 'jz JmpHandler'.
The condition codes can be a bit tricky to work with; it is advisable to add a
sequence such as the following to the macro file:
%define EQUAL e
%define NOTEQUAL ne
%define G-THAN g
%define L-THAN l
%define G-THAN-EQ ge
%define L-THAN-EQ le
%define ZERO z
%DEFINE NOTZERO nz
so that you could call the IF macro as follows:
IF PassWd, EQUAL, GoodVal
;if code here
...etc etc. Note also that the IF-THEN-ELSE macros put the passed values into
eax and ebx for compatison, so these registers will need to be preserved.
;===========================================================-IF-THEN-ELSE Loops
%macro IF 3
%push if
%assign __curr 1
mov eax, %1
mov ebx, %3
cmp eax, ebx
j%+2 %%if_code
jmp %$loc(__curr)
%%if_code:
%endmacro
%macro ELSIF 3
%ifctx if
jmp %$end_if
%$loc(__curr):
%assign __curr __curr+1
mov eax, %1
mov ebx, %3
cmp eax, ebx
j%+2 %%elsif_code
jmp %$loc(__curr)
%%elsif_code:
%else
%error "'ELSIF' can only be used following 'IF'"
%endif
%endmacro
%macro ELSE 0
%ifctx if
jmp %$end_if
%$loc(__curr):
%assign __curr __curr+1
%else
%error "'ELSE' can only be used following an 'IF'"
%endif
%endmacro
%macro ENDIF 0
%$loc(__curr):
%$end_if:
%pop
%endmacro
;==========================================================================-END
For/While Loops
---------------
The DO...FOR and DO...WHILE do nothing differnet from the previous macros, but
are simply a different application of the same principles. The syntax for
calling these macros is:
DO
;code to do here
FOR min, Cond, max, step
DO
;code to do here
WHILE variable, Cond, value
It is perhaps easiest to illustrate this by comparing the macros with C code.
for( x = 0; x <= 100; x++) { SomeFunc() }
Equates to:
DO
call SomeFunc
FOR 0, l, 100, 1
Likewise,
for( x = 0; x != 100; x--) { SomeFunc() }
Equates to:
DO
call SomeFunc
FOR 0, e, 100, -1
The WHILE macro is similar:
while( CurrByte != BadAddr) {SomeFunc() }
Equates to:
DO
call SomeFunc
WHILE CurrByte, ne, BadAddr
Once again, eax and ebx are used in the FOR and WHILE macros.
;====================================================-DO-FOR and DO-WHILE Loops
%macro DO 0
%push do
jmp %$init_loop
%$start_loop:
push eax
%endmacro
%macro FOR 4
%ifctx do
pop eax
add eax, %4
cmp eax, %3
j%-2 %%end_loop
jmp %$start_loop
%$init_loop:
mov eax, %1
jmp %$start_loop
%%end_loop:
%pop
%endif
%endmacro
%macro WHILE 3
%ifctx do
pop eax
mov ebx, %3
cmp eax, ebx
j%+2 %%end_loop
jmp %$start_loop
%$init_loop:
mov eax, %1
jmp %$start_loop
%%end_loop:
%pop
%endif
%endmacro
;==========================================================================-END
Data Declarations
-----------------
Declaring data is relatively simple in assembly, but sometimes it helps to make
code more clear if you create macros that assign meaningful data types to
variables, even if those macros simply resolve to a DB or a DD. The following
macros demonstrate this concept. They are invoked as follows:
CHAR Name, String ;e.g. CHAR UserName, "Joe User"
INT Name, Byte ;e.g. INT Timeout, 30
WORD Name, Word ;e.g. WORD Logins
DWORD Name, Dword ;e.g. DWORD Password
Note that when invoked with a name but not a value, these macros create empty
[DB 0] variables.
;============================================================-Data Declarations
%macro CHAR 1-2 0
%1: DB %2,0
%endmacro
%macro INT 1-2 0
%1: DB %2
%endmacro
%macro WORD 1-2 0
%1: DW %2
%endmacro
%macro DWORD 1-2 0
%1: DD %2
%endmacro
;==========================================================================-END
Procedure Declarations
----------------------
Procedure declarations are another matter of convenience. It is often useful in
your code to clearly delineate the start and end of a procedure; each of the
PROC macros below does that, as well as creating a stack fram for the procedure.
The ENTRYPROC macro creates a procedure named 'main' and declares main as a
global symbol; the standard PROC declares the provided name as global. These
macros can be used as follows:
PROC ProcName Parameter1, Parameter2, Parameter3
;procedure code here
ENDP
ENTRYPROC
;entry-procedure code here
ENDP
Note that the Parameters to PROC are set up to EQU to offsets from ebp, e.g.
ebp-4, ebp-8, etc. I have also included support for local variables, which
will EQU to positive offsets from ebp' these may be used as follows:
PROC ProcName Parameter1, Parameter2, Parameter3...
LOCALDD Dword_Variable
LOCALDW Word_Variable
LOCALDB Byte_Variable
;procedure code here
ENDP
;=======================================================-Procedure Declarations
%macro PROC 1-9
GLOBAL %1
%1:
%assign _i 4
%rep %0-1
%2 equ [ebp-_i]
%assign _i _i+4
%rotate 1
%endrep
push ebp
mov ebp, esp
%push local
%assign __ll 0
%endmacro
%macro ENDP 0
%ifctx local
%pop
%endif
pop ebp
%endmacro
%macro ENTRYPROC 0
PROC main
%endmacro
%macro LOCALVAR 1
sub esp, 4
%1 equ [ebp + __ll]
%endmacro
%macro LOCALDB 1
%assign __ll __ll+1
LOCALVAR %1
%endmacro
%macro LOCALDW 1
%assign __ll __ll+2
LOCALVAR %1
%endmacro
%macro LOCALDD 1
%assign __ll __ll+4
LOCALVAR %1
%endmacro
;==========================================================================-END
Further Extension
-----------------
Continued experimentation will of course prove fruitful. It is recommended that
you read/print out chapter 4 of the NASM manual for reference. In addition, it
is very helpful to test your macros by cpmpiling the source with "nasm -e",
which will output the preprocessed source code to stdout and will not compile
the program.
____________________________________________________________________________
______ _____. ____ ```
._____/\______.________._________. ._\___ |__\_ /. \\
| | | _ | | (_ | __/ |CE ,
.=|_____/\______| |----)____| |______|______|======[ Win 32 ASM ]===.
'===============| :==================================================='
NASM specific Win32 coding
by Tamas Kaproncai
Contents
========
0. Preface
1. Compiling
2. Include files
3. Library files
4. Importing API functions
5. Calling API functions
6. WinMain
7. Window procedure
8. Sections
9. Self modification
0. Preface
==========
I will introduce the win32 coding and I will focus on the NASM specific part.
Downloadable working examples:
ftp://ftp.szif.hu/pub/demos/tool/w32nasm.zip
http://rs1.szif.hu/~tomcat/win32
There is another tutorial on this topic, called:
"The Win32 NASM Coding Toolkit v0.02 by Gij"
that uses the LCC linker and the resource compiler which comes with LCC.
1. Compiling
============
I'm working with the following free programs in connection with NASM:
- linker: ALINK v1.5 by Anthony A.J. Williams.
- resource compiler: GoRC v0.50b by Jeremy Gordon.
The process of compiling a win32 program involves a number of steps which can
be divided into three main processes: preparing the include files, preparing
the library files, and writing the actual program.
The compiling flow chart
------------------------
.h -> ? -> .inc
\
.asm -> NASM -> .obj
\
.rc -> GORC -> .res -> ALINK -> .exe
/
.dll -> IMPLIB -> .lib (? means handwork)
2. Include files
================
The include files (*.inc) must be generated from existing header files (*.h)
that come with win32-compatible C or Pascal compilers. Files needed:
WIN32N.INC (Thanks for the inital MASM version to S.L.Hutchesson).
The compiler will be NASM version 0.97
http://www.cryogen.com/nasm
Usage: nasmw -fobj -w+orphan-labels -pwin32n.inc %1.asm
3. Library files
================
Files Needed:
WIN32.LIB
The linker will be ALINK
http://www.geocities.com/SiliconValley/Network/4311/#alink
Usage: alink -oPE %1 win32.lib %1.res %2 %3
More lib files can be created with IMPLIB.
Example: IMPLIB DDRAW.DLL
4. Importing API functions
==========================
EXTERN MessageBoxA
IMPORT MessageBoxA use32.dll
5. Calling API functions
========================
PUSH UINT MB_OK
PUSH LPCTSTR title1
PUSH LPCTSTR string1
PUSH HWND NULL
CALL [MessageBoxA]
6. WinMain
==========
You don't need to use the name, WinMain:
You must start the program with the label, ..start:
At the begening there is nothing special in the stack, so you should call
GetModuleHandleA for hInstance and GetCommandLineA for the command line.
(Command line consists the full path, the file name and the parameters).
You can exit the program with: RETN
or you should call the ExitProcess function:
PUSH UINT 0 ; the error code
CALL [ExitProcess]
7. Window procedure
===================
There are four parameters on the top of the stack:
PUSH EBP
MOV EBP,ESP
%DEFINE hwnd EBP+8 ;handle of window
%DEFINE message EBP+12 ;message
%DEFINE wParam EBP+16 ;first message parameter
%DEFINE lParam EBP+20 ;second message parameter
You can handle the messages depends on WPARAM [wParam]
and the rest you can pass to DefWindowProcA:
PUSH LPARAM [lParam]
PUSH WPARAM [wParam]
PUSH UINT [message]
PUSH HWND [hwnd]
CALL [DefWindowProcA]
POP EBP
RETN 16
8. Sections
===========
You need a code section:
SECTION CODE USE32 CLASS=CODE
and a data section:
SECTION DATA USE32 CLASS=DATA
You don't need bss section, instead of you should append
every RESB, RESW, RESD, RESQ to the end of the source code.
This zero data not will be included to the exe file.
9. Self modification
====================
You can include your code and data together in one section:
SECTION CODE USE32 CLASS=CODE
In that case you need another object file, with only one line source:
SECTION CODE USE32 CLASS=DATA
ALINK will combine the properties of these two sections.
EXTERN MessageBoxA
EXTERN ExitProcess
SECTION CODE USE32 CLASS=CODE
..start:
PUSH UINT MB_OK
PUSH LPCTSTR title1
PUSH LPCTSTR string1
PUSH HWND NULL
CALL MessageBoxA
PUSH UINT NULL
CALL ExitProcess
SECTION DATA USE32 CLASS=DATA
string1: db 'Hello world!',13,10,0
title1: db 'Hello',0
____________________________________________________________________________
______ _____. ____ ```
._____/\______.________._________. ._\___ |__\_ /. \\
| | | _ | | (_ | __/ |CE ,
.=|_____/\______| |----)____| |______|______|======[ Win 32 ASM ]===.
'===============| :==================================================='
More about Text
by Iczelion
We will experiment more with text attributes, ie. font and color.
Preliminary:
------------
Windows color system is based on RGB values, R=red, G=Green, B=Blue. If you
want to specify a color in Windows, you must state your desired color in
terms of these three major colors. Each color value has a range from 0 to
255 (a byte value). For example, if you want pure red color, you should use
255,0,0. Or if you want pure white color, you must use 255,255,255. You can
see from the examples that getting the color you need is very difficult
with this system since you have to have a good grasp of how to mix and
match colors.
For text color and background, you use SetTextColor and SetBkColor, both of
them require a handle to device context and a 32-bit RGB value. The 32-bit
RGB value's structure is defined as:
RGB_value struct
unused db 0
blue db ?
green db ?
red db ?
RGB_value ends
Note that the first byte is not used and should be zero. The order of the
remaining three bytes is reversed,ie. blue, green, red. However, we will
not use this structure since it's cumbersome to initialize and use. We will
create a macro instead. The macro will receive three parameters: red, green
and blue values. It'll produce the desired 32-bit RGB value and store it in
eax. The macro is as follows:
RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm
You can put this macro in the include file for future use.
You can "create" a font by calling CreateFont or CreateFontIndirect. The
difference between the two functions is that CreateFontIndirect receives
only one parameter: a pointer to a logical font structure, LOGFONT.
CreateFontIndirect is the more flexible of the two especially if your
programs need to change fonts frequently. However, in our example, we will
"create" only one font for demonstration, we can get away with CreateFont.
After the call to CreateFont, it will return a handle to a font which you
must select into the device context. After that, every text API function
will use the font we have selected into the device context.
Content:
--------
Below is our source code:
;======================================================================TEXT.ASM
include windows.inc
includelib user32.lib
includelib kernel32.lib
includelib gdi32.lib
RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
TestString db "Win32 assembly is great and easy!",0
FontName db "script",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.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:SDWORD
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 hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,0
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,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
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL hfont:HFONT
mov eax,uMsg
.IF eax==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF eax==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or
FF_SCRIPT,\
ADDR FontName
invoke SelectObject, hdc, eax
mov hfont,eax
RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax
invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
invoke SelectObject,hdc, hfont
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
;===========================================================================EOF
Let's begin our analysis : )
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName
CreateFont creates a logical font that is the closest match to the given
parameters and the font data available. This function has more parameters
than any other function in Windows. It returns a handle to logical font to
be used by SelectObject function. We will examine its parameters in detail.
HFONT CreateFont(int nHeight, int nWidth, int nEscapement, int
nOrientation, int nWeight, BYTE cItalic, BYTE cUnderline, BYTE cStrikeOut,
BYTE cCharSet, BYTE cOutputPrecision, BYTE cClipPrecision, BYTE cQuality,
BYTE cPitchAndFamily, LPSTR lpFacename);
nHeight --> The desired height of the characters . 0 means use default size.
nWidth --> The desired width of the characters. Normally this value should be
0 which allows Windows to match the width to the height. However, in our
example, the default width makes the characters hard to read, so I use the
width of 16 instead.
nEscapement --> Specifies the orientation of the next character output
relative to the previous one in tenths of a degree. Normally, set to 0. Set
to 900 to have all the characters go upward from the first character, 1800
to write backwards, or 2700 to write each character from the top down.
nOrientation --> Specifies how much the character should be rotated when
output in tenths of a degree. Set to 900 to have all the characters lying
on their backs, 1800 for upside-down writing, etc.
nWeight --> Sets the line thickness of each character. Windows defines the
following sizes:
FW_DONTCARE equ 0
FW_THIN equ 100
FW_EXTRALIGHT equ 200
FW_ULTRALIGHT equ 200
FW_LIGHT equ 300
FW_NORMAL equ 400
FW_REGULAR equ 400
FW_MEDIUM equ 500
FW_SEMIBOLD equ 600
FW_DEMIBOLD equ 600
FW_BOLD equ 700
FW_EXTRABOLD equ 800
FW_ULTRABOLD equ 800
FW_HEAVY equ 900
FW_BLACK equ 900
cItalic --> 0 for normal, any other value for italic characters.
cUnderline --> 0 for normal, any other value for underlined characters.
cStrikeOut --> 0 for normal, any other value for characters with a line
through the center.
cCharSet --> The character set of the font. Normally should be OEM_CHARSET
which allows Windows to select font which is operating system-dependent.
cOutputPrecision --> Specifies how much the selected font must be closely
matched to the characteristics we want. Normally should be
OUT_DEFAULT_PRECIS which defines default font mapping behavior.
cClipPrecision --> Specifies the clipping precision. The clipping precision
defines how to clip characters that are partially outside the clipping
region. You should be able to get by with CLIP_DEFAULT_PRECIS which defines
the default clipping behavior.
cQuality -->Specifies the output quality. The output quality defines how
carefully GDI must attempt to match the logical-font attributes to those of
an actual physical font. There are three choices: DEFAULT_QUALITY,
PROOF_QUALITY and DRAFT_QUALITY.
cPitchAndFamily --> Specifies pitch and family of the font. You must combine
the pitch value and the family value with "or" operator.
lpFacename A pointer to a null-terminated string that specifies the
typeface of the font.
The description above is by no means comprehensive. You should refer to
your Win32 API reference for more details.
invoke SelectObject, hdc, eax
mov hfont,eax
After we get the handle to the logical font, we must use it to select the
font into the device context by calling SelectObject. SelectObject puts the
new GDI objects such as pens, brushs, and fonts into the device context to
be used by GDI functions. It returns the handle to the replaced object
which we should save for future SelectObject call. After SelectObject call,
any text output function will use the font we just selected into the device
context.
RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax
Use RGB macro to create a 32-bit RGB value to be used by SetColorText and
SetBkColor.
invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
Call TextOut function to draw the text on the client area. The text will be
in the font and color we specified previously. The syntax of TextOut is as
follows:
BOOL TextOut(
HDC hdc, // handle of device context
int nXStart, // x-coordinate of starting position
int nYStart, // y-coordinate of starting position
LPCTSTR lpString, // address of string
int cbString // number of characters in string
);
invoke SelectObject,hdc, hfont
When we are through with the font, we should restore the old font back into
the device context. You should always restore the object that you replaced
in the device context.
____________________________________________________________________________
______ _____. ____ ```
._____/\______.________._________. ._\___ |__\_ /. \\
| | | _ | | (_ | __/ |CE ,
.=|_____/\______| |----)____| |______|______|======[ Win 32 ASM ]===.
'===============| :==================================================='
Keyboard Input
by Iczelion
We will learn how a Windows program receives keyboard input.
Preliminiary:
------------
Since there's only one keyboard in each PC, all running Windows programs
must share it between them. Windows is responsible for sending the key
strokes to the window which has the input focus.
Although there may be several windows on the screen, only one of them has
the input focus. The window which has input focus is the only one which can
receive key strokes. You can differentiate the window which has input focus
from other windows by looking at the title bar which is highlighted.
Actually, there are two main types of keyboard message. You can view a
keyboard as a group of keys. For example, if you press the "a" key, Windows
sends a WM_KEYDOWN message to the window which has input focus, notifying
that a key is pressed. When you release the key, Windows sends a WM_KEYUP
message. In this case, you treat a key as a button. Another way to look at
the keyboard is that it's a character input device. When you press "a" key,
Windows sends a WM_CHAR message to the window which has input focus,
telling it that the user sends "a" character to it. In fact, Windows sends
WM_KEYDOWN, WM_CHAR, and WM_KEYUP messages to the window which has input
focus. The window procedure may decide to process all three messages or
only the messages it's interested in. Most of the time, you can ignore
WM_KEYDOWN and WM_KEYUP since TranslateMessage function call in the message
loop translate WM_KEYDOWN and WM_KEYUP messages to a WM_CHAR message. We
will focus on WM_CHAR in this tutorial.
Content:
-------
;=======================================================================KEY.ASM
include windows.inc
includelib user32.lib
includelib kernel32.lib
includelib gdi32.lib
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
char WPARAM 20h ; the character the program receives from keyboard
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.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:SDWORD
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 hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,0
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,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
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
mov eax,uMsg
.IF eax==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF eax==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
.ELSEIF eax==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke TextOut,hdc,0,0,ADDR char,1
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
;===========================================================================EOF
Let's analyze it:
char WPARAM 20h ; the character the program
receives from keyboard
This is the variable that stores the character received from the keyboard.
Since the character is sent in WPARAM of the window procedure, we define
the variable as type WPARAM for simplicity. The initial value is 20h or the
space since when our window refreshes its client area the first time, there
is no character input. So we want to display space instead.
.ELSEIF eax==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
This is added in the window procedure to handle the WM_CHAR message. It
just puts the character into the variable named "char" and then calls
InvalidateRect. InvalidateRect makes a specified rectangle in the client
area invalid which forces Windows to send WM_PAINT message to the window
procedure. Its syntax is as follows:
BOOL InvalidateRect(
HWND hWnd, // handle of window with changed update region
CONST RECT * lpRect, // address of rectangle coordinates
BOOL bErase // erase-background flag
);
lpRect is a pointer to the rectagle in the client area that we want to
declare invalid. If this parameter is null, the entire client area will be
marked as invalid.
bErase is a flag telling Windows if it needs to erase the background. If
this flag is TRUE, then Windows will erase the backgroud of the invalid
rectangle when BeginPaint is called.
So the strategy we used here is that: we store all necessary information
about how to paint the client area and generate WM_PAINT message to paint
the client area. Of course, the codes in WM_PAINT section must know
beforehand what's expected of them. This seems a roundabout way of doing
things but it's the way of Windows.
Actually we can paint the client area during processing WM_CHAR message by
calling GetDC and ReleaseDC pair. There is no problem there. But the fun
begins when our window needs to repaint its client area. Since the codes
that paint the character are in WM_CHAR section, the window procedure will
not be able to repaint our character in the client area. So the bottom line
is: put all necessary data and codes that do painting in WM_PAINT. You can
send WM_PAINT message from anywhere in your code anytime you want to
repaint the client area.
invoke TextOut,hdc,0,0,ADDR char,1
When InvalidateRect is called, it sends a WM_PAINT message back to the
window procedure. So the codes in WM_PAINT section is called. It calls
BeginPaint as usual to get the handle to device context and then call
TextOut which draws our character in the client area at x=0, y=0. When you
run the program and press any key, you will see that character echo in the
upper left corner of the client window. And when the window is minimized
and maximized again, the character is still there since all the codes and
data essential to repaint are all gathered in WM_PAINT section.
____________________________________________________________________________
::::::::::.___ . ```
::::::::::| _/__. |__ ____ . __. ____ ____ __. \\
:::::: |____ | __/_ _\_ (.___| .___) |__\_ (._) /___) | ,
::::::::::/ / | \ | - | \ | - | - | \/| - |
.=:::::::::/______|_____|_____| (___|_____|______|____|_____|===============.
'=::::::::::==================| . ____ | (____====[ The C Standard lib ]==='
:::::::::: | |------| - |
:::::::::: | |______|______|CE
. :
C string functions: introduction, _strlen
by Xbios2
I. INTRODUCTION
---------------
Beware: this is going to be long...
String handling in assembly is - anyway - a difficult subject. There are few
string-oriented x86 opcodes, and most of them are slow. There is not a standard
library providing even basic functions. There is no string specific syntax in
assembly, like C's printf('hello world') or, even worse, BASIC's a$=b$+'hello'.
In a few words, if easy string-related programming is your goal, maybe you
should consider PERL, or another text-manipulation language.
Yet, string functions are really needed, since almost any program in assembly
uses text for I/O. (An alternative to this would be using animated paper-clips
to communicate with the user :)).
Furthermore, coding those functions in assembly allows for smaller and faster
functions. Actually many of the string functions in C _were_ written in
assembly (e.g. strlen, strcat, strcpy, etc). Those can be divided in two
categories:
-'Traditional' functions, using the x86 string instructions
-'Modern' functions, which run faster by being Pentium-optimized
Borland C++ 4.02 and KERNEL32.DLL only have traditional functions. Borland's
C++ Builder v1.0 (once given free as a demo) includes both types. MSVCRT.DLL
(version 5) contains 'modern' versions.
The three main aspects considered in these articles (and generally when compar-
ing different versions of the same function) are speed, size and common sense.
'Common sense' indicates how easy it is to understand the way a function
operates by reading the source code, how 'elegant' the code is. In a library
module distributed as a binary (in a 'static' reuse of code), common sense is
not important. It becomes important when the source code is distributed too,
because it allows 'dynamic' reuse. 'Elegant' code can be easily optimized for
specific needs or expanded to become a more general function.
'Size' is, obviously, the size of the resulting code. Besides creating smaller
files, small size has two interesting 'side-effects'. It (usually) creates more
elegant code and faster code (it decreases k, but it usually increases l (for
an explanation of k and l see 'speed'). For very small functions like strlen it
has the added advantage of allowing the code to be inlined without wasting too
much space, thus decreasing k even more.
'Speed' indicates the number of cycles needed to execute the function. For
simple string functions the number of cycles needed can be expressed as
c=k+l*n
where c is the total number of cycles, k is the number of cycles needed to
'prepare' the function, l is the number of cycles needed to process each chara-
cter and n is the number of characters in the string. It is obvious that small
values of c mean faster execution. In order to compare two versions of a
function that run at speeds of
c1=k1+l1*n and c2=k2+l2*n
the ratio of c1/c2 is calculated:
c1 k1+l1*n
r=----=---------
c2 k2+l2*n
if r=1 then both versions run at the same speed.
if r>1 then version 2 is faster. if r<1 then version 1 is faster.
Simple maths prove that:
1. When n becomes infinite, r becomes equal to l1/l2. Especially if l1=l2,
then r=1
2. If k1<k2 but l1>l2, c1<c2 (version 1 is faster) if n<(k2-k1)/(l1-l2).
Point 1 means that for long strings speed is (almost) independent of the value
of k. Especially if l1=l2 both versions will run at (almost) the same speed.
Point 2 means that for small strings k strongly affects the value of c.
For those of you that are fed up with maths, here is a simple example that
demonstrates what I've been trying to say all this time :)
If version 1 runs at c1=10+3*n and version 2 at c2=30+1*n then:
-For strings up to 9 chars version 1 is faster
-For strings of 10 chars both versions run at the same speed
-For strings of 11 or more version 2 is faster
-For strings of 50 chars, version 2 is 2x faster than version 1
-For strings of 770 chars version 2 is 2.9x faster than version 1
The problem is that none of the above versions can be classified as better than
the other. Think of the parser of a compiler. It receives as input lines from a
text file, which are strings longer than 10 characters, but also has to deal
with tokens, which are short strings (in an assembler, three-char tokens are
very common).
Keep in mind that, while l depends only on the method used to implement the
function, k also depends on the 'push arg/call/prepare stack/resore stack/
ret/get arg' times. So if n is low, overall speed can be increased by inlining
the code, thus subtracting from k the time needed to call the function.
Well, I think you've had enough. Let's see all this stuff in practice.
II. THE _STRLEN FUNCTION
------------------------
Attention: especially for _strlen, ALL versions I have either written or found
in libraries will be explained. This means you'll get source code for 8
functions...
size_t strlen(const char *s);
Calculates the length of a string. strlen returns the number of characters in
s, not counting the null-terminating character.
_strlen is the simplest of the string functions. The 'traditional' way to
implement it is through 'repne scasb'. BC 4.02 implements it as:
; ------------ version 1 ------------
; Borland C++ 4.02
; 25 bytes
; c=27+4*n
_strlen proc near
push ebp
mov ebp, esp
push edi
mov edi, [ebp+8]
mov ecx, -1
xor al, al
cld
repne scasb
not ecx
lea eax, [ecx-1]
pop edi
pop ebp
retn
_strlen endp
; -----------------------------------
A shorter, and a bit faster version of this would be:
; ------------ version 2 ------------
; Improved 'repne scasb'
; 18 bytes
; c=21+4*n
_strlen proc near
xor eax, eax
push edi
mov edi, [esp+8]
or ecx, -1
repne scasb
sub eax, 2
pop edi
sub eax, ecx
retn
_strlen endp
; -----------------------------------
The win32 API also includes a strlen function, called lstrlenA. It is based on
'repne scasb' as well, but you are _strongly_ advised to avoid it. It runs at
c=56+4*n cycles.
The most 'common sense' function (IMHO) is also the smallest:
; ------------ version 3 ------------
; Elegant and very small
; 15 bytes
; c=27+4*n
; k is so big because we have a retn immediately after a jump
; if a nop is added between those two, k drops to 13
_strlen proc near
or eax, -1
mov ecx, [esp+4]
loop1: inc eax
cmp byte ptr [ecx+eax], 0
jnz short loop1
; nop
retn
_strlen endp
; -----------------------------------
Which gets a little less elegant but faster if tweaked a little:
(The trick is that the carry flag is set by the 'cmp' instruction if the byte
read is 0, else it is cleared. The 'inc' instruction doesn't affect the carry
flag).
; ------------ version 4 ------------
; Very small and faster than repne scasb
; 15 bytes
; c=12+3*n
_strlen proc near
mov ecx, [esp+4]
xor eax, eax
loop1: cmp byte ptr [ecx+eax], 1
inc eax
jnc short loop1
dec eax
retn
_strlen endp
; -----------------------------------
But it gets even better as inlined code, as a macro:
; ------------ version 4.5 ------------
; Very small, extremely elegant macro
; 10 bytes
; c=8+3*n
strlen macro srcreg, cntreg
xor cntreg, cntreg
cmp byte ptr [srcreg+cntreg], 1
inc cntreg
jnc $-5
dec eax
endm
; -----------------------------------
This macro returns in cntreg the length of the string at srcreg.
It uses no other registers, srcreg is unchanged, it is only 10 bytes long and
it runs at a speed of only 8+3*n cycles. It also returns its value in any
register, without altering the other registers.
Suppose we need in ecx the length of the string at esi. The following code:
push esi
call _strlen
pop ecx ; restore stack
mov ecx, eax
takes 9 bytes, only one less than the macro version. Plus, of course, the at
least 15 bytes of code in _strlen.
Another 'elegant' version, which is also small and much faster is the following:
; ------------ version 5 ------------
; Elegant, small and fast
; 16 bytes
; c=12+2*n
_strlen proc near
mov ecx, [esp+4]
xor eax, eax
loop1: mov dl, [ecx+eax]
inc eax
or dl, dl
jnz short loop1
dec eax
retn
_strlen endp
; -----------------------------------
I believe that version 5 is the best version that could have elegance, speed
and small size together. It can also be converted to a macro and inlined to
drop to a speed of c=8+2*n (it will use one register more, but this register
would anyway be lost if a call to the function was made).
It also has what I believe is the smallest value of k. However, it doesn't have
the smallest value of l. To reduce the cycles needed, data can be read not byte
after byte but dword after dword. Here is a routine given by Agner Fog in his
document on Pentium optimization (which you are _strongly_ advised to read):
; ------------ version 6 ------------
; [by Agner Fog] Very fast
; 61 bytes
; c=18+1*n (not exactly, as data is read in 4 byte blocks)
_strlen proc
mov eax, [esp+4] ; get pointer
mov edx, 7
add edx, eax ; pointer+7 used in the end
push ebx
mov ebx, [eax] ; read first 4 bytes
add eax, 4 ; increment pointer
l1: lea ecx, [ebx-01010101h] ; subtract 1 from each byte
xor ebx, -1 ; invert all bytes
and ecx, ebx ; and these two
mov ebx, [eax] ; read next 4 bytes
add eax, 4 ; increment pointer
and ecx, 80808080h ; test all sign bits
jz l1 ; no zero bytes, continue loop
test ecx, 00008080h ; test first two bytes
jnz short l2
shr ecx, 16 ; not in the first 2 bytes
add eax, 2
l2: shl cl, 1 ; use carry flag to avoid a branch
pop ebx
sbb eax, edx ; compute length
ret
_strlen endp
; -----------------------------------
The only problem with this routine is that it expects the string to be aligned
on a 4 byte boundary. If the string is misaligned, the speed drops to
c=24+1.75*n. In the extreme case that the string is misalinged AND it ends on a
page boundary, the function will cause an access violation error.
The fastest version (I have found) is the one in the Borland C++ builder
library:
; ------------ version 7 ------------
; [C++ Builder, slightly modified] Fastest
; 88 bytes
; c=20+0.75*n (not exactly, see notes)
_strlen proc near
mov eax, [esp+4]
test al, 3
jnz short unalgn
loop1: mov edx, [eax]
add eax, 4
mov ecx, edx
sub edx, 1010101h
and edx, 80808080h
jz short loop1
not ecx
and edx, ecx
jz short loop1
test dl, dl
jnz short minus4
test dh, dh
jnz short minus3
test edx, 0FF0000h
jnz short minus2
jmp short minus1
unalgn: add eax, 3
xor cl, cl
cmp byte ptr [eax-3], cl
jz short minus3
cmp byte ptr [eax-2], cl
jz short minus2
cmp byte ptr [eax-1], cl
jz short minus1
and al, 0FCh
jmp short loop1
minus4: dec eax
minus3: dec eax
minus2: dec eax
minus1: mov ecx, [esp+4]
dec eax
sub eax, ecx
retn
_strlen endp
; -----------------------------------
Actually, the original version is 90 bytes long. I have only changed the
'unalgn:' block, to reduce k if the string is unaligned.
This function works well even on unaligned strings, as it first check the unali-
gned bytes, and the proceeds in the main loop with aligned data (for unaligned
strings it runs at c=31+0.75*n cycles). Since all dwords read are aligned,
unaligned strings that end on page boundaries don't cause problems.
This function is not always the fastest. If the string contains characters in
the range 128 to 255 (i.e. signed bytes) the speed drops. If all the characters
are signed (actually if at least one byte in every dword read), the speed
becomes c=1.25*n. Of course most of the time (especially for english text) this
is not the case, but if you have to process strings in another language that
has characters in the range 128 to 255, it is a bit slower.
Another fast version of strlen can be found in MSVCRT.DLL (the one I checked is
version 5.00.7303). It runs at c=20+1*n, and handles unaligned strings almost
like the Builder version. Misaligned strings give a value of k ranging from a
minimum of 25 to a maximum of 52.
What the MSVCRT function lacks completely is common sense and small size. In
fact it is 144 bytes long and it is divided in two different pieces of the dll's
code, causing most jumps to be in the long form.
The main loop MSVCRT uses is good, but the rest of the function isn't. Based on
that function, I came up with the following one:
; ------------ version 8 ------------
; My fast version
; 92 bytes
; c=17+1*n
_strlen proc
mov eax, [esp+4]
xor ecx, ecx
loop2: test al, 3
jz loop1
cmp byte ptr [eax], cl
jz short ret0
cmp byte ptr [eax+1], cl
jz short ret1
cmp byte ptr [eax+2], cl
jnz short adjust
inc eax
ret1: inc eax
ret0: sub eax, [esp+4]
ret
adjust: add eax, 3
and eax, 0FFFFFFFCh
loop1: mov edx, [eax]
mov ecx, 81010100h
sub ecx, edx
add eax, 4
xor ecx, edx
and ecx, 81010100h
jz loop1
sub eax, [esp+4]
shr ecx, 9
jc minus4
shr ecx, 8
jc minus3
shr ecx, 8
jc minus2
minus1: dec eax
ret
minus4: sub eax, 4
ret
minus3: sub eax, 3
ret
minus2: sub eax, 2
ret
_strlen endp
; -----------------------------------
This one has the advantage of having k=17 for aligned strings and k=24 to 25
for misaligned ones.
The only question left to be answered is: 'Which version should we prefer?'.
If your program does not include string handling in it's time-critical parts,
I higly recommend either versions 5 or 4.5 (the inlined macro). As said before,
the size overhead of the inlined version is very small (if any), and it has
another advantage: it keeps the source code more readable, as it only involves
the needed registers (input and output) in one single line.
If string handling IS time-critical, I recommend version 8 (of course, it's
mine... :)). Even then, the average size of the handled strings is to be consi-
dered, as well as the percentage of unaligned strings. For unaligned strings of
16 or less characters, the fastest version would be an inlined version 5,
running at c=8+2*n.
The choice is yours....
____________________________________________________________________________
::::::::::.___ . ```
::::::::::| _/__. |__ ____ . __. ____ ____ __. \\
:::::: |____ | __/_ _\_ (.___| .___) |__\_ (._) /___) | ,
::::::::::/ / | \ | - | \ | - | - | \/| - |
.=:::::::::/______|_____|_____| (___|_____|______|____|_____|===============.
'=::::::::::==================| . ____ | (____====[ The C Standard lib ]==='
:::::::::: | |------| - |
:::::::::: | |______|______|CE
. :
C string functions: _strcpy
by Xbios2
I. INTRODUCTION
---------------
C syntax: char *strcpy(char *dest, const char *src);
_strcpy copies string src to dest, stopping after the terminating null character
has been moved, and returns dest.
The 'traditional' way to do this is with the 'rep movs' instruction. BC 4.02
and kernel32 use it. The problem is that it is rather slow (BC _strlen takes
53+5.5*n cycles, lstrlenA takes 74+5.5*n cycles, and optimizing their code
leads to 46+5.5*n cycles wher n the number of chars, see part I of these
articles). This is because even though the 'rep movs' instruction is fast it
needs to know the number of bytes to copy in advance. So, _strcpy includes a
_strlen function before the actual copying, which is implemented through 'repne
scasb', a slow instruction.
In this article we will examine two 'modern' _strcpy functions, found in
MSVCRT.DLL and Borland C++ Builder library. Those functions are (supposed to be)
optimized for Pentium processors. If you're not familiar with optimization for
Pentium processors, I suggest you read the document on Pentium optimization by
Agner Fog (http://announce.com/agner/assem).
II. STRCPY IN MSVCRT
--------------------
; c=39+1.75*n / 146 bytes
strcpy proc
push edi
mov edi, [esp+8] ; dest
mov ecx, [esp+0Ch] ; src
test ecx, 3
jz short loop1
algn: mov dl, [ecx]
inc ecx
test dl, dl
jz short one
mov [edi], dl
inc edi
test ecx, 3
jnz short algn
loop1: mov edx, -81010101h
mov eax, [ecx]
add edx, eax
xor eax, -1
xor eax, edx
mov edx, [ecx]
add ecx, 4
test eax, 81010100h
jz short nozero
test dl, dl
jz short one
test dh, dh
jz short two
test edx, 0FF0000h
jz short three
test edx, 0FF000000h
jz short four
nozero: mov [edi], edx
add edi, 4
jmp short loop1
;... in the DLL, there is code here, not used by strcpy
one: mov [edi], dl
mov eax, [esp+8]
pop edi
retn
two: mov [edi], dx
mov eax, [esp+8]
pop edi
retn
three: mov [edi], dx
mov eax, [esp+8]
mov byte ptr [edi+2], 0
pop edi
retn
four: mov [edi], edx
mov eax, [esp+8]
pop edi
retn
strcpy endp
This procedure does the following:
1. Read arguments (src, dest) from stack
2. Check if src is aligned on a 4 byte boundary
If not, copy byte after byte until src gets aligned
3. Loop
Read one dword from src
Test if there is a zero byte in the dword
If no zero, copy dword to dest, loop back
4. Copy the remaining bytes
5. Return with dest in eax
Actually the code above compiles to 130 bytes. The extra 16 bytes are added
because between the loop and the 'one:' label there is the strcat function. So
4 conditional jumps take the 6-byte form, not the 2-byte one.
This function takes 39+1.75*n. This means that the loop takes 7 cycles to
execute (since each time the loop runs, it copies 4 bytes). Here is the explan-
ation of the loop (U and V refer to the pipe the commands run in).
loop1: mov edx, -81010101h ; U 1st
mov eax, [ecx] ; V
add edx, eax ; U 2nd
xor eax, -1 ; V
xor eax, edx ; U 3rd
mov edx, [ecx] ; V
add ecx, 4 ; U 4th
test eax, 81010100h ; V
jz short nozero ; U 5th
...
nozero: mov [edi], edx ; U 6th
add edi, 4 ; V
jmp short loop1 ; U 7th
The problem here is that both jumps run in the U pipe so they will not pair.
Generally it's better to have an even number of instructions in each block of
code. Just by moving one instruction this code will run in 6 cycles (i.e.
39+1.5*n cycles):
loop1: mov edx, -81010101h ; U 1st
mov eax, [ecx] ; V
add edx, eax ; U 2nd
xor eax, -1 ; V
xor eax, edx ; U 3rd
mov edx, [ecx] ; V
test eax, 81010100h ; U
jz short nozero ; V 4th
...
nozero: mov [edi], edx ; U 5th
add ecx, 4 ; V <<< moved instruction
add edi, 4 ; U
jmp short loop1 ; V 6th
Everything pairs perfectly, and so 12 instructions only take 6 cycles. Pay
attention to one thing: if 'add ecx, 4' and 'add edi, 4' are swapped, we get
back to 7 cycles per loop, even though the pairing is the same. This is because
the 'mov eax, [ecx]' instruction uses ecx to access memory, but ecx was changed
in the previous clock cycle (add ecx, 4 / jmp short loop1). This causes an AGI
stall (Address Generation Interlock), which wastes one cycle.
As you 've noticed, _strcpy makes sure that the data read from src is aligned,
because reading aligned dwords is faster. If src is aligned, the test only takes
one cycle more, so it shouldn't trouble us. Yet, aligning src is not always a
good idea. Suppose that you have an unaligned string and want to copy it in a
buffer that is aligned. So what happens is that by aligning src we misalign
dest. The problem is that misaligned writes are more expensive in cycles than
misaligned reads. So _strcpy should either align dest or leave everything
untouched. (not aligning src introduces an extremely small possibility of an
access violation error, read section V below for details).
III. STRCPY IN C++ BUILDER
--------------------------
; c=66+1.75*n / 146 bytes
_strcpy proc
push ebp
mov ebp, esp
mov ecx, [ebp+0Ch] ; src
mov edx, [ebp+8] ; dest
mov eax, ecx
and eax, 3
jmp algn[eax*4]
; ------------------------------------
algn dd offset loop1
dd offset algn3
dd offset algn2
dd offset algn1
; ------------------------------------
algn3: mov al, [ecx]
or al, al
jz short one
mov [edx], al
add ecx, 1
add edx, 1
algn2: mov al, [ecx]
or al, al
jz short one
mov [edx], al
add ecx, 1
add edx, 1
algn1: mov al, [ecx]
or al, al
jz short one
mov [edx], al
add ecx, 1
add edx, 1
loop1: mov eax, [ecx]
or al, al
jz short one
or ah, ah
jz short two
test eax, 0FF0000h
jz short three
test eax, 0FF000000h
jz short four
mov [edx], eax
add ecx, 4
add edx, 4
jmp short loop1
four: mov [edx], eax
mov eax, [ebp+arg_0]
pop ebp
retn
three: mov [edx], ax
mov byte ptr [edx+2], 0
mov eax, [ebp+arg_0]
pop ebp
retn
two: mov [edx], ax
mov eax, [ebp+arg_0]
pop ebp
retn
one: mov [edx], al
mov eax, [ebp+arg_0]
pop ebp
retn
_strcpy endp
This function runs at 66+1.75*n cycles. The aligning is done in an awful way.
If the aligning code is removed, we gain 39 cycles. By not using ebp, we save
4 more cycles. The loop takes 7 cycles, as shown below:
loop1: mov eax, [ecx] ; U 1st
or al, al ; U 2nd
jz short one ; V
or ah, ah ; U 3dr
jz short two ; V
test eax, 0FF0000h ; U 4th
jz short three ; V
test eax, 0FF000000h ; U 5th
jz short four ; V
mov [edx], eax ; U 6th
add ecx, 4 ; V
add edx, 4 ; U 7th
jmp short loop1 ; V
The first two instructions don't pair because 'or al,al' accesses a register
changed by the previous instruction. Anyway, there are 13 instructions, not 12
as in the MSVCRT function. So, one instruction has to be removed. This instruct-
ion is the unconditional jump (generally unconditional jumps can be avoided).
Notice that if we get through to 'jz short four', the 'mov [edx], eax' instruct-
ion will be executed anyway. So we rewrite the code as:
loop1: mov eax, [ecx] ; U 1st
inc ecx ; V
or al, al ; U 2nd
jz short one ; V
or ah, ah ; U 3dr
jz short two ; V
test eax, 0FF0000h ; U 4th
jz short three ; V
mov [edx], eax ; U 5th
add edx, 4 ; V
shr eax, 24 ; U 6th
jnz short loop1 ; V
Notice that we use 'shr eax, 24' instead of 'test eax, 0FF000000h', because we
no longer need the value in eax, and the 'shr' form is two bytes shorter.
A modified version of this strcpy is the best I could come up with:
IV. A FAST STRCPY
-----------------
; c=25+1.5*n / 80 bytes
_strcpy proc
mov ecx, [esp+8] ; src
mov edx, [esp+4] ; dest
push edx ; save return value
test edx, 3 ; check if dest is aligned
jz short loop1
algn: mov al, [ecx]
inc ecx
mov [edx], al
inc edx
test al, al
jz short return
test edx, 3
jnz short algn
loop1: mov eax, [ecx]
add ecx, 4
or al, al
jz short one
or ah, ah
jz short two
test eax, 0FF0000h
jz short three
mov [edx], eax
add edx, 4
shr eax, 24
jnz short loop1
pop eax
retn
three: mov byte ptr [edx+2], 0
two: mov [edx], ax
return: pop eax ; restore return value
retn
one: mov [edx], al
pop eax ; restore return value
retn
_strcpy endp
This function aligns dest instead of src, which, as discussed above, is faster.
It can run at one cycle less, by reading the return value directly from the stack
and not push/popping it, but it would take 8 bytes of code more.
Slight modifications to this routine give us three other functions:
- _stpcpy is exactly the same as _strcpy, only that it returns a pointer to the
ending null char copied in dest
- _strdup is a combination of _strlen, _malloc and _strcpy
- _strcat is a combination of _strlen and _strcpy
The MSVCRT _strcat actually counts the chars in src and then jumps into the code
of _strcpy to perform the actual copying. Strangely, _strdup is implemented
through 'repne scas' and 'rep movs'. It even has two 'repne scasb' instructions,
one to calculate the length of the string to pass to the malloc function, and
one to calculate the length of the string to copy, even though these two values
are the same. So, even if coding in MS C++, using _strlen/_malloc/_strcpy is
faster than using _strdup.
V. IS IT FOOLPROOF?
-------------------
To be honest, no. But programming is hardly ever so... First of all, any version
of strcpy (of any function, generally) will fail if it tries to read or write
data in a page it doesn't have access. This is hardly the case, but it can
happen:
-If there is no NULL character between the address of src and the last byte in
the valid page.
-If src is longer than the distance between dest and the last valid byte.
The first case is extremely unlikely, because even if src was corrupt and had no
terminating NULL, one is very likely to be encountered somewhere. The second
case is also unlikely, and it means that the programmer didn't allocate enough
space for dest. Anyway, corrupt data or not enough allocated space even if they
don't cause an access violation, they cause problems. But the problem was
created by the programmer, not the function. Yet there are also two cases where
the strings ARE ok but an access violation occurs. These cases appear only on
'optimized' versions of strcpy, not on the 'rep movsb' version.
The first case would appear in a strcpy function that doesn't align src (it
either aligns dest or leaves both unchanged), if src is not aligned and it ends
on a page boundary. Then the last read operation would try to read one to three
bytes on a page it doesn't have access. This doesn't happen on the Builder and
MSVCRT functions and happen on the one I give. Yet, it is really unlikely to
happen, and aligning dest is faster.
The second case would appear if dest points to a character in src (including
the terminating NULL). What happens is that the NULL of src (and any other
found) are overwritten, so no NULL is found and we finally get an access
violation. This doesn't happen in the 'traditional' versions, because we only
copy strlen(src) bytes. But even in those versions the last character copied
wouldn't be a NULL, so dest wouldn't be a proper string.
____________________________________________________________________________
____ . : . ```
| |___ : | ---- _____ ______ _____ | \\
| | |___|_ |______\ |---/ ._____/\____) -- (_) / |____) | ,
| ' | / | \___ __/ | | \/| | - |CE
.==|________| (______|_______/ \==|_____/\__________|____| |______|===.
'=========| |===========/----|___\==================[ The Unix World ]==='
:
X-Windows in Assembly Language: Part II
by mammon_
OK, let's face it: you've seen the tedium of XLib, one *has* to use widgets in
order to get any programming done in XWindows. 'But this is assembly langauge',
the masochist might point out. 'Aren't widgets a little Visual-Basicy?'
Not in the slightest. A widget is simply a C++ class exported for use --much
like the windows API functions, only a little more object oriented...maybe a
good comparison would be MFC or VCL. Xt, or 'X toolkit Intrinsics', is the
interface that widget sets [such as Athena, Qt or GTK] use to interface with
XLib. The Xt include files are in /usr/X11R6/include/X11, its libraries are in
/usr/X11R6/lib, and its exported functions are all prefixed with "Xt".
For the following examples I will be using the Atehna widget set, which is
supplied with XFree86. The include files for Athena are in /usr/X11R6/include/Xaw
and the libraries are in /usr/X11R6/lib.
A barebones Xt/Athena app in C would run as follows:
//====================================================================-xthell.c
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Command.h>
void Quit(w, client_data, call_data) //CallBack function
Widget w;
XtPointer client_data, call_data;
{
exit(0);
}
main(argc,argv)
int argc;
char **argv;
{
XtAppContext app_context;
Widget ShellWidge, ButtnWidge;
ShellWidge = XtVaAppInitialize( &app_context, "toplevel", NULL, 0, &argc, \
argv, NULL, NULL);
ButtnWidge = XtVaCreateManagedWidget("hellbutton", commandWidgetClass, \
ShellWidge, NULL);
XtAddCallback(ButtnWidge, XtNcallback, Quit, 0);
XtRealizeWidget(ShellWidge);
XtAppMainLoop(app_context);
}
// compile with cc -o xthell xthello.c -lXmu -lXaw -lXt -lX11 -L/usr/X11R6/lib
//=========================================================================-EOF
Pretty ugly, eh? This boils down to the following steps:
1) Create the top-level 'Canvas' widget [the window]
ShellWidge = XtVaAppInitialize( &app_context, "toplevel", ..... )
2) Create the button widget
ButtnWidge = XtVaCreateManagedWidget("hellbutton", ..... )
3) Register a callback for the button
XtAddCallback(ButtnWidge, XtNcallback, Quit, 0);
4) Show the top-level widget
XtRealizeWidget(ShellWidge);
5) Transfer control to the Xt message loop
XtAppMainLoop(app_context);
The most interesting thing about Xt programming is in fact the callbacks.
Instead of writing a message processing loop, you register a callback function
for each widget and then pass control to Xt, which processes the messages for
you and dispatches each message to the appropriate callback function. The call-
back receives a pointer to the widget that sent the message [the same argument
as passed to XtAddCallback], a client_data pointer [the last argument passed to
XtAddCallback, used to pass data from the main routine to the callback], and a
call_data pointer, which contains information from the message [such as cursor
or scrollbar position].
The calls themselves are pretty straightforward:
XtVaAppInitialize initializes [sic] the X app and takes as its arguments a
pointer to an XtAppContext structure, the class name of the application,
application-specific command line options {args 3 and 4], argc, argv, a default
resource-settings file, and a NULL to terminate the arguments list [the
XtVaAppInitialize function actually takes a number of different parameters]; it
returns a handle to the 'canvas' or 'top-level' widget, on which all other
widgets will be painted.
XtVaCreateManagedWidget is used to create any of the Xt widgets [Athena, GTK,
etc], and takes as its parameters the instance name, the widget class, the
parent widget, and a NULL to terminate the arguments list; it returns a pointer
to the created widget.
XtAddCallback is used to register a callback function with a specific widget;
it takes as its parameters a pointer to the Widget, the callback type, the
function being registered, and a pointer to client_data which will be passed to
the callback.
XtRealizeWidget is simply used like ShowWindow in Windows; it takes a single
parameter which is the widget to 'show'; it displays that widget and its
children.
XtAppMainLoop takes the current application context [which was filled with the
XtVaAppInitialize call] and turns control over to the Xt message processing
loop. Note that the program does not have to return; in this example, the exit
call is placed in the callback function.
Here is the same application written for NASM:
;===================================================================-xthell.asm
BITS 32
GLOBAL main
GLOBAL bail
EXTERN XtVaAppInitialize
EXTERN XtVaCreateManagedWidget
EXTERN XtAddCallback
EXTERN XtRealizeWidget
EXTERN XtAppMainLoop
EXTERN commandWidgetClass
EXTERN exit
SECTION .data
AppContext DD 0
ShellWidge DD 0
ButtnWidge DD 0
ARGC times 128 DB 0
ClassName DB "toplevel",0
ButtnName DB "hellbutton",0
XtNcallback DB "callback",0 ;XtNcallback
SECTION .text
bail:
pop eax ; Xt_Pointer call_data
pop ebx ; Xt_Pointer client_data
pop ecx ; Xt_Pointer widget
push dword 0
call exit
;-------------------------- main
main:
mov eax, esp
push dword 0 ;Number of Args
push dword 0 ;Args
push dword 0 ;Fallback Resources
push dword 0 ;argv
push dword ARGC ;&argc
push dword 0 ;Number of Options
push dword 0 ;Options Array
push dword ClassName ;Class Name (String)
push dword AppContext ;Application Context (Ptr)
call XtVaAppInitialize
add esp, 36
mov [ShellWidge], eax
push dword 0
push eax ;Button parent (ShellWidge)
push dword [commandWidgetClass] ;Button widget type
push dword ButtnName ;Button class name
call XtVaCreateManagedWidget
add esp, 16
mov [ButtnWidge], eax
push dword 0 ; client_data
push dword bail ;CallBack function
push dword XtNcallback ; callback type
push eax ;CallBack widget (ButtonWidge)
call XtAddCallback
add esp, 16
push dword [ShellWidge] ;Widget Handle
call XtRealizeWidget
add esp, 4
push dword [AppContext]
call XtAppMainLoop
add esp, 4
ret
;==========================================================================-EOF
This can be compiled with the following commands:
nasm -f elf xthell.asm
gcc -o xthell xthell.o -lXaw -lXt -lX11 -L/usr/X11R6/lib
Most of the operation is the same as the C file; naturally you must push dword
0's instead of NULLs...and do not forget to push the arguments in reverse
order and to clean up the stack afterwards; this is C after all and not stdcall
is used in Windows.
You will have to study up on Athena to learn what the names of the various
widgets are ... I found it helpful to use
grep extern /usr/X11R6/include/Xaw/*
for a general overview. Note that the class names are strings in assembly; also
each of the various 'handles' [widgets, contexts, etc] is simply defined with a
DD 0 -- your generic 32-bit variable. The Callback type turned out to be a
string defined in the Xt header files; I simply recreated it above.
Another interesting gemis the need to call 'exit' rather than simply using a
'ret' as you would in console mode; the latter causes segmentation faults, most
likely due to the XtAppMainLoop call. In addition you *must* provide a pointer
to ARGC whether you check the command line or not; hence the 'ARGC: DB 128'.
In case you didn't notice, the Xt asm example is huge and clunky, with a lot of
not-so-obvious variable definitions. Having included a lengthy introduction to
NASM macros in this issue, I took the opportunity to create an xt.mac file
which will take some of the burden off of experimenting with small Xt apps. The
InitXt and RegisterCallback macros probably are not ready for prime-time just
yet, but they will do for testing purposes.
;=======================================================================-xt.mac
%macro CLASS 2
%1: DB %2,0
%endmacro
%macro WDGTPTR 1
%1: DD 0
%endmacro
%macro CONTEXT 1
%1: DD 0
%endmacro
%macro CHARSTR 2
%1: DB %2,0
%endmacro
%define WIDGET EXTERN
%define XLibAPI EXTERN
%define XtAPI EXTERN
%define PUBLIC GLOBAL
%define NULL dword 0
%define TERM_VARARGS dword 0
%macro InitXt 2
SECTION .data
CONTEXT AppContext
CLASS XtShell, "XtShell"
SECTION .text
EXTERN XtVaAppInitialize
push dword 0 ;Number of Args
push dword 0 ;Args
push dword 0 ;Fallback Resources
push dword 0 ;argv
push dword %2 ;&argc
push dword 0 ;Number of Options
push dword 0 ;Options Array
push dword XtShell ;Class Name (String)
push dword AppContext ;Application Context (Ptr)
call XtVaAppInitialize
add esp, 36
mov [%1], eax
%endmacro
%macro XtMsgLoop 0
EXTERN XtAppMainLoop
push dword [AppContext]
call XtAppMainLoop
add esp, 4
%endmacro
%macro RegisterCallback 1
SECTION .data
CBType: DB "callback",0
SECTION .code
push NULL ;
push dword %1 ;CallBack function
push dword CBType ;
push eax ;CallBack parent (ButtonWidge)
call XtAddCallback
add esp, 16
%endmacro
%macro CALLBACK 1
SECTION .data
Call_Data_%1: DD 0
Client_Data_%1: DD 0
Widget_%1: DD 0
GLOBAL %1
SECTION .text
%1:
pop eax
mov [Call_Data_%1], eax
pop ebx
mov [Client_Data_%1], ebx
pop ecx
mov [Widget_%1], ecx
%endmacro
%define ENDCALLBACK nop
%macro ENTRYPOINT 1
GLOBAL %1
%1:
%endmacro
;==========================================================================-EOF
Most of the macro file should be readily apparent if you are familiar with the
NASM macro facility. I did take the opportunity to clean up the callback
function, so that the parameters to the callback are saved in variables, but for
the most part it does the same as the equivalent code in the preceding asm
example.
Now the xthell.asm sample will look as follows:
;===================================================================-xthell.asm
BITS 32
%INCLUDE "xt.mac"
;========================================================XTRN=====
XtAPI XtVaCreateManagedWidget
XtAPI XtAddCallback
XtAPI XtRealizeWidget
WIDGET commandWidgetClass
EXTERN exit
;========================================================DATA=====
SECTION .data
;------------
WDGTPTR ptrShell
WDGTPTR ptrButton
CLASS XHELL, "XHell"
CLASS HellButton, "HellButton"
CallbackType DB "callback",0 ;XtNcallback
ARGC times 128 DB 0
;========================================================CODE=====
SECTION .text
;------------
CALLBACK bail
push dword 0
call exit
ENDCALLBACK
ENTRYPOINT main
InitXt ptrShell, ARGC
push TERM_VARARGS
push eax ;Button parent (ShellWidge)
push dword [commandWidgetClass] ;Button widget type
push dword HellButton ;Button class name
call XtVaCreateManagedWidget
add esp, 16
mov [ptrButton], eax
RegisterCallback bail
push dword [ptrShell] ;Widget Handle
call XtRealizeWidget
add esp, 4
XtMsgLoop
ret
;==========================================================================-EOF
Much prettier and hey, only twice as long as the C version! ;)
Next issue I will dwell on Xt/Athena a little longer and come up with some more
practical methods of automating the coding process.
____________________________________________________________________________
____ ___ _____ _| |_ ____ . ```
.__\ /__ ______ _) /.\ _/__ ___ ______\_ (_. | \\
| \/ | | \/ | \ | | | - | |CE ,
.==|________|______|______|_______|_______|_______| |======================.
'=================================================| :=[ Virtual Machines ]='
An Intro to the Java Virtual Machine
by Cynical Pinnacle
For awhile C/C++ reigned supreme and nothing challenged it but then along
comes Java, creating a splash, and causing outright corporate warfare to claim
right of ownership. Strangely enough the result of this war has not been dead
bodies but buckets and buckets of API's all given away for free. Just stop by
and take a look at Java's Official Website (http://java.sun.com) and what do
you find a good development kit with compiler, symbolic debugger, disassembler,
complete toolkit for creating GUI's, built in support for compression, encrypt-
ion, http, ftp, SMTP, POP3, IHMP, and more. Wow! But how can we take advantage
of all this?
First we have to step back a look at what Java really is. Because one of
the main goals of Java is platform independence (both from the chip and the OS).
The JVM, which supports Java, has to be both a chip and an OS. If any of us
(well lets say us programmers) were to design a chip and a OS in one, we would
fill it with features like built in security, automatic dependency resolution
and linking, network support, video and audio acceleration, along with more
common things such as built in data types (ints, floats, arrays, and objects ),
support for local variables, exception handling, support for debugging, and on
and on. This is really what Java is because it does all the things I mentioned
above and more. This is what Sun has tried to do - design an "Ideal" environ-
ment for writing and executing code, or write once run anywhere. But this
wealth of features comes at the price of speed of execution and further distance
further from the native code of your machine (unless you are running on a real
Java machine). And if you are like me the latter hurts as much as the former.
Still there are a alot of really appealing things about Java. And so the
challenge is to use these appealing features on our own terms. We can do this
by programming at a lower level to at least touch the native language of the
Java Virtual Machine (Java Assembly language!!)
The JVM:
I am going to take a programmers view of the JVM and say it is simple
because from our perspective it is. But for the sake of completeness I will
list the other components of the JVM:
Memory Manager: This is the unit responsible for the famous garbage collect-
ion and heap management. I say clean up your own garbage.
Error/Exception Manager: Handles unforseen conditions.
Native Method Support: This is to allow you to call WaitForSingleObject
from within Java. It is responsible for loading DLLs, resolving entry points
and executing them. Note Java only supports dynamic linking.
Threads: Java doesn't have to worry so much about memory because it is all
allocated on the stack. Each thread gets its own stack frame (chunk of stack
for its personal use). Switching threads in a stack based machine is easy. You
just make the threads stack top the machine's stack top and go.
Class Loader: This is just like the loaders in NT and 95. It brings up
class files from the disk initializes them (headers memory etc) and passes
execution to the classes entry point.
Security Manager: Want to find out whether or not you can do something.
Ask him.
Execution Engine: This is where the JVM opcodes are translated into native
opcodes. This is the part of the JVM which a low level Java programmer will
interact with most. The Execution engine has a much simpler structure than a
Pentium. At its heart is a stack where instuctions are executed. Note that
the JVM has no registers, which is more a trait of Virtual machines than Java.
Basically opcodes and operands are popped off of the stack and executed by the
VM bases upon a mapping between Java opcodes and Native opcodes. In addition,
there is built in support for local variables (more later). And as mentioned
before the stack is symbollically divided up by the JVM. Each method (read
method to mean function) gets it own stack frame which is allocated when the
method starts and deallocated when the method exits (sound familiar eg x86 push
bp -- mov bp, sp .. leave instructions). The Execution engine understands the
int, long, float, double, byte, char, short, reference (eg pointer) and the
instructions it understands are strongly typed (for example: there is an
instruction called iload which loads an integer from a local variable onto the
stack (like mov eax, myLocalVariable) but there is also a dload, lload, and
fload for doubles, longs, and floats).
Now with a little background it is time to learn or burn but first we need
some tools. First you will need to download the JDK from http://java.sun.com
(I recommend 1.1.x and the current I think is 7) you will also need a Java
assembler which is called Jasmin also free at
http://www.cat.nyu.edu/meyer/jasmin/
and you will need a good editor (I heartily recommend Visual SlickEdit 3.0 or
4.0 at http://www.slickedit.com ). You can get docs about the JVM from
http://www.javasoft.com/
And the best book I have found on the JVM (and Jasmin) is "Java Virtual Machine"
by Jon Meyer and Troy Denning. There are several useful tools with the JDK
comes a program called javap which is a Java .class file disassembler!
With the -c switch it will produce JASM code from any .class file. Note that
reversing Java programs is not nearly as hard as x86. Try it. Take some Java
.class file you have laying around ( Keep it small so you don't get confused )
and disassemble it then you will see what I mean. I can see a very difficult
future for Java Shareware programmers. There are also many other 3rd party
tools out there for mainpulating .class files.
With all of those tools installed we are ready to write the mandatory
"Hello from JASM!" program. First comes the Java Assembler code followed by
its Java equivilant.
;***************************************************************
;Export the class name so Java can find it
class public Hello
;Simplest class to derive from
super java/lang/Object
; General facts
; .method - means this is a function
;
; public - means you are it is visible externally (low
; level Object Orientedness)
;
; Java always uses explicit paths in Unix notation hence you
; end up with a lot of notation like java/lan/Object.
; java/lang/Object is the path to the superclass. In a lot
; of the functions there is a V, this means it returns void
; the syntax for specifying arguments is strange. I
; recommend reading Jonathan Meyer's Jasmin documentation
; http://www.cat.nyu.edu/meyer/jasmin/
; This method is called init and is in every class
; all that is done here is to push the contents of local
; variable 0 onto the stack and call the superclass's
; (Object) init method. Local variable 0 is always
; the reference (pointer) to the equivalent of C++'s this
.method public <init>()V
aload_0
; This just calls the superclasses init method
invokenonvirtual java/lang/Object/<init>()V
; Get out
return
end method
; Here is the main function which is publically visible
; is static and thus shared by all instances of the class,
; it takes one argument of type [ljava/lang/String, which
; is an array of strings eg: argv**, and returns void hence
; the V.
.method public static main([Ljava/lang/String;)V
; Delcare your stack memory usage
.limit stack 2
.limit locals 1
; Push 5 onto the stack and store it in local variable 1
bipush 5
istore_1
LoopTop:
; These next two lines put the parameters for the println
; function onto the stack in the right order. Java uses
; the pascal calling convention (push left to right and
; the callee cleans up). First a reference to the stream
; object doing the work is pushed onto the stack. Next a
; reference to the string to be printed is pushed.
;get the pointer to the stream object and push it
getstatic java/lang/System/out Ljava/io/PrintStream;
;get the pointer to the string and push it
ldc "Hello from JASM!"
;call println
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
; These next three lines are the loop condition
; iinc adds -1 to local variable 1. iload_1 pushes
; local variable 1 onto the stack and ifne compares it to
; zero (just like jnz in x86). If it is not equal to zero
; we jump to LoopTop
iinc 1 -1
iload_1
ifne LoopTop
; Go home
return
;Declare the end of the function
end method
;
; Java equivalent:
;
; public class Hello
; {
; public static void main ( String args[] )
; {
; int i;
; for( i = 0;i < 5; i++ )
; {
; System.out.println( "Hello from JASM!" );
; }
;
; }
;
; }
;***************************************************************
To run this JASM sample cut out the stuff between the *'s and save it to a
file called Hello.j. Next type jasmin Hello.j. This should generate a
Hello.class file. Now type java Hello and you should see the string "Hello
from JASM!" printed out 5 times. This is enough to get you started poking
around in the JVM and looking a little closer at the .class files you find
lying around ;) . More to come?
____________________________________________________________________________
____ . __ ```
._| __/___ : | ___ _____ __) |_ _____ \\
| |_____ |____|__ |_______ ______ ______ _) _ |_\_ __)___| __/_____ ,
| | ( | | __ | __ | ----' | | |_______ |CE
.=|_________| /_______|_______| _____| _____|________|____________________|=.
'===========| |===========|___|==|___|===================[ snippets ]==='
NumFactors
by Troy Benoist
;Summary: Routine to determine the number of factors for a 16-bit value
;Compatibility: All DOS versions/8088+ instructions
;Notes: 22 BYTES Input: AX = Value to check for number of factors
; * If CX is 2, check value in AX is prime.
mov bx,1 ;Test=1 (Test is the testing value of each theoretical
;factor of AX, from 1-AX).
xor cx,cx ;Count=0 (Count is running total with # of factors for AX).
ChkFctr:
xor dx,dx <--- ;Need to divide DX:AX by BX, but DX is not used-- clear it.
push ax | ;Dividing by BX puts quotient in AX, but quotient is not
| ;needed, and we need to keep the value to check, so save it.
div bx | ;Divide DX:AX by BX. Remainder is in DX.
pop ax | ;Restore value to check into AX.
cmp dx,0 | ;Is remainder=0? (Did Test divide evenly into check value?)
jnz NC -----| | ;If not, Count remains unchanged.
inc cx | | ;If so, factor found, so Count=Count+1.
NC: inc bx <---| | ;Test=Test+1.
cmp bx,ax | ;Is Test greater than check value?
jbe ChkFctr___| ;If not, go back and check next Test factor.
______________________________________________. __________________________ ```
| . ```
._____ ___ ____ ___ ___ ____ : | ______ \\
| __/__| |______\_ (_) | (____\_ ( ______ |________ _) __ |___. ,
| | | - | | | - | _ / | __ | ----' |CE
.==|_______|--)___|______|___|___|_______|--(_______|_______ |___________|===.
'=============================================[ issue | _____| challange ]==='
|___\
Convert ASCII hex to binary in 6 bytes
by mammon_ [and help]
The Challenge
-------------
Write a routine for converting ASCII hex to binary in 6 bytes.
The Solution
------------
Well, actually, I cheated: I found the following text on the internet a few
months ago and decided to see if I could beat it:
===================================================================-Asc2hex.txt
An efficient algorithm for converting ASCII hex to binary
Ken Sykes (72177,141)
David Ogle (75676,2612)
There is a well-known algorithm for converting a binary number between
one and fifteen to its equivalent hex form in ASCII that only requires
four assembly language instructions. Assuming that the number to convert is
in AL, the following sequence performs the conversion:
add al,90h
daa
adc al,40h
daa
This instruction sequence is, as far as we know, the shortest (16 cycles)
self-contained routine to convert a binary number to hex. Inspired by
this code and the fact that a similar routine to convert ASCII hex to
binary would be useful, we came up with two algorithms that convert an
ASCII hex digit to binary in five assembly instructions or less.
The first algorithm takes advantage of an undocumented feature of the
8088. The AAM instruction (Ascii Adjust for Multiply) divides AL by 10,
placing the remainder in AL and the quotient in AH. The opcode for
AAM is: 0D4h,0Ah. It's no coincidence that the second byte is 10 - the
8088 uses the second byte of the opcode as the divisor! The same rule
applies to the AAD (Ascii Adjust for Division) instruction. With this in
mind, a conversion routine goes as follows (assuming the ASCII digit is in
AL and in the range '0..9,A..F'):
sub al,'0'
aam 16
aad 9
The only problem is the Microsoft Assembler does not accept this form.
By placing the opcodes in data statements, however, the following code
will assemble properly:
sub al,'0'
db 0D4h,10h
db 0D5h,09h
At three instructions and six bytes of code space, We are reasonably
sure this is the shortest self-contained sequence to perform the conversion.
The only drawbacks are the use of non-standard opcodes and the execution
time (147 cycles!). The second algorithm, loosely-based on this one,
relieves these restrictions.
The second algorithm makes exclusive use of fast instructions to
perform the conversion (again, AL holds the digit to convert):
add al,40h
cbw
and ah,09h
add al,ah
and al,0Fh
While two additional instructions are required, the routine executes
in 17 cycles. We are reasonably sure this is the fastest-executing self-
contained code to perform the conversion. It has the added benefit of
handling the ASCII values 'a'..'f'.
These algorithms will hopefully trim down the execution times of your
programs, and we welcome any suggestions or improvements on our code.
Happy Hacking!
===========================================================================-EOF
Sadly, I was unable to come up with a smaller version, or a faster one. Tinara
posted a similar solution to the APJ Message Board:
SUB AL, 30h
AAM 10h ; db D4h, 10h
AAD 09h ; db D5h, 09h
...so he gets kudos for uncovering by work what I managed by stealth. As for
next issue, I haven't had time to prepare a challenge, but I'm sure one will
crop up in the next month or so.
Next Issue Challenge
--------------------
None so far... submissions welcome.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::.......................................................FIN
--
----------------------
%-% 身 人
% * * % 不 在
% . % / 由 江
\\--/ 己湖
-----------------------
※ 来源:.紫 丁 香 bbs.hit.edu.cn.[FROM: 202.118.243.47]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:1,263.849毫秒