THE ASSEMBLY LANGUAGE "MAGAZINE" #4 1989
THE ASSEMBLY LANGUAGE "MAGAZINE" VOL 1 NUMBER 4
December 1989
## #### #### ####### ## ## ###### #### ## ##
#### ## ## ## ## ## # ### ### ## ## ## ## ##
## ## ### ### ## # ####### ## ## ## ## ##
## ## ### ### #### ####### ##### ## ####
###### ### ### ## # ## # ## ## ## ## # ##
## ## ## ## ## ## ## # ## ## ## ## ## ## ##
## ## #### #### ####### ## ## ###### ####### ####
#### ## ## ## #### ## ## ## #### #######
## #### ### ## ## ## ## ## #### ## ## ## #
## ## ## #### ## ## ## ## ## ## ## ## #
## ## ## ## #### ## ## ## ## ## ## ####
## # ###### ## ### ## ### ## ## ###### ## ### ## #
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #
####### ## ## ## ## ##### ###### ## ## ##### #######
Written by and for assembly language programmers.
Table of Contents
Editorial.......................................2
Policy and Guide Lines..........................3
Beginners' Corner...............................5
Structure, Speed and Size
By Thomas J. Keller........................7
Editorial Rebuttal........................11
Accessing the Command Line Arguments
By Thomas J. Keller.......................13
Original Vector Locator
by Rick Engle.............................15
How to call DOS from within a TSR
by David O'Riva...........................22
Environment Variable Processor
by David O'Riva...........................26
Program Reviews................................35
Multi-Edit ver 4.00a .....................35
SHEZ......................................36
4DOS......................................36
Book Reviews...................................37
Assembly Language Quick Reference
Reviewed by George A. Stanislav......37
GPFILT.ASM.....................................39
;Page 1
Editorial
It has been much too long since the last issue of the Magazine was
published. Much of this time was due to the lack of submissions but
there has been enough to assemble since early November. I hope that
it will not be as long till the next one is ready for distribution.
You can help make that possible by writing up and sending in an
article.
I'm trying out a new editor for this issue. That makes it four
editors for 4 issues. There is a review of it in the review section.
There is a continueing and probably insoluable problem in
formatting the 'Magazine'. The readability of the text portions is
enhanced with wider margins and is more easily bound with a wide
left margin. The difficulty arises when source code is included. 80
columns is little enough in which to fit the code and comments,
allowing nothing for margins. So this time we'll try a 5 space
margin on the left for the text portion. Further offset should
be done with your printer.
A couple of quick notes here as I don't know where else to put
them.
For the assembly programmer the principle difference in writing
for DOS4+ is that there is a possible disk structure using 32 bit
FAT entries. This of course has no effect as long as you use only
the DOS calls for disk access, but if you are going to do direct
disk editing this must be checked for.
The occasional ~ is for the use of my spelling checker.
;Page 2
Policy and Guide Lines
The Assembly Language 'Magazine' is edited by Patrick and David
O'Riva. We also operate the AsmLang and CFS BBS to distribute the
'Magazine' and to make available as much information as possible to
the assembly language programmer. On FidoNet the address is
1:143/37. Address:
2726 Hostetter Rd
San Jose, CA 95132
408-259-2223
Most Shareware mentioned is available on the AsmLang board if local
sources cannot be found
Name and address must be included with all articles and files.
Executable file size and percent of assembly code (when available)
should be included when a program is mentioned and is required from
an author or publisher. Any article of interest to Assembly
language programmers will be considered for inclusion. Quality of
writing will not be a factor, but I reserve the right to try and
correct spelling errors and minor mistakes in grammar, and to remove
sections.
Non-exclusive copyright must be given. No monetary
compensation will be made.
Outlines of projects that might be undertaken jointly are
welcome. For example: One person who is capable with hardware
needs support from a user friendly programmer and a math whiz.
Advertisements as such are not acceptable. Authors and
publishers wishing to contribute reviews of their own products will
be considered and included as space and time permit. These must
include executable file size, percent of assembly code and time
comparisons.
Your editor would like information on math libraries, and
reviews of such.
Articles must be submitted in pclone readable format or sent
E-mail.
Money: Your editor has none. Therefore no compensation can be
made for articles included. Subscription fees obviously don't
exist. Publication costs I expect to be nil (NUL). Small
contributions will be accepted to support the BBS where back issues
are available as well as files and programs mentioned in articles(if
PD or Shareware ONLY).
Shareware-- Many of the programs mentioned in the "Magazine"
are Shareware. Most of the readers are prospective authors of
programs that can be successfully marketed as Shareware. If you
make significant use of these programs the author is entitled to his
registration fee or donation. Please help Shareware to continue to
;Page 3
be a viable marketing method for all of us by urging everyone to
register and by helping to distribute quality programs.
;Page 4
Beginners' Corner
I finished up the last column by saying I would discuss more
techniques this time. I have entirely forgotten what they were. So
without dwelling on that we will just move on the means of getting
your program ready to run. The two formats (.com and .exe) are very
different and so will be discussed separately.
COM Programs
On Entry all of your segment registers are set to the same
value, that of the start of the PSP. Your stack pointer is set to
the top of the segment, and your instruction pointer is set to 100h.
You need to make a generous estimate of the maximum amount of stack
that your program can use (or count it exactly) Each level of Call
uses 2 bytes (for the address of the next instruction). An INT uses
6 bytes. (2 for the IP, 2 for the CS, and 2 for the Flags). Each push
of course uses 2. So if your subroutines can go 4 levels deep and
contain 7 pushes (without intervening pops) and the deepest contains
an INT21h, then you would need at least 28 bytes of stack. But
stack space is cheap, and you might need to change things. So use a
nice round number of 128 bytes. BIOS also uses YOUR stack in the
earlier versions of DOS, and the guideline for that is at least 128
bytes. Result: 256 bytes is safe for a modest program. To implement
this the following lines of code could be used at the start of the
program: ~
org 100h
jmp main
defstack db 32 dup('stack ')
stacktop label byte
;other data
main:
cli
mov sp,offset stacktop
sti
~
The db statement is 32 times the string of 8 characters
totaling 256 bytes. It could just all well be db 256, but it is
kind of nice when looking at it with a debugger to see the stack
area and how much has been used all nicely labeled. The cli and
sti aren't really necessary here because it is only one instruction,
but you are dealing with the stack, and it's well to remember that.
At the end of your program you need a label e.g.
~
progend label byte
Then following your stack adjustment above:
mov bx,offset progend
mov cl,4
shr bx,cl
inc bx
~
;Page 5
These instructions change the offset value into a number of
paragraphs (16 bytes) and to the end of the last paragraph. This is
the total number of paragraphs that will be occupied by your
program. Then it is necessary to inform DOS of this information:
~
mov ah,4ah
int 21h
~
4a is the DOS function to modify allocated memory. It needs the
new number of paragraphs in BX (which is where it was put)
At this point, your program is in an orderly condition. Your
data as well as that in the PSP is available with the DS and ES
registers, The stack is large enough and well mannered, and all
surplus memory is available to you or other programs.
;Page 6
Structure, Speed and Size
as
Elements of Programming Style
By Thomas J. Keller
P.O. Box 14069
Santa Rosa, CA, 95402
Let us examine the reasons for choosing to implement a given
program in assembly language as opposed to some high level language.
The reasons most commonly given are execution speed and memory image
size.
Execution speed, except in certain highly critical realtime
applications, or certain high resolution graphics applications, is
probably not a realistic reason to opt for assembly language. For
example, a good C compiler with optimization (which precludes use of
Turbo or Quick C) produces code which only suffers a 10-15% speed
penalty, over typical hand crafted assembly language code. It is
possible to write assembly language code which will run faster than
this, but few programmers have the requisite skills.
In most applications, a 10-15% speed penalty is simply
irrelevant. It is unlikely that the typical user would even notice
such a difference. In particular, programs which are highly
interactive, and thus spend far and away the greatest amount of time
waiting for user input are highly insensitive to such speed
penalties. Many people don't realize that even assuming that the
user is typing at a rate of 100 wpm (approximately 500
keystrokes/minute), the CPU is still spending the bulk of its time
idling, waiting for the next keystroke.
There are, of course, always exceptions to virtually any rule,
and there are most certainly exceptions to this rule. Word
processors, for example, while actually accepting text input, are
not speed critical. When performing global search and replace, or
spell checking, for example, even a 10% penalty can become expensive
on large documents. So there is a tradeoff to be made.
Assembly language programs cost considerably more than 10-15%
more to develop than high level programs. The minutiae involved in
managing a massive assembly language programming effort are
overwhelming. Assembly language programs take MUCH longer to
complete, in almost all cases, than high level programs do, a major
contributory factor in the overall cost of development. Finally,
projects developed in high level programming languages are much more
likely to be easily ported to platforms based on processors other
than the platform on which the project is developed, and very
important consideration for a major project. The ability to port a
project easily to other platforms increases the market for a
product, thereby not only increasing the profitability of the
product, but also helping to reduce the sale price of the product
(larger market generally translates to lower per unit cost).
;Page 7
So the vendor or developer must analyze the relative impact of a
small improvement in execution speed vs a large increase in
development time and cost, which consequently translates to higher
selling prices, thereby reducing the anticipated market for their
product. In many cases, the tradeoffs do not merit choosing
assembly language.
Let us turn now to binary image size (memory size). The
advantages of small programs are clear, when examining programs
which are, in the DOS world, TSRs (The MAC and AMIGA worlds have
similar cases, though I am not sufficiently familiar with them to
know what they are called). These programs are loaded into memory,
and remain there until explicitly removed, which means that the
memory they use is NOT available for other uses. Device drivers
similarly use memory, precluding its use for other programs, and
therefore also clearly benefit from small size. In the
multi-tasking world (DeskView or PC/MOS, in the PC clone market),
small executables also have an advantage, permitting more programs
to be run "simultaneously" in a given memory configuration, though
running multi-taskers in severely restricted memory configurations
probably qualifies as a technical error.
What of normal, single tasking, single user environments (such as
DOS, the MAC and AMIGA environments)? Besides the ego boost of
creating a very small, very tight utility or application, what benefit
is there in generating very small programs?
They take less disk space to store, but realistically, at least
under DOS, lots of very small utilities may actually not achieve a
significant savings in disk space, due to granularity of storage
allocation. They load a little faster, in most cases.
But once again, the economics of the issue comes back to haunt us.
It is not clear that the effort and expense of writing most
applications in assembly language due to size considerations is an
economically rational decision. The same economic pressures and
considerations apply as do to the execution speed issue discussed
above.
On to structure. I must take issue with Patrick O'Riva regarding
their purpose and nature of "structured programming." While much of
his definition is true, it is incomplete, and appears to reflect a
misunderstanding of certain aspects of the structured approach to
programming.
Firstly, it is entirely possible (and not altogether a rare
occurrence) to write thoroughly unstructured code in PASCAL or C. One
must take care to recognize the difference between references to a
"block structured" language, as PASCAL and C both are, and "structured
programming," which is really a totally separate issue.
Structured programming is an approach to programming that is
thoroughly applicable to whatever language a project is being
implemented in. It implies firstly a step-wise refinement approach
to defining the solution to a problem which the program is to address
(in other words, determining the nature of the desired goal, and an
at least rational approach to reaching said goal). Secondly, it
;Page 8
involves determining, to the extent possible, the nature and structure
of the data that is to be processed by the program. Finally, it
involves a top-down approach to the actual coding process.
Just what is a top-down approach? Essentially, this means that we
code the high level functionality of the program first, programming
simple "do nothing" stubs for the lower levels of the program. As
necessary to test the high level code, we implement lower level
functions, again, if needed, programming still lower level stubs.
Assuming that the structured design approach of step-wise refinement
was used to begin with, the actual coding should really amount to
translating the logic flow diagrams, or pseudo-code, or whatever means
of recording the refinement process was used, into actual program
code. In the ideal situation, the program almost literally codes
itself at this point.
There is a myth that "structured programming" means "goto-less"
programming. In fact, this is not the case. This myth came into
being through misunderstanding of the rather harsh criticism of the
"go to" which occurred in the computer science journals beginning in
approximate the mid to late sixties. This criticism was based
primarily upon the typically excessive use of the "go to" in FORTRAN
and BASIC programming at the time. Such indiscriminate use of "goto"
led to what has been called "spaghetti" code, code which is virtually
impossible to trace or analyze.
In fact, there are many cases in programming where the goto is most
structured solution available. Structured coding techniques are
intended to clarify and make easier the process of analysis, design
and implementation of computer programs, not to define rigid, strictly
enforced rules in the face of all reason.
Structured programming is ALWAYS the best approach to ANY computer
program. If the internal requirements of the program, as regards
speed or memory utilization, dictate the use of goto's, then use them.
A properly documented GOTO can be far more "structured" than an
undocumented string of modular function calls.
So, back to assembly language programming. When is it appropriate
to choose assembly language to implement a program? First, and most
obviously, when the speed or memory utilization requirements of the
application demand the capabilities that well crafted assembly
language offers. Second, perhaps not so obviously, when it is
necessary to work at the hardware level a great deal. High level
languages, even C, do not generally manipulate hardware registers
efficiently. So, if your program makes frequent or widespread use of
direct hardware manipulation, it is a likely candidate for assembly
language.
Finally, and probably the most gratifying reason of all to choose
assembly language, is when you want the satisfaction of having tackled
a project in assembly and pushed the bits around to suit your purpose.
There is little I can imagine that is more satisfying than to reach
down into the microprocessor chip and twiddle those bits. Just be
sure that you don't let your ego cloud your judgment, when the
economics of the project are important (e.g., when a project is to be
distributed commercially, or there is an urgent need for speedy
;Page 9
completion).
I believe that all PROGRAMMERS (as opposed to casual computer
users) should learn the assembly language for the machines on which
they work. Besides offering the flexibility of shifting to assembly
to meet a specific goal, learning assembly intimately familiarizes the
programmer with the hardware on which s/he is working. The more you
know about your hardware environment, the better off you are.
;Page 10
Editorial Rebuttal
I thank Mr. Keller very much for his article and agree with
many of the points he has made. However I must still argue the
points of size and speed and justification.
Whenever a program is user limited and will not be used in a
multi-tasking environment as is often the case with a word processor
and certain drawing programs, there may be little to be gained in
assembly programming. Also there are programs which are DOS limited
and little speed increase is possible.
Mr. Keller uses a figure of 10 to 15 percent speed penalty. My
experience indicates a value closer to 300 to 400 percent though
direct comparisons are difficult to make because the same programs
are usually not written in both assembly and in C. The size
difference seems to be a factor of 5 to 10. The two prime examples
I can offer are both by Microsoft, and it can be assumed they make
use of an optimizing compiler. Their assembler is approximately 110k
in size. A86 while not compatible in syntax has comparable features.
It's size is 22k and assembles code in about one eighth the time.
Microsoft's programmers' editor is vaguely 250k. Qedit is
about 50k and is a mix of high level and assembly. You can grow gray
hairs waiting for the MS editor to do a search and replace, but if
you blink you'll miss it with Qedit. A fully capable full screen
editor without the extras that make it a pleasure to use can easily
be written in less that 5k. Give another 5k for features. What has
MS gained with the extra 240k of code?
David has recently completed (though they are still adding
modules) a database and accounting program for a multi-office
company. A much abbreviated version was threatening to overflow their
384k limit. Investigation of a Dbase implementation indicated in
excess of 500k. Data base sorts used to take 10 hours. They now take
20 minutes. Savings in processing time and entry time plus increased
functionality suggest a savings of $5000 to $10,000 per month PER
OFFICE. Code size? 35k. Are they unhappy about the $15,000 they've
been charged for a program that will get lost in a single floppy
disk?
Given the above examples, I must maintain that the use of high
level language, when there is significant processing to be done, and
when it will be used on a regular and continuing basis, benefits
only the software corporation, and is detrimental to the end user.
On Structured Programming I fully agree with Mr. Keller and
hope that he clarified any misconceptions I left you with. I prefer
a bottom up construction, but that is only preference and has no
effect on the end product.
Dave's notes: Mr. Keller mentions that it is possible to get
great size/speed reductions, but that few programmers have the requisite
skills. But to a large extent, it isn't the skill that makes the
program, it's the toolbox. The C language is extremely close to
assembly - MSC does a very good job of optimizing - and it takes care of
the minutiae for you. The problem with this is that the libraries
;Page 11
supplied with the compilers were written to handle very general cases.
The printf() function is an extreme example, but it typifies the
problem: If you use printf once in your program to print "Hello", it
adds 30K of code!
Another concern is that many high-level-language programmers
don't even realize that with a tweak here, using putc instead
of printf there, they can get much(!) better performance from their
programs. Familiarity with the quirks of the compiler being used is a
necessity... And even that isn't enough to get good performance out of a
large program. AND, it decreases portability. So you're right back
into the twiddling usually associated only with assembly.
I've found that if I use C for anything except flow control and
one-shot tools, my programs start to get huge and slow, relative to
anything that I've banged out in assembly. The database is a great
example - it's a very complicated application, with a completely
separated data engine & OS interface. If it had been written in C, it
would be working in multiple code segments on a 286 with 4 megs and
STILL take hours to run a balance, instead of 35K of code on an XT
network terminal with half-hour runs.
The database was indeed a massive effort, but at this point it
would be possible to strip out the engine and write with ease (and
macros - lots of macros) anything that could be done in C or Dbase, and
do it much better. And average runtime is cut at least in half, size by
50-90%. With a reasonably solid and application-specific toolbox, the
advantages TO THE CUSTOMER of assembly programming completely eclipse
those of any other language and the disadvantages of assembly itself.
Portability is another issue entirely. If you NEED
portability and fast development, and IF run time and general
productivity are not a concern, then C probably makes more sense.
There's this nagging feeling, though, that if the UNIX OS core had been
written in assembly by a reasonably good programmer, and been ported to
new systems in kind, that the university systems would be clipping
instead of slogging.
As far as structured programming goes, I usually design as I go
along, and end up with a functional (even rational) structure. Call it
"random-access programming." This is probably because I find it
difficult to call a routine until I've laid out the calling conventions
for it, and while I'm doing that I'll remember another routine that
should be written for another module... This is not the generally
recommended method, I gather.
;Page 12
Accessing the Command Line Arguments
in Assembly Language Programs
By Thomas J. Keller
P.O. Box 14069
Santa Rosa, CA, 95402
If you're like me, you program in several languages, under
several different operating systems. Under DOS, one very useful
feature is the capability to pass arguments to a program as part of
the invocation command line. The use of command line arguments
significantly increases the power and flexibility of your programs,
as well as improving the "professional look." Many languages
support this capability with intrinsic or library routines which
facilitate access to these command line arguments. Assembly
language, of course, does not. What is a programmer to do?
As it turns out, it is quite simple to access the command line
arguments under DOS. DOS places the so-called "command tail" (the
command line less the actual program name) into a buffer area
reserved in the PSP (Program Segment Prefix). This buffer area is
known as the DTA (Disk Transfer Area).
It is extremely important that you parse the command tail, if you
plan to do so at all, immediately upon entering your program. DOS
does some particularly obscure and insidious things with this DTA
buffer, which will destroy the command tail information.
In a .COM format program, the PSP is the first 100h (256) bytes
of the program memory image, making access quite straightforward.
How do we locate the PSP in a .EXE format program, however?
Fortunately, DOS sets the ES segment register to point to the
beginning of the PSP under both .COM and .EXE programs. It happens
to be the case that DOS also sets all other segment registers to the
same location for a .COM program, simply because .COM programs
reside in one and only one segment. In an .EXE invocation, the DS
and ES registers are set to point to the segment in which the PSP
resides as the first 100h bytes. This is the default data segment
as well.
The DTA begins at offset 80h (128d) from the beginning of the
PSP. When it contains a command tail, the byte at 80h contains the
count of the number of bytes actually in the command tail, and the
command tail string begins at offset 81h (129d) from the beginning
of the PSP. The first byte of this string is always a blank (20h),
and the string is terminated with a <cr> (0dh).
The exact means you use to parse the command line arguments is,
of course, up to you. One possible approach is as follows:
1) Use the data definition directives to set aside any memory you
will need to store information about command line arguments
(e.g., buffers for file names, byte or word values for flags and
numeric arguments, etc.).
2) Design a routine that starts scanning the command tail string for
;Page 13
arguments. a 'first fit' (the shortest match possible) scheme is
easiest to program. As each item is located and identified as to
type and purpose, store the appropriate information in the data
areas you have already set aside.
3) Have a "usage" message defined, and a small routine to print it
to the screen (a good idea is to print it to STDERR). Invoke
this routine when the first argument on the command line is a
'?,' or, if the program requires arguments, when it is invoked
without them.
4) You now have the switches, filenames, and other command line
arguments available. Write your program to use them
appropriately.
Included in this issue of Assembly Language Magazine is an source
listing which is a sample template GPFILT.ASM for a general purpose
assembly language filter. This program provides an excellent sample
of command line argument parsing and one way of using these
arguments (though the method used here is not the same as the one
described above).
;Page 14
Original Vector Locator
by Rick Engle
November, 1989
INTTEST is a small assembly program which attempts to find the
original address of the INT 21h function handler. This is valuable
if you need to be able to make calls to the original INT 21h
function even if a TSR or other program has that interrupt hooked or
trapped. This gives your program secure control over the interrupt
regardless of who is using it.
I did this prototype in an attempt to make certain programs
somewhat immune to the effects of destructive viruses that may
intercept INT 21h and use it for their own use. This technique
could be used to find the original address of other MS-DOS
interrupts. I wrote test programs to dump out the address of MS-DOS
interrupts (such as INT 21h) and then disassembled portions of
MS-DOS at those addresses to identify a stable signature of the
interrupt. Then by following the chain to MS-DOS through the PSP
(Program Segment Prefix) at offset 5h, I was able to find the
segment:offset of the address of the handler for old CP/M calls.
This pointed to the correct segment in memory of MS-DOS and
from there, after moving my offset backwards about 100h in memory, I
scanned for my interrupt signature. Once I got a hit, I calculated
the address of the interrupt and then could make calls to INT 21h at
the segment:offset found. This program is a "brute-force" method of
finding the original address. If anyone finds or has a better way,
I'd be very interested in hearing about it.
NOTE: I have tested this program successfully on MS-DOS
2.11, 3.20, and 3.30.
~
; -----------------------------------------------------------------------
; INTTEST.ASM November, 1989 Rick Engle
;
; Finds the address of the INT 21h function dispatcher to
; allow the user to make INT 21h calls to the original
; interrupt regardless of who or what has INT 21h hooked.
;
; -----------------------------------------------------------------------
;
print macro print_parm
push ax
push dx
mov ah,9
mov dx,offset print_parm
int 21h
pop dx
pop ax
endm
; -----------------------------------------------------------------------
; - Start of program -
; -----------------------------------------------------------------------
;Page 15
cseg segment para public 'code'
assume cs:cseg,ds:cseg
org 100h
int_test proc far
print reboot_first
print int_address
mov cl,21h
mov ah,35h ; get interupt vector
mov al,cl ; for interupt in cl
int 21h ; do it
mov ax,es ; lets display the es
push cs ; set es = cs so that
pop es ; the stosb works
mov di,offset out_byte
call conv_word
print out_byte
print colon
mov ax,bx ; lets display the bx
mov di,offset out_byte
call conv_word
print out_byte
print crlf
print display_header2
mov ah,byte ptr cs:[05h] ; Get info from the PSP
mov al,byte ptr cs:[06h] ;
push cs ; set es = cs so that
pop es ; the stosb works
mov di,offset out_byte
call conv_word
print out_byte
print dash
mov ah,byte ptr cs:[07h] ;
mov al,byte ptr cs:[08h] ;
mov di,offset out_byte
call conv_word
print out_byte
print dash
mov ah,byte ptr cs:[09h] ;
mov al,byte ptr cs:[0ah] ;
mov di,offset out_byte
call conv_word
print out_byte
print crlf
print display_header
mov ah,byte ptr cs:[50h] ; Addess if INT 21 op code
mov al,byte ptr cs:[51h] ; in the PSP
;Page 16
push cs ; set es = cs so that
pop es ; the stosb works
mov di,offset out_byte
call conv_word
print out_byte
print dash
mov ah,byte ptr cs:[52h] ;
mov al,byte ptr cs:[53h] ;
mov di,offset out_byte
call conv_word
print out_byte
print dash
mov ah,byte ptr cs:[54h] ;
mov al,byte ptr cs:[55h] ;
mov di,offset out_byte
call conv_word
print out_byte
print crlf
print far_address
mov ax,word ptr cs:[08h] ;
mov segm,ax
push cs ; set es = cs
pop es ;
mov di,offset out_byte
call conv_word
print out_byte
print colon
mov ax,word ptr cs:[06h] ;
mov off,ax
push cs ; set es = cs so that
pop es ; the stosb works
mov di,offset out_byte
call conv_word
print out_byte
print crlf
mov ax,segm
mov es,ax
mov di,off
inc di
print function_jmp
mov ax,word ptr es:[di+2] ;
mov segm2,ax
push cs ; set es = cs so that
pop es ; the stosb works
mov di,offset out_byte
call conv_word
print out_byte
print colon
mov ax,segm
mov es,ax
mov di,off
;Page 17
inc di
mov ax,word ptr es:[di] ;
mov off,ax ; save found offset of int 21h
push cs ; set es = cs so that
pop es ; the stosb works
mov di,offset out_byte
call conv_word
print out_byte
print crlf
;-----------------------------------------------------------------
;si = string di = string size es:bx = pointer to buffer to search
;ax = number of bytes in buffer to search. Zero flag set if found
;-----------------------------------------------------------------
mov ax,segm2
mov es,ax ;segment
mov bx,off ;offset
sub bx,0100h ;backup a bit to catch DOS
mov si,offset dos_sig ;start at modified byte
mov di,dos_sig_len ;enough of a match
mov ax,0300h ;# of bytes to search
call search ;use our search
jnz sig_not_found ;didn't find int 21h signature
mov START_SEGMENT,es ;set page
mov START_OFFSET,ax ;address of found string
print good_address
mov ax,START_SEGMENT ;
push cs ; set es = cs so that
pop es ; the stosb works
mov di,offset out_byte
call conv_word
print out_byte
print colon
mov ax,START_OFFSET ;
mov off,ax ; save found offset of int 21h
push cs ; set es = cs so that
pop es ; the stosb works
mov di,offset out_byte
call conv_word
print out_byte
print crlf
push cs ; set es = cs
pop es
mov bx,START_OFFSET
mov ax,START_SEGMENT
mov word ptr [OLDINT21], bx
mov word ptr [OLDINT21+2],ax
mov dx,offset test_message
mov ah,9
call dos_function
jmp terminate
sig_not_found:
;Page 18
print no_int21_found
terminate: mov ax,4c00h ; terminate process
int 21h ; and return to DOS
out_byte db 'XXXX'
db '$'
colon db ':$'
dash db '-$'
crlf db 10,13,'$'
reboot_first db 13,10,'INTTEST 1.0',13,10
db 'Reboot before running this, or',13,10
db 'make sure INT 21h is not hooked',13,10,13,10,'$'
display_header db 'HEX data at PSP address 50h is : $'
display_header2 db 'HEX data at PSP address 05h is : $'
int_address db 'Original INT 21h address is : $'
function_jmp db 'Jump address at DOS dispatcher : $'
far_address db 'Far address of DOS dispatcher : $'
good_address db 'Good INT 21h address found at : $'
test_message db 13,10,10,'This message is being printed using the INT '
db '21h Interrupt',13,10
db 'Found by Brute Force!!!!',13,10,10,'$'
no_int21_found db 13,10,'Int 21h address not found!$'
segm dw 0
segm2 dw 0
off dw 0
START_OFFSET dw 0 ;top addr shown on screen
START_SEGMENT dw 0
;dos_sig db 08Ah, 0E1h, 0EBh ; mov ah,cl
; ; jmp short label
dos_sig db 080h, 0FCh, 0F8h ; cmp ah,0F8h
dos_sig_len equ $ - dos_sig
OLDINT21 dd ? ; Old DOS function interrupt vector
int_test endp
; -----------------------------------------------------------------------
; - -
; - Subroutine to convert a word or byte to hex ASCII -
; - -
; - call with AX = binary value -
; - DI = address to store string -
; - -
; -----------------------------------------------------------------------
conv_word proc near
push ax
mov al,ah
call conv_byte ; convert upper byte
pop ax
call conv_byte ; convert lower byte
ret ; and return
conv_word endp
conv_byte proc near
push cx ; save cx
;Page 19
sub ah,ah ; clear upper byte
mov cl,16
div cl ; divide binary data by 16
call conv_ascii ; the quotient becomes the
stosb ; ASCII character
mov al,ah
call conv_ascii ; the remainder becomes the
stosb ; second ASCII character
pop cx ; restore cx
ret
conv_byte endp
conv_ascii proc near ; convert value 0-0Fh in al
add al,'0' ; into a "hex ascii" character
cmp al,'9'
jle conv_ascii_2 ; jump if in range 0-9
add al,'A'-'9'-1 ; offset it to range A-F
conv_ascii_2: ret ; return ASCII character in al
conv_ascii endp
;-----------------------------------------------------------------------
; This routine does a dos function by calling the old interrupt vector
;-----------------------------------------------------------------------
assume ds:nothing, es:nothing
dos_function proc
; mov cl,ah ;move our function # into cl
pushf ;These instructions simulate
;an interrupt
cli ;turn off interrupts
call CS:OLDINT21 ;Do the DOS function
sti ;enable interrupts
push cs
pop ds
push cs
pop es
ret
dos_function endp
;-----------------------------------------------------------------
;si = string di = string size es:bx = pointer to buffer to search
;ax = number of bytes in buffer to search. Zero flag set if found
;-----------------------------------------------------------------
SEARCH PROC NEAR ;si points at string
PUSH BX
PUSH DI
PUSH SI
XCHG BX,DI ;string size, ptr to data area
MOV CX,AX ;# chars in segment to search
BYTE_ADD:
LODSB ;char for first part of search
NEXT_SRCH:
REPNZ SCASB ;is first char in string in buffer
JNZ NOT_FOUND ;if not, no match
PUSH DI ;save against cmpsb
;Page 20
PUSH SI
PUSH CX
LEA CX,[BX-1] ;# chars in string - 1
JCXZ ONE_CHAR ;if one char search, we have found it
REP CMPSB ;otherwise compare rest of string
ONE_CHAR:
POP CX ;restore for next cmpsb
POP SI
POP DI
JNZ NEXT_SRCH ;if zr = 0 then string not found
NOT_FOUND:
LEA AX,[DI-1] ;ptr to last first character found
POP SI
POP DI
POP BX
RET ;that's all
SEARCH ENDP
cseg ends
end int_test
;Page 21
~
How to call DOS from within a TSR
by David O'Riva
Just a few ramblings on interactions between TSRs & DOS.
Cardinal rule: DON'T CALL DOS UNLESS YOU'RE SURE OF THE
MACHINE STATE!!!
There are a few interrupt calls and memory locations you can
play with to get this information. A list & explanation of sorts is
below. The reason you don't call DOS if you've interrupted the
machine in the middle of DOS is that:
1. The stack is unstable as far as DOS is concerned, and
you'll probably end up overwriting DOS data or going
into the weeds.
2. DOS only keeps one copy of certain crucial information
as it processes a disk-related request. i.e. BPB's,
current sectors, FAT memory images, fun stuff like that.
If you interrupt it in the middle, ask for something
different, then go back, you will probably destroy your
disk, possibly beyond recall.
3. DOS simply was not designed to be re-entrant. The first
9 or 10 function calls are cool most of the time, the
rest are strictly single-processing-stream functions.
However, there is hope. And, (extra bonus) it happens to be
compatible with most true MS-DOS releases, and many, many brand-name
DOSes. As well as most clones.
What you need to do is after determining that the user wants to
pop your program up, you set a few flags. One of them prevents your
program from being popped up AGAIN while the current DOS call is
completing, and the other tells a timer trap routine to start
looking for DOS to finish it's current process (usually a matter of
split seconds). When the timer routine detects that DOS is no
longer active, it grabs control of the system and runs your TSR.
At this point, all DOS calls are as safe as they are for a
normal application.
What follows is an outline of the code necessary to activate a
TSR that uses DOS calls. Depending on the TSR, other things may
need to be done in these routines as well. Definitely make sure you
understand the interactions of the various routines before TSRing
your background disk formatter.
Okay, nitty-gritty time...
;Page 22
You need 5 main chunks of code to do this right:
a) a bit of extra initialization code
b) your TSR's main program
c) activation request server (usually a keypress
trap)
d) timer tick inDOS monitor
e) DOS busy loop monitor
And here's what they do:
a) asks DOS for the location of the inDOS flag, and stores
that away.
b) does whatever you want it to.
c) when the activation requirement is sensed (the user
pressed the hot-key, the print buffer is empty, the modem
is sending another packet, whatever) the following steps
need to be taken: 1. have we already tried to activate,
and are waiting for DOS to finish? if so, then
ignore the activation request.
2. check the inDOS flag. if we're not in DOS, then
activate as usual.
3. set a flag indicating that the TSR wants to
activate, but can't right now
4. return to DOS
d) this is linked in AFTER interrupt 08 - that is, when this
interrupt happens, call the original INT 08 handler, then
run your checking code:
1. does the TSR want to run? if not, return from the
interrupt.
2. check the inDOS flag. If it's out of DOS, then
run your code as normal
3. return from the interrupt
* * NOTE: This code has to run FAST. If it's poorly coded,
you may very well see downgraded performance of the
entire system.
e) link in to the DOS keyboard busy loop - INT 28. This
interrupt is called when DOS is waiting for a
keystroke via functions 1,3,7,8,0A, and 0C. If the TSR
takes control from this loop, then DOS functions ABOVE 0C
are safe to use. Functions 0 - 0C are NOT safe to use.
;Page 23
1. Does the TSR want to run? if not, continue down
the interrupt chain.
2. run the TSR as usual
3. continue down the interrupt chain.
NOTES: The first action your main TSR code should take is to
clear the flag that indicates the TSR is trying to run. If
this is not done, your TSR will re-enter itself at least
18.2 times per second... i.e. a MESS.
The last action your main TSR code should take before
leaving is to RESET the flags that prevent the TSR from
being activated. If you forget to do this, your TSR will
run once, then never again... I know from personal
experience that this is frustrating to a dangerous degree.
Some of this code is really complicated, so don't get
discouraged if it takes a few days of tweaking and
hair-pulling to get it right.
All numbers in this text are in hex.
The timer tick routine is really touchy, at least the way I
wrote it. Be very sure yours is reliable if you distribute
a program with this structure.
The reason that functions 0-0C are separated from the rest
of the DOS calls as far as re-entrancy is concerned is that
they use an entirely separate stack frame. I believe this
must have been done specifically for the purpose of helping
TSR writers.
Does anyone know why the hell Microsoft built these neat
functions into DOS and then refused to acknowledge their
existence?
INTERRUPT & FUNCTION CALLS
INT 08
Timer tick interrupt. Called 18.2 times a second on IRQ 0.
The interrupt is triggered by timer 0.
INT 21, FUNCTION 34
inDOS flag address request. This function returns the
address of the "inDOS flag" as a 32 bit pointer in ES:BX.
The inDOS flag is a byte that is zero when DOS is not
processing a function request, and is non-zero when DOS is
in a function.
;Page 24
NOTE: This function is officially specified as RESERVED.
It's use could change in future versions of DOS, and it can
only be guaranteed to work in straight IBM PC-DOS or MS-DOS
versions 2.0 to 3.30. Use at your own risk.
INT 28
DOS keyboard busy loop. This interrupt is called when DOS is
waiting for a keystroke in the console input functions.
When this interrupt is issued, it is safe to use any DOS
call ABOVE 0C. Calls to DOS functions 0 - 0C will trash the
stack and do nasty things.
NOTE: This function is officially RESERVED. See the note
for function 34 above.
AUTHOR'S NOTE:
First, the references I listed are really great. They've
helped me out a lot over the past few years. Second, if your
hard disk gets munched by your TSR, read the disclaimer.
! ! ! ! ! ! C A V E A T P R O G R A M M E R ! ! ! ! ! !
& Disclaimer
The techniques described in here are, for the most part,
UNDOCUMENTED by Microsoft or IBM. This means that you CAN NOT BE
SURE that they will work on all IBM clones, and could even cause
crashes on some! The timer tick interrupt provides some essential
system services, and messing with it incautiously can wreak havoc.
The program outlines presented here are what worked for me on
my system, and what should work on about 90% of the clones out
there. However, I still suggest that you find a reference for all
of the interrupts and functions described here. This file is meant
to be a guideline and aid only.
REFERENCES:
DOS Programmer's Reference, by Terry R. Dettmann.
$22.95, QUE Corporation
IBM DOS Technical Reference, version 3.30
$(?), International Business Machines Corp.
I can't remember how much it cost...
;Page 25
Environment Variable Processor
by David O'Riva
~
PAGE 60,132
TITLE Q43.ASM - editor prelude & display manager
;
;
COMMENT~***********************************************************************
* ---===> All code in this file is copyright 1989 by David O'Riva <===--- *
*******************************************************************************
* *
* The above line is there only to prevent people (or COMPANIIES) from *
* claiming original authorship of this code and suing me for using it. *
* You're welcome to use it anyhow you care to. *
* *
*
* Environment Variable Finder & Processor - *
* *
* The "get_environment_variable" routine is complete in itself, and can *
* be extracted and used in anything else that needs one. Just copy the entire
* routine, from the header to the endp (don't forget the RADIX and DW).
* Theroutine currently uses 315 (decimal) bytes.
*
*
* This program's purposeis to invoke an editor (or any program, really,
* with a specific machine state depending on environment variables. (Yeah!!!)
* Currently it is set up to change my screen to one of various modes, with
* the variable ED_SCRMODE being set to:
* 100/75 = 100 columns by 75 lines
* 132/44 = 132 by 44
* 80/44 = 80 by 44
* ...and then to EXEC my editor (qedit) with that mode set. You could
* set the screen back to the standard 80x25 after the EXEC returns.
*
* Note: The 80/44 set code should work on most (ar all?) EGAs. The
* other two high-res text modes use built-in extended BIOS modes in my
* Everex EV-657 EGA card (the 800x600 version) w/multisync monitor. If you've
* got one of those, you're in luck - no mods needed. It will also work on the
* EV-673 EVGA card w/appropriate monitor.
*
*
* Note to BEGINNERS: This is not an example of "good" asm code. This
* file is an example of what happens when you're up at 1:00am with too much
* coffee and a utility that needs to be fixed.
*
*
*
* This is a COM program, not an EXE. Remember to use EXE2BIN.
*
*
*
******************************************************************************~
; ; TRUE EQU 0FFH FALSE EQU 0 ;
;******************************************************************************
; CODE SEGMENT PARA PUBLIC 'CODE' ASSUME
CS:CODE,DS:CODE,ES:CODE,SS:CODE ; MAIN PROC NEAR
;Page 26
ORG 100H
entry:
;------------------------------------------------------------------------------
; set the screen to the correct mode
;------------------------------------------------------------------------------
call set_screen_mode
;------------------------------------------------------------------------------
; check for pathname change in environment
;------------------------------------------------------------------------------
call set_exec_name
;------------------------------------------------------------------------------
; setup memory and run the program
;------------------------------------------------------------------------------
MOV BX,OFFSET ENDRESIDENT ;deallocate unnecessary memory
MOV CL,4
SHR BX,CL
INC BX
MOV AH,04AH
INT 021H
MOV AX,CS ;exec the program
MOV INSERT_CS1,AX
MOV INSERT_CS2,AX
MOV INSERT_CS3,AX
MOV AX,04B00H
MOV BX,OFFSET EXECPARMS
MOV DX,OFFSET PROGNAME
INT 021H
;------------------------------------------------------------------------------
; clean up and leave
;------------------------------------------------------------------------------
MOV AH,04DH ;get return code from program
INT 021H
MOV AH,04CH ;leave
INT 021H
;
;******************************************************************************
;
; data
;
PROGNAME DB 'F:\UTILITY\MISC\Q.EXE',0
db 100 dup(' ')
EXECPARMS DW 0 ;use current environment
DW 080H ;use current command tail
INSERT_CS1 DW ?
DW 05CH ;use current FCB's
INSERT_CS2 DW ?
DW 06CH
INSERT_CS3 DW ?
;Page 27
ENDRESIDENT:
;******************************************************************************
; more data - used only for setup & checks
;
valid_modes db '80/44 '
db '132/44'
db '100/75'
screen_mode db ' '
mode_jump dw goto_43
dw goto_132
dw goto_100
ev_mode db 'ED_SCRMODE',0
ev_pathname db 'ED_PATH',0
PAGE
;******************************************************************************
; set_screen_mode -
;
;
; ENTRY:
;
; EXIT:
;
; DESTROYED:
;
;------------------------------------------------------------------------------
set_screen_mode:
MOV AH,012H ;check for presence of EGA/VGA
MOV BL,010H
INT 010H
CMP BL,010H ;BL changed? (should have # of
; bytes of EGA memory)
JE ssm_no_ega ;This is no EGA!
;------------------------------------------------------------------------------
; check environment for correct mode set -
; don't set mode if none specified
;------------------------------------------------------------------------------
mov si,offset ev_mode
mov di,offset screen_mode
mov cx,6 ;accept 6 chars
mov ax,4 ;get fixed-length string
call get_environment_variable
and ax,0feh
jne ssm_no_env_mode
;------------------------------------------------------------------------------
; look up the variable's value in my mode table
;------------------------------------------------------------------------------
mov bx,0
mov di,offset valid_modes
;Page 28
ssm_check_mode: mov dx,di
mov si,offset screen_mode
mov cx,6
repe cmpsb
je ssm_found_mode
mov di,dx
add di,6
inc bx
cmp bx,3
jne ssm_check_mode
jmp ssm_bad_mode
;------------------------------------------------------------------------------
; set the correct screen mode
;------------------------------------------------------------------------------
ssm_found_mode: shl bx,1
jmp mode_jump[bx]
goto_100: mov ax,0070h
mov bx,8
int 010h
jmp ssm_leave
goto_132: mov ax,0070h
mov bx,0bh
int 010h
jmp ssm_leave
goto_43: MOV AX,3
INT 010H
MOV AX,01112H ;set to 8x8 chars (43/50 lines)
MOV BL,0
INT 010H
ssm_no_env_mode:
ssm_bad_mode:
ssm_no_ega:
ssm_leave:
ret
PAGE
;******************************************************************************
; set_exec_name -
;
;
; ENTRY:
;
; EXIT:
;
; DESTROYED:
;
;------------------------------------------------------------------------------
set_exec_name:
;
; If you want, write a chunk here that will read an alternate pathname
; for the editor to be executed from a different variable (like ED_PATH)
;Page 29
; I was going to do it, but ran out of time and need. (My editor never wanders
; around!)
;
ret
PAGE
;******************************************************************************
; Get_environment_variable -
;
;
;
;
; ENTRY: ds:[si] -> ASCIIZ environment variable name
; ds:[di] -> (up to) 129 byte buffer for string
; es = segment of program's PSP
; cx = maximum # of characters to accept
; al = variable return format
; 0 - return string in ASCIIZ format
; xxxxx 0 ........
;
; 1 - return string in DOS string ('$' terminated) format
; xxxxxxxx $ ........
;
; 2 - return string in DOS input buffer format
; maxchrs,numchrs,xxxxxxxx CR ............
;
; 3 - return string in command tail format
; numchrs,xxxxxxxxxxx CR ..........
;
; 4 - return string in fixed-length (CX chars) format
; xxxxxx
;
; EXIT: al = return codes:
; bit 0 - if set, string was longer than max, truncated
; 1 - if set, string did not exist
; 2 - if set, invalid return format requested
;
; DESTROYED: ah is undefined
;
;------------------------------------------------------------------------------
.RADIX 010h
gev_flags dw ?
Get_environment_variable:
push bx
push cx
push dx
push si
;Page 30
push di
push es
mov cs:gev_flags,ax
mov es,es:[02c] ;es -> program's environment
;------------------------------------------------------------------------------
; make sure the environment has at least one variable in it
;------------------------------------------------------------------------------
mov ax,es:[0]
cmp ax,0
jne gev_exists
mov ax,2
jmp gev_leave
;------------------------------------------------------------------------------
; find length of search string
;------------------------------------------------------------------------------
gev_exists: push cx
push di
mov di,si
mov cx,0ffff
gev_sourcelen:
inc cx
mov al,[di]
inc di
cmp al,0
jne gev_sourcelen
cmp cx,0
jne gev_startfind
pop di
pop cx
mov ax,2
jmp gev_leave
;------------------------------------------------------------------------------
; find string
;------------------------------------------------------------------------------
gev_startfind: mov bx,cx
mov dx,si
mov di,0
gev_checknext:
mov cx,bx
mov si,dx
repe cmpsb
je gev_found?
gev_tonextvar: mov cx,0ffff
mov al,0
repne scasb
cmp es:[di],al
jne gev_checknext
mov ax,2
pop di
pop cx
jmp gev_leave
;Page 31
gev_found?: cmp byte ptr es:[di],'='
jne gev_tonextvar
;------------------------------------------------------------------------------
; found the string in the environment
;------------------------------------------------------------------------------
gev_found: inc di
mov si,di
pop di
pop cx
cmp cs:gev_flags,1
ja gev_ibufform
;------------------------------------------------------------------------------
; move normal string with 0 or $ terminator
;------------------------------------------------------------------------------
gev_nextchar0: mov al,es:[si]
cmp al,0
je gev_setterm0
mov ds:[di],al
inc si
inc di
dec cx
jne gev_nextchar0
mov al,es:[si]
cmp al,0
je gev_setterm0
mov al,1
gev_setterm0: cmp cs:gev_flags,0
jne gev_setterm1
mov byte ptr ds:[di],0 ;ASCIIZ string
jmp gev_leave
gev_setterm1: mov byte ptr ds:[di],'$' ;DOS string
jmp gev_leave
;------------------------------------------------------------------------------
; move string into DOS input buffer format (int 21 function 0A)
;------------------------------------------------------------------------------
gev_ibufform: cmp cs:gev_flags,2
jne gev_ctailform
mov ds:[di],cl ;set max length
inc di
mov bx,di
inc di
mov dx,0
gev_nextchar2: mov al,es:[si]
cmp al,0
je gev_setterm2
mov ds:[di],al
inc si
inc di
inc dx
dec cx
jne gev_nextchar2
mov al,es:[si]
cmp al,0
;Page 32
je gev_setterm2
mov al,1
gev_setterm2: mov byte ptr ds:[di],0d ;add carriage return
mov ds:[bx],dl ;set actual # of chars
jmp gev_leave
;------------------------------------------------------------------------------
; move string into command tail format
;------------------------------------------------------------------------------
gev_ctailform: cmp cs:gev_flags,3
jne gev_fixedform
mov bx,di
inc di
mov dx,0
gev_nextchar3: mov al,es:[si]
cmp al,0
je gev_setterm3
mov ds:[di],al
inc si
inc di
inc dx
dec cx
jne gev_nextchar3
mov al,es:[si]
cmp al,0
je gev_setterm3
mov al,1
gev_setterm3: mov byte ptr ds:[di],0d ;set carriage return
mov ds:[bx],dl ;set # of bytes
jmp gev_leave
;------------------------------------------------------------------------------
; move string into fixed-length area (pad it out with spaces)
;------------------------------------------------------------------------------
gev_fixedform: cmp cs:gev_flags,4
jne gev_badform
gev_nextchar4: mov al,es:[si]
cmp al,0
je gev_padout4
mov ds:[di],al
inc si
inc di
dec cx
jne gev_nextchar4
mov al,es:[si]
cmp al,0
je gev_setterm4
mov al,1
jmp gev_setterm4
gev_padout4: mov byte ptr ds:[di],' '
inc di
dec cx
jne gev_padout4
;Page 33
mov al,0
gev_setterm4: jmp gev_leave
gev_badform: mov ax,4
gev_leave: pop es
pop di
pop si
pop dx
pop cx
pop bx
ret
.RADIX 00ah
MAIN ENDP
;
;******************************************************************************
;
CODE ENDS
;
;******************************************************************************
;
END ENTRY
~
;Page 34
Program Reviews
Multi-Edit ver 4.00a (demo version): Reviewed by Patrick O'Riva.
Multi-Edit is a high feature text editor with many word
processor features. The demo version is completely functional though
some of the reference material is not supplied and there are
advertising screens. I consider this fully acceptable as shareware.
The complete version with the macro reference library is available
for 79.95 and an expanded version with a spelling checker,
integrated Communication terminal and phone book is $179.95.
I couldn't list all of its features here, but in addition to
everything you have come to expect in a quality programming editor
(multi meg files, programmable keyboard etc.) there are a number of
powerful additions you might not expect. The word processor
functions rival most of the specialty ones that I've tried. It
won't compete with the major names for those of you who are addicted
to them, but it does offer full printer support, preview file, table
of contents generation, and extension keyed formatting. It will
right or left justify, and supports headers and footers, and auto
pagination.
It contains a calculator and an Ascii table
Saving the best for last: The language support is very strong.
It has built in templates for many common constructs, and the
assembler/compiler is invoked from within the editor with a single
key. It will read the error table generated by a variety of software
and with successive key presses move you to each line where an error
was found.
Something which I found unique is Multi-Edit's help system. It
is a hypertext system, and is wonderfully context sensitive most
everywhere in the system. From the Help menu it has a complete table
of contents and index. It is also fully user extendible. I have
integrated a database I have documenting the full set of interrupts
that totals about 400k and the documentation on my spelling checker
as well (which integrated into Multi-Edit almost seamlessly).
In many ways this is the best editor I've ever used, but it
does have a few faults, some of which are very subtle and may not
even be problems to most users. It is a 'tad' slower that what I'm
used to with Qedit. This is seldom noticed except in the execution
of complex macros. It is quite slow in paging through long files.
There are some true bugs in this version such as a crash of the
program (but not the data or the system) when large deletes from
large files are made. Multi-edit's treatment of file windows while
very versatile is slightly different and may take some time to get
used to.
For all of its advantages, until putting this Magazine
together, I still found myself reverting to Qedit for the speed and
ease of use. It is the first software that has made this anything
other than an exercise in frustration.
;Page 35
SHEZ
Just a quick mention because it isn't programming related.
Shez is a compression shell along the lines of ArcMaster and Qfiler.
It is a fine and versatile piece of programming, supporting all
common compression types. The more recent versions have virus
detection when used with the SCANV programs by John McAfee.
4DOS
This is a program that is an absolute joy to use. It is a
complete and virtually 100% compatible replacement for Command.com.
The code size is just slightly larger than MSDOS 3.3 command.com but
the added and enhanced functions save many times that amount in
TSR's you no longer need to install. Just to mention a few features:
An alias command whereby you can assign whatever mnemonic you
wish to a command or string of commands. Select is a screen
interface that allows you to mark files for use with a command.
Except will execute a command for a set of files excluding one or
more. There is an environment editor, built in Help, command
and filename completion, Global that will execute through the
directory tree, A Timer to keep track of elapsed time, as well as
many enhanced batch file commands. Additional features are too
numerous to mention. The current version is 4.23 and is available as
Shareware, but you should register after your first 10 minutes of
use. You will be hooked forever.
The above 3 programs should all be available on your local BBS's.
Please be sure and register programs you use.
;Page 36
Book Reviews
Assembly Language Quick Reference
by Allen L. Wyatt, Sr.
Reviewed by George A. Stanislav
This 1989 book published by QUE is a nice and handy reference for
assembly language programmers.
Instruction sets for six microprocessors and numeric coprocessors
are listed:
8086/8088 8087
80286 80287
80386 80387
I could find no reference to the 80186 microprocessor, not even a
suggestion that it uses the 80286 instruction set but does not
multitask. Because the 80186 was the brain of Tandy 2000, quite a
popular computer in its own time, its omission from the book is
surprising.
There is no division into chapters. This makes it somewhat hard to
figure out where the instruction sets of individual processors
start. Each higher processor set contains only the list of
instructions that are new for the processor or that changed
somewhat.
After a brief introduction, the book starts by listing,
alphabetically, all 8086/8088 instructions. The listing itself is
very well done. Each instruction stands out graphically from the
rest of the text. For every code there is some classification, e.g.
arithmetic, bit manipulation, data-transfer.
This is followed by a very brief description ended with a colon.
Next, a more detailed explanation gives sufficient information to
any assembly language programmer what the instruction does.
If applicable, the book lists flags affected by the instruction.
Most instructions also contain some coding examples.
The 8086/8088 instruction set is followed by the 80286 set, or
rather subset as it only contains the instructions new to this
microprocessor. Similarly, the 80386 section contains only those
instructions not found in the 8086/8088 and 80286 sections as well
as those that changed somewhat.
I find it puzzling that among those instructions considered changed
in the 80386 microprocessor we can find AND, NEG, POP - because they
can be used as 32-bit instructions in addition to their original
usage - but cannot find JE, JNE, and all other conditional jumps.
These did indeed change in the 80386 processor inasmuch they can be
used either as SHORT or as NEAR while on the older microprocessors
they could only jump within the SHORT range.
;Page 37
The rest of the book contains instructions for the math
coprocessors, the 8087, 80287 and 80387. This section is divided in
the same way as the microprocessor part, i.e. describing first the
8087 set, then the one new instruction for the 80286, followed by
the new 80387 instructions.
There are several possibilities of improvement QUE might consider
for future editions of this book:
o Make it easier to find the start of each section by color
coding the side of the paper;
o Include references to the instructions of the older
processors within the listing for the new processors.
Small print of the instruction with the page number where
a more detailed description can be found would be a nice
enhancement;
o At least a brief mention of the 80186 microprocessor and
perhaps the V-20 and V-30 would be useful.
Despite the possibility of improvement, this is an excellent
reference for any assembly language programmer. Its small size makes
it very handy to keep it next to the computer as well as to take it
along when travelling.
The book costs $6.95 in USA and $8.95 in Canada.
;Page 38
GPFILT.ASM
~
page ,132
TITLE GPFILT
subttl General Purpose Filter Template
;
; GPFILT.ASM
; This file contains a template for a general-purpose assembly language
; filter program.
;
; Fill in the blanks for what you wish to do. The program is set up to
; accept a command line in the form:
; COMMAND [{-|/}options] [infile [outfile]]
;
; If infile is not specified, stdin is used.
; If outfile is not specified, stdout is used.
;
; To compile and link:
; MASM GPFILT ;
; LINK GPFILT ;
; EXE2BIN GPFILT GPFILT.COM
;
; Standard routines supplied in the general shell are:
;
; get_arg - returns the address of the next command line argument in
; DX. Since this is a .COM file, the routine assumes DS will
; be the same as the command line segment.
; The routine will return with Carry set when it reaches the end
; of the command line.
;
; err_msg - displays an ASCIIZ string on the STDERR device. Call with the
; address of the string in ES:DX.
;
; do_usage- displays the usage message on the STDERR device and exits
; with an error condition (errorlevel 1). This routine will
; never return.
;
; getch - returns the next character from the input stream in AL.
; It will return with carry set if an error occurs during read.
; It will return with the ZF set at end of file.
;
; putch - writes a character from AL to the output stream. Returns with
; carry set if a write error occurs.
;
cseg segment
assume cs:cseg, ds:cseg, es:cseg, ss:cseg
org 0100h ;for .COM files
start: jmp main ;jump around data area
;
; Equates and global data area.
;
; The following equates and data areas are required by the general filter
; routines. User data area follows.
;
;Page 39
STDIN equ 0
STDOUT equ 1
STDERR equ 2
STDPRN equ 3
cr equ 0dh
lf equ 0ah
space equ 32
tab equ 9
infile dw STDIN ;default input file is stdin
outfile dw STDOUT ;default output file is stdout
errfile dw STDERR ;default error file is stderr
prnfile dw STDPRN ;default print file is stdprn
cmd_ptr dw 0081h ;address of first byte of command tail
PSP_ENV equ 002ch ;The segment address of the environment
;block is stored here.
infile_err db cr, lf, 'Error opening input file', 0
outfile_err db cr, lf, 'Error opening output file', 0
aborted db 07, cr, lf, 'Program aborted', 0
usage db cr, lf, 'Usage: ', 0
crlf db cr, lf, 0
;************************************************************************
;* *
;* Buffer sizes for input and output files. The buffers need not be *
;* the same size. For example, a program that removes tabs from a text *
;* file will output more characters than it reads. Therefore, the *
;* output buffer should be slightly larger than the input buffer. In *
;* general, the larger the buffer, the faster the program will run. *
;* *
;* The only restriction here is that the combined size of the buffers *
;* plus the program code and data size cannot exceed 64K. *
;* *
;* The easiest way to determine maximum available buffer memory is to *
;* assemble the program with minimum buffer sizes and examine the value *
;* of the endcode variable at the end of the program. Subtracting this *
;* value from 65,536 will give you the total buffer memory available. *
;* *
;************************************************************************
;
INNBUF_SIZE equ 31 ;size of input buffer (in K)
OUTBUF_SIZE equ 31 ;size of output buffer (in K)
;
;************************************************************************
;* *
;* Data definitions for input and output buffers. DO NOT modify these *
;* definitions unless you know exactly what it is you're doing! *
;* *
;************************************************************************
;
; Input buffer
ibfsz equ 1024*INNBUF_SIZE ;input buffer size in bytes
inbuf equ endcode ;input buffer
ibfend equ inbuf + ibfsz ;end of input buffer
;
;Page 40
; ibfptr is initialized to point past end of input buffer so that the first
; call to getch will result in a read from the file.
;
ibfptr dw inbuf+ibfsz
; output buffer
obfsz equ 1024*OUTBUF_SIZE ;output buffer size in bytes
outbuf equ ibfend ;output buffer
obfend equ outbuf + obfsz ;end of output buffer
obfptr dw outbuf ;start at beginning of buffer
;************************************************************************
;* *
;* USER DATA AREA *
;* *
;* Insert any data declarations specific to your program here. *
;* *
;* NOTE: The prog_name, use_msg, and use_msg1 variables MUST be *
;* defined. *
;* *
;************************************************************************
;
; This is the program name. Under DOS 3.x, this is not used because we
; can get the program name from the environment. Prior to 3.0, this
; information is not supplied by the OS.
;
prog_name db 'GPFILT', 0
;
; This is the usage message. The first two lines are required.
; The first line is the programs title line.
; Make sure to include the 0 at the end of the first line!!
; The second line shows the syntax of the program.
; Following lines (which are optional), are discussion of options, features,
; etc...
; The message MUST be terminated by a 0.
;
use_msg db ' - General Purpose FILTer program.', cr, lf, 0
use_msg1 label byte
db '[{-|/}options] [infile [outfile]]', cr, lf
db cr, lf
db 'If infile is not specified, STDIN is used', cr, lf
db 'If outfile is not specified, STDOUT is used', cr, lf
db 0
;
;************************************************************************
;* *
;* The main routine parses the command line arguments, opens files, and *
;* does other initialization tasks before calling the filter procedure *
;* to do the actual work. *
;* For a large number of filter programs, this routine will not need to *
;* be modified. Options are parsed in the get_options proc., and the *
;* filter proc. does all of the 'filter' work. *
;* *
;************************************************************************
;
main: cld
call get_options ;process options
;Page 41
jc gofilter ;carry indicates end of arg list
mov ah,3dh ;open file
mov al,0 ;read access
int 21h ;open the file
mov word ptr ds:[infile], ax ;save file handle
jnc main1 ;carry clear indicates success
mov dx,offset infile_err
jmp short err_exit
main1: call get_arg ;get cmd line arg in DX
jc gofilter ;carry indicates end of arg list
mov ah,3ch ;create file
mov cx,0 ;normal file
int 21h ;open the file
mov word ptr ds:[outfile],ax ;save file handle
jnc gofilter ;carry clear indicates success
mov dx,offset outfile_err
jmp short err_exit
gofilter:
call filter ;do the work
jc err_exit ;exit immediately on error
mov ah,3eh
mov bx,word ptr [infile]
int 21h ;close input file
mov ah,3eh
mov bx,word ptr [outfile]
int 21h ;close output file
mov ax,4c00h
int 21h ;exit with no error
err_exit:
call err_msg ;output error message
mov dx,offset aborted
call err_msg
mov ax,4c01h
int 21h ;and exit with error
;
;************************************************************************
;* *
;* get_options processes any command line options. Options are *
;* preceeded by either - or /. There is a lot of flexibility here. *
;* Options can be specified separately, or as a group. For example, *
;* the command "GPFILT -x -y -z" is equivalent to "GPFILT -xyz". *
;* *
;* This routine MUST return the address of the next argument in DX or *
;* carry flag set if there are no more options. In other words, return *
;* what was returned by the last call to get_arg. *
;* *
;************************************************************************
;
get_options proc
call get_arg ;get command line arg
jnc opt1
; If at least one argument is required, use this line
; call do_usage ;displays usage msg and exits
; If there are no required args, use this line
ret ;if no args, just return
opt1: mov di, dx
mov al,byte ptr ds:[di]
;Page 42
cmp al,'-' ;if first character of arg is '-'
jz opt_parse
cmp al,'/' ;or '/', then get options
jz opt_parse
ret ;otherwise exit
opt_parse:
inc di
mov al,byte ptr ds:[di]
or al,al ;if end of options string
jz nxt_opt ;get cmd. line arg
cmp al,'?' ;question means show usage info
jz do_usage
;
;************************************************************************
;* *
;* Code for processing other options goes here. The current option *
;* character is in AL, and the remainder of the option string is pointed*
;* to by DS:DI. *
;* *
;************************************************************************
;
jmp short opt_parse
nxt_opt:
call get_arg ;get next command line arg
jnc opt1 ;if carry
vld_args: ;then validate arguments
;
;************************************************************************
;* *
;* Validate arguments. If some options are mutually exclusive/dependent*
;* use this area to validate them. Whatever the case, if you must *
;* abort the program, call the do_usage procedure to display the usage *
;* message and exit the program. *
;* *
;************************************************************************
;
ret ; no more options
;
;************************************************************************
;* *
;* Filter does all the work. Modify this routine to do what it is you *
;* need done. *
;* *
;************************************************************************
;
filter proc
call getch ;get a character from input into AL
jbe filt_done ;exit on error or EOF
and al, 7fh ;strip the high bit
call putch ;and output it
jc filt_ret ;exit on error
jmp short filter
filt_done:
jc filt_ret ;carry set is error
call write_buffer ;output what remains of the buffer
filt_ret:
;Page 43
ret
filter endp
;
;************************************************************************
;* *
;* Put any program-specific routines here *
;* *
;************************************************************************
;
;************************************************************************
;* *
;* For most programs, nothing beyond here should require modification. *
;* The routines that follow are standard routines used by almost every *
;* filter program. *
;* *
;************************************************************************
;
;************************************************************************
;* *
;* This routine outputs the usage message to the STDERR device and *
;* aborts the program with an error code. A little processing is done *
;* here to get the program name and format the output. *
;* *
;************************************************************************
;
do_usage:
mov dx, offset crlf
call err_msg ;output newline
mov ah,30h ;get DOS version number
int 21h
sub al,3 ;check for version 3.x
jc lt3 ;if carry, earlier than 3.0
;
; For DOS 3.0 and later the full pathname of the file used to load this
; program is stored at the end of the environment block. We first scan
; all of the environment strings in order to find the end of the env, then
; scan the load pathname looking for the file name.
;
push es
mov ax, word ptr ds:[PSP_ENV]
mov es, ax ;ES is environment segment address
mov di, 0
mov cx, 0ffffh ;this ought to be enuf
xor ax, ax
getvar: scasb ;get char
jz end_env ;end of environment
gv1: repnz scasb ;look for end of variable
jmp short getvar ;and loop 'till end of environment
end_env:
inc di
inc di ;bump past word count
;
; ES:DI is now pointing to the beginning of the pathname used to load the
; program. We will now scan the filename looking for the last path specifier
; and use THAT address to output the program name. The program name is
; output WITHOUT the extension.
;Page 44
;
mov dx, di
fnloop: mov al, byte ptr es:[di]
or al, al ;if end of name
jz do30 ;then output it
inc di
cmp al, '\' ;if path specifier
jz updp ;then update path pointer
cmp al, '.' ;if '.'
jnz fnloop
mov byte ptr es:[di-1], 0 ;then place a 0 so we don't get ext
jmp short fnloop ; when outputting prog name
updp: mov dx, di ;store
jmp short fnloop
;
; ES:DX now points to the filename of the program loaded (without extension).
; Output the program name and then go on with rest of usage message.
;
do30: call err_msg ;output program name
pop es ;restore
jmp short gopt3
;
; We arrive here if the current DOS version is earlier than 3.0. Since the
; loaded program name is not available from the OS, we'll output the name
; entered in the 'prog_name' field above.
;
lt3: mov dx, offset prog_name
call err_msg ;output the program name
;
; After outputting program name, we arrive here to output the rest of the
; usage message. This code assumes that the usage message has been
; written as specified in the data area.
;
gopt3: mov dx, offset use_msg
call err_msg ;output the message
mov dx, offset usage
call err_msg
mov dx, offset use_msg1
call err_msg
mov ax,4c01h
int 21h ;and exit with error
get_options endp
;
;************************************************************************
;* *
;* Output a message (ASCIIZ string) to the standard error device. *
;* Call with address of error message in ES:DX. *
;* *
;************************************************************************
;
err_msg proc
cld
mov di,dx ;string address in di
mov cx,0ffffh
xor ax,ax
repnz scasb ;find end of string
;Page 45
xor cx,0ffffh
dec cx ;CX is string length
push ds
mov ax,es
mov ds,ax ;DS is segment address
mov ah,40h
mov bx,word ptr cs:[errfile]
int 21h ;output message
pop ds
ret
err_msg endp
;
;************************************************************************
;* *
;* getch returns the next character from the file in AL. *
;* Returns carry = 1 on error *
;* ZF = 1 on EOF *
;* Upon exit, if either Carry or ZF is set, the contents of AL is *
;* undefined. *
;* *
;************************************************************************
;
; Local variables used by the getch proc.
eof db 0 ;set to 1 when EOF reached in read
last_ch dw ibfend ;pointer to last char in buffer
getch proc
mov si,word ptr ds:[ibfptr] ;get input buffer pointer
cmp si,word ptr ds:[last_ch];if not at end of buffer
jz getch_eob
getch1: lodsb ;character in AL
mov word ptr ds:[ibfptr],si ;save buffer pointer
or ah,1 ;will clear Z flag
ret ;and done
getch_eob: ;end of buffer processing
cmp byte ptr ds:[eof], 1 ;end of file?
jnz getch_read ;nope, read file into buffer
getch_eof:
xor ax, ax ;set Z to indicate EOF
ret ;and return
getch_read: ; Read the next buffer full from the file.
mov ah,3fh ;read file function
mov bx,word ptr ds:[infile] ;input file handle
mov cx,ibfsz ;#characters to read
mov dx,offset inbuf ;read into here
int 21h ;DOS'll do it for us
jc read_err ;Carry means error
or ax,ax ;If AX is zero,
jz getch_eof ;we've reached end-of-file
add ax,offset inbuf
mov word ptr ds:[last_ch],ax;and save it
mov si,offset inbuf
jmp short getch1 ;and finish processing character
;Page 46
read_err: ;return with error and...
mov dx,offset read_err_msg ; DX pointing to error message string
ret
read_err_msg db 'Read error', cr, lf, 0
getch endp
;
;************************************************************************
;* *
;* putch writes the character passed in AL to the output file. *
;* Returns carry set on error. The character in AL is retained. *
;* *
;************************************************************************
;
putch proc
mov di,word ptr ds:[obfptr] ;get output buffer pointer
stosb ;save the character
mov word ptr ds:[obfptr],di ;and update buffer pointer
cmp di,offset obfend ;if buffer pointer == buff end
clc
jnz putch_ret
push ax
call write_buffer ;then we've got to write the buffer
pop ax
putch_ret:
ret
putch endp
;
;************************************************************************
;* *
;* write_buffer writes the output buffer to the output file. *
;* This routine should not be called except by the putch proc. and at *
;* the end of all processing (as demonstrated in the filter proc). *
;* *
;************************************************************************
;
write_buffer proc ;write buffer to output file
mov ah, 40h ;write to file function
mov bx, word ptr ds:[outfile];output file handle
mov cx, word ptr ds:[obfptr]
sub cx, offset outbuf ;compute #bytes to write
mov dx, offset outbuf ;from this buffer
int 21h ;DOS'll do it
jc write_err ;carry is error
or ax,ax ;return value of zero
jz putch_full ;indicates disk full
mov word ptr ds:[obfptr],offset outbuf
clc
ret
putch_full: ;disk is full
mov dx,offset disk_full
stc ;exit with error
ret
write_err: ;error occured during write
;Page 47
mov dx,offset write_err_msg
stc ;return with error
ret
write_err_msg db 'Write error', cr, lf, 0
disk_full db 'Disk full', cr, lf, 0
write_buffer endp
;
;************************************************************************
;* *
;* get_arg - Returns the address of the next command line argument in *
;* DX. The argument is in the form of an ASCIIZ string. *
;* Returns Carry = 1 if no more command line arguments. *
;* Upon exit, if Carry is set, the contents of DX is undefined. *
;* *
;************************************************************************
;
get_arg proc
mov si,word ptr [cmd_ptr]
skip_space: ;scan over leading spaces and commas
lodsb
cmp al,0 ;if we get a null
jz sk0
cmp al,cr ;or a CR,
jnz sk1
sk0: stc ;set carry to indicate failure
ret ;and exit
sk1: cmp al,space
jz skip_space ;loop until no more spaces
cmp al,','
jz skip_space ;or commas
cmp al,tab
jz skip_space ;or tabs
mov dx,si ;start of argument
dec dx
get_arg1:
lodsb ;get next character
cmp al,cr ;argument seperators are CR,
jz get_arg2
cmp al,space ;space,
jz get_arg2
cmp al,',' ;comma,
jz get_arg2
cmp al,tab ;and tab
jnz get_arg1
get_arg2:
mov byte ptr ds:[si-1], 0 ;delimit argument with 0
cmp al, cr ;if char is CR then we've reached
jnz ga2 ; the end of the argument list
dec si
ga2: mov word ptr ds:[cmd_ptr], si ;save for next time 'round
ret ;and return
get_arg endp
;Page 48
endcode equ $
cseg ends
end start
Comments
Post a Comment