This piece of code allows you to make a memory dump of any region of conventional memory (i.e. below 1 Mb) to a diskfile.
The program itself.
The source is documented so it speaks for itself. At points of
interest I have insterted "break" and "restart" lines with space for
additional remarks.
So just read the source and read the remarks. This way, the text is
where the code is, and you don't need to go back and forth in the
text. I think this will easier to read than explanation afterwards.
--- Mem2File ------------------------------------------------- Start ---
name mem2file
title Send an area of memory to a diskfile.
page 80, 120
; version 1.0 : Had to be compiled for each area/filename OK: 01-01-1991
; version 1.1 : Same as above, for A86 format OK: 01-01-1999
; version 1.2 : Make it commandline driven OK: 01-02-1999
; version 1.3 : Make it reliable OK: 02-02-1999
; ----------------------
stdout = 1
tab = 9
lf = 10
cr = 13
clr MACRO ; macro called CLeaR
mov #1, 0 ; move it with zero
#EM ; and get outa here
Dum1 STRUC ; a structure definition
OffVal dw ?
SegVal dw ?
ENDS
; ----------------------
DATA segment ; this is where the volatile data lives
ByteF = $
dummy db ? ; just to fool D86....
; if this dummy variable is not here, D86 will
; reference variable "Start" as "ByteF".
even
Start dw ?, ? ; segment:address to start
Stop dw ?, ? ; segment:address to stop
Blocks dw ? ; number of 16K chucks to save
Rest dw ? ; remaining part to save
ArgNum dw ? ; nr of bytes in this argument
OldClp dw ? ; current pointer into command line
Handle dw ?
Length dw ?, ?
FileName: ; this storage is used twice....
Argument db 80 dup (?) ; storage for next argument from command-line
Output db 16K dup (?) ; buffered output
--- Mem2File ------------------------------------------------- Break ---
An A86 enhancement: if you need 16K elements of data, just ask for it.
No need to remember that 16 Kb is 16.384 bytes. The "K" will do.
No big deal, just a nice feature.
Also, if you need to process large binary numbers you may group them
into sub-units separated by underscores. So the number:
1000100100111101
is hard to read back. But if we insert "_" markers like:
1000_1001_0011_1101
the grouping of bits makes them easier to understand. Not that it is a
matter of life and death, but it can come in handy once in a while.
--- Mem2File ------------------------------------------------ Restart --
Bytes = $ - ByteF ; number of volatile databytes
; ----------------------
CODE segment ; no ORG, so we start at 0100
jmp main
HexTable db '0123456789ABCDEF', 0
db 'VeRsIoN=Mem2File 1.3', 0
db 'CoPyRiGhT=CopyLeft Jan Verhoeven, {
This e-mail address is being protected from spam bots, you need JavaScript enabled to view it
}', 0
Mess001 db 'Mem2File collects a part of conventional memory and '
db 'sends it to a file.', cr, lf, lf
db 'The syntax is:', cr, lf, lf
db tab, 'Mem2File segm1:offs1 [-] segm2:offs2 '
db '<path>file.ext.', cr, lf, lf
db 'Mem2File is GNU GPL style FREE software. ', cr, lf
db 'Please read the GNU GPL if you are in doubt.', cr, lf, lf
Mess002 db 'Mem2File was made by Jan Verhoeven, NL-5012 GH 272, '
db 'The Netherlands', cr, lf
db 'E-mail address : {
This e-mail address is being protected from spam bots, you need JavaScript enabled to view it
}', cr, lf, lf
Len001 = $ - Mess001
Len002 = $ - Mess002
Mess004 db 7, 'Error! All numbers are expected to be hexadecimal.'
db cr, lf
Len004 = $ - Mess004
;------------------------
InitMem: mov di, ByteF ; Init volatile memory with zero's.
mov cx, Bytes ; saves a lot of strange problems.
mov al, 0
rep stosb
ret
;------------------------
--- Mem2File ------------------------------------------------- Break ---
I use volatile data to store data that does not need initialising. This
saves a lot of diskspace and it loads a lot faster. Drawback of volatile
data can be that any rubbish left there by other programs can make your
software go berzerk if you yourself forget to initialise the data.
Therefore I -always- prime the volatile data memory with zero's. Just to
have a well defined starting position.
It should not be necessary, but, on the other hand, how much overhead
and extra execution time is such an initialisation routine?
--- Mem2File ------------------------------------------------ Restart --
L0: mov b [di], 0 ; terminate argument string
mov [OldClp], si ; done, => clean up.
clc ; indicate "No Error"
L3: pop di, si, ax ; restore registers, ...
ret ; ... and leave.
GetArg: push ax, si, di ; get next argument from command-line in ASCIIZ
format
mov si, [OldClp] ; now, where did we leave last time?
cmp si, 0 ; Have we ever used this routine?
IF E mov si, 081 ; if not, prime SI, ...
mov di, offset Argument ; ... DI and ...
mov [ArgNum], 0 ; ... nr of chars in argument.
L1: lodsb ; get byte
cmp al, ' ' ; skip over spaces, ...
je L1
cmp al, tab ; ... and tabs.
je L1
cmp al, 1 ; ONLY if AL is 0, we get a carry
jc L3 ; if CARRY, we're done
--- Mem2File ------------------------------------------------- Break ---
This construction is what I particularly like. I want to check if AL is
Zero. Normally you can code
cmp al, 0
jz L3
but L3 is the error-exit and needs the carrybit to be set as an error
flag. Normally you would enter a
stc
instruction to fullfill the specification. But that is poor programming.
It is better to let the software do this for us.
AL can have any value between 020 and 0FF, plus 00, tab, lf and cr. 01
is not an option. So the sequence
cmp al, 1 ; ONLY if AL is 0, we get a carry
jc L3 ; if CARRY, we're done
will send us to the errorexit WITH the carryflag set, all in one,
without explicitly having to set the carry flag.
--- Mem2File ------------------------------------------------ Restart --
L2: stosb ; else store char in Arguments array
inc [ArgNum] ; adjust counter
lodsb ; and get next char
cmp al, ' ' ; is it a delimiting space?
je L0
cmp al, tab ; or a tab?
je L0
cmp al, ':' ; or a colon?
je L0
cmp al, 0 ; or an end-of-line?
jne L2 ; if not, loop back,
mov si, 0FFFF ; else make SI ridiculously high, ...
stc ; ... set carry flag, ...
jmp L0 ; and get out.
--- Mem2File ------------------------------------------------- Break ---
Ok, ok, ok. I was influenced to make this function by a compiler. No, it
wasn't C. It was Modula-2.
GetArg (if necessary A86 can operate in a case sensitive mode!) extracts
the next argument from the command tail. It puts it in a seperate buffer
at address "Arument" which can hold 80 bytes. Shoyuld be more than
enough for one word or expression.
--- Mem2File ------------------------------------------------ Restart --
;------------------------
L1: stc ; byte not in table!
pop dx ; we came here with carry set!
ret ; exit
L2: sub bx, dx ; calculate position in table
pop dx
clc ; make sure carry is cleared
ret
--- Mem2File ------------------------------------------------- Break ---
This is a typical A86 construction. The subroutine is called TableFind
and it starts in the next line and ends in the previous one!
This is done to have the local labels declared for when they are needed
in the main functionbody. All jumps are "backward". For the CPU there's
no big influence, but for the assembler there is. No guessing about
labels.
--- Mem2File ------------------------------------------------ Restart --
TableFind: ; find AL in ASCIIZ table [BX] ...
push dx ; ... and report position
mov dx, bx ; keep value of SI
L0: cmp b [bx], 0 ; is it end of table?
je L1 ; if so, jump out
cmp al, [bx] ; compare byte with table
je L2 ; if same, jump out
inc bx ; else increment pointer
jmp L0 ; and loop back
;------------------------
MakeUpper:
cmp al, 'a' ; too low?
jb ret
--- Mem2File ------------------------------------------------- Break ---
An A86 enhancement: a conditional return instruction. All sensible CPU's
have conditional CALL and RET instructions. Not the 80x86 line. This CPU
was meant to be structured.
So you have to put a conditional jump before the call, and introduce yet
another silly labelname for the next instruction.
The "Jcc ret" is a good way to circumvent this ommission. What it
does is the same as what, on a Z-80, would be done with a "RET cc"
instruction.
There is one catch, however: there must be a RET instruction within
reach PRIOR to the "Jcc Ret" (internal) macro.
If that is a problem, you could also use the line:
IF cc Ret
So either way you, the programmer, win.
--- Mem2File ------------------------------------------------ Restart --
cmp al, 'z' ; if in range, ...
ja ret
and al, not bit 5 ; ... make uppercase
--- Mem2File ------------------------------------------------- Break ---
A86 is very programmer-oriented and allows us to write down what and how
we think. So if I need to set bit 0 of register Ax, I will simply write
or ax, bit 0
Any value between 0 and 15 is valid in A86 (0 - 31 for A386) to refer to
the respective bit in the respective source.
--- Mem2File ------------------------------------------------ Restart --
ret
;------------------------
BadNumber: ; hey typo, you made a dumbo!
mov dx, offset Mess004
mov cx, Len004
mov bx, StdOut
mov ah, 040
int 021
mov ax, 04C02 ; and exit with errorcode 2
int 021
;------------------------
SyntErr: mov dx, offset Mess001
mov cx, Len001
mov bx, StdOut
mov ah, 040 ; print out "help" screen and ...
int 021
mov ax, 04C01 ; ... exit with errorcode 1
int 021
;------------------------
L8: mov ax, dx ; Convert has result in DX, that's why.
pop dx, bx
ret
Convert: push bx, dx ; convert ASCII to Hex.
mov si, offset Argument
clr dx ; dx will contain result
--- Mem2File ------------------------------------------------- Break ---
Here the macro is invoked. It is used to load the DX register with
zero. If later you decide to change the way in which you want to clear
registers, just change the macro.
In LST files (the assembler listings) the expansions are controlled by
means of the +L switch. If you issue the option "+L35" macro's will not
be expanded in the listings file.
--- Mem2File ------------------------------------------------ Restart --
L1: lodsb ; get first character
cmp al, 0 ; end of string?
je L8
call MakeUpper ; if not, make uppercase
mov bx, offset HexTable
call TableFind ; and lookup in table
jc BadNumber
shl dx, 4 ; multiply DX by 16
--- Mem2File ------------------------------------------------- Break ---
This is another A86 goody. I coded a "SHL DX, 4" instruction, although
I do not know what the target processor will be.
No problem with A86. It will find out with which CPU you are assembling
and use that. If your CPU supports this function, it is implemented as
such. If it doesn't this instruction is expanded as a macro into the
following:
shl dx, 1
shl dx, 1
shl dx, 1
shl dx, 1
More code in the executable, but it makes programming easier.
If on a modern CPU, you can force A86 to act as if the CPU were a
vintage 88 with the commandline switch +P65.
--- Mem2File ------------------------------------------------ Restart --
or dl, bl ; bx = index into table
jmp L1 ; repeat until done
;------------------------
Credits: mov dx, offset Mess002
mov cx, Len002
mov bx, stdout
mov ah, 040
int 021 ; print some egotripping data
ret
;------------------------
main: call InitMem ; prime volatile data
mov al, [080] ; get tail length
cbw ; make 16 bits long
mov si, 081 ; point to start of tail
add si, ax ; point to end of tail
mov [si], ah ; make commandtail ASCIIZ
call GetArg ; get argument from command tail
jc SyntErr ; if error, get out
call Convert ; convert text to hex
mov [Start.SegVal], ax ; store it
call GetArg ; etcetera
jc SyntErr
call Convert
mov [Start.OffVal], ax
L0: call GetArg
jc SyntErr
cmp b [Argument], '-' ; single '-' character?
je L0 ; if so, ignore it
call Convert
mov [Stop.SegVal], ax
call GetArg
jc SyntErr
call Convert
mov [Stop.OffVal], ax
call GetArg
IF C jmp SyntErr
--- Mem2File ------------------------------------------------- Break ---
This is one of the A86 enhancements. This IF construct prevents that you
have to make up all kinds of ridiculous labelnames like jmp_001F in a
construct as follows:
call GetArg
jnc jmp_01F
jmp SyntErr
jmp_01F: ...
The IF construct (not to be confused with the "#IF" construct which is
for conditional assemblies) enables you to just make a fast jump by
stating the reverse condition in the IF statement and acting further
like a high level language:
IF C jmp SyntErr
Neat, isn't it?
--- Mem2File ------------------------------------------------ Restart --
mov si, offset Argument
add si, [ArgNum]
mov b [si], 0 ; make it ASCIIZ
mov dx, offset FileName ; same as Argument buffer....
mov cx, 0
mov ah, 03C
int 021 ; create the file
IF C jmp SyntErr
--- Mem2File ------------------------------------------------- Break ---
See how powerful the IF construct can be? It is a very convenient way to
circumvent the foolish conditional instructions of the x86 architecture.
--- Mem2File ------------------------------------------------ Restart --
mov [Handle], ax
mov ax, [Stop.SegVal]
mov dx, 0 ; prime DX
mov cx, 4
L0: shl ax, 1
rcl dx, 1
loop L0 ; shift upper 4 bits of address into DX
add ax, [Stop.OffVal]
adc dx, 0 ; now, dx:ax = linear address to stop at
mov [Stop.SegVal], dx
mov [Stop.OffVal], ax ; store linear address STOP
mov ax, [Start.SegVal]
mov dx, 0 ; prime DX
mov cx, 4
L0: shl ax, 1
rcl dx, 1
loop L0 ; shift upper 4 bits of address into DX
add ax, [Start.OffVal]
adc dx, 0 ; now, dx:ax = linear address to start
from
mov [Start.SegVal], dx
mov [Start.OffVal], ax ; store linear address START
cmp dx, [Stop.SegVal] ; start > stop?
ja >L1 ; fix it!
jb >L2
--- Mem2File ------------------------------------------------- Break ---
A86 likes to have as much as possible labels declared before they are
referenced. That's why many times there is code "before" the subroutine
name is declared.
For local labels (i.e. labels that consist of 1 letter and the rest
decimal digits) it is a MUST that they are defined before being
referenced.
If, for some reason, you do not want to put a label backwards in memory,
you can forward reference a local label by prefixing it with a ">" sign.
A86 now knows that the local label still has to come. Not a luxury since
many A86 programmers can do with 4 or 5 local labels in over 2000 lines
of code.... Especially L0 is always very well available.
--- Mem2File ------------------------------------------------ Restart --
cmp ax, [Stop.OffVal] ; start > stop?
jbe >L2 ; if not, OK
L1: push [Stop.SegVal]
push [Stop.OffVal] ; swap start and stop addresses
mov [Stop.SegVal], dx
mov [Stop.OffVal], ax
pop [Start.OffVal]
pop [Start.SegVal]
L2: mov dx, [Stop.SegVal]
mov ax, [Stop.OffVal]
add ax, 1
adc dx, 0 ; limits are INCLUSIVE
sub ax, [Start.OffVal]
sbb dx, [Start.SegVal] ; dx:ax = bytes to move
shl ax, 1
rcl dx, 1
shl ax, 1
rcl dx, 1 ; dx = nr of 16 Kb blocks to move
mov [Blocks], dx ; store it
shr ax, 2 ; ax = remainder to move
mov [Rest], ax ; save it
mov ax, [Start.SegVal] ; end of linear addressing, we're going
to DOS!
mov cl, 12
shl ax, cl
mov [Start.SegVal], ax
mov es, ds ; use es to refer to data
mov bx, [Handle]
lds dx, d [Start]
--- Mem2File ------------------------------------------------- Break ---
Normally this instruction would require a secretary with 100 letters per
minute typing rate:
lds dx, dword ptr [Start]
But the "ptr" argument is always the same, so it is only there to please
the assembler and humiliate the programmer: entering data that nobody
needs.
Therefore A86 only needs the first letter of such prose. In our case:
the "dword ptr" is abbreviated to a "d". "Byte ptr" is a "b". "Word Ptr"
is a "w". Simple as that.
So if your coding skills outweigh your typing speed, you should consider
switching to the superior assembler. :)
--- Mem2File ------------------------------------------------ Restart --
es cmp [Blocks], 0 ; if less than 16 Kb, skip this one.
--- Mem2File ------------------------------------------------- Break ---
For people who still remember that "Seg ES" is a legal instruction
(used for a segment override) this might bring back memories.
A86 allows the user to put the segmentation override before the actual
instruction. This way, the operand field looks neater. And it is also
the way in which D86 shows segmentation overrides.
--- Mem2File ------------------------------------------------ Restart --
je >S0
mov cx, 16K
L0: mov ah, 040
int 021
mov ax, ds ; store ds into ax
add dx, 04000 ; next buffer to load data from
IF C add ax, 01000 ; if carry, inc ds
mov ds, ax ; ds:dx now ready for next bufferfull of data
es dec [Blocks]
jnz L0
S0: es cmp [Rest], 0
je >L1
mov ah, 040
es mov cx, [Rest]
int 021
L1: mov ds, es
mov bx, [Handle]
mov ah, 03E
int 021 ; close file
call Credits ; show my ego
mov ax, 04C00
int 021 ; exit to DOS
--- Mem2File -------------------------------------------------- End ----
That's it.
{
This e-mail address is being protected from spam bots, you need JavaScript enabled to view it
}
|