2020--2024 by E. C. Masloch. Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument. DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
This document has been compiled on 2024-12-21.
lDebug is a 86-DOS debugger based on the MS-DOS Debug clone FreeDOS Debug. It features DPMI client support for 32-bit and 16-bit segments, a 686-level assembler and disassembler, an expression evaluator, an InDOS and a bootloaded mode, script file reading, serial port I/O, permanent breakpoints, conditional tracing, buffered tracing, auto-repetition of some commands, and a number of extensions. There is also a symbolic debugging option being developed.
For a tour, definitely start with the interface reference. To help invoke the debugger read the section on ‘Invoking the debugger as an application’. It may help to read the manual while testing the debugger on another terminal. Use the main page of the online help as a reference to what is possible, then check the command reference for details. To explain what parameter types are used refer to the parameter reference. For how to calculate refer to the expression reference.
Reconfiguring the debugger can be done using variables, including the Debugger Assembler Options
(DAO) and the Debugger Common Options
(DCO) 1 to 7. Use the variable reference and the online help pages on the options for those. Change variables using the R commands, such as ‘r dco or= 800
’ or ‘r ior := #50
’. Some configuration can be done using the INSTALL
command, refer to section 10.26.
[more]
’ prompt for pagination, entering Ctrl-C aborts the current command and returns to the debugger command line.
GT
or GNT
commands to skip past conditionals
G ABO
to skip past calls or loops if P
would not work
POINTER
type expression allows using a 32-bit number as a 16:16 segmented address whereever an address parameter is parsed
(...)
’
AGAIN
keyword as the first breakpoint, and can save to the list without actually executing the debuggee by ending the breakpoint list with a REMEMBER
keyword
INSTALL GETINPUT
enables the line editor and input history even when using DOS for input
INSTALL BIOSOUTPUT
will use the ROM-BIOS for output instead of DOS, including for register change highlighting
INSTALL INDOS
will act as if the debugger is always running with the InDOS flag set, avoiding DOS calls from the debugger itself
INSTALL AMIS
and INSTALL TIMER
commands can be used to provide the debugger's AMIS interface and hook the timer interrupt
DW ss:csp
command is useful to view the current stack formatted as words
[more]
’ prompt that pauses the output until a keypress is received
BP NEW ptr ri2Dp
’, if the interrupt handler is writeable
#
prefix, and binary with 2#
. Character codes can be used as numbers with #"..."
tp FFFFF while ! value from linear 0:1*4 length 3*4 in writing silent
’. Add a conditional breakpoint like ‘bp new ptr ri21p when value ax in 2501, 2503
’ beforehand to intercept DOS calls to change these interrupts. (lCDebug or lDDebug require INSTALL INDOS
to write a breakpoint on the int 21h handler.)
g word [ss:sp]
’. For a 16-bit far or interrupt function use ‘g ptr [ss:sp]
’
G AGAIN address REMEMBER
’ (insert an offset or (NOT)TAKEN
keywords for the address), then finally run ‘G AGAIN
’
?O
help page as well as individual pages like ?O6
for DCO6
?BUILD
displays the description and source control revision IDs. The description includes a build date or release number.
?F
command to list the meanings of the flag states.
maxpara=-1
)
nosplit
keyword
loop
instructions if ASIZE prefix is present, rather than D or W suffix
MODRM
keywords
xchg
with a ModR/M, in both the assembler and disassembler
MODRM
keyword to select or indicate non-default forms of instructions
movzx
to 32-bit register with memory source lacking a size keyword
JMP FAR imm
with a single immediate, which becomes the offset that is combined with the AAS as a segment
BYTE 1
to assembler to use an imm8 shift/rotate count (the 186+ form) similar to NASM, and disassemble this with the BYTE
keyword as well
adc dx, FFFF
’, ‘imul dx, dx, FFFF
’, ‘push FFFF
’, ‘mov dx, [bx + FFFF]
’)
push [100]
’ or ‘pop [100]
’ without a size specified, defaulting to word size (in 86 Mode or a 16-bit CS), or to dword size (in a 32-bit CS for lDebugX). Disassembler however always shows the size.
@(
’ required two closing parens
LDEBUGPROGRESS
to a number: 1 (dots), 2 (percentage), 3 (bar), 4 (bar and percentage)
::empty::
keyword to skip automatic scripts path search in EXT and Y commands
::scripts::
and ::config::
prefixes in boot Y and EXT commands, as well as the BOOT DIR command
AS size
’ keywords.
INTFAULTS
to enable these hooks.
INSTALL SERIAL, TIMER, AMIS
.
COUNT
command and variable. S command also sets the variable.
?VERSION
’ help page.
END
’ keyword to range parameter parsing.
INSTALL
command keywords to aid reconfiguring the debugger.
getinput
allow Ctrl-A (like Home) and Ctrl-E (like End).
LDEBUG$$
device will now cause a critical error.
$RESULTEXT
variable for mak.sh can set a filename extension other than ‘.com
’.
IF EXISTS R variable THEN
’ command added
/E+
mode a zero word is now pushed to the initial stack
-dumb
mode now detected specifically when needed for register change highlighting to ROM-BIOS interrupt 10h
R VD
’ accesses VD variable, rather than run ‘RVD
’ command
unused
’.
cmd3
’. The offset 1 entrypoints will additionally display a linebreak.
SILENT
keyword followed by a number before the S command's range or list, display only up to a certain amount of results
?
should always give its result an unsigned type
dword
size keyword.
GT
and GNT
commands, as well as TTEST=TAKEN
and TTEST=NOTTAKEN
to change (e)ip
.)
BOOTUNITFLx
flag variables
u(bootldpunit).(bootldppart)
if LDP is equal to FDA
r f .
no longer displays garbage
/2
switch to use alternative video adapter for debugger output if available (pick from FreeDOS Debug)
?OPTIONS
help page and specific pages for DCO1, DCO2, DCO3, DCO4, DCO6, DIF, and DAO
INICOMP_WINNER
build variable so as to use lzsa2 compression for current releases
_DEBUG_COND
build option to allow toggling debug mode on and off at run time
INT8CTRL
variable which contains number of ticks to wait for Control pressed entrypoint; set to zero to disable
AAM
and AAD
instructions is omitted in disassembler
00
to a 16-bit register name to get a 32-bit value with the register value in the high word
/C=''
switch
PARAS
keyword to range length parsing, to multiply a count by 16 (size of a paragraph)
RIxxP
variables to read IVT entries in a way suitable to be used as POINTER
type expressions
retf
instruction. (This supports a method for relocation, used for example by the debugger itself.)
MACHX86
and MACHX87
variables to read machine type
R size [mem] := val
causing a fault in the debugger if value ends in FFFFh
POINTER
types for handling a 32-bit expression as a 16:16 far pointer
?BUILD
command list the amount of ancestors to help to compare revisions
CH
’ would be misparsed as ‘CHAR
’ type instead of the expected variable
jmp
R WORD [memory]
prompt would not consider the size keyword as part of the input line prompt
QQCODE
variable
BOOT[L|Y|S][UNIT|PART]
variables, BOOTUNITFL(x)
variables
HHRESULT
variable
_MEMREF_AMOUNT
enabled by default
direction
and stackhinting
enabled by default
AMISNUM
to read the multiplex number
mov ax, 0
’ fail to assemble now
$
’ prefix to segments in DebugX while in Real/Virtual 86 Mode
WIDTH=
keyword handling to H BASE=
BOOT DIR
command (SFN name only, attributes, size (using FAT+), datetime)
#"..."
to expression evaluator
H BASE=
command
merge
and debug
switches to mktables. Both are default off for now. Merging means redundant operand list tails are merged.
cs xlatb
, as the segment override prefix may be ignored on CPUs below 386
?? ::
construct operator
uumemref
and made memrefs available in default branch. The build option _MEMREF_AMOUNT
must be enabled to use them.
direction
and stackhinting
to mktables program. (Default off for now.)
LOOPNZ
’
OR=
’ as synonym for ‘|=
’ (especially useful if shell does not allow specifying pipe symbol for /C)
LOOPxx destination, (E)CX
’ as in NASM instruction reference to specify address size
INT BYTE 3
’ to get CDh encoding and display it this way in disassembler
add [100], 12
’)
G REMEMBER
’ command to work with the saved temporary breakpoint list
R var
’ and indirection in expression evaluator
S range REVERSE
’ command
f 100 l 10 0
’ \ ‘s 100 l 10 0
’ should result in 16 matches
RANGE
’ specifications for source data
P/TP/T ... SILENT
’ which writes to an internal buffer during the run then replays the last entries from it upon finishing the run
P/TP/T ... WHILE
’ conditions
Building lDebug is not supported on conventional DOS-like systems. (DJGPP environments may suffice but are not tested.) Assembling the main debugger executable may require up to 1 GiB of memory.
The following components are required to build with the provided scripts:
%deftok
’ is not supported
%deftok
’ is implemented wrongly
%assign %$foo%[bar] quux
’ doesn't function right
hg pull
’ to update the repo
hg up
’ or ‘hg up default
’ or any other available commit you want to build
hg fetch
’ or the sequence of ‘hg pull
’ then ‘hg up
’ to update the repos. (Usually the additional source repos do not have multiple branches.)
INICOMP_METHOD
in ovr.sh to select none, one, or several compression methods. Surround multiple values with quotes and delimit with blanks. If the value "none" is used no compression will occur. If several values are given the smallest of the resulting files will be used as the ldebug.com
result. This favours LZMA-lzip (lzd) and Exomizer 3 (exodecr) compression as they result in the best ratios. The uncompressed ldebugu.com
file will always be generated, you can rename or copy or symlink it to use it as ldebug.com
if you want.
use_build_decomp_test
option by setting it to a nonzero number. This insures that the compressed executables will actually succeed in decompression when entered in EXE mode, and will lower the required minimum allocation given in the EXE header to the minimally required value so that decompression will still succeed. This defaults to using dosemu2, which must have a DOS installed that allows filesystem redirection. DEFAULT_MACHINE
can be used to select qemu instead. The options BOOT_KERNEL
, BOOT_COMMAND
, and BOOT_PROTOCOL
must be set up then to allow building a bootable diskette. (This is needed because qemu does not offer filesystem redirection for DOS.)
use_build_decomp_test
is enabled, you may also enable INICOMP_PROGRESS
(again by setting it to a nonzero number). This enables code for a progress display during depacking of a compressed executable. The default for application mode and device driver mode is to not display the progress indicator. Setting the (DOS) environment variable LDEBUGPROGRESS
to a value between 1 and 4 will enable the indicator. This is now enabled by default in release and current daily builds.
INICOMP_WINNER
to name one method or the keyword ‘smallest
’ or ‘fastest
’. The latter requires use_build_decomp_test
to be enabled, as well as to set the variable INICOMP_SPEED_TEST
to a nonzero number. This number specifies how often to decompress the image during the decompression timing test. A number of 16 or higher is recommended.
use_build_revision_id
option is by default on. It requires that the sources are in hg (Mercurial) repos and that the hg command is available to run ‘hg id
’. The resulting revision IDs are embedded into the executable and will be shown for the ?B (long) and ?BUILD (short) commands.
$NASM
specifies the nasm executable to use, with path if needed.
./makec
’ (or use whatever C compiler to build mktables) then ‘./mktables
’ next. Note that mktables only needs to be used if either the source files (instr.*) changed or the mktables program itself has been altered. If the assembler and disassembler tables are not to change then mktables need not be used.
./mak.sh
’ from the ldebug/source directory. You may pass environment variables to it, such as ‘INICOMP_METHOD=exodecr ./mak.sh
’ to select Exomizer compression. You may also pass it parameters which will be passed to the main assembly command, such as ‘./mak.sh -D_DEBUG4
’ to enable debugging messages.
The mak.sh script expects that the current working directory is equal to the directory that it resides in. So you'll always want to run it as ‘./mak.sh
’ from that directory. The same is true of the make* scripts.
The make* scripts work as follows:
ldebug/tmp, ldebug/lst, and ldebug/bin will receive the files created by the mak script. The following filenames are for the default when running mak.sh on its own which is to create debug. (When ddebug, debugx, or ddebugx are created, the names change accordingly.) In the ldebug/bin subdirectory, debug.com
will be a nonbootable executable (even if the _BOOTLDR
option is enabled). This executable can safely be compressed using EXE packers such as the UPX. (In cfg.sh the option use_build_shim
now controls whether debug.com
is created. It defaults to disable this output file.) If the _BOOTLDR
option is enabled, ldebug.com
will be a compressed bootable executable (if any compression method is selected), whereas ldebugu.com
will be an uncompressed bootable executable. These bootable executables must not be compressed using any other programs. Doing that would render the kernel mode entrypoints unusable. Incidentally, UPX rejects these files because their ‘last page size’ MZ EXE header field holds an invalid value.
The bootable executables can be used as MS-DOS 6 protocol IO.SYS
, MS-DOS 7/8 IO.SYS
, PC-DOS 6/7 IBMBIO.COM
, EDR-DOS DRBIO.SYS
, FreeDOS KERNEL.SYS
, RxDOS.3 RXDOS.COM
, or as a Multiboot specification or Multiboot2 specification kernel. In any kernel load protocol case, the root FS that is being loaded from should be a valid FAT12, FAT16, or FAT32 file system on an unpartitioned (super)floppy diskette (unit number up to 127) or MBR-partitioned hard disk (unit number above 127). In addition, the bootable executables also are valid 86-DOS application programs that can be loaded in EXE mode either as application or as device driver. (Internally, all the .com files are MZ executables with a header, but they are named with a .COM file name extension for compatibility.)
It is valid to append additional data, such as a .ZIP archive, to any of the executables. However, if too large this may render loading with the FreeDOS or EDR-DOS load protocols impossible. All the other protocols work even in the presence of arbitrarily large appended data.
The mktables tool is a C program that prepares the debugger assembler/disassembler tables from three input files. The input files are:
The output files are:
As mentioned, mktables only needs to be built and ran if any of the input files, or mktables itself, or the mktables options have changed. The debugger's hg repo carries a copy of the current full default tables so running mktables is usually not needed.
The following options are available:
[no]direction
[no]stackhinting
[no]merge
[no]debug
filename
debugtbl.inc
’.
[no]offset
[no]discardunused
8086
, 186
, 286
, 386
, 486
, all
all
’.
mktables is built using a C compiler. OpenWatcom and gcc should both work. An example to build mktables on a DOS host with OpenWatcom is:
wcl -ox -3 -d__MSDOS__ mktables.c
An example to build mktables with gcc on a Linux host:
gcc -xc MKTABLES.C -o mktables -Wno-write-strings -DOMIT_VOLATILE_VOID
hg pull
’ to update the repo
hg up
’ or ‘hg up default
’ or any other available commit you want to build
hg fetch
’ or the sequence of ‘hg pull
’ then ‘hg up
’ to update the repos. (Usually the additional source repos do not have multiple branches.)
$NASM
specifies the nasm executable to use, with path if needed.
./makinst.sh
’ from the ldebug/source directory. You may pass environment variables to it. You may also pass it parameters which will be passed to the assembly commands.
The makinst.sh script expects that the current working directory is equal to the directory that it resides in. So you'll always want to run it as ‘./makinst.sh
’ from that directory.
ldebug/tmp, ldebug/lst, and ldebug/bin will receive the files created by the makinst script. ldebug/bin/instsect.com will be the instsect application, which has boot sector loaders for FAT12, FAT16, and FAT32 embedded. The default protocol is lDOS and the default kernel name LDEBUG.COM. Read the instsect help page for instructions on how to use it. Refer to section 17.2 for the instsect help. The help can also be obtained by running instsect.com /?
from DOS. The kernel name can be modified with the /F=
switch to instsect. For instance, ‘instsect.com /f=lddebugu.com a:
’ installs the loader onto drive A: with the name set up to load the uncompressed lDDebug.
Current lDOS boot32 uses the FSIBOOT4 protocol for an additional stage. This is interoperable with the upcoming RxDOS version 7.25's use of the FSIBOOT4 protocol, as well as with loaders that use a different sector for their additional stage (like Microsoft's), or those that do not use an additional stage (like FreeDOS's).
The test suite (test/test.py) by default uses qemu. (dosemu2 tends to need more than 5 seconds to start while qemu manages in 2 seconds or less.)
If the debugger is run as a DOS application and qemu is used then a boot image containing a DOS kernel, shell, autoexec.bat, and quit program must be created. If the build option use_build_qimg is enabled then calls to mak.sh will create such an image. The script file makqimg.sh carries out this task.
If the debugger is run as a DOS application and dosemu2 is used then the DOS installed in dosemu is used. The -K and -E switches to dosemu2 are used to mount a host directory and execute the debugger.
If the debugger is bootloaded (in either qemu or dosemu2) then a boot image with only the debugger executable and a startup boot script file must be created. If the build option use_build_bimg is enabled then calls to mak.sh will create such an image. The script file makbimg.sh carries out this task.
The test script creates symlinks to bin/ and tmp/qemutest/ and tmp/bdbgtest/ on its own. It can be executed from any directory, as it should find its files based on its own location. The test suite uses pseudoterminals, qemu or dosemu2, and the default Python unittest module.
Some tests may require having executed the script file test/scripts/mak.sh from within the test/scripts directory. When booting the debugger or using qemu, this must be run before makbimg.sh or makqimg.sh is run.
The DPMI tests currently require manual setup, with a directory test/dpmitest/ containing the dpmitest programs (for dosemu2) or a diskette image test/dpmi.img containing the programs as well as the HDPMI host executable (for qemu).
run
, and are uninstalled within the function intrtn1_code
or intrtn1_entry.code
. This allows debugging everything except this section. This is intended to be used with a default build of lDebug as the outer debugger. However, there is nothing preventing usage of a different debugger. To indicate that the debuggable debugger is running, its default command prompts are prepended by a tilde ‘~
’.
(To debug everything including the section from run
to intrtn1_*
, or the DPMI entry of lDebugX, a lower-level debugger must be used, such as dosemu's dosdebug or other debuggers that are integrated into emulators.)
~
’.
The command-line switch /D+ can be used to start up in debuggable mode. /D- instead insures to start up in non-debuggable mode. The DCO6 flag 100h can be toggled subsequently to toggle debuggable mode. This DCO6 flag can also be changed using INSTALL DEBUG
commands.
lDebug with the _DEBUG option disabled accepts a no-op /D- switch. lDDebug with _DEBUG enabled but _DEBUG_COND disabled accepts a no-op /D+ switch.
-
’ to a hash sign ‘#
’. (lDDebugX or lCDebugX in debuggable mode prepends its tilde to that resulting in ‘~#
’.)
ldebugu.com
and ldebug.com
. In bootloaded mode, I/O is never done using DOS, as if InDOS mode was always on. The DOS's current PSP is not switched during debugger operation. The MCB chain can only be displayed using the DM command by specifying the start segment explicitly. The BOOT commands are supported, refer to section 18.11. Disabling this option allows to save a little memory for a special-purpose build.
./mktables direction stackhinting
. (These mktables switches are now default enabled.) This allows for memrefs to indicate whether an explicit memory operand is a read or write (direction
), as well as for stack accesses like push
, pop
, call
, retn
to be recognised in memrefs (stackhinting
). Memrefs are initialised by disassembly. Memrefs can be accessed using the access variables like READADR0
, READLEN0
, etc. Refer to section 12.19. The access variables are written after an R command's register dump and disassembly (refer to section 10.37). Access variables can be accessed using special keywords behind the IN
of a VALUE x IN y
construct (refer to section 9.8).
Note that memrefs are not always exact. For instance, accesses by some instructions are not detected (eg lgdt
, sgdt
, fsave
). Some instructions' accesses are not always correctly detected, such as enter
with non-zero second operand, string instructions spanning segment boundaries, or instructions using ss
after a write to ss
that causes disassembly repetition. Some types of accesses are never detected either, such as GDT/LDT accesses to load descriptors. The stack access of software interrupt instructions is correctly detected only when tracing interrupts (Trace Mode set to 1, refer to section 10.50); if the interrupt call is proceeded past then like any proceeded-past function call it may use more stack space.
The stand-alone and FreeDOS release packages contain the following files:
In the bin
or BIN
directory:
ldebug.com
ldebugx.com
instsect.com
LDEBUG.COM
from a FAT12, FAT16, or FAT32 file system
*.eld
Only in the stand-alone package, bin
also contains:
ldebugu.com
ldebugxu.com
In the FreeDOS package, SOURCE/LDEBUG/ldebug/bin
contains all the same files as bin
in the stand-alone package. (All files in BIN
are duplicated from this bin
directory.) Likewise, the stand-alone package's tmp
, doc
, and lst
are carried over into the FreeDOS package's source tree.
Unlike the stand-alone package, the FreeDOS package contains all sources needed to build the debugger from scratch, except for the build toolchain. The following repos' most recent tip revisions are copied into the SOURCE/LDEBUG
source tree:
The tmp
or SOURCE/LDEBUG/ldebug/tmp
directory contains subdirectories for each used compression method. For example, there is a subdirectory named lz4
. These subdirectories contain the compressed executables ldebug.com
and ldebugx.com
built with the corresponding compression method.
NB: The default choice of compression method (lzsa2) is chosen based on the executable size and depacker performance. Other compression method choices may be desired. The uncompressed executables may be used, or those compressed with another method (as found in the tmp
subdirectories).
In the doc
directory, or DOC/LDEBUG
:
ldebug.htm
ldebug.txt
ldebug.pdf
fdbuild.txt
LDEBUG.LSM
In the root directory, or also DOC/LDEBUG
:
license.txt
In the APPINFO
directory, only for FreeDOS package:
LDEBUG.LSM
In the lst
or SOURCE/LDEBUG/ldebug/lst
directory:
debug.map
ldebug.com
and ldebugu.com
debugx.map
ldebugx.com
and ldebugxu.com
instsect.map
instsect.com
boot12.map
, boot16.map
, boot32.map
instsect.com
Listing files are no longer shipped with release builds (as of release 8), neither in the stand-alone package nor the FreeDOS package. To obtain a listing file that will match the binaries of a release build, one may obtain the daily current build of the same day as the release build. The only differences should be in the debugger version strings, which are filled to the same size to avoid changing the offsets of everything.
Alternatively, in the source
directory ‘./make reproduce
’ may be run on a host system to recreate all artefacts of the build of the main debugger. (Exact identicalised output may require the same toolchain as originally used.) Likewise, in the source
directory ‘./makinst.sh
’ may be run to recreate the instsect artefacts and in the source/eld
directory ‘./mak.sh
’ to create the ELDs.
The debugger can be loaded as a variety of kernel formats.
The Multiboot1 and Multiboot2 entrypoints will expect that a kernel command line is provided. The EDR-DOS, FreeDOS, RxDOS.3, and lDOS load protocols allow specifying a kernel command line, but it is optional.
If a kernel command line is detected then its contents are entered into the command line buffer. Unescaped semicolons are translated into Carriage Returns. Semicolons and backslashes may be escaped with backslashes.
If no kernel command line is given, the debugger assumes a default. It is equivalent to checking for a file and label using the IF command (section 10.25), then if found to execute that script file. The IF condition is like if exists y ldp/LDEBUG.SLD :bootstartup then
and the subsequent script command is y ldp/LDEBUG.SLD :bootstartup
(section 10.58). The filename is however LDDEBUG.SLD
for DDebug builds, and LCDEBUG.SLD
for CDebug builds.
Executing the Q
command (section 10.34) makes the debugger uninstall itself then continue running whatever code the debuggee is in. Executing the BOOT QUIT
command (section 18.11) makes the debugger attempt to shut down the machine. First it will try to call a dosemu-specific callback. Next it will attempt shutting down with APM. (This works in qemu.) Finally it will give up if no attempt worked.
The debugger is internally an MZ .EXE style application. It may need MS-DOS version 3 level features. A few switches are supported:
/?
/C
/IN
%LDEBUGCONFIG%
variable or in the debugger executable's directory.
/A
MIN
, MAX
, a hexadecimal number, or a hash sign #
followed by a decimal number. If a blank follows directly behind /A
this is parsed like /A=MAX
. Minimum size is 8208 Bytes, maximum size is by default 24576 Bytes. The auxiliary buffer currently cannot be resized by the resident debugger. This switch is processed by the debugger's init.
/S
/B
/F
/E
.BIN
or using the /F switch) will set up a Stack Segment at the end of the process memory block. That is, in a different segment than the process segment. If disabled, then flat binaries always get SS = PSP even if that leaves the stack pointing into the binary image. /F implies /E+.
/V
/2
/P
If the last component of the filename (after the right-most forward slash, backslash, or colon) does not yet contain a dot, the debugger will first try to load from the filename as given. Then it will try to append three different filename extensions in order: .COM
, .EXE
, and .BIN
.
If the filename does not contain any path components (indicated by forward slashes, backslashes, or colons) and no match is found in the current directory, the debugger will read the %PATH%
variable if any. It will then attempt to find a match in every path element in order. (If the filename does not contain a dot, then for every path element, it is searched for the filename as-is and then with each of the three extensions added. If all four attempts fail, the next path element is tried.)
/PE
/PS
/PW
If the filename is not empty, and if the last component of the filename (after the right-most forward slash, backslash, or colon) does contain a dot, and the last four text bytes do not match a known extension, then the warning is displayed by init. The known filename extensions are: .HEX
, .ROM
, .COM
, .EXE
, and .BIN
.
/D
After the switches a filename may follow. After the filename, command line contents for the process to be debugged may follow. These are both passed to the N command. Then, an L command for loading an application is run.
Executing the Q
command (section 10.34) makes the debugger try to terminate the debuggee application and to then terminate itself. The debugger returns to whatever application called it.
If the TSR command (section 10.51) is used, the debugger patches the parent of the currently running application to be the debugger's parent. A subsequent Q
command will then behave much like it does in boot loaded mode: The debugger uninstalls itself and continues execution in the current debuggee context.
The debugger detects its configuration directory as follows:
LDEBUGCONFIG
is set, read it.
The command line buffer receives a command that uses the configuration directory. This command is written before any /C= switch contents.
It is equivalent to checking for a file and label using the IF command (section 10.25), then if found to execute that script file. The IF condition is like if exists y ::config::LDEBUG.SLD :applicationstartup then
and the subsequent script command is y ::config::LDEBUG.SLD :applicationstartup
(section 10.58). The filename is however LDDEBUG.SLD
for DDebug builds, and LCDEBUG.SLD
for CDebug builds. This default command can be disabled using the /IN switch.
The debugger's MZ .EXE style executable can also be loaded as a device driver. Loading as a device driver requires an MS-DOS version 5 level feature. Namely, the loader has to initialise and pass the pointer to the end of memory available to the device driver. (The debugger attempts to detect whether this pointer is passed and indicates enough memory, but it is unclear how well that works.)
Device drivers can be loaded from CONFIG.SYS using a DEVICE=
directive. Other loaders such as DEVLOAD may work too. (DEVLOAD 3.25 specifically needs a patch to fix some problems keeping track of memory and to allow DEVLOAD to report more than 64 KiB of memory available to the device driver.)
DOS device loaders generally convert the device driver's command line to allcaps. To work around this, the debugger will interpret the exclamation mark in a special way: An exclamation mark indicates to convert the next letter to a small letter, if it is a capital letter. To pass a literal exclamation mark, double it.
All command line switches of the application mode are also accepted by the device mode debugger. (The /T switch is the exception, it is only valid to the application mode.) In particular, /C=
can be used to pass commands to execute. The configuration Script for lDebug file is detected in the same way as for application mode, except the label used is :devicestartup
.
The debugger will start up with debuggee client registers set up from the way they were passed by the device loader. CS:IP will point to a far return instruction in the debugger's entry segment. The stack will be preserved from what the device loader passed, too. That means running the debuggee allows to return control to DOS and have it finish installation of the debugger as a device. Subsequently, DOS and other device drivers and applications can be debugged, just like when resident in TSR mode.
The device mode debugger can terminate in two different modes. Both require a specific command letter appended to the Q command.
QD may be used if control did not return to the device loader yet. The debugger checks this condition by stashing away a copy of all regular registers to compare to their current values. This includes all GPRs, all segment registers, EIP, and EFL. Also, the debugger's device header fields for pointing to the next device header are compared to FFFFh. If both match, it is assumed that we can still modify the request header passed by the device loader. This allows to report an error and set up an empty memory block to keep, so that the loader will know to discard the device.
QC may be used if control has returned to the device loader already and the debugger device has been installed into the system. It requires locating the device header in the chain of devices that starts with the NUL device in the DOS data segment. It also requires to find the memory block containing the debugger. It must be either a PSP-alike MCB (self-owned regular MCB containing exactly the debugger allocation) or an ‘SD
’ (System Data) container MCB with one or more sub-MCBs (one of which contains exactly the debugger allocation). If these conditions are met, the debugger can be quit. It re-uses parts of the TSR application mode termination.
NOTE: Using QC currently assumes that no system file handles are left allocated to the placeholder character device that the debugger installs to keep itself resident. This device is currently called ‘LDEBUG$$
’. If this rule is not followed the system might crash.
Use the test.py script in the test subdirectory. Use the -v switch to do verbose output. Specify test name patterns to use with -k, or omit to run all tests. (Refer to the Test Reference in section 20 for a list of all tests.)
The script uses the following environment variables:
r dco6 clr= 100
’ or ‘uninstall debug
’ if testing lCDebug to disable its debuggable mode.
qemu
’ or ‘dosemu
’ (default is ‘qemu
’)
The most common reason for random failures is timing. If this is suspected to be the case, the duration variables allow increasing the time spent waiting on debugger output. They were added to replace the workflow of editing durations manually in the test script.
The debugger provides a line-based text interface. The interface is written to DOS standard output by default. If InDOS mode is entered, INSTALL BIOSOUTPUT
has been used, or the debugger is bootloaded then the interface is written to the terminal using interrupt 10h. Serial I/O can be enabled to write the interface to the serial port.
The default command prompt indicates that a command may be entered. It is a dash ‘-
’ by default, or a hash sign ‘#
’ when DebugX is in Protected Mode. An exclamation point ‘!
’ is prepended by a DOS application or device mode debugger (not bootloaded) while DOS's InDOS flag is set. (This check always uses the actual InDOS flag, ignoring the "force InDOS" mode of the debugger.) A tilde ‘~
’ is prepended for DDebug, or CDebug while in debuggable mode.
If DOS command line input is done using getinput
(eg if DCO option 800h is set or INSTALL GETINPUT
was run) or the input is from a raw (ROM-BIOS) terminal, or from a serial port, then the line editing history is enabled. Prior commands may be recalled using the Up arrow key. The Down arrow key may also be used to reverse the recall. As soon as any prior or new line is edited the history recall is disabled. To discard the current line and re-enable history recall, Control-C may be entered.
Long command output may be paged. In that case, once a screenful has been displayed, a ‘[more]
’ prompt is displayed to pause the output. After pressing any key the output is continued. If Control-C is pressed, the current command is aborted.
Refer to section 12.11 for the serial configuration variables. Setting the DCO flag 4000h enables serial I/O. This flag can also be set using the INSTALL SERIAL
command. Upon enabling serial I/O a prompt is sent to the serial port. This prompt looks like the following example:
lDebug connected to serial port. Enter KEEP to confirm.
=
(The name of the debugger is modified to indicate DebugX, DDebug, DDebugX, CDebug, or CDebugX. The prompt indicator is ‘~=
’ for DDebug or CDebug while in debuggable mode.) If the keep prompt is successfully displayed by the serial terminal and is responded to with the requested ‘KEEP
’ keyword then serial I/O is established.
If the confirmation does not occur after a timeout then serial I/O is disabled again. The timeout defaults to about 15 seconds. In this case the debugger itself clears the DCO flag 4000h.
If the DCO flag 4000h is cleared then serial I/O is disabled. The flag can also be cleared using the UNINSTALL SERIAL
command.
The R command (refer to section 10.37) without any parameters dumps the current register values. Then it disassembles a single instruction, or occasionally more than one. The register dump looks like this by default:
-r
AX=0000 BX=0001 CX=58A0 DX=0000 SP=0800 BP=0000 SI=0000 DI=0000
DS=1BEC ES=1BEC SS=35A9 CS=1BEC IP=0140 NV UP EI PL ZR NA PE NC
1BEC:0140 8CC8 mov ax, cs
-
If the ‘RX
’ command was used to switch on 32-bit register dumping, then the register dump looks like this:
-r
EAX=00000000 EBX=00000001 ECX=000058A0 EDX=00000000 ESP=00000800 EBP=00000000
ESI=00000000 EDI=00000000 NV UP EI PL ZR NA PE NC
DS=1BEC ES=1BEC SS=35A9 CS=1BEC FS=0000 GS=0000 EIP=00000140
1BEC:0140 8CC8 mov ax, cs
-
The RE command (section 10.37.1) runs the RE buffer commands. The default RE buffer content is a single ‘@R
’ command. After running the program being debugged, usually the RE buffer commands are also being run. This includes a step with the T, TP, or P commands. (Section 10.49, section 10.49.1, section 10.33.) It also includes a run with the G command. (Section 10.21.) Further, a permanent breakpoint which is configured as a pass point being passed also runs the RE buffer commands. (Section 10.7.)
Setting the flags 1_0000 or 4_0000 in the DCO3 variable enables register change highlighting. (The command INSTALL RHIGHLIGHT
sets DCO3 4_0000.) When output is written to DOS standard output or to a serial port then ANSI escape sequences are used to highlight. Specifically, ‘\x1B[7m
’ is used to reverse video and then ‘\x1B[m
’ to reset the colours.
For DOS standard output it may be needed to install an ANSI escape sequence parser.
For serial I/O the terminal connected to the debugger is expected to handle the escape sequences.
If the output is to a terminal using interrupt 10h and DCO3 flag 2_0000 is clear and the terminal is detected as functional then highlighting is done using interrupt 10h video attributes.
The functionality check is done by calling interrupt 10h service 03h. If the indicated current column is nonzero then the terminal is considered functional. (Recent dosemu2 in -dumb
terminal mode is detected as not being functional. Current dosemu2 is queried for -dumb
mode directly, and considered not functional if in this mode.)
If this check fails or the DCO3 flag 2_0000 is set then escape sequences are written using interrupt 10h.
Another basic command is the D command (section 10.12). It is used to dump memory contents. For example, to dump part of a program:
-d
1BEC:0140 8C C8 31 DB 05 70 14 50-53 CB 70 03 91 67 BC 45 ..1..p.PS.p..g.E
1BEC:0150 3F 10 C1 6F F9 70 BA 22-7C 71 C3 72 0A 81 0A 81 ?..o.p."|q.r....
1BEC:0160 47 74 68 76 6C 77 32 72-A7 2F BD 78 4B 16 9F 7B Gthvlw2r./.xK..{
1BEC:0170 C9 2B 09 37 0A 81 81 7D-E2 7E AC A0 00 00 00 00 .+.7...}.~......
1BEC:0180 10 49 00 00 0F 00 00 00-00 00 00 00 10 49 00 00 .I...........I..
1BEC:0190 0F 00 00 00 F8 30 80 00-00 00 00 00 80 00 00 00 .....0..........
1BEC:01A0 07 00 00 00 07 00 00 00-00 00 00 00 00 00 00 00 ................
1BEC:01B0 00 00 00 00 97 65 00 00-00 00 00 00 00 00 00 00 .....e..........
-
Or, to dump the stack as words:
-dw ss:sp
header 0 2 4 6 8 A C E 0123456789ABCDEF
35A9:0800 0000 0000 0000 0000-0000 0000 0000 0000 ................
35A9:0810 0000 0000 0000 0000-0000 0000 0000 0000 ................
35A9:0820 0000 0000 0000 0000-0000 0000 0000 0000 ................
35A9:0830 0000 0000 0000 0000-0000 0000 0000 0000 ................
35A9:0840 0000 0000 0000 0000-0000 0000 0000 0000 ................
35A9:0850 0000 0000 0000 0000-0000 0000 0000 0000 ................
35A9:0860 0000 0000 0000 0000-0000 0000 0000 0000 ................
35A9:0870 0000 0000 0000 0000-0000 0000 0000 0000 ................
-
The U command is used to disassemble one or several instructions. Example:
-u
305C:0000 8CD0 mov ax, ss
305C:0002 8CDA mov dx, ds
305C:0004 29D0 sub ax, dx
305C:0006 31D2 xor dx, dx
305C:0008 B90400 mov cx, 0004
305C:000B D1E0 shl ax, 1
305C:000D D1D2 rcl dx, 1
305C:000F E2FA loop 000B
305C:0011 50 push ax
305C:0012 01E0 add ax, sp
305C:0014 83D200 adc dx, +00
305C:0017 83C00F add ax, +0F
305C:001A 83D200 adc dx, +00
305C:001D 24F0 and al, F0
305C:001F 83FA01 cmp dx, +01
-
A program to examine can be loaded using the N and L commands. If the debugger is loaded as a DOS application with a filename specified in its command line, it will run the N and L commands on its own.
The N command sets up some buffers internal to the debugger. One of those specifies the pathname of the executable file to load. The pathname must include the filename extension, if any. The pathname must be relative to the current directories at the time the L command runs, or it must be absolute. This is not true of the pathname initially specified on the debugger command line tail if the debugger switch /P is used. The path Extension for lDebug (refer to section 15.41) can be loaded to provide extension guessing and path search for later N or K commands after the debugger has been loaded properly.
The tail of the N command after the pathname is used as the command line tail for a new debuggee process.
The L command without any parameters attempts to load the program specified to the last N command into a new process. If the L command does not display any messages this indicates success.
Once a program is loaded into the debugger it can be run in several ways:
All run commands support auto-repeat: Submitting an empty line to the debugger (blanks allowed but no comment) will make the debugger run the last command again. For the G command auto-repeat, the specified temporary breakpoints will be used again. Refer to section 10.1.
Permanent breakpoints can be set up and changed using the B commands. They can be configured to behave as pass points as well. Refer to section 10.7.
The ?RUN help page in section 18.8 lists some additional features of the T, P, and TP commands.
The online help can be accessed using the ‘?
’ command. Refer to section 18 for copies of the online help.
There are debuggable builds of the debugger, called lDDebug (unconditionally debuggable) and lCDebug (conditionally debuggable).
The debuggable mode works by installing the mandatory interrupt handlers of the debuggable debugger only within the ‘run
’ function, so as to return the control flow to this instance when it runs its debuggee code. On return into this instance, it uninstalls its mandatory handlers again. This mechanism allows to debug most of the debugger using a different instance of lDebug (or potentially another debugger).
In debuggable mode, an additional command is supported, the BU command (which stands for "Break Upwards"). It will run a breakpoint within the debugger's code segment which will break into the other debugger. Its code was updated so it will break at the command dispatcher after the label cmd4. This means if the outer debugger is also an lDebug then it can be instructed to skip to the next command being dispatched by entering the command ‘G ip
’.
lDDebugX (or lCDebugX) can also install its exception areas into the other lDebugX instance. For this, the other debugger needs to have run an ‘INSTALL AMIS
’ command. Then the debuggable debugger can run its ‘INSTALL AREAS
’. Afterwards, faults in the debuggable debugger will make the other lDebugX indicate the area of the fault.
After lDebugX has caught a fault in the CODE or CODE2 segment, it can be instructed to resume the lDDebugX (or lCDebugX) command input loop (cmd3) by running a ‘G=0
’ command. If ‘G=1
’ is used instead, an additional linebreak will be displayed by the debuggable debugger before it starts prompting for input. This is useful if the fault occurred with some partial output currently displayed. The offset 0 and offset 1 entries are also supported by non-DPMI builds and can of course be used at any point in time other than after a fault, too.
There are some DCO6 flags to control breakpoints and entering lCDebug's debuggable mode in the functions debuggerexception and putrunint. They can be displayed using a ‘?O6
’ command.
Other than for the most trivial sessions it is recommended to control the outer debugger by serial I/O, separately from the I/O of the debuggable debugger. If the latter also should be controlled by serial I/O then two different ports can be used. The terminal connected to the outer debugger can also be set up for TracList, the lDebug companion application which traces a listing file. For instance, if lDDebugX is to be traced, TracList should be run with the ldebug/lst/ddebugx.lst listing file.
To allow the debuggable debugger to relocate and initialise its code sections, the outer debugger should generally start running the debuggable debugger with a plain ‘G
’ command. The debuggable debugger can then return control to the outer debugger using its ‘BU
’ command.
If the initialisation of the debuggable debugger is to be debugged, the ‘/B
’ switch may be of use. Otherwise, note that the NEC V20/V30 and 486 CPU detections may fail when traced using an outer lDebug.
The NEC detection may lock the machine up if its specially encoded ‘pop cx
’ is traced or run with a breakpoint directly behind it. To allow to continue tracing after it, a breakpoint must be set up at the ‘jcxz
’ instruction or later. There must not be a breakpoint on the ‘mov sp, ax
’ instruction. The ‘pop cx
’ instruction must not be traced with the Trace Flag set. Failure to honour these requirements may lock up the NEC CPUs, for example the one used in the HP 95LX, which then may require resetting the system with Ctrl-Shift-On. This also resets the system date and time.
The 486 detection may wrongly detect a 386 instead of a 486+ when traced on some systems, such as some revisions of dosemu(2).
The sections of the debugger are declared in debug.asm:
org 100h
addsection lDEBUG_DATA_ENTRY, align=16 start=100h
data_entry_start:
%define DATASECTIONFIXUP -data_entry_start+100h
_CURRENT_SECTION %+ _start:
%xdefine %[_CURRENT_SECTION %+ FIXUP] - _CURRENT_SECTION %+ _start+100h
addsection ASMTABLE1, align=16 follows=lDEBUG_DATA_ENTRY
addsection ASMTABLE2, align=16 follows=ASMTABLE1
addsection MESSAGESEGMENT, align=16 follows=ASMTABLE2 vstart=0
messagesegment_start:
addsection lDEBUG_CODE, align=16 follows=MESSAGESEGMENT vstart=0
code_start:
%define CODESECTIONFIXUP -code_start+0
_CURRENT_SECTION %+ _start:
%xdefine %[_CURRENT_SECTION %+ FIXUP] - _CURRENT_SECTION %+ _start+0
addsection lDEBUG_CODE2, align=16 follows=lDEBUG_CODE vstart=0
code2_start:
%define CODE2SECTIONFIXUP -code2_start+0
_CURRENT_SECTION %+ _start:
%xdefine %[_CURRENT_SECTION %+ FIXUP] - _CURRENT_SECTION %+ _start+0
addsection DATASTACK, align=16 follows=ASMTABLE2 nobits
addsection INIT, align=16 follows=lDEBUG_CODE2 vstart=0
%if _DEVICE
addsection DEVICESHIM, align=16 follows=INIT vstart=0
%endif
addsection RELOCATEDZERO, vstart=0 nobits
relocatedzero:
These are the sections:
?BOOT
help page when not in bootloaded mode.
/Y=
switch to the application or device driver init. The maximum ELD data size will expand this section up to offset 65_520.
There are some additional segments that do not correspond to sections in the debugger's sources. All of these segments are allocated at init time.
/A=
switch to non-boot init.
/H=
switch to non-boot init. Can shrink to nearly 260 Bytes as well.
/X=
switch to non-boot init.
Some additional memory allocations show up as gaps in the memory map of the debugger:
NDEB
’ and size information on the allocation of the debugger.
The ELD code segment has a different convenience entrypoint that returns the control flow to the cmd3
loop. Due to the structure of ELD instances, there can be no code at offset 0. Instead, there is an entry at offset 79h. Therefore, when the inner debugger is running code in the ELD code segment, a ‘G=79
’ command can be used to cancel the currently running ELD. (At most points in an ELD, to cancel the currently running ELD should be safe. In particular, any calls to debugger code that may do I/O or that may invoke the error handler must be safely cancellable.)
ELDs can be loaded at arbitrary offsets in the ELD code segment. The first ELD is always loaded at offset 80h, but subsequent ELDs may be loaded at higher offsets. The only certainty is that the offset is paragraph-aligned.
To help in using TracList to debug an ELD, a special handshake is provided to communicate an ELD's offset.
The outer debugger should ‘install amis
’ and install the AMIS message ELD by running ‘ext amismsg.eld install
’. When the ELD linker runs in the inner debugger, it will send a hint message to the outer debugger's AMIS function 40h (refer to section 13.5.5). The hint is received by the AMIS message ELD of the outer debugger and displayed to the outer debugger's terminal before processing the next command in the cmd3
loop.
The hint line looks like this example:
TracList-add-offset=ldmem.lst::0080h
When the hint line is received by tractest, it will hand it over into the line file and hint file, both read by TracList. TracList will then parse the offset provided by the hint. In the current use case this hint is always used to add a file-scoped offset.
There are additional ELDs, hint.eld and hintoth.eld. Both will enumerate listing hints for all currently loaded ELDs.
The first, hint.eld, will send a string containing all the hints to the AMIS message service of another debugger instance.
The second, hintoth.eld, will display the hints of an other link debugger to the terminal of the debugger instance that runs the hintoth.eld. This requires the other debugger instance to have installed its AMIS interface and loaded the amisoth.eld to provide the other link.
Houdinis are conditional breakpoints. They run an int3
instruction only if three conditions are met:
install houdini
’
ALL
if no nouns are specified.
IF [NOT] EXT extensionname THEN command
construct.
X?
or ?X
.
getaddrX
to get an 86 Mode segmented address, even in Protected Mode. This happens to work. The address is read if the DPB command is used with the LIST AT
keywords.
Plain numbers are evaluated as expressions. Refer to section 9. Expressions consist of any number of the following:
Plain number parsing for an expression continues for as long as a valid expression is continued. For example, in the command ‘D 100 + 20 L 10
’ the starting address (its offset to be specific) is calculated as ‘100 + 20’. Then the expression evaluator encounters the ‘L
’, which is not a valid binary operator. Plain number expression parameters are used by a lot of commands. Sometimes, the plain number parameter type is called ‘count’ or ‘value’.
An address parameter is calculated with a default segment. First, a plain number is parsed. If it is followed by a colon, the first number is taken as segment, and then another number is parsed for the offset. If the first number is specified as a pointer type using the type keyword ‘POINTER
’ then its upper 16 bits are taken as segment and its lower 16 bits are taken as the offset. Otherwise, the first number is used as the offset. Offsets may be 16 bits or 32 bits wide, though 32-bit offsets are only valid for DebugX and only in 32-bit segments.
If a segment or pointer type expression are prefixed by a dollar sign ‘$
’ then the specified segment is always taken as a Real/Virtual 86 Mode segment, even if DebugX is in Protected Mode. Otherwise, in Protected Mode a segmented address refers to a selector.
Instead of an address, the address parameter may consist of the taken keywords: TAKEN
or T
for taken, and NOTTAKEN
or NT
for not taken. This is only valid if the current cs:(e)ip
points at a conditional branch instruction, and will cause a parsing error otherwise. The taken keywords will evaluate to a segmented address pointing at the target of the conditional branch. The not taken keywords will evaluate to a segmented address pointing to behind the conditional branch instruction.
Address parameters are used by a lot of commands.
A range parameter may have a default length, or it may be disallowed to omit a length. Parsing a range starts with parsing an address. Then, if the end of the line is not yet reached, an end for the range may be specified. The end may be a plain number, which is taken as the offset of the last byte to include in the range. The address of the last byte to include must be equal or above the address of the first byte that is included in the range.
The end may instead be specified with an ‘L
’ or ‘LENGTH
’ keyword. In that case, the keyword is followed by a plain number and an optional item size keyword. A length of zero is not valid. The item size keyword may be ‘BYTES
’, ‘WORDS
’, ‘DWORDS
’, ‘QWORDS
’, ‘PARAS
’, ‘PARAGRAPHS
’, ‘PAGES
’, ‘KiB
’, ‘MiB
’, or ‘GiB
’. Except for the first, the plain number will be shifted as for the specified unit size (multiplying by 2, 4, 8, 16, 512, 1024, 1_048_576, or 1_073_741_824). It is an error if the shifting overflows the debugger's 32-bit arithmetic. The ‘BYTES
’ keyword is only provided for symmetry; currently all commands taking ranges default to byte size for the ‘LENGTH
’ number.
For example, the command ‘DD 100 LENGTH 4 DWORDS
’ will dump memory from address 0100h (in the current data segment) in dword units, for a length of 4*4 = 16 bytes. The item size keywords were introduced primarily for the ‘DW
’ and ‘DD
’ commands (refer to section 10.12), but they can be used for any command that accepts a range.
There is a new keyword called ‘END
’. This keyword may appear where the ‘L
’ or ‘LENGTH
’ keyword would be expected. After the ‘END
’ keyword the end offset is parsed. This is the same behaviour as if no keyword was present but crucially it allows an end offset starting with the text ‘L
’, which would otherwise be misparsed as an ‘L
’ keyword.
If the default length is used (the line ends after the start address) then a start address near the end of a segment (1_0000h or 1_0000_0000h) will shorten the length if it would otherwise overflow the segment.
Range parameters are used by a lot of commands.
LINES
keyword allowed #This type of parameter is an extension of the range parameter type. Both the default length and the explicit length may be specified as a number of lines instead of an address length.
An explicit ‘LINES
’ length is specified by prepending an ‘L
’ or ‘LENGTH
’ keyword (like an address length) but then specifying a unit as ‘LINES
’ instead. The number of lines specified must be nonzero and below 8000h.
The exact details of how a lines length is used depend on the command in question. A range with lines length is allowed for the U command (section 10.52) and the D/DB/DW/DD commands (section 10.12).
A list is made up of a sequence of items. Each item is either a plain number or a quoted string. List parsing continues until the end of the line. Each plain number represents a single byte. Quoted strings represent as many bytes as there are quoted. A quoted string can be delimited by single quotes '
or double quotes "
. If the used delimiter quote mark occurs twice back to back while reading the quoted string, this is taken as an escape to include the delimiter mark itself as a byte of the string. List parameters are used by the E, F, and S commands. Refer to section 10.18, section 10.20, and section 10.47.
A list may have its type changed with an AS
keyword, followed by a size keyword BYTES
, WORDS
, or DWORDS
. When a larger size is selected, each subsequent number expression and each byte of quoted text are written to a full word or a full dword instead of to a byte. Text bytes are zero-extended. Numbers can calculate to any value fitting the specified size. A list's type may be changed multiple times within the list.
A list or range can be specified for this parameter. The range is identified by a leading ‘RANGE
’ keyword. Otherwise, a list is parsed. A list or range parameter is as yet used by the S command and the F command, refer to section 10.47 and section 10.20.
A keyword is checked insensitive to capitalisation. Keywords depend on each command. Only the keywords used to specify a range's length are shared by all commands that parse ranges.
An index is a plain number that specifies a breakpoint index. It allows operating on one specific breakpoint. The index parameter type is used by the B commands, refer to section 10.7.
A segment is a plain number for parsing purposes. The segment parameter type is used by the DM command and some BOOT commands, refer to section 10.14 and section 18.11.
Each breakpoint is a single address, which defaults to the code segment. The address may instead be specified starting with an AT sign ‘@
’, followed by a blank or an opening parenthesis. In that case, the following plain number specifies the non-segmented linear address to use. The breakpoint parameter type is used by the B and G commands, refer to section 10.7 and section 10.21.
A label is a (not quoted) string keyword. A label can be used by the GOTO and Y commands, refer to section 10.22 and section 10.58. For the Y commands a label must start with a colon. For the GOTO command the colon may be specified but it is optional.
A port is a plain number for parsing purposes. The port parameter type is used by the I and O commands, refer to section 10.24 and section 10.32.
A drive may be either an alphabetic letter followed by a colon, or a plain number. The number zero corresponds to drive A: then. The drive parameter type is used by the L and W sector commands, refer to section 10.28 and section 10.56. The EXT, N, and Y commands (section 10.19, section 10.31, and section 10.58) also accept drive parameters, but only as part of their filenames. These must be in the drive letter followed by colon format.
A sector is a plain number, which can be equal to any 32-bit value. The sector parameter type is used by the L and W sector commands, refer to section 10.28 and section 10.56. Some BOOT commands also use sector numbers, refer to section 18.11.
A condition is a plain number. It is evaluated either to nonzero (true) or zero (false). The condition parameter type is used by the IF command, as well as the P, TP, and T commands when specified with a ‘WHILE
’ keyword. The BW and BP (with a ‘WHEN
’ keyword) commands also use conditions. Refer to section 10.25, section 10.33, section 10.49, section 10.7.3, section 10.7.1. The length of a condition for B commands is limited by how much space is left in the permanent breakpoint conditions buffer. This buffer currently defaults to 1024 bytes. It is shared for all conditions of all permanent breakpoints.
A register specifies an internal variable of the debugger. Most prominently these include the debuggee's registers as stored by the debugger in its data segment. A register or variable may be an operand in a plain number's expression. However, several forms of the R command also use register parameters. These allow reading and writing the register values. Refer to section 10.37.
Command is a special parameter type that is used only by the RE.APPEND, RE.REPLACE, RC.APPEND, and RC.REPLACE commands (section 10.37.2 and section 10.37.4). It is read verbatim and entered into the RE or RC command buffer. Semicolons within a command parameter are not parsed as end of line comment markers. Instead, they are converted to CR (13) codes in the buffer. This delimits the parts of the parameter into several commands. A semicolon may be prefixed by a backslash to escape it and thus enter a literal semicolon into the buffer.
ID is a special parameter type that is used only by the BP and BI commands (section 10.7.1 and section 10.7.2). Leading and trailing whitespace is ignored. An ID can be empty, or contain up to 63 bytes of data. The length of an ID is also limited by how much space is left in the permanent breakpoint ID buffer. This buffer currently defaults to 384 bytes. It is shared for all IDs of all permanent breakpoints.
This parameter type is used by EXT, N, K, and Y commands, as well as some BOOT commands. EXT and Y commands allow to use double quote marks. When using DOS, EXT and Y commands can access files using Long File Names (LFNs). When using DOS, all available commands parsing filenames may specify drive letters. EXT and Y commands when bootloaded, and some BOOT commands, may specify partitions at the beginning of filenames.
Command line tails are parsed by EXT, N, and K commands. They always are located behind a filename parameter. A command line tail may be empty. The N and K commands will store the command line tail for use by the L program-loading command. The EXT command passes its command line tail to the Extension for lDebug that it loads. The ELD may parse its command line tail in whatever way is desired, which may involve parsing other parameter types.
Literals consist of one or more digits. A literal must start with a digit or hash sign ‘#
’. Embedded underscores ‘_
’ are skipped. Literals must not overflow 4 giga binary minus 1, that is FFFF_FFFFh.
The default base for literals is sixteen (hexadecimal). A hash sign ‘#
’ indicates a base change. If nothing preceeds the hash sign the base is changed to ten (decimal). Otherwise, the number before the hash sign is read in the prior base and taken as the base to change to. The base must be between 2 and 36, inclusive. Multiple hash signs are allowed in the same literal.
String literals consist of up to 4 bytes. The bytes are specified starting with a hash sign ‘#
’ followed by a single-quote mark '
or double-quote mark "
. The same quote mark is used to end the string literal. If the delimiter quote mark occurs twice back to back while reading the string literal, that is handled as an escape to include the delimiter mark itself as a byte. Strings are read in a little-endian order, same as NASM does. That is, the first byte of a multi-byte string is read into the lowest byte of the numeric value. This matches the order obtained by writing the string to memory and reading it as a word, 3byte, or dword.
A variable consists of a variable name, possibly followed by parentheses with an index expression. Variable names are capitalisation insensitive. Variables differ in size, there are variables consisting of 8, 16, 24, or 32 bits. Variables can be written to using the R command. Some variables are read-only. A few variables allow writing some but not all bits.
Indirection is indicated by square brackets. Within the brackets an address is parsed, defaulting to ds
as the segment. The size of the indirect access can be specified with a type specifier before the brackets. The usual types are BYTE
, WORD
, 3BYTE
, and DWORD
. Like variables, indirection terms can be written to using the R command.
Parentheses can be used to force a different order of operations.
LINEAR
keyword #
A keyword reading LINEAR
introduces an address to parse. The address defaults to ds
as the segment. The address may be separated from subsequent text with a comma. If the expression is to be separated from a subsequent element using a comma after a LINEAR
address then two commas are needed. Depending on the segmentation scheme of the current mode the segmented address is converted into a linear address. If DebugX is in Protected Mode and the segment base cannot be determined the expression is rejected as an error.
DESCTYPE
keyword #This keyword introduces a descriptor type read. The following expression is taken to be a selector specification. This keyword is only valid for (DPMI-enabled) lDebugX builds, and only while in Protected Mode.
The value is read from a ‘lar
’ instruction on the following expression, and shifted to the right by 8. If the instruction indicates that the selector does not refer to a valid descriptor then the result of this keyword is zero.
VALUE IN
construct #
A keyword reading VALUE
starts a VALUE IN
construct. Between the VALUE
and subsequent IN
keyword there is a single value expression, or a range of the form FROM expression TO expression
or FROM expression LENGTH expression
. Next follows the IN
keyword. After this, there is a list of match ranges. A match range is either a single value expression, or a range of the form FROM expression TO expression
or FROM expression LENGTH expression
. After each match range a comma indicates another match range follows.
In a FROM TO
specification the first expression has to evaluate to unsigned below-or-equal the second expression. In a FROM LENGTH
specification the length must be nonzero. If these conditions are not met then the value or match range in question is always considered as not matching.
The entire VALUE IN
construct evaluates to how many of the match ranges match the value range. The construct only evaluates to zero if no matches occurred. A nonzero value indicates that at least one match occurred.
VALUE IN
construct keywords #
Instead of a value or match range as specified here, the keyword EXECUTING
may be specified. This expands to the following input:
FROM LINEAR cs:cip LENGTH abo - cip
If the _MEMREF_AMOUNT
build option is enabled and paired with the direction
and stackhinting
switches to mktables then additional keywords are available for VALUE IN
match ranges. That is, these keywords must be specified behind the IN
and cannot be specified between the VALUE
and IN
.
These keywords are as follows:
READING
FROM readadr0 LENGTH readlen0
constructs, for every read access variable pair (refer to section 12.19).
WRITING
FROM writadr0 LENGTH writlen0
constructs, for every write access variable pair (refer to section 12.19).
ACCESSING
READING, WRITING, EXECUTING
.
?? ::
construct #The ternary conditional operator takes three operands. It is the only ternary operator.
The first operand, the condition, is specified before the ??
keyword. Note that the ??
keyword must be terminated by a blank or an opening square bracket or round parenthesis.
The second operand is specified between the ??
keyword and the ::
keyword. Its value is used as the construct's return value if the condition is true.
The third operand is specified after the ::
keyword. Its value is used as the construct's return value if the condition is false.
The conditional operator can be nested freely. The conditional operator must not be combined into the R command's assignment operator as in ??:=
. The third operand may be separated from subsequent text with a comma. If the expression is to be separated from a subsequent element using a comma after a conditional's third operand then two commas are needed.
Any side effects that may happen from parsing and reading the second operand or the third operand will always happen, even if the operand in question is not selected as the result by the construct.
Some uses of the expression evaluator may have side effects. These side effects may happen even if the parsing of an expression or a command ultimately fails. As a special case, side effects may occur up to twice if a machine mode command (section 10.30) is parsed.
The ternary ?? ::
operator and the VALUE IN
construct will both always evaluate every operand that they're given, even if that operand is not selected as the result or does not contribute to the match count.
Possible side effects include:
LINEAR
construct includes a dollar sign prefixed segment or pointer type expression, then lDebugX may request a selector from the DPMI host.
Entering an empty command at an interactive prompt results in autorepeat. Empty means no content except for blanks. A line starting with a semicolon comment is not considered empty. Interactive prompts for this purpose include:
int 21h
)
int 16h
/int 10h
)
Input that does not count as an interactive prompt includes:
int 21h
)
int 21h
)
int 13h
)
Autorepeat is not supported by all commands. The following commands support autorepeat:
CS:(E)IP
at the start of the command's execution, it is skipped once.
Online help ?
The question mark command (?) lists the main online help screen.
There are additional help topics that can be listed by using the question mark command with an additional letter or keyword. These keywords are as follows:
Registers ?R
Flags ?F
Conditionals ?C
Expressions ?E
Variables ?V
R Extended ?RE
Run keywords ?RUN
Options pages ?OPTIONS
Options ?O
Boot loading ?BOOT
lDebug build ?BUILD
lDebug build ?B
lDebug sources ?SOURCE
lDebug license ?L
The full help pages are listed in section 18.
A leading colon indicates a destination label for GOTO, see section 10.22.
A dot command can be used to invoke the immediate assembler. This is only available if the _IMMASM build option was enabled. Following the dot, an instruction is parsed which is assembled. If successful, then the assembled instruction is immediately run.
Branches and instructions involving CS as a prefix or operand are handled specifically to do something equivalent to the assembled instruction, by a combination of special detection and modification or as-if handling. This includes jmp far/near/short, jcc, loop, call far/near, retf/retn/iret, mov to ds, mov from ds or cs, push cs, push ds, pop ds, lds, and all memory operands with cs prefix. (The instructions involving ds are handled specifically because a cs prefix is replaced by a ds prefix and ds is temporarily replaced by the cs value then.)
Interrupt calls are always proceeded past, even if TM is set. The int instruction is run from a buffer internal to the debugger. Interrrupts which depend on the CS or (E)IP they're called from may not work as expected. Calls are usually traced, but can be proceeded past by including a comma after the dot command.
Warning: It is generally not safe to modify SS or SP to relocate the stack with the immediate assembler. This will fail because the immediate assembler traces most of the instructions assembled with it, which uses the stack both before and after running the instruction. LSS SP or LSS ESP are okay if the stack is valid immediately after the instruction is traced. To relocate the stack otherwise you may use the R command to modify the SS and (E)SP debugger variables. This does not trace anything in between modifying the two variables.
assemble A [address]
Starts assembly at the indicated address (which defaults to CS segment), or if no address is specified, at the "a_addr" (AAS:AAO variables).
Assembly mode has its own prompt. Entering a single dot (.
) or an empty line terminates assembly mode. Comments can be given with a prefixed semicolon. In assembly mode, whereever an immediate number occurs an expression can be given surrounded by parentheses (
and )
. In such expressions, register names like AX are evaluated to the values held by the registers at assembly time. To refer to a register as an assembly operand, it must occur outside parentheses.
attach process ATTACH psp
While in resident mode, the ATTACH command can be used to attach the debugger to a process. This is the opposite operation to the TSR command (see section 10.51). The device driver mode starts out as detached while the application mode starts out as attached.
The provided parameter must be a segment value, even if lDebugX is in Protected Mode. It refers to the PSP to attach to. This PSP must not be self-owned.
To attach to a process, the debugger stores away the current PRA and parent of the process, and modifies both to point to the debugger instead.
When attached to a process, commands like QA and the process-loading L command can be used sensibly. The Q command will also try to terminate the attached process.
When detached, the Q command will continue to run the current debuggee context after the debugger has been uninstalled.
The usual parameter to the attach command consists of the ‘PSP
’ variable, which will have the command try to attach to the current debuggee process. Other choices like ATTACH PARENT
are also valid.
There are a fixed number of permanent breakpoints provided by the debugger. The default is to provide 16 permanent breakpoints. They are specified by indices ranging from 00 to 0F. A breakpoint can be unused, used while enabled, or used while disabled. A breakpoint that is in use has a specific linear address. It is allowed, though not advised, for several breakpoints to be set to the same address.
When running the debuggee with the commands G, T, TP, or P, hitting a permanent breakpoint stops execution, and indicates in a message "Hit permanent breakpoint XX" where XX is replaced by the hexadecimal byte index of the breakpoint. If the breakpoint counter is not equal to 8000h when the breakpoint is hit, then the "Hit" message is followed by a "counter=YYYY" indicator. If the breakpoint ID is not empty, then the ID is shown with an "ID: " prefix. The ID is shown either on the same line as the "Hit" message, or on the next line if the ID exceeds 28 bytes. After that message a register dump occurs, same as for default breaking for the Run commands.
The exceptions are as follows:
Each breakpoint has a breakpoint counter, which defaults to 8000h if not set explicitly by the BP or BN commands. The breakpoint counter behaves as follows:
Example counter values:
The point being passed means that during running the debuggee with a Run command, execution is not stopped, but a message indicating "Passed permanent breakpoint XX, counter=YYYY" is displayed. As for the "Hit" message the ID, if any, is also shown. After that message, a register dump occurs. Then execution is continued in accordance with the command that is running debuggee code.
Each breakpoint can have a breakpoint condition. If the condition expression evaluates to false when the point breaks, then the point is not considered hit or passed. The breakpoint counter is not stepped then either.
set breakpoint BP index|AT|NEW address
[[NUMBER=]number] [WHEN=cond] [ID=id]
BP initialises the breakpoint with the given index. It must be a yet unused breakpoint. If the index is specified as the keyword NEW, the lowest unused breakpoint (if any) is selected. If there is the keyword AT instead of an index or a keyword NEW, then an existing breakpoint at the same linear address, if any, is reset (unlike the NEW keyword), or a new one is added (same as if given the NEW keyword).
The address can be given in a segmented format, which defaults to CS, and which in DebugX is subject to either PM or 86M segmentation semantics depending on which mode the debugger is in. The address can also be given with an @ specifier (followed by an opening parenthesis or whitespace) in which case it is specified as the 32-bit linear address. Debug without DPMI support limits breakpoints to 24-bit addresses, of which 21 bits are usable.
The optional number, which defaults to 8000h, sets the breakpoint counter to that number.
The optional WHEN keyword introduces a breakpoint condition. If the breakpoint is reached then the condition, if specified, is checked before stepping the counters. If the condition is false at that point the point is not considered hit or passed and its counter is not stepped.
There is an optional OFFSET keyword (not shown in the example) which allows overriding the breakpoint's preferred offset. Refer to section 10.7.4 for details.
The optional ID keyword allows setting the breakpoint ID. The ID is displayed by BL and when a breakpoint is hit or passed. The default ID is an empty ID. Note that the ID extends for the remainder of the line. There cannot be a breakpoint counter number nor WHEN condition nor OFFSET after the ID keyword.
set ID BI index|AT address [ID=]id
BI sets the breakpoint ID of the specified breakpoint. The ID is displayed by BL and when a breakpoint is hit or passed. The ID may be specified as empty.
set condition BW index|AT address [WHEN=]cond
The BW command sets the breakpoint condition. If the WHEN keyword and the condition are absent then the condition is reset. That means the point is no longer conditional.
set offset BO index|AT address [OFFSET=]number
The BO command sets the breakpoint preferred offset. The preferred offset is used only by the BL command. It is used to determine the segmented address to display. The offset is a word variable for Debug and a dword variable for DebugX. If the OFFSET keyword and the number are absent then the offset is disabled, as if the breakpoint was specified with a linear address. (Internally this is done by setting the offset to all 1 bits. The offset can be explicitly set to FFFFh (Debug) or FFFF_FFFFh (DebugX) for the same effect.)
set number BN index|AT address|ALL number
BN sets the breakpoint counter of the specified breakpoint with the given index, or all used breakpoints when given the keyword ALL, or the first breakpoint with a matching linear address when given the AT keyword. The number defaults to 8000h.
clear BC index|AT address|ALL
BC clears the specified breakpoint with the given index, or all breakpoints when given the keyword ALL, or the first breakpoint with a matching linear address when given the AT keyword. This returns the specified breakpoint (or all of them) to the unused state. Any associated ID or condition is deleted by BC too.
disable BD index|AT address|ALL
Given an index or the keyword ALL or the keyword AT (like BC), BD disables breakpoints that are in use. A disabled breakpoint's address is retained and BP will not allow initialising it anew (except with AT), but it is otherwise skipped in breakpoint handling.
enable BE index|AT address|ALL
Like BD, but enables breakpoints.
toggle BT index|AT address|ALL
Like BE and BD, but toggles breakpoints: A disabled breakpoint is enabled, while an enabled breakpoint is disabled.
swap BS index1 index2
This command is provided to allow re-ordering existing breakpoints. It takes two indices both of which must refer to valid breakpoints. However, it is allowed to specify the index of an unused breakpoint for either of the parameters (or even both). All data associated with the two breakpoints is swapped.
list BL [index|AT address|ALL]
BL lists a specific breakpoint given by its index, or all used breakpoints if given the keyword ALL or given neither an index nor the keyword. When given the AT keyword, all breakpoints with a matching linear address are listed. (This differs from all other B commands, which only select the first matching breakpoint when the AT keyword is given.)
When listing all breakpoints only used breakpoints are displayed.
The output format for unused breakpoints is as follows:
The output format for used breakpoints is as follows:
Example output of BL:
-bp at 100 id = start
-bp at 103 counter = 4000
-bp at 105 when al == 7
-bl
BP 00 + Lin=01_BB70 1BA7:0100 (CC) Counter=8000, ID: start
BP 01 + Lin=01_BB73 1BA7:0103 (CC) Counter=4000
BP 02 + Lin=01_BB75 1BA7:0105 (CC) Counter=8000
WHEN al == 7
-
break upwards BU
This command, which is only supported by Debuggable builds (DDebug) or Conditionally Debuggable builds (CDebug), causes the debugger to execute an int3 instruction in its own code segment (lDEBUG_CODE). This breaks to the next debugger that was installed prior to DDebug or CDebug. Prior to the breakpoint, the message "Breaking to next instance." is displayed. The breakpoint is in the cmd4
dispatcher. When the next instance is another lDebug then running the command ‘G ip
’ in it can be used to run the debuggable debugger until the next command is dispatched. (This does not work if an Extension for lDebug command handler processes a command instead of passing it on to the debugger.)
In non-debuggable lDebug builds, the following error message is displayed instead:
-bu
Already in topmost instance. (This is no debugging build of lDebug.)
-
In conditionally debuggable builds, the following message is displayed instead if CDebug is currently not in debuggable mode:
-bu
Debuggable mode is disabled.
Enable with this command: r DCO6 or= 0100
-
The BOOT commands are only available if the debugger is running in boot loaded mode.
BOOT PROTOCOL=proto [parameters] [partition] [pathnames [cmdline]]
This command is used to load a boot sector or kernel using the loaders implemented by the debugger. These loaders attempt to be highly compatible to the original loaders whose load protocols they simulate.
Using the keyword PROTOCOL
, the load protocol to use as a base can be specified. This keyword is required, unless the special protocol named SECTOR
is to be used.
When specifying a protocol other than the special SECTOR
protocol, the protocol parameters can be altered. Each protocol will set up defaults for all of those parameters. Each protocol can be completely described by a combination of parameters and default filenames. Every parameter is indicated by a keyword followed by a numeric expression, or in some cases followed by a segmented address.
The following parameters are available:
The following boolean parameters are available. Like the other parameters they read a numeric expression, but this is only checked to be true (non-zero) or false (zero).
dword [ss:bp - 4]
to include the number of hidden sectors. (The hidden sectors are the partition's start offset in its unit.) This is used by the MS-DOS (v6/v7) and IBMDOS protocols.
After the parameters, the debugger will try to parse a partition specification. Partition specifications are capitalisation-insensitive. A partition may be specified in the following ways:
If no partition can be parsed, SDP is assumed. Note that partition numbers are parsed as decimal numbers, except if the partition number is specified as an expression with parentheses, in which case the default expression base is used (hexadecimal).
One or two pathnames may be specified to load, after the parameters or the partition specification. Both will have a default specified by the protocol. The default for the second name may be empty. If the second name is empty, no additional file is searched for. If two names are specified, they must be separated by one or more blanks.
Each pathname may include subdirectory names, indicated by trailing slashes. If a pathname ends in a slash, the default filename is searched in the directory indicated by the pathname. The second pathname may be specified as two slashes to indicate no second file. If the second name defaults to empty, a lone dot may be used to indicate no second file. If the second name defaults to not empty, a lone dot may be used to indicate searching the default name in the same directory as the first file. If either pathname is specified as only one slash, then the corresponding default name is searched for in the root directory.
If the second name is not specified at all, or is specified as a lone dot, the default additional filename is searched for in the same directory as the first file. If the second name is specified but does not start with a slash, then it is assumed to be a pathname relative to the directory of the first file. Otherwise, if the second name is specified and starts with a slash, the second name is searched in the directory of the specified pathname which is interpreted as being relative to the root directory.
The 32-byte directory entry of the first file is loaded to 00500h. The 32-byte directory entry of the second file is loaded to 00520h. These entries are used by the MS-DOS v6 and IBMDOS protocols. If no second file is searched for, the 32 bytes at 00520h are filled with zeroes.
The blank-padded FCB filenames of the two files are stored within the pseudo boot sector with (E)BPB that the loader sets up for the kernel. This supports kernels scanning the boot sector for informational filenames.
If the CMDLINE boolean parameter is enabled, then after the two pathnames specifying filenames a command line is parsed. The command line should be separated from the second filename specification with one or more blanks. This command line is passed to the loaded kernel as specified for the lDOS load protocol. (The FreeDOS kernel was extended to also use a command line passed this way.)
If the CMDLINE parameter is enabled but no command line content is specified, then an empty command line is passed. Note that this differs from passing no command line. To pass no command line, the CMDLINE parameter must be disabled.
As a special case, semicolons are allowed within the specified command line and do not indicate comments.
The following notes are from lDOS boot.asm, in a comment titled ‘Notes about partial load compatibilities’:
This is true of the lDebug BOOT command as well. However, the original FreeDOS loader relocation can be emulated by specifying a BPB=1FE0:7C00
parameter.
This is not a problem to the lDebug BOOT command.
This is not a problem to the lDebug BOOT command. The full 29 KiB can be loaded regardless of sector size and cluster size.
This is not a problem to the lDebug BOOT command. In fact the DRDOS protocol in lDebug exactly matches the IBMDOS protocol except that it defaults to a MAXPARA=-1
parameter. Therefore a small enough IBMBIO.COM file can be loaded with the DRDOS protocol even if it expects any of the bits and bobs provided by the IBMDOS protocol.
Applicable to lDebug as well. The table can be relocated and modified by the user however, if need be. This should be done after running a BOOT PROTOCOL command so as to pass the original DPT address to the kernel.
This is not a problem to the lDebug BOOT command.
Applicable to lDebug as well. The table can be relocated and modified by the user however, if need be. This should be done after running a BOOT PROTOCOL command so as to pass the original DPT address to the kernel.
The message table is provided by the lDebug BOOT command if enabled using the MESSAGE_TABLE parameter. This is default enabled for the MSDOS7 protocol.
The original FreeDOS loaders may misbehave trying to load a file that is larger than 128 KiB, when rounded up to a full cluster boundary. The lDebug loader is impacted by this problem less.
The DR-DOS and the original Enhanced DR-DOS load (prior to lDOS single-file EDR-DOS load in 2024) are similar. They depend on the BIO file to be fully loaded. The BIO file is limited to 29 KiB for DR-DOS load. (For original EDR-DOS load the BIO file has limits similar to the kernel file for FreeDOS load.)
These loads do not use any directory entries nor file start clusters passed anywhere. Consequently, they have to scan the root directory for their DOS files, named IBMDOS.COM
or DRDOS.SYS
. That means these files and their filenames cannot be overridden by the debugger.
DR-DOS load presumably uses only the load unit passed in the DL register to find the file system that it wants to load its DOS file off. Original EDR-DOS load also used the hidden sectors in the BPB pointed to by DS:BP to identify the correct file system.
Although the initial loader originally introduced for MS-DOS v4.00 is called the "Non-Contiguous IBMBIO Loader (MSLOAD)" it does still depend on the first cluster of the BIO file being cluster 2, the first data cluster of the file system. This was fixed as of MS-DOS v5.00 however.
Original loaders expect the BIO file as the first entry in the root directory and the DOS file as the second entry. They also arrange for these entries to be loaded to linear 00500h and 00520h. The debugger will allow any file in the file system to be used and will load the directory entries to the expected locations, which may enable booting differently named or located files.
There is a problem with the DOS file loader: It may select a drive to load from using only the load unit, but read the start cluster from the DOS file directory entry and use the data start sector passed from the prior loader.
BOOT LIST [unit|partition]
This command is used to list partitions on an MBR-style partitioned unit.
BOOT DIR [partition] [pathname]
This command is used to list files within a directory of a FAT12, FAT16, or FAT32 file system. A partition specification may be included. A pathname may be included; if it refers to a directory then the contents are listed, otherwise the specified file is listed.
BOOT READ|WRITE [unit|partition] segment [[HIDDEN=sector] sector [count]]
These commands are used to read or write sectors from disks. After the command keyword, a partition specification may be listed. Then, a segment must follow. This specifies the buffer to use.
After the segment, an optional HIDDEN= keyword can be specified to specify a 32-bit sector number base. This is useful to implement 33-bit LBA access, but note it will overwrite the partition offset if a partition is specified. Instead of the HIDDEN= keyword, a HIDDENADD= keyword can be specified. It also reads a 32-bit sector number base. However, as opposed to HIDDEN=, HIDDENADD= will add to the hidden value of a specified partition, instead of replacing it. If a whole unit without a partition is specified then HIDDEN= and HIDDENADD= will result in the same offset being used.
After the segment and optional hidden keywords, a 32-bit sector number may be specified. It defaults to zero. After the sector number, a 16-bit count may be specified. It defaults to one.
Note that sectors are read or written one sector at a time. If the interrupt 13h function used returns an error 9 (boundary error), the debugger will attempt to use its auxiliary buffer to carry out the read or write, copying data as appropriate. The auxiliary buffer is aligned so as not to cross a 64 KiB boundary in memory. (Error 9 is usually returned if trying to access too many sectors at once or when diskette ISA DMA would cross a 64 KiB boundary.)
BOOT QUIT
This attempts to shut down the machine. A dosemu-specific callout will be attempted first, if dosemu is detected. Using APM will be attempted next, which works on qemu. If neither works, the debugger gives up.
compare C range address
Given a range, the address of which defaults to DS, and another address that also defaults to DS, this command compares strings of bytes, and lists the bytes that differ.
count length COUNT [RANGE range|list]
This command parses a range or list parameter. The resulting pattern's length is displayed in hexadecimal and decimal. The COUNT
variable is also set to the length count number. If a range is given then the length has to fit into the segment.
dump D [range]
dump bytes DB [range]
dump words DW [range]
dump dwords DD [range]
Given a range, the address of which defaults to DS, this command dumps memory in hexadecimal and as ASCII characters. The range may be specified with a lines length (refer to section 8.4). The default length if none is specified defaults to the number of lines specified in the variable DEFAULTDLINES
if it is nonzero, or else the number of bytes specified in the variable DEFAULTDLEN
.
If a lines length is used, that many lines are dumped. The count of lines does not include the header or trailer if they're used. The count of lines will be inaccurate if the symbolic build is used and the dump lists symbols that point into the dumped data. (The amount of data in this case will match what it should be to produce the requested count of lines if no symbols were listed.) The count of lines will be accurate if either the 40-column friendly mode is enabled or not. If enabled, roughly half as much data is dumped for a given amount of lines, as the 40-column mode dumps up to 8 bytes per line as opposed to the 80-column mode which dumps up to 16 bytes per line.
If the DCO option 4 is set, text with the high bit set (80h to FFh, the top half of the 8-bit encoding space) is displayed as-is in the text dump. If a TOP
keyword is used before the range, then top half text is displayed as-is as well. Otherwise, it will be treated like nonprintable text, which means it is replaced by dots in the text dump.
The variable DDTEXTAND
is used as a mask to modify the text code before display. It defaults to 0FFh. Setting this to 7Fh will mimic MS-DOS Debug's display of text in its data dump, masking off the high bit. In this case the TOP
setting has no effect.
If no range is specified, the D command continues dumping at "d_addr" (ADS:ADO), which is updated by each D command to point after the last shown byte. The default length is determined in the same way as for if a range without a length is specified. If autorepeat is used it behaves the same way as a D command without a range.
The default is for D to dump bytes. After a DW or DD command, the autorepeat and plain D (without a range) default to the last-used size. If the default range should be used but the size should be reset to bytes, the DB command can be used. The D command with a range always acts the same as DB.
dump interrupts DI[R][M][L] interrupt [count]
The DI command dumps interrupt vectors from the IVT (86M) or IDT (PM). In PM, for the vectors 00h to 1Fh, the exception handlers are also dumped. In 86 Mode, an interrupt chain is displayed if more than one entrypoint is reachable from the topmost handler. To make the next handler reachable, a handler must match one of several header / entry formats:
If the R is specified (directly after DI) then 86 Mode handlers are dumped even if in PM.
If the M is specified then MCB names are displayed.
If the L is specified then AMIS interrupt lists are queried for the interrupt number being dumped. This is so that the involved multiplex numbers and interrupt list indices can be displayed, and also so that hidden chains can be dumped. This means chains that are not reachable from the topmost IVT handler, but are found through the AMIS "Determine Chained Interrupts" call (either 03h pointer or 04h list return). The list index is displayed as FFFFh if the handler was found with 03h pointer return. Otherwise it indicates how many list entries precede the found handler's entry. For example, ‘list:0000h
’ means that the first list entry matched, and ‘list:0001h
’ means that the second list entry matched.
Specifying the L makes the debugger use its auxiliary buffer. That means the DIL command cannot be used from the RE buffer if the T/TP/P silent buffer is used, or if RH mode is enabled. In addition, note that with the default buffer size, no more than about a 1000 handlers can be handled. (The actual limit may be as low as 500 handlers if a lot of hidden chains occur.) If the limit is exceeded then the DIL command will display an error. The same error can also occur if the chain loops, or references a single handler from more than one other handler, or a single handler is listed by more than one multiplexer.
dump MCB chain DM [segment]
The DM command dumps an MCB chain. If not given a start MCB segment, and the debugger is running as an 86-DOS application or device driver, the start of DOS's MCB chain is used. If given a start MCB segment, this is used as the starting MCB. (Note: In current RxDOS builds, the start MCB is always at segment 60h.)
The DM command initially lists the debuggee's PSP. This is only valid when the debugger is running as an 86-DOS application or device driver.
The MCB chain dump is continued until an MCB is encountered that has neither an M nor a Z signature letter, or the MCB address wraps around the 1 MiB boundary. In particular, this means that a disabled UMB link MCB (usually pointing to the MCB at segment 9FFFh if there is no EBDA nor any pre-boot-loaded programs) will not end the dump.
Example output:
-dm
PSP: 2ACA
02B4 4D 0008 0018 384 B SD
02CD 4D 0000 0013 304 B
02E1 4D 02E2 00A8 2.6 KiB COMMAND
038A 4D 038B 00A8 2.6 KiB COMMAND
0433 4D 0434 2695 154 KiB LDEBUG
2AC9 5A 2ACA 7535 468 KiB DEBUGGEE
9FFF 4D 0008 2100 132 KiB SC
C100 4D 0008 0144 5.0 KiB SD
C245 4D 0000 0006 96 B
C24C 4D C24D 00A8 2.6 KiB COMMAND
C2F5 4D 0000 1D09 116 KiB
DFFF 4D 0008 1000 64.0 KiB SC
F000 4D F001 0019 400 B SEEKEXT
F01A 4D 0000 07F3 31.7 KiB
F80E 4D 0000 0090 2.2 KiB
F89F 4D 038B 001E 480 B COMMAND
F8BE 4D 02E2 0040 1024 B COMMAND
F8FF 5A C24D 0100 4.0 KiB COMMAND
-
The columns are as follows:
M
’) for linking MCB and 5A (‘Z
’) otherwise.
SC
/SD
/S
system MCB owner. Higher values are generally process segments. A process segment is usually a memory block that is preceded by an MCB, which is owned by that block itself.
B
(Bytes) or KiB
(Kilo binary Bytes).
display strings DZ/D$/D[W]# [address]
The D string commands each dump a string at a specified address, which defaults to DS as the segment.
$
.
These commands are only available in lDebugX (DPMI-enabled) builds. They can only be used in Protected Mode. RC is set to 800h when attempting to use any of these commands while not in Protected Mode.
Descriptor modification commands:
(only valid in Protected Mode)
Allocate D.A
Deallocate D.D selector
Set base D.B selector base
Set limit D.L selector limit
Set type D.T selector type
Allocate D.A
Allocates an LDT descriptor from the DPMI host. Sets the variable DARESULT to the selector if successful, else FFFFh. Sets RC to 801h or a DPMI error code (>= 8000h) on failure.
Deallocate D.D selector
Deallocates an LDT descriptor. Sets RC to 802h or a DPMI error code (>= 8000h) on failure.
Set base D.B selector base
Sets the base of an LDT descriptor. A useful shorthand is to use a construct like ‘LINEAR cs:0
’ to get the base of a descriptor referenced by another selector. Sets RC to 803h or a DPMI error code (>= 8000h) on failure.
Set limit D.L selector limit
Sets the limit of an LDT descriptor. Limits beyond FFFFFh must be 4 KiB aligned (low 12 bits set). Sets RC to 804h or a DPMI error code (>= 8000h) on failure.
Set type D.T selector type
Sets the type of an LDT descriptor. 00FAh is a 16-bit code segment, 4000h is the D/B bit (Default size / Big), so 40FAh is a 32-bit code segment. 00F2h is a 16-bit data segment. 8000h is the G bit (Granularity); modifying it may change the limit. The DESCTYPE keyword can be used in an expression to read the current type of a descriptor, refer to section 9.7. Sets RC to 805h or a DPMI error code (>= 8000h) on failure.
dump text table DT [T] [number]
Without a number parameter and without the T specifier, an ASCII table (codepoints 00h to 7Fh) is listed with short names for unprintable ASCII but the text itself for printable ASCII. The table lists the decimal and hexadecimal numbers. Without a number parameter but with the T specifier, a similar table depicting the top half of the 8-bit codepoint space is listed (values 80h to FFh).
With a parameter, each byte of the specified number is displayed in decimal, hexadecimal, and the short name or the text itself (quoted). If the T specifier is present, top half text (value >= 80h) is displayed quoted, otherwise it instead displays as ‘top
’. The command will loop starting with the least-significant byte of the number, then continue with subsequent bytes until all remaining bytes are equal to zero. All up to four bytes are listed on the same line.
Note that without the T specifier, no codepage dependent text is displayed. The T stands for ‘top
’.
enter E [address [list]]
The E command is used to enter values into memory. If the list is specified, its contents are written to the address specified. Otherwise, the interactive enter mode starts at the address specified. If no address is specified then interactive enter mode starts at the last used address. This is behind the last byte written by a prior E command, or at the last byte displayed in interactive enter mode.
In the interactive enter mode, the segmented address is displayed, and then the current byte value (2 hexadecimal digits) found at that address yet. Following the value a dot is displayed. For example:
-e 100
1FFE:0100 C3.
At this point the debugger accepts several different inputs:
After entering a blank, the debugger will either display the next byte's current value in the same line or start a new line with the current segmented address and then the current byte value. A new line is started if the current offset is divisible by 8. For example, after entering 8 blanks:
-e 100
1FFE:0100 C3. CC. CC. CC. CC. CC. CC. CC.
1FFE:0108 CC.
After entering a minus, the minus is displayed on the current line and then (always) a new line is started to display the new segmented address (with its offset decremented). For example, entering a new value (‘A0
’), then a blank, then a minus, and then another new value (‘A1
’), then a CR:
-e 100
1FFE:0100 C3.A0 CC.-
1FFE:0100 A0.A1
-
run extension EXT [partition/][extensionfile] [parameters]
The EXT command is used to run an Extension for lDebug (ELD) file.
This command and the infrastructure needed for it, including two buffers of usually several dozen KiB together, are only included when the debugger is built with the _EXTENSIONS option enabled. (This is now the default.)
The ELD must be in a special executable format defined by the debugger. The current ELD format starts with the magic bytes ‘ELD1
’ at offset zero in the file. An ELD file typically has a filename extension .ELD
or .XLD
though this is not required. (If the extname.eld
is installed it will check for either of the two known extensions, and if none is specified it will guess the same two extensions in order.)
To load ELDs in the application mode or device mode debugger, the DOS file system is used. That means DOS must be available for loading ELDs then. To load ELDs in the boot loaded mode debugger, the auxiliary buffer must be available and int 13h is used to access a FAT file system.
Like the Y command (refer to section 10.58.1), the EXT command pathname can be specified with one of the debugger configuration path keywords. Also, if an EXT command doesn't find a file specified without a configuration path keyword, the debugger will retry the file open with the ::scripts::
path prepended.
Unlike Script for lDebug files opened with the Y command, Extensions for lDebug may be run with parameters specified after the ELD filename. The ELD receives the parameters as free form string data which it is free to interpret as it wishes. A semicolon may or may not be interpreted as a comment indicator by an ELD. The ELD can expect that within 256 bytes a Carriage Return occurs.
Some ELDs can install themselves as resident Extensions to the debugger, hooking into the command dispatch to run their own command rather than the debugger's default. All resident ELDs provide a command hook, at least for their respective uninstall commands. Some resident ELDs may hook into other parts of the debugger as well.
The ELDs are described in detail in section 15. The following ELDs are provided currently:
INSTALL
parameter to provide the LDMEM
command, and uninstalled with an LDMEM UNINSTALL
command.
INSTALL
parameter to provide the HISTORY
command, and uninstalled with a HISTORY UNINSTALL
command.
_INT=0
build option. Can be installed residently with INSTALL
parameter to provide the DI
command, and uninstalled with a DI UNINSTALL
command.
_MCB=0
build option. Can be installed residently with INSTALL
parameter to provide the DM
command, and uninstalled with a DM UNINSTALL
command.
_RN=0
build option (as is the default now). Can be installed residently with INSTALL
parameter to provide the RN
command, and uninstalled with an RN UNINSTALL
command.
_RM=0
build option (as is the default now). Can be installed residently with INSTALL
parameter to provide the RM
command, and uninstalled with an RM UNINSTALL
command.
_EMS=0
build option (as is the default now). Can be installed residently with INSTALL
parameter to provide the X
commands, and uninstalled with an X UNINSTALL
command.
_DX=0
build option (which is the default as of release 9). Can be installed residently with INSTALL
parameter to provide the DX
command, and uninstalled with an DX UNINSTALL
command. This ELD requires a 386+ machine.
INSTALL
and UNINSTALL
commands. Can be installed residently with INSTALL
parameter to provide the INSTNOUN
command, and uninstalled with an INSTNOUN UNINSTALL
command.
cmd3
command input loop using a command injection handler, or when the command ‘AMISMSG DISPLAY
’ is run.
ELDAMOUNT
variable. This variable can be read to obtain the amount of installed ELDs.
BASE=
specifier or the name of a base (HEXADECIMAL
, DECIMAL
, BINARY
, OCTAL
) the input number may be specified as a literal, which accepts unsigned numbers of up to 64 bits. Output is in the four known bases, formatted to resemble the expression evaluator's literal input format. Output can be in one arbitrary base using a trailing OUTPUT
keyword, followed by BASE=
, GROUP=
, and WIDTH=
specifiers. The BASES ELD can be installed residently using an INSTALL
parameter to provide the resident BASES
command.
COPYOUTPUT
commands. Once a file is specified with ‘COPYOUTPUT NAME filename
’ the debugger opens the file to append to it. While not InDOS all output to the debugger terminal is written to the opened file.
IF [NOT] EXT "extension name" THEN command
. May be used as a transient ELD or installed residently using an INSTALL
command to provide the IF EXT
commands.
VERBOSE
to display technical details, HELP
to display the help, and SFN
to force use of the DOS SFN find interface instead of trying the DOS LFN find interface.
SET
command using an INSTALL
parameter, or run transiently using a RUN
parameter.
%VARIABLE%
’ specifications in commands.
WITH
. This can be used as WITH HEADER
or WITH TRAILER
to temporarily set DCO flags to enable D command headers or trailers. Command injection is used to reset the flags afterwards.
fill F range [RANGE range|list]
The F command fills memory with a byte pattern. The first parameter is the range to fill. The next parameter can be a list, in which case it provides the pattern with which to fill. If the RANGE keyword is provided then the pattern is read from memory as indicated by the range parameter that follows the keyword. The pattern is repeated so as to fill the destination. If the RANGE keyword is used, then the length of the pattern address range is optional. If the length is absent, it is assumed to equal that of the destination range.
go G [=address] [breakpts]
The G command runs the debuggee. It can be given a start address (the segment of which defaults to CS), prefixed by an equals sign, in which case CS:EIP is set to that start address upon running. Note that if there is an error parsing the command line, CS:EIP is not changed. Further, if a breakpoint fails to be written initially, CS:EIP also is not changed.
The G command allows specifying breakpoints, which are either segmented addresses (86M or PM addresses depending on DebugX's mode) or linear addresses prefixed by an "@ " or "@(", similar to how the BP command allows a breakpoint specification. G breakpoints are identified by their position in the command line, as the 1st, 2nd, 3rd, etc. The build option _NUM_G_BP
specifies how many G breakpoints are supported. By default, 16 G breakpoints are supported.
The G AGAIN command re-uses the breakpoints given to the last (successfully parsed) G command. It also allows an equals-sign-prefixed start address like the plain G command, in front of the AGAIN keyword. After the AGAIN keyword, additional breakpoints may be specified.
If the command repetition of G is used, it is handled as if "G AGAIN" was entered, that is it re-uses the same breakpoints as those given to the prior G command.
A G command that fails to parse will not modify the stored G breakpoint list. If an error occurs during writing breakpoints, the list will have been modified already however.
The G LIST command lists the breakpoints given to the last (successfully parsed) G command.
The "content" byte in G LIST is usually CCh (the int3 instruction opcode), but retains its original value if a failure occurs during breakpoint byte restoration.
Example output of G LIST:
-g 100 103 105
AX=3000 BX=0000 CX=0200 DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000
DS=1BA7 ES=1BA7 SS=1BA7 CS=1BA7 IP=0103 NV UP EI PL ZR NA PE NC
1BA7:0103 CD21 int 21
-g list
1st G breakpoint, linear 0001_BB70 1BA7:0100, content CC
2nd G breakpoint, linear 0001_BB73 1BA7:0103, content CC (is at CS:IP)
3rd G breakpoint, linear 0001_BB75 1BA7:0105, content CC
-
The output is as follows:
CS:
’ if the code segment's base matches the preferred offset. Otherwise, an R86M segment is shown with a dollar sign ‘$
’ prefix if the preferred offset matches any R86M segment. Failing that the offset is shown with a prefix reading ‘????:
’.
There is another G command: After any equals sign, AGAIN keyword, and/or specified breakpoints, the line can be ended with a REMEMBER keyword. This saves the specified G breakpoint list and then returns control to the user. (The equals address, if any, is discarded.) It allows preparing a G breakpoint list ahead of its use. Auto-repeat, if enabled, will run like G AGAIN and actually run the debuggee after a G REMEMBER command.
goto GOTO :label
The GOTO command can only be used when executing from a script file, the command line buffer, or the RE buffer. It lets execution continue at a different point in the file or buffer. Labels are identified by lines that start with a colon, followed by the alphanumeric label name, and optionally followed by a trailing colon. The destination label of the GOTO command may be specified with or without the leading colon.
There are several special cases:
hex add/sub H value1 [value2 [...]]
base display H BASE=number [GROUP=number] [WIDTH=number] value
The H command performs calculation and displays the result. If a single expression is given then its value is displayed, in hexadecimal and then in decimal. If more than one expression is given then two results are displayed, in hexadecimal only. The first result is that which is calculated by adding all expressions. The second result is calculated by subtracting all subsequent expressions from the first expression's value.
If a value is above or equal to 8000_0000h then along each display of that value, the value interpreted as a negative two's complement number is listed in parentheses.
If the form with the BASE
keyword is given then only one number is displayed. The specified base may be between 2 and 36, inclusive. If the GROUP
keyword is also used then digits are grouped. The group separator is the underscore, ‘_
’. The grouping number must be below or equal 32 (20h). The default grouping is none, same as GROUP=0
. If the WIDTH
keyword is also used then at least that many digits are displayed. The width must be below or equal 32 (20h). The default width is one digit, same as WIDTH=0
or WIDTH=1
.
Examples:
-h 1
0001 decimal: 1
-h 1 1
0002 0000
-h 1 1 1
0003 FFFFFFFF (-0001)
-h 1 + 2 * 3
0007 decimal: 7
-h cs * 10
0001A730 decimal: 108336
-h -26
FFFFFFDA (-0026) decimal: 4294967258 (-38)
-h base=2 group=8 AA55
10101010_01010101
-h base=2 group=4 width=#16 #1234
0000_0100_1101_0010
-h base=#10 group=3 400*400
1_048_576
-h base=3 group=3 FFFF_FFFF
102_002_022_201_221_111_210
-
input I[W|D] port
The I commands input from an x86 port. The port can be any number between 0 and FFFFh. Plain I inputs a byte from the specified port. The IW and ID commands input a word or dword respectively.
if numeric IF [NOT] (cond) THEN cmd
if script file IF [NOT] EXISTS Y file [:label] THEN cmd
if variable IF [NOT] EXISTS R variablename THEN cmd
The IF command allows specifying a conditionally executed command. This is especially useful for creating conditional control flow branches with the GOTO command (see section 10.22).
For the first form, the condition is a numeric expression. If it evaluates to non-zero it is considered true. If the NOT keyword is absent then a true condition expression leads to executing the THEN command. With the NOT keyword present the logic is reversed. Note that if an error occurs in parsing, the THEN command is not executed, regardless of whether the NOT keyword is present.
The second form specifies a script file in the same format as accepted by the Y command (refer to section 10.58). A label may be specified behind the filename, as for the Y command. If the file is found, and contains the specified label if any, then the EXISTS clause is considered true. Depending on the presence of the NOT keyword the THEN command is executed next, or skipped. Note that if an error occurs in parsing, the THEN command is not executed, regardless of whether the NOT keyword is present.
Likewise, if an unanticipated error occurs during access then the THEN command is not executed. Anticipated errors include:
The third form checks for a variable name being recognised like for the R variable access command. If the variable name is recognised the condition is considered true. Otherwise, the candidate name is skipped by scanning for the first blank or comma, except for parenthetical index expressions which are parsed as expressions. The condition is considered false if no variable is recognised.
This command can be used to enable certain optional features. The parameters are a list of comma-separated keywords. First the entire list will be parsed. Upon successful parsing of all keywords the command will then start to handle the keywords.
The first keyword to the INSTALL
command may be the TOGGLE
keyword. In this case the subsequently specified keywords are toggled rather than enabled.
Save for the ‘AREAS
’ keyword, these features can be accessed by using the corresponding DCO flags as well. The allowed keywords are:
getinput
function rather than using the DOS interrupt 21h service 0Ah line editor to read from standard input. The debugger's line editor includes proper line editing, allows overlong input with a horizontally scrolling view of the buffer, and enables history recall. This keyword controls DCO flag 0800h.
RX
command, section 10.41.
TM
command, section 10.50.
load program L [address]
load sectors L address drive sector count
Loads sectors from a local DOS drive. The count specifies how many sectors to load. The sector number specifies where on the drive to load from. The drive specifies the drive to access, and is specified either as an expression or a drive letter with trailing colon (section 8.13). The address is a segmented address indicating where to write the sector data.
The reverse is the Write Sectors command, section 10.56.
move M range address
This command copies data from one memory range to another range. The source range is specified as the first parameter. The destination is specified as the second parameter, which only accepts an address. The length of the destination is equal to the length of the source.
If the source and destination overlap, the data movement is insured to be done in the direction which will end up with the original source data found intact at the destination. If the source and destination do not overlap then the movement may be done forwards or backwards.
80x86/x87 mode M [0..6|C|NC|C2|?]
An M command without parameters, with a single ‘?
’ parameter, with an ‘NC
’ parameter, or a single expression parameter is a get or set machine mode command.
The machine mode is used by the assembler and disassembler to show machine requirements exceeding the current machine.
A plain ‘M
’ or ‘M ?
’ command displays the current machine.
An ‘M NC
’ or ‘M C0
’ command sets the current coprocessor to absent.
An ‘M C
’ command sets the current coprocessor to present. It is set to the coprocessor type corresponding to the current machine.
An ‘M C2
’ command sets the current coprocessor to present, and the coprocessor type to 287. This command is only valid if the current machine is a 386.
An M command with an expression evaluating to 0 to 6 sets the current machine to the specified numeric value. It also sets the current coprocessor type corresponding to the specified numeric value. Coprocessor presence is not modified by this command however.
Note that all machine mode commands that parse a numeric expression (not ‘M
’, ‘M ?
’, nor ‘M NC
’) will actually parse the expression twice due to the internal dispatching between the machine mode commands and the move memory command. If the expression has side-effects then these side-effects will also occur twice. (An example of a side effect is reading the LFSR variable, which will step the LFSR.)
set name N [[drive:][path]progname.ext [parameters]]
This command sets up the filename and parameters to use when setting up a new process using the L (Load program) command. If the filename ends in .COM
or .EXE
it will be loaded as a DOS program using the interrupt 21h service 4B01h. If the filename ends in .HEX
it will be interpreted by the debugger to load the contained data as a binary image. Otherwise the file is loaded as a flat binary by the debugger itself. In any case, the PSP of the process created by the L command will receive the command line tail, which starts after the filename.
Unlike Microsoft's Debug the executable filename is not included in the command line tail, and an existing process won't be modified by the N command. It only sets the filename and tail for L to use.
output O[W|D] port value
The O commands output to an x86 port. The port can be any number between 0 and FFFFh. Plain O outputs a byte to the specified port. The OW and OD commands output a word or dword respectively. The value to write is specified by the second expression.
proceed P [=address] [count [WHILE cond] [SILENT [count]]]
The P command causes debuggee to run a proceed step. This is the same as tracing (T command) for most instructions, but behaves differently for ‘call
’, ‘loop
’, and repeated string instructions. For these, a proceed breakpoint is written behind the instruction (similarly to how the G command writes breakpoints), and the debuggee is run without the Trace Flag set.
As an exception, if a near immediate ‘call
’ (opcode E8h) is to be executed and its callee is a ‘retf
’ or ‘iret
’ instruction, then the ‘call
’ instruction is traced and not proceeded past. (This supports some relocation sequences.)
Like for the G command, a start address can be given to P prefixed by an equals sign. Next, a count may be specified, which causes the command to execute as many P steps as the count indicates.
After a count, a WHILE keyword may be specified, which must be followed by a conditional expression. Execution will only continue if the WHILE expression evaluates to true.
After a count (when no WHILE is given) or after a WHILE condition, a SILENT keyword and optional count may be given. In this case, the debugger buffers the register dump and disassembly output of the executed steps, until control returns to the debugger command line. Then, the last dumps stored in the buffer are displayed. If a non-zero count is given, at most that many register dumps are displayed.
quit Q
This command attempts to quit the debugger. It may fail if the currently attached process (if any) does not return into the debugger's Parent Return Address upon running an interrupt 21h function 4C00h in the debuggee context. It may also fail if any of the hooked interrupts cannot be restored. In this case, setting the corresponding DCO4 flags can force unhooking upon a retry.
For quitting a device driver mode debugger, the QC and/or QD flags to the quit command must be used. Refer to section 5.3 for a description of them.
If the debugger was attached to a DOS process the quit command will try to terminate the process. If the quit command succeeds, the debugger will return to its own parent process.
If not attached to a DOS process the quit command acts differently. This is always true of the bootloaded debugger, is true of the device driver mode debugger by default (absent any ATTACH commands), and is true of the application mode debugger after a successful TSR command (absent subsequent ATTACH commands). In this case, a quit command that succeeds will continue to run the current debuggee code in the exact state it was left in last.
The bootloaded debugger offers the BOOT QUIT command which will try to quit the currently running (virtual) machine rather than quitting the debugger. It is described in section 10.9.5.
The quit Extension for lDebug (section 15.44) acts like the BOOT QUIT command but can be loaded as an ELD even when in a DOS mode.
If the QB flag to the quit command is used, then the debugger will run a breakpoint in its quit handler, if the quit command succeeds. This breakpoint runs after the debugger has uninstalled its interrupt handlers. Refer to section 10.36.
quit process QA
The QA command tries to quit an attached process. It does this by resetting the current cs:eip, ss:esp, efl, and (only for DebugX) all segment registers. Then it runs interrupt 21h service 4C00h in the context of the current debuggee. Afterwards it reports on how the debugger regained control and whether the attached process terminated.
(If between the current debuggee's process and the debugger's process there is any process that is self-parented, or a breakpoint interrupt or trace interrupt is caused by the current process having terminated, then the attached process may be considered not terminated.)
The same underlying function is used by the program-loading L command and the default Q command (except if the debugger is running in TSR mode or bootloaded).
quit and break QB
The QB command is composed of a Q command with a B flag. It indicates to the debugger to quit as usual, but to then run a breakpoint just before the debugger returns the control flow to either the OS, the application that executed the debugger, or (when resident as TSR, device driver, or bootloaded) the current debuggee.
When successful, this instance of the debugger has already uninstalled all its interrupt hooks, so the breakpoint will run the interrupt 3 handler that was installed prior to the debugger having been installed.
register R [register [value]]
The R command without any register specified dumps the current registers, either displayed as 16-bit or 32-bit values (depending on the RX option), and disassembles the instruction at the current CS:(E)IP location. If the instruction is a conditional jump then the R command disassembly will include a trailing notice that reads ‘jumping
’ or ‘not jumping
’ depending on the current status.
R with a register, named debugger variable, or memory variable (of the form BYTE/WORD/3BYTE/DWORD [segment:offset]
) displays the current value of the specified variable. It then displays a prompt, allowing the user to enter a new value for that variable. Entering a dot (.) or an empty line returns to the default debugger command line.
R with a variable, followed by a dot (.), only displays the current value of that variable.
R with a variable, followed by an optional equals sign, and followed by an expression, evaluates the expression and assigns its resulting value to the variable. The equals sign may instead be a binary operator with a trailing equals sign, which is handled as an assignment operator.
Examples:
-r ax .
AX 0000
-r ax
AX 0000 :1
-r ax
AX 0001 :.
-r ax += 4
-r ax
AX 0005 :
-r word [cs:0]
WORD [1867:0000] 20CD :
-r dif .
DIF 0100B00B
-
Run R extended RE
The RE command runs the RE buffer commands. Refer to section 18.7. The RE buffer has the highest priority among all buffered commands. (The RC buffer and Y command Script for lDebug files have a lower priority than the RE buffer.)
RE buffer commands are displayed with a prompt consisting of a percent sign %
or, for DDebug or for CDebug while in debuggable mode, a tilde followed by a percent sign ~%
.
RE commands RE.LIST|APPEND|REPLACE [commands]
RE.LIST lists the RE buffer contents in a way that can be re-used as input to RE.REPLACE.
RE.APPEND appends the following commands to the RE buffer. This command can overflow the RE buffer, in which case the command aborts with an error. In this case the command has no effect on the RE buffer contents.
RE.REPLACE replaces the RE buffer with the following commands. This command generally cannot overflow the RE buffer.
The RE buffer usage is described in the ?RE help page (section 18.7).
Run Commandline RC
The RC command runs the command line buffer commands. This is similar to the RE command, except it uses a different buffer. Further, the RC buffer contents have the lowest priority among all buffered commands. (The RE buffer and Y command Script for lDebug files have a higher priority than the RC buffer.) Upon initialisation of the debugger the RC buffer is filled.
In case the debugger is loaded as a DOS application or DOS device driver, the RC buffer first gets the configuration command. Then the debugger's init appends the content of the /C
switch (if any). If an /IN
switch is specified, the RC buffer is cleared.
In case the debugger is bootloaded, the RC buffer receives the contents of the kernel command line (if any) or the default kernel command line contents.
If the RC buffer is not empty, the equivalent to an RC command is run on startup of the debugger. (This running happens after the initial N and L commands.)
Command line buffer commands are displayed with a prompt consisting of an ampersand &
or, for DDebug or for CDebug while in debuggable mode, a tilde followed by an ampersand ~&
. When both RE and RC are running out of their respective buffers, the RE buffer contents take precedence.
RC commands RC.LIST|APPEND|REPLACE [commands]
RC.LIST lists the command line buffer contents in a way that can be re-used as input to RC.REPLACE.
RC.APPEND appends the following commands to the command line buffer. Like RE.APPEND this will cause an error if the buffer overflows.
RC.REPLACE replaces the command line buffer with the following commands.
regdump history RH [IN value, value, ...|value]
The RH command displays steps from the RH/silent buffer while RH mode is enabled. RH mode can be enabled using the INSTALL RH
command. (Refer to section 10.26.) The parameter to the RH command can be the keyword IN, followed by one or more comma-separated match ranges, or by a single number, or no parameter at all.
The no parameter form displays all steps still saved in the RH buffer.
The one parameter form displays a single step from the RH buffer. The number 0 refers to the most-recent saved step. The number 1 refers to the second most-recent saved step. And so on.
The RH IN
form accepts one or more match ranges. A match range can be:
FROM
followed by a number followed by the keyword TO
followed by a number. (The second number must be above-or-equal the first number. Both must be below 1_0000h.)
FROM
followed by a number followed by the keyword LENGTH
followed by a number. (The length number plus the first number must be below-or-equal 1_0000h. A zero-length is valid, and will produce no output.)
The RH IN
command will treat each match range by displaying the corresponding steps from the RH buffer, but in chronological order. For instance, the following two commands are equivalent:
rh in from 2 length 4
rh in 5,4,3,2
When parameters specify a number that is above the oldest still saved step in the RH buffer, the behaviour is not certain and may yet change. (For now, each older step will display nothing. This still may be subject to change.)
The RH command defaults to page its output, if paging is enabled. Paging can be disabled for the RH command using the silent buffer output paging control. (This will, of course, also affect the silent buffer output.) The command r dco3 = dco3 clr 200 or 100
will instruct the RH command to not page its output.
As an exception, if the RH buffer is empty then any valid RH command will produce no output at all.
If RH mode is not enabled then the RH command may display stale or corrupted output, except when run directly after a silent-buffered T/TP/P command. In the latter case, the RH command will operate on the contents stored by the last run command.
MMX register RM [BYTES|WORDS|DWORDS|QWORDS]
This command dumps all 8 MMX registers. It is only available if MMX is supported by the machine. The optional size keyword specifies an item size, which defaults to BYTES. The BYTES size will match memory order of the byte values, displaying the least significant byte's value first. A size keyword of WORD will display the least significant word first, and so on.
MMX support is redetected when lDebugX enters or leaves Protected Mode. (dosemu2 may support MMX in its Protected Mode while not supporting it in 86 Mode.)
This command is no longer included in the default build as of release 7. To use it, either enable the build time _RM define or run ‘ext rm.eld install
’ to install it as an Extension for lDebug.
FPU register RN
This command is no longer included in the default build as of release 7. To use it, either enable the build time _RN define or run ‘ext rn.eld install
’ to install it as an Extension for lDebug.
toggle 386 regs RX
This command toggles the DCO flag 0001h. The same flag can be set using the command INSTALL RX
or cleared using the command UNINSTALL RX
. This command is not valid on a non-386 machine.
This command shows the first 16 user-defined variables (refer to section 12.16), the current options variables DCO (that is DCO1), DCS, DAO, DAS, the internal flags DIF (that is DIF1), as well as the debugger process segment (DPR), the debugger parent return address (DPI), and the debugger parent process (DPP). lDebugX also shows the debugger process selector (DPS), which is zero in 86 Mode and a selector value in Protected Mode. (All of these variables can be queried manually, the RV command lists them merely for convenience.)
Additionally, in the last line the RV command displays the current debuggee's mode. This is either Real 86 Mode, Virtual 86 Mode, or (lDebugX only) Protected Mode with either a 16-bit CS or a 32-bit CS.
This command shows all user-defined variables (refer to section 12.16) that are not currently zero. Variables are always shown four to a line, so a single non-zero variable will additionally show up to 3 variables that are currently zero.
This command shows various segments (and, in Protected Mode, selectors) used by the debugger. It currently shows the following:
A more detailed list of the debugger's segments, including their sizes, can be obtained using the ldmem Extension for lDebug (section 15.1).
This command shows the debugger's mode as well as some client and debugger process addresses. The mode is one of:
The process addresses include:
The process addresses can all be accessed individually too, using the following variables:
The DPARENT and DPRA variables read as all zeros when the debugger is loaded in bootloaded, device driver, or resident application (TSR) mode.
This command shows the device header (segmented 86M) far address as well as the size of the device's allocation, in paragraphs. If the debugger is not loaded in device mode then instead a message indicating this is displayed.
The two variables can be accessed individually, too. These are the DEVICEHEADER and DEVICESIZE variables. Both of them read as all zeros when the debugger is not loaded in device mode.
search S range [REVERSE] [SILENT number] [RANGE range|list]
The S command searches memory for a byte string. The first range specifies the search space. By default, searching will begin at the bottom of the search space and move upwards. If a REVERSE keyword is specified after the range then searching will begin at the top of the search space moving downwards. The search string is specified either with the RANGE keyword followed by another range, or as a list of byte values.
If the SILENT keyword is specified, a silent number must be parsed before the list or range parameter. The number specifies how many result lines are displayed at most. It is a 32-bit unsigned number that may take any value in the range, including zero. If the number is zero, only the amount of matches is displayed.
The read-only variable SRC (Search Result Count) will receive the 32-bit value that is the amount of matched occurrences. The variable SRS0 receives the first Search Result Segment. Likewise SRO0 receives the first Search Result Offset. SRO1 to SROF hold subsequent Search Result Offsets. SRO is an alias to SRO0. SRO variables are 32-bit in the _PM build lDebugX, 16-bit otherwise. Unused SRO variables are zeroed out by a successful search. The COUNT variable is set to the length of the search string.
The display of search results is as follows:
There is an option to disable the data dump so as to only display the match addresses. If the bit 80_0000h is set in the DCO variable then the data dump is suppressed.
sleep SLEEP count [SECONDS|TICKS]
The SLEEP command sleeps for the indicated length. The duration defaults to seconds. If the TICKS keyword is specified then the duration is taken to mean timer ticks. (A timer tick is about 1/18 seconds.) If the input is from DOS or serial I/O then Control-C from the input terminal may be used to cancel the sleep.
trace T [=address] [count [WHILE cond] [SILENT [count]]]
The T command is similar to the P command. However, T traces most instructions. Depending on the TM option (section 10.50), interrupt instructions are also traced (into the interrupt handler) or proceeded past.
trace (exc str) TP [=address] [count [WHILE cond] [SILENT [count]]]
The TP command is alike the T command, but proceeds past repeated string instructions like the P command would.
trace mode TM [0|1]
This instruction accesses the DCO flag 2. If run without an expression then the current status is displayed. Otherwise tracing into interrupts (for the T and TP commands) is enabled (nonzero expression) or disabled (zero expression).
enter TSR mode TSR
This command tries to find the PSP to which the debugger is attached. It starts the search at the current PSP and walks up the parent processes until finding the debugger. If a self-owned process is encountered the search is aborted.
Once found, the attached PSP is patched with the PRA and parent process noted down for the debugger itself. That is, the debugger's would-be parent is made the parent of the attached process. The debugger's fields for original PRA and parent are cleared. Thus the TSR command, if it succeeds, switches the debugger into resident mode.
The reverse operation is performed by the ATTACH command (see section 10.6). The TSR command can be used right away in application mode, or can be used in device driver mode after the debugger has been attached to a process using the ATTACH command. The default state of the device driver mode debugger is resident mode.
The TSR command and the ATTACH command are not usable in bootloaded mode.
unassemble U [range]
Given a range, the address of which defaults to CS, this command disassembles instructions from memory. The range may be specified with a lines length (refer to section 8.4). The default length if none is specified defaults to the number of lines specified in the variable DEFAULTULINES
if it is nonzero, or else the number of bytes specified in the variable DEFAULTULEN
.
If a lines length is used, that many lines are disassembled. However, if a single instruction does not fit within one line due to a too long string of machine code, then the lines used for this instruction will count as one line as concerns the lines length. If an address length is specified, all instructions that are contained within or start within the specified range are disassembled.
If no range is specified, the U command continues disassembling at "u_addr" (AUS:AUO), which is updated by each U command to point after the last disassembled byte. The R command sets "u_addr" to equal the current CS:(E)IP, including if the register dump is called by a run command. The default length is determined in the same way as for if a range without a length is specified. If autorepeat is used it behaves the same way as a U command without a range.
This command can be used to disable certain optional features. The parameters are a list of comma-separated keywords. First the entire list will be parsed. Upon successful parsing of all keywords the command will then start to handle the keywords.
The available keywords are documented for the INSTALL command, refer to section 10.26.
view screen V [ON|OFF [KEEP|NOKEEP]]
The V commands allow to enable or disable video screen swapping. When enabled, the debugger takes care that screen output of debuggee and debugger are strictly separated. This is useful to debug fullscreen text mode programs.
The screen will be swapped whenever the debuggee is run with a run command (T/TP/P/G), or when the plain V command is used. The plain V command is provided to watch the debuggee screen while the debugger is active. It ends upon the user entering any key to the debugger terminal.
Video screen swapping currently requires an XMS driver, and the debugger will allocate an XMS memory block of 32 KiB.
V OFF KEEP will disable video screen swapping but keep the current debugger screen contents. V OFF NOKEEP (and the default for V OFF if the keep flag has not been set) will instead return to the debuggee screen contents. When the Q command succeeds, it executes the equivalent of V OFF. That is it will use the current keep flag.
write program W [address]
write sectors W address drive sector count
Writes sectors to a local DOS drive. The count specifies how many sectors to write. The sector number specifies where on the drive to write to. The drive specifies the drive to access, and is specified either as an expression or a drive letter with trailing colon (section 8.13). The address is a segmented address indicating where to read the sector data.
The reverse is the Load Sectors command, section 10.28.
Note that on MS-DOS v7 this command will lock and unlock the specified drive to insure it is locked when writing. As there is no way to query the lock status, this will unconditionally unlock the drive after writing.
Warning: You should know what you are doing if you use this command. Misuse may corrupt any data stored on this drive.
expanded mem XA/XD/XM/XR/XS, X? for help
These commands are no longer included in the default build as of release 7. To use them, either enable the build time _EMS define or run ‘ext x.eld install
’ to install them as an Extension for lDebug.
run script Y [partition/][scriptfile] [:label]
The Y command runs a Script for lDebug file.
The script file is specified in two different ways, depending on whether the debugger is running as an 86-DOS application or device driver, or rather as a boot-loaded kernel replacement.
In both cases, commas are parsed as separators that end a pathname, except if they occur within doublequotes. The same is true of semicolons. Semicolons may be parsed as end-of-line markers as well.
A pathname may start with one of two special keywords to use one of the debugger configuration pathes:
::config::
::scripts::
The pathname should not include a path separator (backslash or forward slash) directly after the keyword.
The default path for the debugger configuration directory is detected as follows:
ldp/
.
LDEBUGCONFIG
is set, read it.
The default path for the debugger scripts directory is detected as follows:
ldp/
.
LDEBUGSCRIPTS
is set, read it.
The configuration pathes can be shown or modified using the ‘config
’ Extension for lDebug.
If a Y command filename is not found, and no config path keyword was used, the debugger will retry the file open with the ::scripts::
path prepended to the specified filename. If this is not desired, an explicit ::empty::
path keyword may be used.
A label may be specified after a pathname to cause execution to start at that label instead of at the start of the file. This is equivalent to placing a ‘GOTO :label
’ command at the start of the script file. The colon to indicate a label is required.
If execution already is within a script file, then the Y command may be run with only a label (again with the colon required). In that case, the current script file is opened in a subsequent level (handle or boot-loaded script file context) and execution starts at that label.
Opening a script file as DOS application or device driver only works while DOS is available (InDOS not set). Additionally, if during script file execution DOS becomes unavailable (InDOS is set) then the script file execution is paused. It is resumed once DOS becomes available again. (Control-C with a non-zero IOL variable may still be used to cancel script file execution. DOS is called to close affected handles only if DOS is available.)
These commands are only supported if the _SYMBOLIC build option is enabled.
The /S switch allows to change the symbol table allocation. The symbol tables may take up up to 256 KiB of 86 Mode memory (below 1024 KiB) or up to 2 MiB of XMS memory. XMS use implies an additional 65 KiB is allocated for padding and a transfer buffer.
XMS use can be forced by using a letter X behind the /S. 86 Mode memory use can be forced by using a letter R instead. The prior selection can be undone using an asterisk *, returning to the default behaviour. That means allocate XMS if available, and fall back to 86 Mode memory otherwise.
After the equals sign a size is to be specified. The size can be an immediate number or an expression, or the keyword MAX to use the maximum size. An expression must be surrounded by round parentheses. The size specifies the amount of kibibytes to allocate. The size may be zero, which signals to free all symbol tables. This deletes all symbols yet defined. Otherwise, new symbol tables are allocated. Existing symbols will be transferred from the old symbol tables, if there are any. It is an error to specify a symbol table size that is not large enough to hold all currently defined symbols, except for specifying a zero size.
Multiple /S switches can be specified within the same Z command. They are processed one by one, that is an error during parsing or execution of a subsequent switch will not make it so a prior switch is skipped.
This command shows statistics on the current symbol table sizes, including the amount of total, used, and free units. Each of the symbol main array, symbol hash array, and symbol string heap are listed.
This command is used to add a new symbol. It can be followed by several parameters. These are:
This command deletes a symbol. It can be followed by the symbol name to delete, or a RANGE keyword and an address range parameter, or an UNREFSTRING keyword. The latter is to clean up the symbol string heap by deleting entries that are no longer used.
Z ADD will batch up new symbols as temporary symbols. They are committed into the symbol tables upon several conditions, such as no more space for temporary symbols or execution of a command other than Z ADD or Z ABORT. The Z COMMIT command is for forcing the temporary symbols be committed. This should not usually be required.
This command discards all temporary symbols batched by prior Z ADD commands if they were not yet committed. If the debugger responds to every command with the error message "Invalid symbol table data!" then something went wrong with the committing of temporary symbols. In this case the Z ABORT command may help to return the debugger to a usable state.
The assembler is accessed by running the A command (section 10.5). The prompt for the assembler consists of a segmented address, with a 16-bit segment/selector and a 16-bit or 32-bit offset. Entering an empty line (whitespace only) or a single dot to the assembler exits back to the debugger prompt. Trailing comments may be added using semicolons.
Differences from MSDebug's assembler include:
retn
, int3
, and xlatb
are used in the disassembly, and may be used with the assembler
The assembler generally strives to accept assembly input matching the syntax of NASM (the Netwide Assembler). Key differences:
#
’ prefix)
W
’ or ‘D
’ whereas NASM requires a second operand that reads ‘CX
’ or ‘ECX
’. The NASM form is supported too, but the disassembler defaults to the suffix form.
PTR
’ keyword, which has no effect
The disassembler is supposed to generate output which is valid input to the assembler. Ideally, any disassembly would make it through a "roundtrip", that is assembling the disassembly output should result in exactly the same machine code bytes. This is not always true however.
For instance, the SIB long form encoding of a disp32-only memory address is disassembled just as the non-SIB form, and the assembler currently lacks a way to force this encoding. Invalid prefixes may also not roundtrip successfully. The order of multiple prefixes may also change, which can be significant in some cases (eg the dispatchers for the disassembler's repeated string op simulation). Additionally, the disassembler may display annotations like ‘(unused)
’ and ‘[needs 386]
’. These must be stripped from the instruction to enter it into the assembler. The same is true when running an R command (register dump) and its disassembly shows a memory content annotation or the ‘jumping
’ or ‘not jumping
’ notice.
The disassembly consists of several fields:
The assembly input consists of:
The assembler may emit an annotation after it accepts a line. This will be written to the next line, and can read like ‘[needs 386]
’ or ‘[needs math coprocessor]
’ depending on the current machine type (section 10.30) and the needed machine type for the instruction.
Additionally, if the AFORMAT Extension for lDebug (section 15.12) is installed and enabled, each accepted line to the assembler will reply with a machine code dump in a subsequent line. This dump looks similar to the disassembler's dump. It will correspond exactly to what was written by the assembler. (It checks which address was used for the last prompt and which address is used for the next prompt. The ORG directive is handled as a special case.)
It is recommended to keep an instruction reference on hand. The old NASM instruction reference does nicely for all 386-level instructions, though it lacks descriptions of system structures like descriptors, the TSS, and the FSAVE format. It is hosted on the web at https://pushbx.org/ecm/doc/insref.htm with its sources available in the repo at https://hg.pushbx.org/ecm/insref/
A mnemonic is a keyword that maps to a certain instruction. Mnemonics are generally matched cap-insensitively. Some mnemonics allow different forms with optional or required size suffix letters. If present, such a letter can be either a ‘W
’ or a ‘D
’ to indicate a word (16-bit) or dword (32-bit) size.
Every explicit operand to an assembly language instruction is one of:
[...]
’.
All debuggee registers can be accessed numerically:
al
, cl
, dl
, bl
, ah
, ch
, dh
, bh
ax
, cx
, dx
, bx
, sp
, bp
, si
, di
eax
, ecx
, edx
, ebx
, esp
, ebp
, esi
, edi
es
, cs
, ss
, ds
, fs
, gs
fl
, efl
, ip
, eip
Each 16-bit register can be used in a register pair, such as:
dxax
bxcx
(used by L
load program and W
write program commands)
sidi
csip
If MMX is available, the debuggee's MMX registers can be accessed as variables. However, as the debugger only supports 32-bit numbers, only half of a 64-bit MMX register can be accessed. The ‘x
’ is a digit from 0 to 7. The ‘y
’, if present, is one of the following letters:
DCO1 (alias DCO) to DCO7. Dword. Writable.
DCS1 (alias DCS) to DCS7. Dword. Read-only.
DIF1 (alias DIF) to DIF7. Dword. Read-only.
Dword. Writable.
Dword. Read-only.
Alias DPRA. Dword. Read-only. Always a 86M segmented pointer. 0 if in TSR mode, or loaded as a device driver, or in bootloaded mode.
Alias DPSP. Word. Read-only. Always a 86M segment.
Alias DPARENT. Word. Read-only. Always a 86M segment. 0 if in TSR mode, or loaded as a device driver, or in bootloaded mode.
0 while in Real or Virtual 8086 Mode, debugger process selector otherwise. (The process selector addresses DebugX's PSP and DATA ENTRY section.) This variable does not exist on non-DPMI lDebug builds.
The debugger's PSP segment while in 86 Mode, a selector pointing to the same base while in Protected Mode. This variable exists even on non-DPMI builds, where it is always the same as DPSP.
All of these are doublewords and default to 1. For the respective commands, these counts specify the number of steps to take if none is specified explicitly. This includes when a command is run by autorepeat, refer to section 10.1. If one of these is set to zero then it is an error to not specify a count explicitly for the corresponding command.
Doubleword. Default is 256. If this many commands are executed from the RE buffer, the execution is aborted and the command that called RE is continued.
Doubleword. This is reset to zero when RE buffer execution starts. Each time a command is executed from the RE buffer, this variable is incremented. If it reaches the value of RELIMIT, RE buffer execution is aborted.
Doubleword. Default is 4096. If this many commands are executed from the RC buffer, the execution is aborted.
Doubleword. This is reset to zero when RC buffer execution starts. Each time a command is executed from the RC buffer, this variable is incremented. If it reaches the value of RCLIMIT, RC buffer execution is aborted.
Word. This holds the most recent command's return code. If the most recent command succeeded, then this is zero.
Word. This holds the most recent non-zero return code.
AAS: word, AAO: doubleword. Default address for the assembler. Updated to point after each assembled instruction.
Default address for memory dumping. Updated to point after each dumped memory content.
Default address for the disassembler.
Default address for memory entry.
Default address for DZ command, ASCIZ strings. Terminated by zero byte.
Default address for D$ command, CP/M strings. Terminated by dollar sign ‘$
’.
Default address for D# command, Pascal strings. Prefixed by length count byte.
Default address for DW# command. Prefixed by length count word.
Default address for DX command. (Only included in DebugX.)
Byte. Default 1. Sets the number of rows of the terminal used by DOS or BIOS output. Setting this to zero disables paging to the DOS or BIOS output. Setting this to 1 uses the automatic selection. That means the BIOS Data Area byte at address 484h, plus one, is used. If using that byte and it is zero, paging is disabled.
Byte. Default 1. Sets the number of columns of the terminal used by BIOS input or DOS input if getinput
is enabled. Setting this to zero selects a default (80). Setting this to 1 uses the automatic selection. That means the BIOS Data Area word at address 44Ah is used. This is used by the line input handling if inputting from the BIOS terminal (int 16h, int 10h), or if inputting from a DOS terminal when DCO flag 800h is set (eg by using INSTALL GETINPUT
). A value between 2 and 39, inclusive, is not recommended.
getinput
#
Byte. Default 0. Sets the number of columns of the terminal at which to split overlong lines after the user submits an input line with the getinput
line editor. If this is zero, splitting overlong lines is disabled and it is assumed that the terminal will split lines as desired. Otherwise, overlong lines (ie those extending past the IOC width) will be split to have no more than the amount of bytes specified by the IOCLINE variable. Makes most sense to set this equal to IOC or DSC, or else to zero. Any nonzero value is allowed.
Word. Default 0 or 1Eh. Indicates where the ROM-BIOS's circular keypress buffer starts. Value can be nonzero to force a particular offset in segment 40h. Value can be zero to force using the value at word [40h:80h]
, using an extension not available on all systems.
On startup the debugger checks whether the extension values are valid. If they are then the default of the IOS variable is left as zero. Otherwise, the default is set to 1Eh, which is the default buffer location.
This variable is used to check for Ctrl-C keypresses if the InDOS mode is on (either InDOS flag set, DCO flag 8 set, or in bootloaded mode) and serial I/O is not in use and the flag DCO3 2000_0000h is set. Setting this variable nonzero and equal to IOE disables Ctrl-C checking.
Modifying this variable should only be done while it is not in use. That means using DOS for input, using serial I/O for input, or clearing the DCO3 flag 2000_0000h. Modifying this variable and the IOE variable should be done together, so that they are valid together when in use.
Word. Default 0 or 3Eh. Indicates where the ROM-BIOS's circular keypress buffer ends. Value can be nonzero to force a particular offset in segment 40h. Value can be zero to force using the value at word [40h:82h]
, using an extension not available on all systems.
Refer to IOS description above.
Word. Default 255. Indicates how many levels of script files and RE buffer execution to cancel when a Control-C input or critical DOS error is detected by the debugger. The effective value will be incremented by one if IOF flag 1 is set and RE buffer execution is in progress.
Zero indicates to only cancel the current command. One indicates to cancel the current command, plus the RE buffer execution if any, else up to one level of script file execution. Two indicates to cancel two levels of execution: either the RE buffer execution and one level of script file execution, or up to two levels of script file execution.
The debugger always cancels RE buffer execution first if it is in progress. Next, the innermost script file execution is cancelled, if any.
Word. Default 1. Flags for I/O handling. Currently defined:
Byte. Default 24. Sets the number of rows of the terminal connected via serial port. Setting this to zero disables paging to the serial port. Setting this to 1 uses the IOR variable handling.
Byte. Default 80. Sets the number of columns of the terminal connected via serial port. Setting this to zero selects a default (80). Setting this to 1 uses the IOC variable handling. This is used by the line input handling. A value between 2 and 39, inclusive, is not recommended.
Byte. Default 15. This gives the number of seconds that the KEEP prompt upon serial connection waits. Setting this to zero waits at the prompt forever.
Byte. Default 16. This gives the size of the 16550A's built-in TX FIFO to use. Set to 15 if using dosemu before revision gc7f5a828 2019-01-22, see https://github.com/stsp/dosemu2/issues/748.
Byte. Default 0Bh, corresponding to COM2. Use 0Ch for COM1. This specifies the interrupt number to hook so as to be notified of serial events. The use of this variable occurs only when connecting to serial I/O. The value at that point in time is cached for as long as the serial connection is in use.
Word. Default 0000_1000b, corresponding to COM2. Use 0001_0000b for COM1. This specifies the IRQ mask of which IRQs to enable. The low 8 bits correspond to IRQ #0 to #7 and the high 8 bits correspond to IRQ #8 to #15. If any bit of the high 8 bits is set then generally the bit 0100b should be set too, to enable the chained PIC. This circumstance is not automatically detected. The use of this variable occurs only when connecting to serial I/O. The value at that point in time is cached for as long as the serial connection is in use.
Word. Default 02F8h, corresponding to COM2. Use 03F8h for COM1. This specifies the I/O port base to address the UART. The use of this variable occurs only when connecting to serial I/O. The value at that point in time is cached for as long as the serial connection is in use.
Word. Default 12, corresponding to 9600 baud. This specifies the DL value to set during initialisation. The use of this variable occurs only when connecting to serial I/O.
Byte. Default 0000_0011b, corresponding to 8n1. (8n1 = 8 data bits, no parity, 1 stop bit.) This specifies the settings to set up in LCR. The high bit (80h) generally must be clear. The use of this variable occurs only when connecting to serial I/O.
Byte. Default 0. This specifies what to write to the FCR. The low 3 bits (07h) generally must be clear. The use of this variable occurs only when connecting to serial I/O. The value at that point in time is cached for as long as the serial connection is in use.
These variables control some details of the debugger's timers used for waiting in a few places. The affected timers are:
Unaffected timers include:
Byte. Default 0. If the getc function invokes the idle handling, it will repeatedly call the idle function if this variable holds a nonzero value. The value specifies the amount of repetitions past the first call. This variable is intended for debugging.
Byte. Default 0. If the SLEEP command invokes the idle handling, it will repeatedly call the idle function if this variable holds a nonzero value. The value specifies the amount of repetitions past the first call. This variable is intended for debugging.
Word. This value is set anew whenever a wait handler has found a delta ticks larger than the prior value of this variable. After any wait iteration the variable inevitably will be nonzero.
Word. Default 5. This variable specifies the upper limit of the delta between tick low words accepted as being accurate.
Note that at midnight the tick low word goes from 00AFh or 00B0h to 0000h. The delta appears to be FF51h or FF50h ticks at that point. Therefore the limit should be set small enough that the total wait time is not majorly skewed at midnight.
However, it is possible that the idle handling takes so long that more than one tick has actually gone by until the wait handling is run again. For such system setups it can be desirable to set the limit to more than 1.
A good compromise is to set the limit to between 1 and 6, inclusive. A limit of 6 ticks only skews for 1/3 of a second at midnight.
These variables are not supported by default. The build option _DEBUG1 must be enabled to include them. The Test Counter variables work similarly to permanent breakpoint counters:
The default values for all counters and addresses is zero.
If a fault is injected into readmem, it returns the value given in TRV.
If a fault is injected into writemem, it returns failure (CY).
If a fault is injected into getlinear, it returns failure (CY).
If a fault is injected into getsegmented, it returns failure (CY).
These variables are not supported by default. The build option _DEBUG3 must be enabled to include them. These variables are used to test the read-only masking. Read-only masking makes it so that bits given in the mask are read-only. Bits that are clear in the mask are writable.
Doubleword. Default 0. Mask AA55_AA55h.
Doubleword. Default 0011_0022h. Mask 00FF_00FFh.
Y command variables can be used when the Y command (as application or bootloaded) has been used to open a script file. YSx (Y Script) variables are generic and refer to whatever Y file is opened. YBx (Y Bootloaded script) variables refer to opened Y files while bootloaded. YHx (Y Handle script) variables refer to opened Y files as application.
Word. Partially read-write, partially read-only.
Flag 4000h controls whether script file input is displayed or not. Prepending an AT sign (@) to a line that is read from a script file will hide the input of that line. Setting YSF flag 4000h will hide all input lines instead. The effect is similar to prepending @ to every line.
YSF variables are only available while executing script files.
Doubleword. Default zero. V0 to VF or V00 to VFF each specify one variable. It is valid to refer to any V variable using an index expression. Index expression means that the variable name (V
) is immediately followed by an opening parenthesis, followed by a numeric expression which evaluates to a number below 100h.
All of these are read-only. All of them are zero if in bootloaded mode.
Word. Always a segment,
Alias PARENT. Word. Always a segment.
Alias PRA. Dword. Always a 86 Mode segmented address.
Alias PSPS. Word. Segment or selector according to mode.
Doubleword. Read only. Amount of matches found by last S command.
Word. Read only. SRS0 to SRSF each specify one variable. Search result segments of last S command's matches.
Word or doubleword (DebugX). Read only. SRO0 to SROF each specify one variable. Search result offsets of last S command's matches. It is valid to refer to any SRO variable using an index expression. Index expression means that the variable name (SRO
) is immediately followed by an opening parenthesis, followed by a numeric expression which evaluates to a number below 10h.
These variables can be left out of the build. The build option _MEMREF_AMOUNT
must be enabled to include them.
Doubleword. Read only. READADR0 to READADR3 each specify one variable. (Amount of READADR variables can be configured at build time with the option _ACCESS_VARIABLES_AMOUNT, which defaults to 4.) Linear addresses of string, stack, or explicit memory operand reads. Initialised by the R command. Unused variables are reset to zero by the R command. It is valid to refer to any READADR variable using an index expression. Index expression means that the variable name (READADR
) is immediately followed by an opening parenthesis, followed by a numeric expression which evaluates to a number below 4.
Doubleword. Read only. READLEN0 to READLEN3 each specify one variable. Length of string, stack, or explicit memory operand reads. Initialised by the R command. Unused variables are reset to zero by the R command. It is valid to refer to any READLEN variable using an index expression.
Doubleword. Read only. WRITADR0 to WRITADR3 each specify one variable. Linear addresses of string, stack, or explicit memory operand writes. Initialised by the R command. Unused variables are reset to zero by the R command. It is valid to refer to any WRITADR variable using an index expression.
Doubleword. Read only. WRITLEN0 to WRITLEN3 each specify one variable. Length of string, stack, or explicit memory operand writes. Initialised by the R command. Unused variables are reset to zero by the R command. It is valid to refer to any WRITLEN variable using an index expression.
These variables provide access to a simple LFSR (Linear Feedback Shift Register). The default taps are chosen so that a full-range 32-bit LFSR is in use. That means there are 4 giga binary steps, minus one, and all possible 32-bit values are in use except for the all zeros value. A step of the LFSR is done by shifting the old value to the right once. If the bit shifted out is a 1, then the new value is obtained by applying the LFSR taps as a XOR mask to the shift result. If the bit shifted out is a 0, then the new value is simply the shift result.
These read-only variables provide access to the 86 Mode interrupt vectors in a more accessible format.
The xx must be a single or two hexadecimal digits, or an index expression in parentheses which evaluates to a number below 256.
The y must be one of the following letters:
PTR
type keyword.)
These read-only variables support reading the flag status of certain flags.
The x must be one letter to form one of the following names:
Note that the FL.xF flags are read-only. To write to a flag with the R command, it is valid to specify just xF as the variable name, but this is not valid within expressions to read the flag status. (It cannot be supported in expressions because some of the flag names are all hexadecimal digits, such as CF.)
Dword. Result of most recent H command. (If H twofold operation is used then the addition result is stored in the variable.)
Word. Result of most recent D.A command. This is set to 0FFFFh by a failed D.A command and to the allocated selector by a successful D.A command. This variable is only present in lDebugX.
Word. Result of most recent XA command. This is set to 0FFFFh by a failed XA command and to the allocated handle by a successful XA command. This variable is only present if the debugger was built with the _EMS
option or the x.eld has been installed.
Word. Number of ticks to wait with Control pressed until breaking into the debugger. This variable is only used if the interrupt 8 handler is installed. If the handler detects that Control is pressed continuously for this length of time while not in the debugger then the handler will break into the debugger. Default is set up for about 5 seconds (5 times 18). Set to 0 to disable Control pressed detection.
Byte. Default 0. This variable is used to set the return code used by the debugger to terminate itself with interrupt 21h service 4Ch. This only happens if the debugger is in application mode and not in TSR mode, or a device mode debugger has been attached to a process.
Word. Debugger sets this value when it is entered from a debuggee having terminated. The value is what interrupt 21h service 4Dh returned.
The variable DDTEXTAND
is used as a mask to modify the text code of a data dump before display. It defaults to 0FFh. Setting this to 7Fh will mimic MS-DOS Debug's display of text in its data dump, masking off the high bit. In this case the TOP
setting of the D command has no effect.
The TRYAMISNUM variable is a writable byte variable. It defaults to 0. Its content is tried first when searching a free multiplex number. After that the debugger currently will search starting from number 0 up to 255.
The AMISNUM variable is a read-only byte variable. It contains the actually used multiplex number while the DIF4 flag 8 is set. Otherwise its content is not used and is considered stale.
The TRYDEBUGNUM variable is a writable byte variable. It defaults to 255. It is similar to TRYAMISNUM, however its content is tried first to find another debugger instance. After that the debugger currently will search starting from number 255 down to 0. Unused if it matches the debugger's own currently installed AMIS handler's multiplex number. This is used by the debugger to find the following services provided by another debugger instance:
None of these services are detected and used if DCO3 flag 800_0000h is set. (In the past this flag was wrongly documented as applying to the Update IISP Header service only.)
TRYDEBUGNUM is as well used by Extensions for lDebug to find the following services:
The DCO3 option flags do not affect the ELD use of AMIS services.
The DEBUGFUNC variable is a read-only word variable. It defaults to 0. When the Update IISP Header function of the debugger is called and a search for another debugger happens, then this variable receives a 0 if no other debugger is found. It receives a value with the low byte equal to 30h if another debugger was found. (The low byte is set up as the AMIS private function number of the Update IISP Header service.) The high byte is equal to the detected multiplex number then.
Dword. Any successfully parsed COUNT or S command will write the length of the pattern it counted, given in bytes, to this variable.
Word. Reads as the current amount of buffered RH mode entries. May be corrupted if RH mode is not currently enabled.
Word. Reads as the amount of residently installed Extensions for lDebug. This variable is only supported if the amount.eld has been installed. Due to the ELD architecture, this variable will always read as above-or-equal 2.
In Protected Mode in lDebugX, this variable is either a word or dword. Which one it is depends on the D bit of the descriptor corresponding to the current CS selector. In Real or Virtual 86 Mode this variable is always a word. In lDebug without DPMI support this variable is also always a word.
In Protected Mode in lDebugX, this variable is either a word or dword. Which one it is depends on the B bit of the descriptor corresponding to the current SS selector. In Real or Virtual 86 Mode this variable is always a word. In lDebug without DPMI support this variable is also always a word.
The boot unit flags are 256 partially writable byte variables. Every flag byte corresponds to an int 13h unit. The following flags are defined:
The flag 01h takes precedence over 02h if both are set.
These flags match the low flags of the lDOS iniload query patch site. All the flags are by default pre-initialised to zero, but this can be overridden in two ways.
The source macro file debug.mac contains the following equates and defines:
lufForceCHS: equ 1
lufForceLBA: equ 2
lufForceGeometry: equ 4
luf_mask_writable equ lufForceCHS | lufForceLBA | lufForceGeometry
numdef LUF_DEFAULT_DISKETTE, 0
numdef LUF_DEFAULT_HARDDISK, 0
The first define is the default value used for all diskette units, that is unit numbers below 80h. The second define is the default value used for hard disk units, that is unit numbers above-or-equal 80h.
Further, as documented for lDOS boot, the query patch site of a bootable lDebug executable can have its default changed during the build or can be patched later. If the highest bit of the active patch site byte (80h) is set then the flags are used to initialise the boot unit flags of the loaded from unit.
The low two flags exactly match the lDebug boot unit flags. The flag 04h differs a little, to lDOS iniload it means to pass along the geometry either detected by or hardcoded in the prior loader. To lDebug, the flag means to probe the boot sector (presumed to be a diskette or superdiskette with a FAT FS BPB). In both cases, an interrupt 13h service 08h call is avoided.
These interrupts are always hooked by the debugger. For the non-_DEBUG builds they are hooked during initialisation and the debugger attempts to unhook them when quitting. The highest 8 bits of the dword variable DCO4 control whether they are unhooked only if reachable (bits in DCO4 zero), or forcibly so if not reachable (bits in DCO4 ones). If not forcibly unhooking and an interrupt handler is not reachable then the Q command fails.
For DDebug, these interrupts are hooked within the run
function and unhooked before the run
function returns. This unhooking in DDebug is always forcible; that is, if not reachable then the interrupts are unhooked by simply updating the IVT entries with whatever handlers are stored as the next vectors in DDebug's entrypoints.
CDebug can run in debuggable mode (like DDebug) or with debuggable mode disabled. If the cmd3 loop detects a change in the DCO6 flag 100h then it will toggle debuggable mode to match the flag. This will involve hooking the mandatory handlers or unhooking them (forcibly).
As a special exception, if the debugger detects that it is running on an HP 95LX, then interrupt 6 is never hooked. This supports the different use of this software interrupt by the software or firmware on this type of device.
This interrupt hook is optional. Setting the DCO flag 4000h (enable serial I/O) instructs the debugger to set up this interrupt hook. Clearing the flag or using the Q
command instructs the debugger to unhook its handler. The DCO4 flag 1_0000h controls whether the interrupt unhooking is forcible (flag set) or not (flag clear).
The exact interrupt number used as serial interrupt depends on the DSPVI variable at the point in time at which serial I/O is enabled. The default is interrupt 0Bh, corresponding to COM2.
This interrupt is only hooked by DebugX. This interrupt hook is optional. Setting the DCO4 flag 2 instructs the debugger to set up this interrupt hook. The debugger tries to hook this interrupt if it runs application code in Real or Virtual 86 Mode. Clearing the flag, entering Protected Mode, or using the Q
command instructs the debugger to unhook its handler. The DCO4 flag 2_0000h controls whether the interrupt unhooking is forcible (flag set) or not (flag clear).
This interrupt is hooked to intercept calls to function 1687h, used to detect the DPMI entrypoint. DebugX attempts to hook this service to return its own entrypoint to the caller. The hook may fail if the DPMI host handles interrupt 2Fh calls before chaining to the 86 Mode handler chain. (MS Windows 4.x and older dosemu are reported to do this.)
This interrupt hook is optional. Setting the DCO4 flag 4 instructs the debugger to set up this interrupt hook. Clearing the flag or using the Q
command instructs the debugger to unhook its handler. The DCO4 flag 4_0000h controls whether the interrupt unhooking is forcible (flag set) or not (flag clear).
This interrupt is used to detect the double Control-C via serial I/O condition. If the serial I/O handler of the debugger receives two Control-C keypresses while the debugger is busy running an application then the interrupt 8 hook will interrupt the run.
This interrupt is also used to detect the Control pressed for 5 seconds condition. Similarly to the serial I/O double Control-C condition, this will make the debugger interrupt the current run.
This interrupt hook is optional. Setting the DCO4 flag 8 or running an INSTALL AMIS
command instructs the debugger to set up this interrupt hook. Clearing the flag or running UNINSTALL AMIS
or using the Q
command instructs the debugger to unhook its handler. The DCO4 flag 8_0000h controls whether the interrupt unhooking is forcible (flag set) or not (flag clear).
This interrupt allows other programs to detect the debugger in the AMIS interface. The vendor string is ‘ecm
’ and the product string ‘lDebug
’. The description string contains the same display name and version as the command line help. There are two real uses of this. First, the AMIS function 4, which will return the list of interrupt entrypoints of the debugger. Second, lDebug's private AMIS functions 30h, 31h, 33h, 40h, 41h, 42h, and 43h. They are described in the next sections.
This interrupt hook only succeeds if the current handler is valid. That is, an offset not equal to FFFFh and a segment not equal to zero. Another condition is that the debugger needs to detect an unused AMIS multiplex number to allocate. This is done automatically when hooking the interrupt. If either condition fails then a message is displayed and the debugger clears the DCO4 flag 8 on its own.
The TRYAMISNUM variable is a writable byte variable. It defaults to 0. Its content is tried first when searching a free multiplex number. After that the debugger currently will search starting from number 0 up to 255.
The AMISNUM variable is a read-only byte variable. It contains the actually used multiplex number while the DIF4 flag 8 is set. Otherwise its content is not used and is considered stale.
Note that the AMIS interface is not AMIS-compliant in a few ways:
This function is provided for use by our programs that use AMIS multiplexers and interrupt handler entrypoints with IISP headers. All TSRs (including RxANSI, lClock, SEEKEXT, KEEPHOOK, FDAPM, FreeDOS SHARE) and SHUFHOOK use this function. (The debugger itself also uses this function, if it is provided by another resident debugger.)
lDebug - Update IISP Header
INP: al = 30h
ds:si -> source IISP header (or pseudo header)
es:di -> destination IISP header
OUT: al = FFh to indicate suppported,
si and di both incremented by 6
destination's ieNext field updated from source
al != FFh if not supported,
si and di unchanged
CHG: -
REM: This function is intended to aid in debugging
handler re-ordering, removal, or insertion.
The 32-bit far pointer needs to be updated
as atomically as possible to avoid using
an incorrect pointer.
Test case: Run a program such as our TSRs'
uninstaller or SHUFHOOK and step through it
with "tp fffff" when operating on something
crucial such as interrupt 21h. Without this
function the machine will crash!
To enable this function to be called, first run
the command "r dco4 or= 8", or "INSTALL AMIS"
(install our AMIS multiplexer handler).
Other workaround: Use SILENT for TP and disable
DCO3 flag 4000_0000 (do not call int 21.0B to
check for Ctrl-C status).
Yet another workaround: Set flag DCO 8 (enable
fake InDOS mode, avoid calling int 21h).
REM: The source may be a pseudo IISP header. In this
case the ieEntry field should hold 0FEEBh
(jmp short $) and the ieSignature field
should indicate the source, eg "VT" for the IVT
or "NH" for inserting a New Handler.
This function is for use by lDDebugX (or lCDebugX). It instructs lDebugX (or lCDebugX) to install its DPMI entrypoint hook. It is called by the debuggable debugger right before it tries to install its own hook. Non-zero return values in AL indicate the function is supported. A return value of 0FFh in AL indicates success. Other non-zero return values indicate that no hook occurred. Non-DPMI builds of lDebug return zero. This function should be called only while the InDOS flag is zero.
lDebugX - Install DPMI hook
INP: al = 31h
OUT: al = FFh if installed
al = FEh..F0h if not installed but call is supported
al = 00h if not supported
CHG: -
STT: not in DOS
lDebugX - Reserved
INP: al = 32h
This function is by default provided by lDebugX (including the variants lCDebugX and lDDebugX) and is for use by lDDebugX as well as lCDebugX (in debuggable mode). It is called when the debuggable debugger is instructed to INSTALL AREAS.
lDebugX - Install fault areas
INP: al = 33h
dx:bx -> fault area structure of client
OUT: al = FFh if installed
al = FEh..01h if not installed but call is supported
al = 00h if not supported
CHG: al, bx, cx, dx, si, di, es, ds
REM: The area structure is defined in the lDebug sources'
debug.mac file. The first 32 bytes of the structure
start with a signature word, which is equal to the
word value CBF9h (encoding the instruction sequence
of stc \ retf) if the structure is not currently
installed into any debugger. The remainder of the
32 bytes, as well as the details of how the first
two bytes are used otherwise, are private to the
debugger that provides this service (the server).
The area structure may be far-called in 86 Mode. The
only currently defined function (in al) for this call
is function 00h, which attempts to uninstall the area
structure which is being called. It is valid for
either the server or the client to uninstall an
area structure if they so wish.
The fields of the structure behind the first 32 bytes
point to a number of sub-structures and area function
lists and area lists. All of these structures are
to be accessed using the same segment as the main
area structure. They contain linear start and linear
end addresses, which the client sets up before it
tries to install the areas. The linear start address
is also assumed to point to the segment base address
which is used as the reference for the area functions
and areas. (They do not have to match the offset part
actually used to run the code, but the lists must be
based on the linear start address.)
This function is provided by an ELD hooking into the debugger's AMIS handler. The ELD is installed by running ‘ext amismsg.eld install
’. The function provides a way to display a single message, of up to 384 Bytes, to the debugger terminal. (Older revisions only allowed a message size up to 128 Bytes.) The message is displayed by the ELD's inject handler, before the next debugger command is read in the cmd3
command loop.
lDebug - AMIS message ELD - Display message to debugger terminal
INP: al = 40h
dx:bx -> ASCIZ message, will be truncated if > 384 Bytes
OUT: al = 00h if not supported
al = FFh if supported and full message stored
(older revisions unconditionally returned al = FFh)
al = FEh if supported and truncated message stored
(truncation may occur at 383 or 384 Bytes of non-NUL text)
Note that the address in dx:bx
is a segmented 86 Mode address as the AMIS interface operates in Real/Virtual 86 Mode. However, dx
was chosen to pass the segment to simplify calling the interface from Protected Mode. PM code must ensure to fill the register with a segment value, not a Protected Mode selector.
This function is used by the ELD linker or hint.eld to pass along offset hints to TracList, refer to section 7.3.1.
This function is provided by the same ELD as function 40h. It allows to query whether a stored messsage has been displayed yet.
lDebug - AMIS message ELD - Query message status
INP: al = 41h
OUT: al = 00h if not supported
al = 01h if supported and no message is stored
al = 02h if supported and message is still stored (not yet displayed)
This function is provided by an ELD hooking into the debugger's AMIS handler. The ELD is installed by running ‘ext amisoth.eld install
’. The function exports the debugger's link data for use by "other link" ELDs running in another debugger instance.
INP: al = 42h
OUT: al = 00h if not supported
al = FFh if supported
bx (86M segment) => link tables
cx (selector) => link tables
dx (86M segment) => PSP
si (selector) => PSP
di -> link info in link tables section
If the debugger is without DPMI support or if not in PM, the selector values may be uninitialised or stale.
This function is provided by an ELD hooking into the debugger's AMIS handler. The ELD is installed by running ‘ext amiscmd.eld install
’. The function allows to send commands to be injected into the command loop of the debugger.
Multiple commands can be injected back to back. They will be injected in the order of the calls to this function. However, the buffer is of a fixed size. (Currently, 1024 Bytes.) If the buffer is full, an error will be returned to the caller.
INP: al = 43h
cx = flags, all reserved for now (must pass as 0)
dx:bx -> message in byte-counted string,
1 byte length (<= 254)
N bytes text, length matching the value of length byte
1 byte Carriage Return (= 13)
OUT: al = 00h if not supported
al = 01h if supported, but buffer is full
al = 02h if supported, but unknown bit set in cx
al = FFh if successfully stored in buffer
These are the services called by the debugger.
Used for output while InDOS, DCO flag 8 set, bootloaded, when ‘INSTALL BIOSOUTPUT
’ was used (DCO6 flag 200h set), or when DCO6 flag 100_0000h set.
Used for input while InDOS, DCO flag 8 set, bootloaded, or DCO6 flag 100_0000h is set.
Called by booted debugger to determine base memory size.
Used by lDebugX while in Protected Mode.
Read sectors from DOS drive. Used to implement L command.
Write sectors to DOS drive. Used to implement W command.
-input
injection. It is normally only needed when booting the debugger.
Used by the booted debugger to load scripts, ELDs, or kernel executables.
Boot load. Used if booting the debugger fails.
Used to access Alternate Multiplex Interrupt Specification TSRs. Can be used while bootloaded too.
Read sectors from DOS drive. Used to implement L command. Only used if the debugger is loaded as a DOS application or DOS device driver.
Write sectors to DOS drive. Used to implement W command. Only used if the debugger is loaded as a DOS application or DOS device driver.
DOS services. Only used while not InDOS. (Only used if the debugger is loaded as a DOS application or DOS device driver.)
EMS services. Used by X commands.
Extensions for lDebug (ELDs) can be loaded using the EXT command (section 10.19). Some ELDs operate in a transient way only; some memory is allocated to them when they load and they free this memory again after their run is done. Other ELDs can be installed residently and will reserve some of their memory as used beyond their initial run.
Displays information on memory use of the debugger. Can be installed residently with INSTALL
parameter to provide the LDMEM
command, and uninstalled with an LDMEM UNINSTALL
command.
The resident LDMEM
command as well as the transient ELD provide a number of keywords to control the output:
Free space
’. For each code instance, the code start, stop, and size is displayed in hexadecimal, as well as the size in decimal Bytes/KiB. For code instances marked as free, the code instance name is listed behind the size. This may be stale. For code instances which have an ELD data block allocated to them, the data start, stop, and size is displayed on a second line. Finally, if the ELD code buffer or the ELD data buffer have trailing free space, it is displayed as ‘Free space
’ at the end.
Some keywords are expanded to a list of other keywords:
The keyword ‘HELP
’ is handled specifically to display an online help page when specified to the transient ELD. It is not valid for the resident ELD's command, however.
If no keywords are specified, the default keyword is assumed as ‘ALLNOSEG
’.
Lists the command history of the debugger, or clears it. Can be installed residently with INSTALL
parameter to provide the HISTORY
command, and uninstalled with a HISTORY UNINSTALL
command.
Provides two subcommands:
If the transient ELD is run without a keyword, then the online help is displayed.
Re-creation of the DI and DIL commands of the debugger. This allows to use the DI commands when the debugger is built with the _INT=0
build option. Can be installed residently with INSTALL
parameter to provide the DI
command, and uninstalled with a DI UNINSTALL
command.
Refer to section 10.13.
Re-creation of the DM command of the debugger. This allows to use the DM command when the debugger is built with the _MCB=0
build option. Can be installed residently with INSTALL
parameter to provide the DM
command, and uninstalled with a DM UNINSTALL
command.
Refer to section 10.14.
As a special addition, the DM command implemented by the ELD additionally allows the following parameters:
Re-creation of the RN command of the debugger. This allows to use the RN command when the debugger is built with the _RN=0
build option (as is the default now). Can be installed residently with INSTALL
parameter to provide the RN
command, and uninstalled with an RN UNINSTALL
command.
Refer to section 10.40.
Re-creation of the RM command of the debugger. This allows to use the RM command when the debugger is built with the _RM=0
build option (as is the default now). Can be installed residently with INSTALL
parameter to provide the RM
command, and uninstalled with an RM UNINSTALL
command.
Refer to section 10.39.
Re-creation of the X commands of the debugger. This allows to use the X commands when the debugger is built with the _EMS=0
build option (as is the default now). Can be installed residently with INSTALL
parameter to provide the X
commands, and uninstalled with an X UNINSTALL
command.
Refer to section 10.57.
Re-creation of the DX command of the debugger. This allows to use the DX command when the debugger is built with the _DX=0
build option (which is the default as of release 9). Can be installed residently with INSTALL
parameter to provide the DX
command, and uninstalled with an DX UNINSTALL
command. This ELD requires a 386+ machine.
Displays information on install flags of the debugger. These are the nouns accepted by the INSTALL
and UNINSTALL
commands. Can be installed residently with INSTALL
parameter to provide the INSTNOUN
command, and uninstalled with an INSTNOUN UNINSTALL
command.
The operation that can be selected is:
After the operation, the name of one or more flags must be listed.
If no operation is specified, all flags are listed. The format is as follows for the first name of a flag:
0000
’ if a special flag.
+
’ if enabled, ‘-
’ if disabled, ‘?
’ if a special flag.
For subsequent alias names of a flag, only the name is listed.
Transient utility to reclaim unused space in the ELD code buffer and the ELD data blocks buffer. This is no longer needed because the debugger now includes the implementation of this tool and automatically reclaims memory before loading an ELD.
This ELD may still be needed in conjunction with ELDCOMP to reset the ELD buffers the way ELDCOMP expects them after a run.
A tool to compare an ELD with its XLD counterpart. XLD is the filename extension typically used to hold a build of an ELD with some linker optimisations. ELDCOMP allows to compare the two, helping to identify and locate relocation errors in the ELD to be tested.
Before running ELDCOMP, you may want to install nofreeoneshot
and uninstall paging
to avoid interfering with the operation of ELDCOMP. In addition, it may be useful to run the debugger with the /X switch and particularly the /Y switch to increase the size of the ELD buffers that may be needed by ELDCOMP.
Up to 8 commands are passed to ELDCOMP, separated by semicolons. The first and fifth command are typically EXT commands. The first one loads an XLD, whereas the fifth loads the corresponding ELD in the same way. The second and sixth command may invoke a residently installed XLD/ELD, and are typically the same. The third and seventh command are used to uninstall a residently installed XLD/ELD, if any, and are typically the same as well. The fourth and eighth command should be ext reclaim.eld
.
The output of the first pass of ELDCOMP is as follows:
If any of the mismatch messages were displayed, ELDCOMP will attempt to run its second pass. If the ELD data space doesn't suffice for running the second pass, the message "Not enough ELD data space left!" is displayed. You may want to retry with a debugger running with the /Y switch.
The output of the second pass of ELDCOMP starts out the same as the first pass's. However, the hashes are expected not to equal those of the first pass because the ELD data block lives at another offset. The second pass will additionally display one or both of the following parts:
The offsets displayed at the beginning and end of each line of the C commands are offsets within the ELD data block or the ELD code instance. They can be referenced in the ELD's listing file to find the source of the mismatch.
Once installed, this ELD hooks into the assembler. After a line is submitted to the assembler, the AFORMAT ELD will dump in hexadecimal the bytes written by the assembler.
This ELD hooks into the debugger's AMIS interface. It provides the AMIS functions 40h and 41h to send messages to the debugger terminal. (Refer to section 13.5.5 and section 13.5.6.) A message may consist of up to 127 bytes of text. The message is displayed either on the next command being read in the cmd3
command input loop using a command injection handler, or when the command ‘AMISMSG DISPLAY
’ is run.
This ELD is intended to be used in the outer debugger to receive the TracList offset hints sent by hint.eld or by the ELD linker of an ELD being loaded in the inner debugger.
This ELD once installed provides the ELDAMOUNT
variable. This variable can be read to obtain the amount of installed ELDs.
A converter for different numeric bases. Can accept a numeric parameter to be evaluated by the expression evaluator. Using a BASE=
specifier or the name of a base (HEXADECIMAL
, DECIMAL
, BINARY
, OCTAL
) the input number may be specified as a literal, which accepts unsigned numbers of up to 64 bits. Output is in the four known bases, formatted to resemble the expression evaluator's literal input format. Output can be in one arbitrary base using a trailing OUTPUT
keyword, followed by BASE=
, GROUP=
, and WIDTH=
specifiers. The BASES ELD can be installed residently using an INSTALL
parameter to provide the resident BASES
command.
Installs the COPYOUTPUT
commands using the INSTALL
parameter. Once a file is specified with ‘COPYOUTPUT NAME filename
’ the debugger opens the file to append to it. While not InDOS all output to the debugger terminal is written to the opened file.
Several commands are provided by this ELD:
The getinput mode determines how getinput output is written to the COPYOUTPUT file:
Allows to show or set the debugger config paths. This ELD operates as a transient ELD only. Run as:
This ELD runs as a transient ELD only. It is only valid to run this ELD when DOS is available.
Allows to run another command conditionally, using a command of the form IF [NOT] EXT "extension name" THEN command
. May be used as a transient ELD or installed residently using an INSTALL
command to provide the IF EXT
commands.
The extension name to provide must match an ELD code instance name as displayed by a command like ‘ext ldmem.eld eld
’. An ELD is considered installed if at least one non-free ELD code instance matches the specified name. The name is matched insensitive to capitalisation. The name must be specified with quote marks if it contains a blank. Names must not be longer than 8 bytes.
For transient use, the ‘IF [NOT] EXT
’ construct follows after the ELD filename. For instance, ‘ext ifext.eld if ext "amiscmd" then r
’ will run an R command if the AMIS command ELD is installed.
This ELD runs as a transient ELD only.
Displays the description lines for ELD files or SLD files that are specified with a single pathname pattern. The pattern may contain wildcards in the last component. After the pathname, several keywords may be specified:
VERBOSE
to display technical details,
HELP
to display the help,
SFN
to force use of the DOS SFN find interface instead of trying the DOS LFN find interface,
LIB
to recurse into library ELDs displaying the same infos for every embedded ELD.
/name
(where the name is a pattern that can contain question marks or a trailing asterisk) to filter library ELDs which match the name pattern. This allows to inspect one or a few of the embedded ELDs without listing all embedded ELDs.
Allows to print formatted output. Can be installed residently or used as a transient tool. The first parameter is a quoted string. Subsequent parameters provide data to format.
Supported escape codes:
Supported format codes:
Text strings are provided in one of three forms:
Allows to access the environment block to read or write variables. Can install a resident SET
command using an INSTALL
parameter, or run transiently using a RUN
parameter.
Running the command, the following parameters are accepted:
xxx
’ indicates which line to choose; ‘/0
’ chooses the first nonempty line, ‘/1
’ the second line, etc.
Installs an output hook into the debugger to display an underscore line after disassembling near or far jumps or near, far, or interrupt return instructions. An equals sign line is displayed after disassembly of a DOS termination call, if it is detected.
Once installed, the command ‘USEPARAT WIDTH expression
’ is provided. It sets the width of the lines to display, between 0 and #80. The default is #39.
Installs a command preprocessor hook to expand ‘%VARIABLE%
’ specifications in commands. This ELD can be used as a transient ELD or as a resident ELD. Once installed, variable uses in commands are expanded. Note that the command to uninstall the resident variable.eld is ‘VARIABLES UNINSTALL
’, not matching the filename of the ELD.
To use the ELD transiently, a parameter reading either RUN or DISPLAY is to be used. RUN will run the subsequent command with variable uses expanded. DISPLAY will instead display the subsequent text with variable uses expanded.
Installs a prefix command called WITH
. This can be used as WITH HEADER
or WITH TRAILER
to temporarily set DCO flags to enable D command headers or trailers. The ELD can also be used as WITH NODUMP
to temporarily set a DCO flag to disable the S command data dump from after S command search results. Command injection is used to reset the flags afterwards.
This ELD hooks into the debugger's AMIS interface. It provides the AMIS function 43h to inject commands into the debugger. (Refer to section 13.5.8.) This requires this debugger to have run ‘install amis
’.
In another debugger instance, inject.eld can be used to inject commands into this debugger.
This ELD hooks into the debugger's AMIS interface. It provides the AMIS function 42h to export the debugger's link info as an "other link". (Refer to section 13.5.7.) This requires this debugger to have run ‘install amis
’.
In another debugger instance, "other link" ELDs (ldmemoth.eld and instnoth.eld) can be used to access this debugger's data.
Port of Ralf Brown's AMIS TSR lister. This ELD can be installed residently or used transiently. If the parameter is not equal to INSTALL
, then the following parameters are accepted:
If no parameters are specified, then only one line is displayed per multiplexer, listing the vendor and product signatures and the description line of each multiplexer.
List directory entries in bootloaded mode. This ELD runs as a transient ELD only. The first parameter must be a pathname pattern. The last component may include wildcards. The available wildcards are:
?
’
*
’
After the pathname pattern, any number of keywords may follow:
When sorting, the ELD will detect a buffer for storing the filenames while scanning the directory in the first pass, for sorting the entries. If this buffer overflows, the ELD cannot succeed.
If the date of a directory entry is zero, which would be decoded as "1980-00-00", the date field is blanked out.
This ELD can be installed residently or used transiently.
When installed residently, three DBITMAP
commands are available in addition to the usual UNINSTALL
command:
SET0
followed by a list, to set the default string for 0 bits. The list must not exceed 10 bytes.
SET1
followed by a list, to set the default string for 1 bits. The list must not exceed 10 bytes.
RUN
keyword, followed by a range, or by an INTERNAL
keyword followed by a word, or by a STR
keyword followed by a list. (This command can be used transiently as well.)
After the RUN
keyword optionally there may be a SET0
or SET1
keyword followed by a list that ends with an END
keyword. After any number of such constructs a range or INTERNAL
keyword or STR
keyword must follow. These constructs allow to override the strings for a 0 bit or for a 1 bit for the duration of a single command.
The three choices for RUN
commands are handled differently:
INTERNAL
followed by a word
STR
followed by a list
The STR
mode allows processing escape codes indicated by a backslash. The following escapes are defined:
\\
’
\M
’
\H
’
\H8
’ is the same as a regular blank, ‘\H4
’ produces a half-width blank.
Examples:
-f 100 l 8 FF
-f 108 l 8 00
-ext dbitmap.eld run 100
2B03:0100 ********
2B03:0101 ********
2B03:0102 ********
2B03:0103 ********
2B03:0104 ********
2B03:0105 ********
2B03:0106 ********
2B03:0107 ********
-ext dbitmap.eld run 108
2B03:0108 ........
2B03:0109 ........
2B03:010A ........
2B03:010B ........
2B03:010C ........
2B03:010D ........
2B03:010E ........
2B03:010F ........
-ext dbitmap.eld run set1 '#' end 100 l 2
2B03:0100 ########
2B03:0101 ########
-ext dbitmap.eld run internal #'L' * 8
0436:9226 ..**....
0436:9227 ..**....
0436:9228 ..**....
0436:9229 ..**....
0436:922A ..**....
0436:922B ..**....
0436:922C ..******
0436:922D ........
-ext dbitmap.eld run str "\MLDOS"
....**...*****....*****...*****.
....**...**..**..**...**.**...**
....**...**...**.**...**..**....
....**...**...**.**...**...***..
....**...**...**.**...**.....**.
....**...**..**..**...**.**...**
******...*****....*****...*****.
................................
-
This ELD can only be used transiently. DOS must be available to run this ELD.
If only a drive letter and colon are specified, then the current drive is changed to the indicated drive.
If the first parameter is a keyword ‘BOTH
’ then the current drive is changed to the indicated drive and the current directory on that drive is changed to the indicated path. It is an error to specify the keyword ‘BOTH
’ if no drive letter is given.
If the first parameter is a keyword ‘IFBOTH
’ then if a drive letter is specified, the current drive is changed to the indicated drive. Then the current directory is changed to the indicated path.
The current drive is changed by using interrupt 21h function 0Eh, and checked for success by using interrupt 21h function 19h. The current directory is changed by using interrupt 21h function 3Bh.
List directory entries using DOS. This ELD runs as a transient ELD only. DOS must be available to run this ELD.
The first parameter must be a pathname pattern. The last component may include wildcards. The available wildcards are:
?
’
*
’
After the pathname pattern, any number of keywords may follow:
When sorting, the ELD will detect a buffer for storing the filenames while scanning the directory in the first pass, for sorting the entries. If this buffer overflows, the ELD cannot succeed.
If the date of a directory entry is zero, which would be decoded as "1980-00-00", the date field is blanked out.
This ELD runs as a transient ELD only. DOS must be available to run this ELD.
If no parameters are specified, gets the current DOS drive and displays it as the drive letter followed by a colon.
If the parameter ‘GET
’ is specified without a trailing pathname, act as if no parameters are specified.
If the parameter ‘GET
’ is specified with a trailing pathname, parses the pathname and displays its drive letter if any is specified, or else the current drive.
If the parameter ‘SET
’ is specified with a trailing pathname, then the pathname must contain a drive letter. The current drive is changed to the specified drive.
If the parameters ‘SET QUIET
’ are specified, then the trailing pathname and its drive letter specification are optional. If no such drive specification is included, the command silently does nothing.
This ELD runs as a transient ELD only. DOS must be available to run this ELD.
If no parameter is specified, displays the current directory on the current drive.
If a parameter ‘NUMBER
’ is specified, then a trailing plain number parameter is parsed from an expression. This number must be below 32. It specifies the drive of which to get the current directory.
Otherwise the parameter must be a drive letter followed by a colon. It specifies the drive of which to get the current directory.
Installs residently to guess EXT and Y command filename extensions. Install using the INSTALL
keyword.
Once installed, running an EXT or Y command without a filename extension will guess the filename extension. The EXT command will guess the extension as ‘.ELD
’ first and ‘.XLD
’ second. The Y command will guess the extension as ‘.SLD
’.
The following commands are additionally provided:
Injects commands into other debugger instance using the other's AMIS function 43h (refer to section 13.5.8). (This function is provided by the AMISCMD ELD, refer to section 15.27.)
INSTNOUN variant (refer to section 15.9) that operates on another debugger instance. This requires the other instance to have installed the AMISOTH ELD (refer to section 15.28) and its AMIS handler to provide the AMIS function 42h (refer to section 13.5.7).
LDMEM variant (refer to section 15.1) that operates on another debugger instance. This requires the other instance to have installed the AMISOTH ELD (refer to section 15.28) and its AMIS handler to provide the AMIS function 42h (refer to section 13.5.7).
Installs residently to display status of program-loading L command. Running a program-loading L command will then display some information on the file being loaded and the process that was created.
Additionally the following commands are provided:
Provides path search and filename extension guessing for the K and N commands. This ELD can be installed residently or used transiently.
If used transiently, the first parameter must be the command letter ‘K
’ or ‘N
’. The path search is done on the program load filename specified to the given command, and then the command is run with the resulting pathname. A warning is displayed if the WHILE buffer or DOS are not available, as path search will not happen in this case. An error is displayed if the expanded pathname doesn't fit into the line_in
buffer.
If used residently, the K or N commands are intercepted in the command handler. The path search is done on the program load filename, and if found the command is modified to receive the resulting pathname. No warning is displayed if the WHILE buffer or DOS are not available, but path search will not happen in this case. An error is displayed if the expanded pathname doesn't fit into the line_in
buffer.
The following commands are additionally provided when installed residently:
Library of ELDs to be used instead of single files. This library contains most other ELDs. To load an embedded ELD, specify its filename or ELD code instance name as a parameter to the EXTLIB ELD. The name may be specified with an ‘.ELD
’ or ‘.XLD
’ filename extension, which is ignored. All trailing parameters after the name are passed to the embedded ELD.
If no ELD name or the name ‘HELP
’ is specified, the EXTLIB ELD's help is displayed instead. If after the ‘HELP
’ keyword one more keyword follows, the short or long list of embedded ELDs is displayed along with the EXTLIB help:
This is used exactly the same as extlib.eld (section 15.42) except that the embedded ELDs are compressed using heatshrink compression. This means that operations like the HELP DESCRIBE
list or scanning for an ELD code instance name may take longer than with the uncompressed extlib.eld.
Quits the machine. Can be installed residently or used transiently. When run with a parameter ‘RUN
’ then this ELD will attempt to quit (shut down) the currently running machine or VM. When ‘install quickrun
’ has been used, running this ELD without a parameter will also attempt to quit the machine.
This ELD only operates transiently, and requires DOS to be available.
The first parameter is either GET
or SET
. The second parameter is an expression that evaluates to a process handle number. This number must refer to a process handle that is opened in the current debuggee process.
For getting the seek, after the process handle there may follow the VAR index
specification. If it is given, then the specified V variable (section 12.16) is set to the current 32-bit seek. Further, a QUIET
keyword may follow to suppress the output of the command. If not both a VAR
and a QUIET
keyword are specified, then the output will be like the following example:
-ext dosseek.eld get 5
Process handle 5 current seek is 0000_1400 #5120 5.0 KiB
For setting the seek, after the process handle another keyword must follow:
After the keyword, an expression follows which gives the 32-bit offset to seek to.
Define simple aliases that are replaced in a command preprocess handler. This ELD must be installed residently.
To add an alias, run as ‘ALIAS ADD aliasname expansion
’.
To delete an alias, run as ‘ALIAS DEL aliasname
’.
To list aliases, run as ‘ALIAS LIST
’ or ‘ALIAS LIST aliasname
’.
Display a DOS drive's DPB (MS-DOS v4 to v6 layout, optionally with FreeDOS, MS-DOS v7.10, or EDR-DOS FAT32 extensions). Can be installed residently with INSTALL
parameter to provide the DPB
command, and uninstalled with a DPB UNINSTALL
command.
An optional parameter specifies the drive to access. It defaults to zero. A drive letter may be specified with a trailing colon. Otherwise, a number is parsed. Zero means to access the DOS current drive. The number one corresponds to drive A:, two to drive B:, and so on. The DPB is accessed using interrupt 21h function 32h, which requires DOS to be available. Enhanced DR-DOS may return DPBs for FAT32 drives on this function whereas FreeDOS and MS-DOS 7.10 may refuse to do so.
Instead of the drive parameter, the keyword ADDRESS
may be specified. It must be followed by an address parameter. This address specifies where to read the DPB from. This address uses a selector in Protected Mode, except when the parameter is prepended by a dollar sign.
Instead of the drive parameter, the keyword LIST
may be specified. After this keyword, either the keyword SKIP
or the keyword DRIVE
has to follow.
After the keywords LIST SKIP
a number is parsed. The list of DPBs is detected from the DOS List of Lists and a number of DPBs are skipped in the chain until reaching the target. The number specified after the keyword indicates how many DPBs to skip.
After the keywords LIST DRIVE
a drive is parsed. This may be either a number or a drive letter. The list of DPBs is detected from the DOS List of Lists and the DPBs are scanned for one matching the specified drive in its first field.
Between the LIST
keyword and the next keyword, an address for the DOS List of Lists may be inserted. This starts with an AT
keyword. Then an address parameter is parsed. This address parameter is always a 86 Mode segmented address, even in Protected Mode. The address parameter's segment defaults to the DOS data segment as obtained from reading the 86 Mode interrupt 31h vector's segment. The address specifies where to find the DOS List of Lists.
Before the LIST
or ADDRESS
keywords or the drive specifier, the keyword EXTENDED=
may be specified to display an EDPB. It must be followed by one of the known DOS version keywords. These are: FREEDOS
, MSDOS
, or EDRDOS
. The DOS keyword indicates what format of EDPB to display.
Before the EXTENDED=
keyword, the keyword ONLY
may be specified. If specified, only the EDPB extended fields are displayed, skipping the base DPB.
As an example, a drive with a typical 1440 KiB diskette file system will display as follows:
-ext dpb.eld A:
DOS function 32h call returned al=00h.
DOS function returned 86 Mode address=00D9:1E40
Drive 00: 00 0
DeviceUnit 01: 00 0
BytesPerSector 02: 0200 512
HighestSectorInCluster 04: 00 0
SectorsPerClusterShift 05: 00 0
ReservedSectors 06: 0001 1
AmountFATs 08: 02 2
AmountRootEntries 09: 00E0 224
DataStart 0B: 0021 33
MaximumCluster 0D: 0B20 2848
SectorsPerFAT 0F: 0009 9
RootStart 11: 0013 19
DeviceHeader 13: 0070_0610 7341584
MediaID 17: F0 240
Accessed 18: 00 0
Next 19: 00D9_1E7D 14229117
FreeClusterNext 1D: 0000 0
AmountFreeClusters 1F: FFFF 65535
-
Note that at least the FreeDOS kernel always resets the FreeClusterNext
and AmountFreeClusters
fields whenever the DPB is obtained using interrupt 21h function 32h.
Invalid drives (including file system redirector drives) will result in a nonzero return in AL:
-ext dpb.eld Z:
DOS function 32h call returned al=FFh.
-
Once installed, this ELD hooks into the debugger's R command register dump. If the dump is in 16-bit mode and not configured for 40-column display, and lDebugX is not in Protected Mode, this ELD will take effect. It will display a trailer to the first line of the register dump, listing what text bytes are found at the addresses indicated by DS:SI and ES:DI.
Example:
AX=5A20 BX=0000 CX=051C DX=0000 SP=FFFE BP=0000 SI=0082 DI=0000 SI='s' DI=top
DS=5A21 ES=5A21 SS=5A21 CS=5A21 IP=04D0 NV UP EI PL ZR NA PE NC
5A21:04D0 AC lodsb
Once installed, this ELD hooks into the debugger's R command register dump. If the dump is in 16-bit mode and not configured for 40-column display, and lDebugX is not in Protected Mode, this ELD will take effect. It will display a trailer to the second line of the register dump, listing what text is found starting at the address indicated by DS:DX.
Example:
AX=0A07 BX=FD2B CX=0000 DX=02BB SP=FFFC BP=0000 SI=0087 DI=0000
DS=5A21 ES=5A21 SS=5A21 CS=5A21 IP=03FF NV UP EI PL NZ NA PO NC "Other kernel,"
5A21:03FF 80FFFD cmp bh, FD
This ELD can be used as a transient command or installed residently. Install with an INSTALL
keyword. The CHECKSUM
command accepts a range parameter, with a default length of 512 Bytes. After the range an optional initial sum can be specified. This sum defaults to 1.
The checksum is an unsigned 16-bit number. It is calculated according to the following pseudocode:
initial sum = 1
for every byte in range:
sum = sum * 31
sum += byte value
sum = sum & 0FFFFh
This ELD can be used as a transient command or installed residently. Install with an INSTALL
keyword. When run, this command displays TracList listing offset hints for all currently installed ELDs of the debugger running this ELD. The hints are written to the terminal of another lDebug instance, assumed to be the outer debugger. This utilises that outer debugger's AMISMSG service, so the amismsg.eld and AMIS interface must be installed in that debugger.
An optional keyword SKIPSELF
may be specified. If it is given, then the hint.eld will not write a hint corresponding to its own ELD code instance. This is useful for running with eldcomp.eld.
This ELD can be used as a transient command or installed residently. Install with an INSTALL
keyword. When run, this command accesses the other link debugger's installed ELDs. This utilises the other link debugger's AMISOTH service, so the amisoth.eld and AMIS interface must be installed in that debugger. The HINTOTH command will display TracList listing offset hints for all currently installed ELDs of the detected other link debugger.
Utilities to work with int 13h unit partitions and geometry. Can be used as a transient command or installed residently.
This ELD provides the same basic command as the S command (section 10.47). However, it allows some extensions as well. The S ELD can be used transiently or installed residently. To invoke a resident S ELD the command letter S must be recognised as a string, that is the search range's first letter or digit must not immediately follow the ‘S
’ letter.
The basic command format is:
search S range [REVERSE] [SILENT number] [RANGE [CAPS] range|list]
The keywords REVERSE, SILENT, and RANGE are all described in section 10.47. The list parameter is based on the debugger's generic list parameter type, refer to section 8.5. However, it adds three new keywords:
This keyword when encountered in the search pattern list inserts one wildcard item of the current element size. As the default item size is byte, a WILD keyword defaults to inserting a byte-sized wildcard. After for example AS WORDS, a WILD keyword instead will insert a word-sized (2-byte) wildcard.
Using wildcards may degrade search performance.
This keyword enables the search to treat every subsequent byte of the search pattern as requiring capitalisation be folded, so that small letters and big letters (in the ASCII alphabet) will match one another.
The CAPS keyword may appear in a search pattern list, or after the RANGE keyword. In the latter case capitalisation is folded for the entire search pattern range.
Using the CAPS keyword may degrade search performance.
This keyword is only allowed in a search pattern list. It resets the status set up by the CAPS keyword, so that byte values are matched exactly again.
The wildcard and caps search modes are implemented using a tag buffer. Every tag consists of two bits, one of which indicates that the corresponding byte is a wildcard and the other one for capitalisation folding.
There are three families of internal functions to handle search that may involve nonzero tags:
The first function is used to scan for the pattern's first byte in almost the entire search space. The second function is used to scan the very last possible match of the first byte. This distinction is needed because of how the debugger handles search space length of up to 64 KiB with a 16-bit counter.
Both of these functions must work either with Direction Flag cleared (UP) or set (DN). Additionally, there is a branch to an a32 variant of each of these functions to allow searcning in 32-bit segments.
The repeated scan byte functions are implemented by calling the single byte scan functions in an appropriate loop.
If the tag of the search pattern's first byte is zero, simple functions that only consist of the a32 dispatching and a single string operation are used. Tnis is a speed optimisation.
The third function is used after a candidate match is identified from the first byte scan. If any tags are nonzero in the search pattern, then complicated functions are used. These will loop like the single string operation ‘repe cmpsb
’ would, but internally they use the single byte scan functions to handle tags appropriately. The tags are iterated through alongside the pattern values.
This function always works with the Direction Flag cleared (UP) although it would work otherwise as well. As for the other two functions there is a32 dispatching for searching in 32-bit segments.
If all tags are zero, the trailing string comparison function is handled by simple functions consisting of a32 dispatching and a single string operation. If the RANGE CAPS keywords are used together, medium complex functions are used which always pass an immediate tag value to the single byte scan function. Otherwise the full handling of all possible tag combinations is done, and the loop keeps track of both the current offset, the remaining length, and the current bit position in the tag buffer.
An ELD file may have an "ELD trailer header". If present, this structure must be located at the very end of the file. It has the following structure:
struc ELD_TRAILER_HEADER
eldthSignature: resb 8 ; "ELD1TAIL"
eldthHeaderOffset: resd 1 ; negative displacement from eldthSignature,
; add this dword to the seek offset
eldthReserved: resw 1 ; reserved, must be initialised to zero
eldthChecksum: resw 1 ; sum of all words in trailer header = 0
endstruc
endarea ELD_TRAILER_HEADER, 1
If the trailer header is present, then the seek offset of eldthSignature plus the value in dword [eldthHeaderOffset]
gives the seek offset of the ELD header. The ELD header must start before the trailer header, that is the computed ELD header offset must be below the offset of the ELD trailer header. If the ELD trailer header is not present then the ELD header must be located at seek offset 0, at the very beginning of the file.
(The eldapend.c tool allows to append an ELD to an existing file along with the appropriate ELD trailer header.)
The ELD header structure has the following structure:
; ELD executable header
struc ELD_HEADER
eldhSignature: resb 4 ; "ELD1"
resb 3 ; reserved
resb 1 ; 26 (Ctrl-Z)
eldhCodeOffset: resd 1 ; position displacement from eldSignature
eldhCodeImageLength: resw 1 ; amount bytes
eldhCodeAllocLength: resw 1 ; amount bytes, added to prior
eldhDataOffset: resd 1
eldhDataImageLength: resw 1 ; (zero allowed)
eldhDataAllocLength: resw 1 ; (zero allowed)
eldhCodeEntrypoint: resw 1
eldhReserved: resb 6 ; reserved
endstruc
endarea ELD_HEADER, 1
The first four bytes and the 8th byte are checked to match the known format by the debugger. Extensions should go into the last 6 bytes which should be zero-initialised for now. That is, unless a new ELD format is chosen, which is indicated by changing the signature in the first four bytes. The header is exactly 32 Bytes long.
The header must be found either using the ELD trailer header or it must be found at the very beginning of the ELD file. The offsets given in the fields eldhCodeOffset
and eldhDataOffset
are to be interpreted as being displacements that are added to the base equal to the position of the start of the ELD header structure. (This is the position of the eldhSignature
field.)
The length fields are typically paragraph-aligned but this is not a requirement. However, the resulting allocation of code and data space in memory is always rounded up to a paragraph boundary. The image and alloc length fields for either the code space or the data space must not overflow 16 bits when added. (Code and data together may exceed 64 KiB however.) Keep in mind that ELD code space is typically 8 or 16 KiB (up to 65_520 Bytes maximum) and ELD data space is by default 16 KiB.
Allocation beyond the images is not generally initialised by the debugger. If the ELD wishes to initialise it, this needs to be included in the ELD code.
The entrypoint field is an offset within the code section. The exact IP value is derived from this by adding the ELD's instance base offset to the entrypoint field value. The CS value points to the ELD code segment or a code selector referencing the same segment. The entrypoint is entered with the following function protocol:
; INP: es:dx -> loaded initial ELD image
; ELD instance structure filled
; es => ELD code area
; ds:di -> link info
; ss:bx -> loaded initial data
; ss:si -> command line tail
; cs:ip -> entrypoint
; ss:sp -> far return address for current mode
; STT: UP, EI
All other registers (cx, ax, bp, fs, gs, high words) are don't cares.
It is expected that the entrypoint will run a linker, which uses the link info pointed to by DS:DI to link the ELD with the exposed interfaces of the debugger.
Each ELD instance is stored in the ELD code space with the following format:
; ELD instance header
struc ELD_INSTANCE
eldiStartCode: resw 1 ; -> this structure itself (para aligned)
eldiEndCode: resw 1 ; -> behind memory used by instance (para aligned)
eldiStartData: resw 1 ; -> data in data entry section (para aligned), or 0
eldiEndData: resw 1 ; -> behind data (para aligned), or 0
eldiIdentifier: resb 8 ; blank-filled text
eldiFlags: resw 1 ; flags
eldiReserved: resb 14 ; reserved
endstruc
; flags for eldiFlags:
eldifResident: equ 1
The first field references its own address. The second field indicates where the next ELD instance lives, unless it matches the value of word [extseg_used]
. The second field must always be at least the value of the first field plus 32 (the size of the instance header). The third and fourth field specify where the ELD data block associated with this ELD instance lives, unless both fields are zero indicating no ELD data block. All of these first four fields are initialised by the loader (usually the debugger EXT command handler) upon initialising an ELD. However, the ELD is free to modify them within the given constraints. (Note that the ELD buffer fields described in section 16.6.1 must be modified to match the ELD instance fields, depending on how they are changed.)
The fifth field contains an 8 byte all-printable-ASCII text name that identifies the ELD. It should be initialised within the ELD executable. When shorter than 8 bytes, it should be padded with blanks (value 32).
The flags field indicates whether the ELD instance is resident. If it is resident, then other commands (including the EXT command and ELD space reclaim) will not re-use the code or data areas referenced by this instance. If it is not resident, then any re-entry into the debugger's cmd3
command loop should be assumed to overwrite this instance's memory. Note that any DOS I/O and any debugger code that may branch to the debugger error handlers may become a point at which the debugger returns to the command loop.
The remaining fields are reserved and should be initialised to all zeroes.
The ELD link info table looks like the following structure:
; ELD link info header
struc ELD_LINKINFO
eldlSignature: resw 1 ; 0E1D1h
eldlReserved: resw 2
eldlUseLinkHash: resw 1
eldlDataAmount: resw 1
eldlDataPrefixes: resw 1
eldlDataEntries: resw 1
eldlDataAddresses: resw 1
eldlCodeAmount: resw 1
eldlCodePrefixes: resw 1
eldlCodeEntries: resw 1
eldlCodeAddresses: resw 1
endstruc
The eldlUseLinkHash
field should be 1 to indicate hashes are used, and 0 to indicate the prefix text is used instead. Other values are not valid to current ELDs.
It is expected that a linker embedded into an ELD will use this table to link to the exposed debugger interfaces. The linker may also be used to resolve internal references of the ELD to its own code section and data block, both of which are loaded to dynamically chosen offsets.
The amount fields indicate how many different links of each type there are. All three arrays of the same type have as many entries as the amount field of that type indicates.
The prefixes table contains either a 16-bit hash value per link name, or the first two text bytes of each link name. The hash value is calculated using the symbolic branch text hash function, which is implemented as follows:
hash = 1
for each text value byte:
hash = (hash * 31 + value byte) & 0FFFFh
The entries table contains a word per link that points to a byte-counted message string in the same segment as the link info table. This message starts with a byte giving the remaining length of the message. If hashes are used, the message is the entire link name. Otherwise, the message is the link name remaining after the first two bytes. (Link names shorter than two bytes are not allowed. Link names should be at most 64 bytes long.)
The address table for the data links contains one word per link. This address is equal to the data link value, typically but not always an offset in the debugger stack segment.
The address table for the code links contains two words per link. The first word is equal to the code link offset value. This is the address of the code in its respective code section. The second word indicates which section the link points to. It must be below 3, and indicates the section as follows:
lDEBUG_CODE
lDEBUG_CODE2
(only used if _DUALCODE build option enabled)
lDEBUG_ENTRY
(not used yet)
These values must be used with the contents of the linkcall_table
to determine the offsets within the default ELD (LINKCALL
) which must be called in order to transfer control to the respective section. The reference to the link call table must be found by linking part of the linker itself first, using the data link to this table.
The link call table contains mappings from section values in the code links to offsets within the ELD code segment. These offsets are entries into the builtin default ELD, called LINKCALL. This ELD is installed by the debugger's init if possible. It currently occupies a fixed size of 128 Bytes, including its own ELD instance header stored at offset 0 in the ELD code segment. Thus if no regular ELDs are currently installed residently then a regular ELD will always be loaded to offset 0080h in the ELD code segment. (Thus a --local-offset 80h
switch to TracList is useful.)
The link call table has the following format:
An ELD link call is constructed as follows:
The ELD linker is responsible for filling in this structure for every link call that is to be used. The displacement is calculated from the call offset field of the link call table entry matching the section value from the code link. It must be calculated by subtracting the offset behind the near call rel16 instruction. The offset word to call is copied from the code link directly.
The ELD linker is composed of three files:
The ELD data macros contain the following mmacros for users:
internalcoderelocation
DATA
’ to indicate a data section. Links a 16-bit word to the place the ELD code section is loaded. Parameter specifies offset from current assembly address, defaulting to -2
to place the relocation at the end of the prior instruction. The relocation must be filled with an address that uses the code section vstart, typically a label in the code section.
internaldatarelocation
DATA
’ to indicate a data section. Links a 16-bit word to the place the ELD data section is loaded. Parameter specifies offset from current assembly address, defaulting to -2
to place the relocation at the end of the prior instruction. The relocation must be filled with an address that uses the data section vstart, typically a label in the data section.
linkdatarelocation
DATA
’ to indicate a data section. Links a 16-bit word to a data link of the debugger. First parameter is data link name. Second parameter specifies offset from current assembly address, defaulting to -2
to place the relocation at the end of the prior instruction. Third parameter is the keyword ‘required
’ (default) or ‘optional
’. The relocation must be filled with an address based on the label relocateddata
. The value of this label is subtracted during linking, and the value of the data link is added. If the data link is optional, a missing link will have the linker zero the relocation field.
The ELD code macros contain the following mmacros for users:
houdini
_HOUDINI=0
define. Can be disabled at run time by clearing DCO7 flag 100h or disabling debuggable mode.
extcall
DATA
’. An appropriate reference to a code link is added to the link tables. There is a second parameter allowed, defaulting to ‘required
’. The other options are ‘optional
’ and ‘PM required
’. When the code link for an optional extcall is not found, the near call instruction is overwritten with three NOP instructions.
extcallcall
retn
instruction. This should only be used in the CODE section, that is a section with type not equal to ‘DATA
’. Multiple extcallcall
uses to the same target will use the same extension call site. This is the same code size for two calls, and saves code size for three or more calls. However, it deoptimises the call stack because an additional indirection is added. (The extension call site spends another word on the near return address that points back to the extcallcall
user.) This macro cannot be used after eldcall_dump_callcall is used.
eldcall_dump_callcall
eldcall_dump_callcall ELDCALL_CALLCALL_LIST
’. Dumps the extension call sites for all prior uses of the extcallcall
mmacro. Also will disable further use of the extcallcall
mmacro. This should be used in the CODE section, before the label to calculate the resident or installed size. (Resident size is the ELD code size after the linker is discarded. Installed size is the ELD code size that remains if the ELD is installed residently.)
The ELD linker does not define any mmacros. The assembly source file generally should be included at the very end of the CODE section, and must be placed after all uses of the ELD data and code macros. The only lines after the include directive should be an alignment directive and the equate for the code section size.
The linker uses two labels: ‘linker
’ is the code entrypoint into the linker, which should be called like this:
; INP: es:dx -> loaded initial ELD image
; ELD instance structure filled
; es => ELD code area
; ds:di -> link info
; ss:bx -> loaded initial data
; ss:si -> command line tail
; cs:ip -> entrypoint
; ss:sp -> far return address for current mode
; STT: UP, EI
This is typically entered into the ELD executable header's field named eldhCodeEntrypoint
, using a directive like ‘dw linker - code
’.
The second label used by the linker is ‘start
’, which the linker will branch to once it has successfully finished linking. This is called with the following protocol:
; INP: es => ELD code segment (writable)
; ds:si -> command line tail
; ds:bx -> ELD data block
; ss:sp -> far return address for current mode
; STT: ds = ss, UP, EI
The far return address passed to these two entrypoints allows to return to the debugger without the need for a code link to the cmd3
command loop. It expects a result code in ax, which it will display as an error code in case it is above-or-equal 0FF00h. The linker returns with code 0FFFFh if a required link is missing. The reclaim ELD returns with code 0FFFDh on errors.
The linker will use two passes by default. The first pass links the linker itself. The second pass links the ELD application. If the first pass succeeded, the second pass may utilise the debugger output interfaces to display diagnostic messages (warnings or errors). The second pass will thus emit error messages indicating exactly which links are missing, if any.
The ELD code section is referenced with the following variables:
extseg
extcssel
extdssel
extseg_size
extseg_used
eldiEndCode
of the last ELD instance
The ELD data area is referenced with the following variables. It always lives in the data/entry/stack section of the debugger.
extdata
extdata_size
extdata_used
The extdata_size
and extdata_used
values have to be added to the offset base in extdata
to receive offsets in the debugger data section. The extdata + extdata_size
calculation must not overflow and may result in up to 65_520. This maximum is reached if the /Y=MAX switch to init was specified.
The ELD command handler allows resident ELDs to be called whenever the cmd3
command loop of the debugger has received a command and is about to execute it. The ELD command handler is called soon after the command loop has received a command from the getline
function. It is valid for the ELD command handler to modify the buffered text, pass on control to the subsequent command handler without changes, or completely take over the command. In the latter case, the ELD should branch back to cmd3
once it is done executing the command.
Multiple ELDs can install command handlers. The command handlers consist of a certain structure which contains either an extcall to the label cmd3_not_ext
(if no further ELD has installed a command handler) or a downlink made of a rel16 near jump to the next ELD's command handler. Branching to this downlink or extcall structure allows to pass on a command (modified or not) to the next ELD, or finally to the debugger's normal command processing.
The command handler entrypoint is called with the following registers:
; INP: al = first non-blank byte of command text
; si -> subsequent command text
; command text is stored in line_in variable
; sp = word [savesp]
; STT: ds = es = ss
; UP, EI
; may be in Protected Mode, Real 86 Mode, or Virtual 86 Mode
The same protocol should generally be followed for passing on a command to the next command handler or back to the debugger's command processing.
The command entrypoint must adhere to this structure for the first ELD installing a command handler:
cmd3_not_ext
, padded to 8 Bytes size (starts with 0E8h)
If another ELD command handler is already installed, one of the two ELDs will have its command handler modified as follows:
The top-most ELD command handler is pointed to by the word variable ext_command_handler
. To install or uninstall a command handler, an ELD should use a data link to this variable.
The suggested procedure for installing a command handler is:
ext_command_handler
ext_command_handler
The suggested procedure for uninstalling a command handler is:
ext_command_handler
ext_command_handler
ext_command_handler
The ELD command injection allows an ELD to run most of the cmd3
command loop and then regain the control flow. At that point, the ELD may either return to the cmd3
command loop, or it may inject a command of its own creation, or it may continue the last part of the command loop which calls getline
.
Installing command injection is done by writing an offset of a handler within the ELD code section into the debugger variable ext_inject_handler
. Note that this variable is always cleared to zero when it is read by the cmd3
command loop. To do command injection again, the inject handler needs to write to the variable again.
An inject handler being installed may preserve the prior value of the ext_inject_handler
variable and restore the value upon uninstalling itself.
The inject handler is called with this protocol:
; INP: sp = word [savesp]
; line_out -> prompt message
; di -> behind prompt message
; STT: ds = es = ss
; UP, EI
; may be in Protected Mode, Real 86 Mode, or Virtual 86 Mode
The inject handler may usually branch to one of three entrypoints:
cmd3
to restart command loop
cmd3_not_inject
to have command loop continue to call getline00
next (must pass di -> behind prompt message)
cmd3_injected
to have command loop accept an injected command
In the latter case, the entrypoint is to be branched to with the following protocol:
; INP: al = first non-blank byte of command text
; si -> subsequent command text
; command text is stored in line_in variable
; sp = word [savesp]
; STT: ds = es = ss
; UP, EI
; may be in Protected Mode, Real 86 Mode, or Virtual 86 Mode
The ELD preprocess handler allows resident ELDs to be called whenever the cmd3
command loop of the debugger has received a command and is about to execute it. The ELD preprocess handler is called directly after the command loop has received a command from the getline
function (or from an inject handler). It is valid for the ELD preprocess handler to modify the buffered text, or pass on control to the subsequent handler without changes.
Preprocess handlers should not completely take over commands passed to them. The purpose of preprocess handlers is to see commands first, before they are passed to the ELD command handler chain. That means all installed preprocess handlers will see a command, even if one of the ELD command handlers will take over the command execution.
Preprocess handlers have the same structure as ELD command handlers, except they use the debugger variable ext_preprocess_handler
to point to the first preprocess handler, and the last preprocess handler should chain to cmd3_preprocessed
. Their installation and uninstallation procedures are the same except for using the other variable and branch destination. The protocol comment shown for ELD command handlers also applies to preprocess handlers.
This handler hooks into the debugger's AMIS interface. The handler is called early, after matching the AMIS multiplex number but before any of the implementations of debugger functions.
AMIS handlers have a similar downlink structure in their entrypoint as command handlers and preprocess handlers. They start with a strict short jump, but the rel8 displacement is only equal to 3. A downlink is composed of a strict near jump. The final AMIS handler contains a far return instruction instead, and the downlink field is padded to 3 bytes using two NOP instructions.
The handler is called with this protocol:
; INP: NC
; ds => entry segment
; ss:sp -> far return address, ds, dx, cx, bx, ax, iret frame
; cx destroyed
; ax, bx, dx, bp, si, di, es, ss = original
; fs, gs, high words = original
; OUT: stack frame modified if desired
; CY to iret after popping frame,
; should retf (not use downlink)
; NC to process function as usual (stack al = function),
; may use downlink
; STT: Real/Virtual 86 Mode
; ss != debugger data/entry/stack segment
; UP
; DI
The ELD multi-purpose puts
handler provides a hook into the debugger's debug terminal output. This allows an ELD to filter, store, and/or suppress output generated by other parts of the debugger or other ELDs.
A multi-purpose puts
handler is installed by writing an offset in the ELD code section to the debugger variable ext_puts_handler
. If this variable is nonzero, calls to puts
(that is, all normal debugger interface output) will transfer control to the specified handler. The puts
handlers now form a chain similar to command handlers, except that the variable ext_puts_handler
is used and the final handler chains back to puts_ext_done
. (Prior revisions of the ELDs did not create a chain of handlers.)
If to suppress the message output, the handler should resume the normal control flow of the debugger by branching to puts_ext_done
using an extcall (not an extcallcall!) with CY set. The es register should be unchanged but all of ax, bx, cx, and dx need not be preserved. 386-specific register upper words should be preserved. If to continue to output the message, the handler should chain to the next handler with NC and es unchanged, and es:dx -> the message to display, length ax. The bx and cx registers never have to be preserved.
Each handler is called with this protocol:
; INP: es:dx -> message to display
; ax = length of message
; NC
; OUT: CY to not pass on the message for display or write to silent buffer,
; have to transfer control directly to puts_ext_done (using extcall)
; ax and dx may be changed
; NC to pass on the message,
; may chain to next handler (or transfer to puts_ext_done)
; CHG: bx, cx, (upon transfer to puts_ext_done with CY: ax, dx)
; STT: ds = ss
; UP, EI
; may be in Protected Mode, Real 86 Mode, or Virtual 86 Mode
; in Protected Mode, es should have a selector that
; references a segment base which matches an 86 Mode segment
If a multi-purpose puts
handler wishes to display text that differs from what it was passed, it should call to the puts_ext_next
handler. The call may be an extcall
or extcallcall
. The handler must pass an appropriate entrypoint into the ELD code section in cx. This is usually the chain entry of the handler, which is either a jump to a downlink or an extcall (not extcallcall) to puts_ext_done
. (If the chain entry is passed, there is no risk of re-entering the calling handler.)
The protocol for puts_ext_next
is as follows:
; INP: es:dx -> message to display
; ax = length of message
; NC
; CHG: ax, bx, cx, dx
; OUT: -
; STT: ds = ss
; UP, EI
; may be in Protected Mode, Real 86 Mode, or Virtual 86 Mode
; in Protected Mode, es should have a selector that
; references a segment base which matches an 86 Mode segment
; (this is true of the debugger stack selector)
Passing a CY to puts_ext_next
could indicate to skip the message displayed. However, if the chain entry passed in cx has a downlink to another multi-purpose puts
handler then the Carry Flag is most likely ignored.
If the entry is a final transfer to puts_ext_done
, the debugger function transfer_ext_cx
used to bounce the control flow back into the ELD code section will now preserve the Carry Flag. (A prior bug would force NC in this transfer function for lDebugX, the _PM=1 build.)
Finally, there is no supported use for passing CY here as the effect of not displaying a message at all can be achieved by not calling puts_ext_next
to begin with.
This handler was added to support the co.eld (Copy Output ELD). It is called whenever the debugger actually outputs text to the debugger terminal. This does not include output written only to the silent buffer or suppressed in the multi-purpose puts
handler. Also not included are paging prompts as they are inserted later, after the puts
copyoutput handler has returned.
The handlers for this hook form a chain similar to the ELD command handler. However, they use the variable ext_puts_copyoutput_handler
and the last handler transfers control to puts_copyoutput_ext_done
.
; INP: es:dx -> message to display
; ax = length of message
; NC
; byte [in_getinput] = boolean flag indicating if output
; is from getinput session, either 0 (false) or 0FFh (true)
; CHG: bx, cx
; OUT: NC
; (could set CY and transfer directly to puts_copyoutput_ext_done
; in order to suppress output)
; STT: ds = ss
; UP, EI
; may be in Protected Mode, Real 86 Mode, or Virtual 86 Mode
; in Protected Mode, es should have a selector that
; references a segment base which matches an 86 Mode segment
As noted above, a puts
copyoutput handler can suppress the output passed to it. However, this is not recommended and no handler currently does this.
This handler was added to support the co.eld (Copy Output ELD). It is called after getline has received a complete input line either from int 21h service 0Ah or from the getinput function. It is not called if a "file" type input source has yielded a line of input. This happens to match when the debugger enters a line into its line editor history, which the debugger does directly after this handler chain returns.
The handlers for this hook form a chain similar to the ELD command handler. However, they use the variable ext_puts_getline_handler
and the last handler transfers control to puts_getline_ext_done
.
; INP: byte [line_in + 1] = length of text (excluding CR), 0 to 254
; es:line_in + 2 -> text received, terminated by a CR
; bl = dispatch type,
; = 0 if input is from int 21h service 0Ah
; = 1 if input is from getinput function
; (note that output corresponding to this input has already
; been written to the ELD puts copyoutput handler)
; >= 2 if unknown dispatch type, should just pass along
; CHG: ax, bh, cx, dx, si, di
; OUT: pass along bl to next handler
; STT: es = ds = ss
; UP, EI
; may be in Protected Mode, Real 86 Mode, or Virtual 86 Mode
; in Protected Mode, es has a selector (stack selector) that
; references a segment base which matches an 86 Mode segment
ELD variables allow an ELD to add variables to the debugger's ‘isvariable?
’ function, which is used in the expression evaluator as well as in the R variable commands.
A certain number of ELD variables are allocated space in the list of multi-byte-text ‘isvariable?
’ structures. The build option to add these defaults to reserving space for 16 ELD variables.
ELD variables can be used for special purpose read-only variables, as the ELD is called for the "special set up" implementation for the variable. However, writable variables are possible using this interface only if they are trivial.
An ELD should use the following to install an ELD variable:
ext_var
points to the 10-byte ELD variable structures
ext_var_amount
specifies how many structures exist
ext_var_format
indicates the format of the structures, currently only format 1 is defined (this should be checked)
ext_var_size
indicates the ISVARIABLESTRUC_size
(this should be checked)
isvariable_morebyte_nameheaders.ext
points to the 2-byte name headers for each ELD variable
var_ext_setup
(albeit actually pointing at code) is provided to fill in the ELD variable structure, specifically the ivSetup
field which must point to a near function in the expr.asm code section (hence an ELD has to use this particular function to branch to the ELD's code)
var_ext_setup_done
which the ELD variable set up code should branch to using an extcall (not an extcallcall!) once it is done
The following protocol specifies what the variable set up code is called as:
; INP: ax = array index (0-based)
; cx = offset of this handler (ip)
; dil = default size of variable (1..4)
; dih = length of variable name
; CHG: si, ax, dx
; OUT: NC if valid,
; bx -> var, di = 0 or di -> mask
; cl = size of variable (1..4)
; ch = length of variable name
An ELD variable should be installed by scanning the structures pointed to by ext_var
for a structure with the first word (ivName
) equal to zero. If this is found, the ELD variable structure is to be copied into this slot of the isvariable?
structure array. The appropriate slot in the name headers also has to be filled with the first two text bytes of the variable name.
An ELD variable is uninstalled by clearing the first three words of the structure (ivName
, ivFlags
, and importantly ivAddress
), and also clearing the name headers slot to a zero value.
This hook allows to enter an ELD from a near call in the (first) debugger code section. The ELD can return to the near caller, too.
The entrypoint is near_transfer_ext_entry
. This entrypoint's offset address is available as a data link in order to allow it to be entered into callback variables.
When this entry is run, the original value of cx
is pushed, then the value of word [near_transfer_ext_address]
is loaded to cx
, and then control is transferred to the ELD via transfer_ext_cx
.
Note that there is only one entrypoint and one variable for this interface. That means either it can only be used for one particular callback, or the called ELD function has to dispatch based on the near return address on the stack.
The bootloaded directory scanner, accessed by calling scan_dir_aux
, provides several offsets in its code as data links to facilitate such dispatching:
..@boot_scan_dir_return_fat16_root_entry
word [handle_scan_dir_entry]
..@boot_scan_dir_return_subdir_or_fat32_entry
word [handle_scan_dir_entry]
..@boot_scan_dir_return_filenotfound
word [handle_scan_dir_not_found]
To return to the near caller, the ELD function should transfer the control flow to near_transfer_ext_return
with an extcall
, not an extcallcall
. This handler will pop cx
four times then return near.
lDebug (YYYY-MM-DD), debugger.
Usage: LDEBUG[.COM] [/C=commands] [[drive:][path]progname.ext [parameters]]
/C=commands semicolon-separated list of commands (quote spaces)
/IN discard command line buffer, do not run config
/A=MAX expand auxiliary buffer to maximum, #24_576 Bytes
/A=MIN restrict auxiliary buffer to minimum, #8_208 Bytes
/A=number set auxiliary buffer size to hex number of bytes
/A=#number set auxiliary buffer size to decimal number of bytes
/A alias for /A=MAX
/X=[MAX|MIN|number] change ELD code buffer size, 0 to #65_520 Bytes
/Y=[MAX|MIN|number] change ELD data buffer size, 0 to #29_504 Bytes
/H=[MAX|MIN|number] change history buffer size, #260 to #65_520 Bytes
/B run a breakpoint within initialisation
/P[+|-] append ext to initial filename and search path
/F[+|-] always treat executable file as a flat binary
/E[+|-] for flat binaries set up Stack Segment != PSP
/V[+|-] enable/disable video screen swapping
/2[+|-] enable/disable use alternate video adapter for output
progname.ext (executable) file to debug or examine
parameters parameters given to program
For a list of debugging commands, run LDEBUG and type ? at the prompt.
INSTSECT: Install boot sectors. 2018--2024 by E. C. Masloch
Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
Options:
a: load or update boot sectors of specified drive
/M=filename operate on FS image file instead of drive
/MN operate on drive instead of image file (default)
/MS=number set sector size of FS image file (default 512)
/MO=number set offset in image file in bytes (default 0)
/MOx=number set offset (x = S sectors, K 1024, M 1024 * 1024)
/Fx=filename replace Xth name in the boot sector, X = 1 to 4
/F=filename alias to /F1=filename
/U KEEP keep default/current boot unit handling (default)
/U AUTO patch boot loader to use auto boot unit handling
/U xx patch boot loader to use XXh as a fixed unit
/P KEEP keep default/current part info handling (default)
/P AUTO patch boot loader to use auto part info handling
/P NONE patch boot loader to use fixed part info
/Q KEEP keep default/current query geometry handling (default)
/Q AUTO patch boot loader to use auto query geometry handling
/Q NONE patch boot loader to use fixed geometry
/L KEEP keep default/current LBA handling (default)
/L AUTO patch boot loader to use auto LBA handling
/L AUTOHDD patch boot loader to use auto LBA (HDD-only) handling
/L NONE patch boot loader to use only CHS
/G KEEP keep all current geometry (default)
/G AUTO read all auto geometry from DOS
/G HEADS=x set geometry CHS heads (x = KEEP, AUTO, numeric)
/G SECTORS=x set geometry CHS sectors (x = KEEP, AUTO, numeric)
/G HIDDEN=x set geometry hidden (x = KEEP, AUTO, numeric)
/SR do not read boot sector from source file (default)
/S=filename read boot sector loader from source file
/S12=filename as /S=filename but only for FAT12 (also /S16, /S32)
/SV validate boot sector jump and FS ID (default)
/SN do not validate boot sector jump and FS ID
/SI validate FAT32 FSIBOOT compatibility (default)
/SJ do not validate FAT32 FSIBOOT compatiblity
/SG=sign check for FAT32 FSIBOOT exact signature match
/BS write boot sector to drive's boot sector (default)
/B=filename write boot sector to file, not to drive
/BN do not write boot sector
/BR replace boot sector loader with built-in one (default)
/BO keep original boot sector
/BC restore boot sector from backup copy
Only applicable for FAT32 with sector size below or equal to 512 bytes:
/IS write FSIBOOT to drive's FSINFO sector (default)
/I=filename write FSIBOOT to file, not to drive
/IB write FSIBOOT to boot sector file (see /B=filename)
/IN do not write FSIBOOT
/IR replace reserved field with built-in FSIBOOT (default)
/IO keep original reserved fields (including FSIBOOT area)
/IC restore FSINFO from backup copy
/IZ zero out reserved fields (including FSIBOOT area)
/II leave invalid FSINFO structure
/IV make valid FSINFO if there is none (default)
Only applicable for FAT32:
/C force writing to backup copies
/CB force writing sector to backup copy
/CI force writing info to backup copy
/CN disable writing to backup copies
/CNB disable writing sector to backup copy
/CNI disable writing info to backup copy
/CS only write backup copies if writing sectors (default)
/CSB only write sector to backup copy if writing sector
/CSI only write info to backup copy if writing sector
lDebug (YYYY-MM-DD) help screen
assemble A [address]
attach process ATTACH psp
set breakpoint BP index|AT|NEW address
[[NUMBER=]number] [WHEN=cond] [ID=id]
set ID BI index|AT address [ID=]id
set condition BW index|AT address [WHEN=]cond
set offset BO index|AT address [OFFSET=]number
set number BN index|AT address|ALL number
clear BC index|AT address|ALL
disable BD index|AT address|ALL
enable BE index|AT address|ALL
toggle BT index|AT address|ALL
swap BS index1 index2
list BL [index|AT address|ALL]
compare C range address
dump D [range]
dump bytes DB [range]
dump words DW [range]
dump dwords DD [range]
dump interrupts DI[R][M][L] interrupt [count]
dump MCB chain DM [segment]
display strings DZ/D$/D[W]# [address]
dump text table DT [T] [number]
enter E [address [list]]
run extension EXT [partition/][extensionfile] [parameters]
fill F range [RANGE range|list]
go G [=address] [breakpts]
goto GOTO :label
hex add/sub H value1 [value2 [...]]
base display H BASE=number [GROUP=number] [WIDTH=number] value
input I[W|D] port
if numeric IF [NOT] (cond) THEN cmd
if script file IF [NOT] EXISTS Y file [:label] THEN cmd
load program L [address]
load sectors L address drive sector count
move M range address
80x86/x87 mode M [0..6|C|NC|C2|?]
set name N [[drive:][path]progname.ext [parameters]]
set command K [[drive:][path]progname.ext [parameters]]
output O[W|D] port value
proceed P [=address] [count [WHILE cond] [SILENT [count]]]
quit Q
quit process QA
quit and break QB
register R [register [value]]
Run R extended RE
RE commands RE.LIST|APPEND|REPLACE [commands]
Run Commandline RC
RC commands RC.LIST|APPEND|REPLACE [commands]
toggle 386 regs RX
search S range [REVERSE] [SILENT number] [RANGE range|list]
sleep SLEEP count [SECONDS|TICKS]
trace T [=address] [count [WHILE cond] [SILENT [count]]]
trace (exc str) TP [=address] [count [WHILE cond] [SILENT [count]]]
trace mode TM [0|1]
enter TSR mode TSR
unassemble U [range]
view screen V [ON|OFF [KEEP|NOKEEP]]
write program W [address]
write sectors W address drive sector count
run script Y [partition/][scriptfile] [:label]
Additional help topics:
Registers ?R
Flags ?F
Conditionals ?C
Expressions ?E
Variables ?V
R Extended ?RE
Run keywords ?RUN
Options pages ?OPTIONS
Options ?O
Boot loading ?BOOT
lDebug build ?BUILD
lDebug build ?B
lDebug sources ?SOURCE
lDebug license ?L
Available 16-bit registers: Available 32-bit registers: (386+)
AX Accumulator EAX
BX Base register EBX
CX Counter ECX
DX Data register EDX
SP Stack pointer ESP
BP Base pointer EBP
SI Source index ESI
DI Destination index EDI
DS Data segment
ES Extra segment
SS Stack segment
CS Code segment
FS Extra segment 2 (386+)
GS Extra segment 3 (386+)
IP Instruction pointer EIP
FL Flags EFL
Enter ?F to display the recognized flags.
Recognized flags:
Value Name Set Clear
0800 OF Overflow Flag OV Overflow NV No overflow
0400 DF Direction Flag DN Down UP Up
0200 IF Interrupt Flag EI Enable interrupts DI Disable interrupts
0080 SF Sign Flag NG Negative PL Plus
0040 ZF Zero Flag ZR Zero NZ Not zero
0010 AF Auxiliary Flag AC Auxiliary carry NA No auxiliary carry
0004 PF Parity Flag PE Parity even PO Parity odd
0001 CF Carry Flag CY Carry NC No carry
The short names of the flag states are displayed when dumping registers and can be entered to modify the symbolic F register with R. The short names of the flags can be modified by R.
In the register dump displayed by the R, T, P and G commands, conditional jumps are displayed with a notice that shows whether the instruction will cause a jump depending on its condition and the current register and flag contents. This notice shows either "jumping" or "not jumping" as appropriate.
The conditional jumps use these conditions: (second column negates)
jo jno OF
jc jb jnae jnc jnb jae CF
jz je jnz jne ZF
jbe jna jnbe ja ZF||CF
js jns SF
jp jpe jnp jpo PF
jl jnge jnl jge OF^^SF
jle jng jnle jg OF^^SF || ZF
j(e)cxz (e)cx==0
loop (e)cx!=1
loopz loope (e)cx!=1 && ZF
loopnz loopne (e)cx!=1 && !ZF
Enter ?F to display a description of the flag names.
Recognized operators in expressions:
| bitwise OR || boolean OR
^ bitwise XOR ^^ boolean XOR
& bitwise AND && boolean AND
>> bit-shift right > test if above
>>> signed bit-shift right < test if below
<< bit-shift left >= test if above-or-equal
>< bit-mirror <= test if below-or-equal
+ addition == test if equal
- subtraction != test if not equal
* multiplication => same as >=
/ division =< same as <=
% modulo (A-(A/B*B)) <> same as !=
** power
Implicit operater precedence is handled in the listed order, with increasing precedence: (Brackets specify explicit precedence of an expression.)
boolean operators OR, XOR, AND (each has a different precedence)
comparison operators
bitwise operators OR, XOR, AND (each has a different precedence)
shift and bit-mirror operators
addition and subtraction operators
multiplication, division and modulo operators
power operator
Recognized unary operators: (modifying the next number)
+ positive (does nothing)
- negative
~ bitwise NOT
! boolean NOT
? absolute value
!! convert to boolean
Note that the power operator does not affect unary operator handling. For instance, "- 2 ** 2" is parsed as "(-2) ** 2" and evaluates to 4.
Although a negative unary and signed bit-shift right operator are provided the expression evaluator is intrinsically unsigned. Particularly the division, multiplication, modulo and all comparison operators operate unsigned. Due to this, the expression "-1 < 0" evaluates to zero.
Recognized terms in an expression:
32-bit immediates
8-bit registers
16-bit registers including segment registers (except FS, GS)
32-bit compound registers made of two 16-bit registers (eg DXAX)
32-bit registers and FS, GS only if running on a 386+
32-bit variables V00..VFF
32-bit special variables DCO, DCS, DAO, DAS, DIF, DPI, PPI
16-bit special variables DPR, DPP, PSP, PPR
(fuller variable reference in the manual)
byte/word/3byte/dword memory content (eg byte [seg:ofs], where both the
optional segment as well as the offset are expressions too)
The expression evaluator case-insensitively checks for names of variables and registers as well as size specifiers.
Enter ?R to display the recognized register names. Enter ?V to display the recognized variables.
Available lDebug variables:
The following variables cannot be written:
Enter ?O to display the options and internal flags.
The RUN commands (T, TP, P, G) and the RE command use the RE command buffer to run commands. Most commands are allowed to be run from the RE buffer. Disallowed commands include program-loading L, A, E that switches the line input mode, TSR, Q, Y, RE, and further RUN commands. When the RE buffer is used as input during T, TP, or P with the SILENT keyword, commands that use the auxbuff are also disallowed and will emit an error noting the conflict.
RE.LIST shows the current RE buffer contents in a format usable by the other RE commands. RE.APPEND appends the following commands to the buffer, if they fit. RE.REPLACE appends to the start of the buffer. When specifying commands, an unescaped semicolon is parsed as a linebreak to break apart individual commands. Backslashes can be used to escape semicolons and backslashes themselves.
Prefixing a line with an @ (AT sign) causes the command not to be shown to the standard output of the debugger when run. Otherwise, the command will be shown with a percent sign % or ~% prompt.
The default RE buffer content is @R. This content is also detected and handled specifically; if found as the only command the handler directly calls the register dump implementation without setting up and tearing down the special execution environment used to run arbitrary commands from the RE buffer.
T (trace), TP (trace except proceed past string operations), and P (proceed) can be followed by a number of repetitions and then the keyword WHILE, which must be followed by a conditional expression.
The selected run command is repeated as many times as specified by the number, or until the WHILE condition evaluates no longer to true.
After the number of repetitions or (if present) after the WHILE condition the keyword SILENT may follow. If that is the case, all register dumps done during the run are buffered by the debugger and the run remains silent. After the run, the last dumps are replayed from the buffer and displayed. At most as many dumps as fit into the buffer are displayed. (The buffer is currently 8 KiB sized by default, though the /A switch can be specified to init to grow it up to 24 KiB.)
If a number follows behind the SILENT keyword, only at most that many dumps are displayed from the buffer. The dumps that are displayed are always those last written into the buffer, thus last occurred.
Enter one of the following commands to get a corresponding help page:
Available options: (read/write DCO, read DCS)
More options: (read/write DCO2, read DCS2)
More options: (read/write DCO3, read DCS3)
More options: (read/write DCO4, read DCS4)
More options: (read/write DCO6, read DCS6)
Internal flags: (read DIF)
Available assembler/disassembler options: (read/write DAO, read DAS)
Boot loading commands:
Available protocols: (default filenames, load segment, then entrypoint)
Available options:
Boolean options: [opt=bool]
lDebug (YYYY-MM-DD)
Source Control Revision ID: hg xxxxxxxxxxxx (vvvv ancestors)
Uses yyyyyyyy: Revision ID hg zzzzzzzzzzzz (www ancestors)
[etc]
lDebug (YYYY-MM-DD)
Source Control Revision ID: hg xxxxxxxxxxxx (vvvv ancestors)
Uses yyyyyyyy: Revision ID hg zzzzzzzzzzzz (www ancestors)
[etc]
DI command
DM command
D string commands
S match dumps line of following data
RN command
Access SDA current PSP field
Load NTVDM VDD for sector access
X commands for EMS access
RM command and reading MMX registers as variables
Expression evaluator
Indirection in expressions
Variables with user-defined purpose
Debugger option and status variables
PSP variables
Conditional jump notice in register dump
TSR mode (Process detachment)
Boot loader
Permanent breakpoints
Intercepted interrupts: 00, 01, 03, 06, 18, 19
Extended built-in help pages
Expanded memory (EMS) commands:
Allocate XA count
Deallocate XD handle
Map memory XM logical-page physical-page handle
Reallocate XR handle count
Show status XS
The original lDebug sources can be obtained from the repo located at https://hg.pushbx.org/ecm/ldebug (E. C. Masloch's repo)
Releases of lDebug are available via the website at https://pushbx.org/ecm/web/#projects-ldebug
The most recent manual is hosted at https://pushbx.org/ecm/doc/ in the files ldebug.htm, ldebug.txt, and ldebug.pdf
lDebug - libre 86-DOS debugger
Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
All contributions by Paul Vojta or E. C. Masloch to the debugger are available under a choice of three different licenses. These are the Fair License, the Simplified 2-Clause BSD License, or the MIT License.
This is the license and copyright information that applies to lDebug; but note that there have been substantial contributions to the code base that are not copyrighted (public domain).
This section lists some benefits of lDebug, as compared to MSDebug. It originates in the MSDebug manual. MSDebug is based on the Debug of the 2018 free software release of MS-DOS version 2.
First, there are some differences between MSDebug and the original MS-DOS Debug which make MSDebug more similar to lDebug:
Here's the advantages of lDebug as compared to MSDebug:
#
’ modifier to enter in arbitrary numeric bases
Furthermore, lDebug can be built using the free software Netwide Assembler, rather than relying on a binary-only Microsoft Macro Assembler. (The assembler executable shipped with MSDebug is technically free, but it does not have sources.)
However, there are some disadvantages to lDebug as well:
This chapter lists all tests in the test suite.
Runs the command ‘... invalid command
’ and checks that the returned error carat display contains a bell codepoint (byte 07h, U+0007).
Checks the ‘?build
’ and ‘?version
’ commands.
Checks the Register dump History mode. Includes subtests for many RH commands. Refer to section 10.38.
Checks the DT command text table and the DT command to dump byte values' corresponding texts. Refer to section 10.17.
Checks that the R command's jumping notices are correct. Also checks for flag status display, referenced memory contents display, and the ‘[needs 386]
’ machine requirement display. Includes subtests for many conditions. Refer to section 10.37.
Tests the assembler's output. Includes many subtests, some of which are expected failures.
Tests the R command to display and modify variables, either debugger variables or memory indirect variables. Includes subtests for a number of different commands. Some of the tests also use complex expressions to test the expression evaluator. Some of the subtests contain no-op commands (commented out as ‘; nothing
’) that only check an additional result of a prior subtest.
Miscellaneous subtests:
Further miscellaneous tests include:
Instructs the debugger to sleep so long that the test tear down handler will terminate the debugger process.
Installs the debugger's AMIS handler (int 2Dh handler) then installs a tiny handler on top of it, testing how attempting to unhook the debugger's handler works in this case. The tiny handler is first of the form ‘90 EA offset segment 00 00
’ (no IISP header detected) and then of the form ‘90 EA offset segment "KB"
’ (uninstalled iHPFS style IISP header detected).
This test requires running under a DOS.
Tests permanent (bb) and temporary (gg) breakpoints.
Tests permanent breakpoint setup, listing, and overflows of structures.
Checks for access variables working. Refer to section 12.19.
Enters Protected Mode using a small DPMI test case.
First, if need be a DPMI host may be run. Next, the client executable is loaded. Subsequently the debugging hint in text form is searched within the loaded program. It is checked that PM is entered successfully.
The fix for a bug in the R command is tested next, except under lDDebugX where a failure would crash the debugger. The PM interrupts hooked by lDebugX are checked, except when lDDebugX is used. Several subtests check PSP variables.
Finally, the return to 86 Mode via client process termination is checked.
This test requires running under a DOS, for a DPMI host to be available, and for an lDebugX build to be used (detected by the "x" in the build name).
This test has a setup similar to test_dpmimini. Two different breakpoints are detected in the debugging hint texts. At both of the breakpoints, several C commands are tested. One point of the tests is to insure that 32-bit offsets in part of an expression such as memory indirection or a LINEAR expression do not set the 32-bit offset status for the entire expression's caller.
This test requires running under a DOS, for a DPMI host to be available, and for an lDebugX build to be used (detected by the "x" in the build name).
This test has a setup similar to test_dpmimini. It checks that when a permanent breakpoint is set in a DPMI allocation (beyond 1088 KiB) and then the debugger is entered in 86 Mode via mode switch (not by terminating the client) and then an interrupt instruction is to be disassembled, the debugger will not have losts its Extra Segment and the disassembly operand stack special opcode scan will succeed.
This is based on a bugfix in the mode switching done by the debugger in this circumstance, added as a test case afterwards, both in 2022 April.
This test requires running under a DOS, for a DPMI host to be available, and for an lDebugX build to be used (detected by the "x" in the build name).
This test tries to load nonexisting executables specified to the N command followed by an L command. First a file should not be found and then a path.
This test requires running under a DOS.
This test tries to load a corrupt executable specified to the N command followed by an L command. The MZ executable header specifies a too large image for any 86 Mode operating system in this executable.
This test requires running under a DOS.
This checks that several BOOT commands work.
An lDOS protocol load is attempted to run without a failure, and its entrypoint address (200h:400h) is checked. A check value mismatch is forced. The RxDOS.3 protocol load is attempted with the default filename, which should not be found. A FreeDOS protocol load is attempted to run without a failure, and its entrypoint address (60h:0) is checked. A boot directory listing is attempted and the presence of the debugger executable and startup script is matched.
The boot sector of the debugger diskette is loaded to segment 1000h and it is checked that the informational ‘FAT12
’ identifier shows up exactly once in the sector. The 55h, AAh signature is checked in the sector. The boot sector is read repeatedly to segment 1020h with implied start sector and length, explicit start sector but implied length, and all explicit parameters. The sectors are matched with the first read. Also, it is insured that a sentinel byte after the 512 Bytes for the subsequent reads does not change.
This test requires running without a DOS.
Tests Y commands to read Script for lDebug files.
A simple script is tested first. Then 20 nested scripts are tested, expecting an error that the nesting is too deep but still executing all commands from the opened files in the correct order. Then 3 nested scripts are tested, which should not cause an error.
Subsequently, sleeping within nested scripts is tested. Further, cancelling a running command with Control-C (codepoint U+0003) is tested, utilising the sleeping scripts. The first cancellation is done with IOL equal to zero, to cancel only the sleep command and none of the Scripts for lDebug. (Refer to section 12.9.6.) The second involves IOL equal to one, cancelling the sleep command as well as one script level. The third involves IOL equal to one again but with two Control-C codes sent from the terminal, twice cancelling one sleep command and one script level. The fourth cancellation uses IOL equal to two, cancelling the sleep and two script levels.
A further SLD tests calling subfunctions (labels) within the same SLD file. A final SLD tests visibility of commands in a Script for lDebug using the ‘@
’ prefix as well as setting YSF flag 4000h to hide some of the commands. (Refer to section 12.15.1.)
This test sends a double Control-C from the serial terminal to the debugger. At this point the debuggee is running in an idle loop so that the double Control-C can act as a breakpoint. In order to work, the timer interrupt 8 is hooked by the debugger.
After the debugger breaks out of the loop, the code segment is matched against the debuggee code segment. If it matches and the offset is plausible, the test is considered finished. Otherwise, the debugger attempts to trace-proceed out of the current interrupt handler until it has executed an iret
or retf imm16
instruction and checks the segment for the debuggee code segment after.
This tests the interactive enter mode (section 10.18).
This tests the RC (command line) buffer and execution from it. First a single R command is written and ran. Then two more R commands are appended and the RC buffer is ran again.
Then a small loop is written into the RC buffer using IF and GOTO commands, as well as labels. The display of the commands themselves is disabled by using ‘@
’ prefixes. The correct amount of iterations as well as hiding of commands is tested.
A longer loop is ran next, but the loop is placed into the RE buffer instead, with the RC buffer containing only an ‘RE
’ command. The RCLIMIT variable (section 12.6.3) is set so low that it would abort the loop if the RE buffer commands were to be counted against it.
A similar construct is used next, except that the loop is in a Script for lDebug file and the RC buffer contains a Y command to load this SLD.
Runs the help of the extlib Extension for lDebug. Both on its own, with the long descriptions, and with the wide list of ELDs. It is checked that at least 40 ELDs are listed.
Runs the ldmem Extension for lDebug. The MEM and ELD keywords are tested by directly running the ELD. The ELD noun should display the transient ELD.
Then the ELD is installed residently. The ELD noun is used first. It is checked that the resident ELD is displayed. Next, COMMANDHANDLER, INJECTHANDLER, STACK, and HISTORY nouns are ran. Finally the resident ELD is uninstalled.
Installs the aformat Extension for lDebug then tests it by assembling several instructions.
Installs the checksum Extension for lDebug and runs it on several data blocks. The results are checked.
Runs the transient list Extension for lDebug. The first run is with an empty command line tail. As only the extlib.eld is known to be available it is specified as the ELD file to list for the second run. Contents and format of the output are tested.
This runs the Extension for lDebug that lists multiplexers installed according to the Alternate Multiplex Interrupt Specification (AMIS). To make sure at least one multiplexer is installed, this test runs ‘install amis
’ initially, which installs the debugger's AMIS handler.
The amitsrs
command is run as is, and then with the keywords ‘int
’, ‘mpx
’, and ‘verbose
’. In the interrupts display it is insured that at least one multiplexer hooks interrupt 2Dh (necessary) and interrupt 3 (always true for normal lDebug builds).
In this test, a resident ldmem Extension for lDebug is installed. It is then tested that running the help for extlib.eld leaves a transient ELD installed. Finally, the reclaim ELD is used to reclaim this transient ELD. (Reclamation is built-in to the debugger's ELD loader as well, however this will not reclaim the last loaded ELD when it returns control to the debugger.)
This test installs the amount Extension for lDebug, which provides the ELDAMOUNT variable. It is checked that this variable reads as at least 2, one each for the LINKCALL ELD and the amount ELD. Then an ldmem.eld is installed and it is checked that the value in the ELDAMOUNT variable increments.
Tests the alias Extension for lDebug. Installs the ELD, then runs a list command, adds an alias, lists the alias, runs the alias, and deletes the alias again.
To test the dosseek Extension for lDebug, a small stub is assembled first to open a file handle. The extlib.eld file is opened, as it always exists. Then the dosseek.eld is used to get the current seek, set it to 4096, get it again, and then set it to the EOF. The final seek is checked to match the filesize. Finally, it is attempted to get the seek for file handle #19 (valid process handle but closed) and FFFF (never a valid process handle) and the error messages are checked.
This test requires running under a DOS.
Installs the history Extension for lDebug, and checks that its show and clear commands work.
Installs the debugger's AMIS handler and the amismsg Extension for lDebug. Several small handlers that pass messages to AMIS function 40h are assembled. An overflow is tested to truncate a message that is too long, along with returning a different status in AL. AMIS function 41h is tested as well.
Installs the debugger's AMIS handler and the amiscmd Extension for lDebug. An rvm command is injected using AMIS function 43h. Next the same command is injected again but with all flags in CX set, which should be rejected. After this, four commands are injected at once. Then 16 commands are injected at once, padded to the maximum length with blanks. This should overflow the ELD's buffer. Finally two differing commands are injected and it is insured that the order of injection matches the order of the calls into the ELD.
Installs the debugger's AMIS handler and the amisoth Extension for lDebug. The AMIS function 42h is tested. The status returned in AL is insured to be FFh. The segment returned in DX is matched to equal the Debugger PSP. The ELD link info header (refer to section 16.3) is tested a little. The signature is matched as E1D1h, the use link hash word is matched as 1 or 0, and the amount of data and code links are both matched to be at least 64 and at most 256.
The program executables can be compressed with a choice of different compressors. The files then contain a decompression stub. Some of these stubs have their own usage conditions. The following stub usage conditions apply, if one of these stubs is used.
One of the Extensions for lDebug, dbitmap.eld, contains a font copied from the GLaBIOS project. Its license follows.
Font bitmaps from "VileR", (CC BY-SA 4.0)
https://int10h.org/oldschool-pc-fonts/
Copied from https://github.com/640-KB/GLaBIOS/blob/b60b2549372e30447ffa454d4ae487390f91d509/src/GLABIOS.ASM#L10975 with the backslash comment fixed to avoid a NASM misfeature.
According to https://int10h.org/oldschool-pc-fonts/readme/#legal_stuff this font insofar as it is copyrightable is available under: Creative Commons Attribution-ShareAlike 4.0 International License.
BriefLZ - small fast Lempel-Ziv
8086 Assembly lDOS iniload payload BriefLZ depacker
Based on: BriefLZ C safe depacker
Copyright (c) 2002-2016 Joergen Ibsen
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
Copyright (c) 2005-2017 Magnus Lind.
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
MIT License
Copyright (c) 2020 David Barina
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
This program is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
Copyright (C) 2019 Emmanuel Marty
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
Copyright (C) 2019 Emmanuel Marty
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
BSD 2-Clause License
Copyright (c) 2021, Milos Bazelides
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
hg c89b435dc8c1, from commit on at 2024-12-21 14:04:39 +0100
If this is in ecm's repository, you can find it at https://hg.pushbx.org/ecm/ldebug/rev/c89b435dc8c1