/*************************************************************************/
TURBO DEBUGGER
Assembler-level debugging
This file contains information about Assembler-level debugging,The
contents of the file are as follows:
1,When source debugging isn't enough
2,The assembler
3,Assembler-specific bugs
4,Inline assembler tips
5,Inline assembler keywords
6,The Numeric Processor window
The material in this file is for programmers who are familiar with
programming the 80x86 processor family in assembler,You don't need
to use the information in this chapter to debug your programs,but
there are certain problems that might be easier to find using
the techniques discussed here.
===================================================================
1,When source debugging isn't enough
===================================================================
Sometimes,however,you can gain insight into a problem by looking
at the exact instructions that the compiler generated,the contents
of the CPU registers,and the contents of the stack,To do this,
you need to be familiar with both the 80x86 family of processors
and with how the compiler turns your source code into machine
instructions,Because many excellent books are available about the
internal workings of the CPU,we won't go into that in detail here.
You can quickly learn how the compiler turns your source code into
machine instructions by looking at the instructions generated for
each line of source code within the CPU window.
Turbo Debugger can detect an 8087,80287,80387,or 80486 numeric
coprocessor and disassemble those instructions if a floating-point
chip or emulator is present.
The instruction mnemonic RETF indicates that this is a far return
instruction,The normal RET mnemonic indicates a near return.
Where possible,the target of JMP and CALL instructions is
displayed symbolically,If CS:IP is a JMP or conditional jump
instruction,an up-arrow or down-arrow that shows jump direction
will be displayed only if the executing instruction will cause the
jump to occur,Also,memory addresses used by MOV,ADD,and other
instructions display symbolic addresses.
===================================================================
2,The assembler
===================================================================
If you use the Assemble command in the Code pane local menu,Turbo
Debugger lets you assemble instructions for the 8086,80186,80286,
80386,and 80486 processors,and also for the 8087,80287,and 80387
numeric coprocessors.
When you use Turbo Debugger's built-in assembler to modify your program,
the changes you make are not permanent,If you reload your program Run|
Program Reset,or if you load another program using File|Open,
you'll lose any changes you've made.
Normally you use the assembler to test an idea for fixing your program.
Once you've verified that the change works,you must change your source
code and recompile and link your program.
The following sections describe the differences between the built-
in assembler and the syntax accepted by Borland C++'s inline
assembler.
Operand address size overrides
==============================
For the call (CALL),jump (JMP),and conditional jump (JNE,JL,and
so forth) instructions,the assembler automatically generates the
smallest instruction that can reach the destination address,You
can use the NEAR and FAR overrides before the destination address
to assemble the instruction with a specific size,For example,
CALL FAR XYZ
JMP NEAR A1
Memory and immediate operands
-----------------------------
When you use a symbol from your program as an instruction operand,
you must tell the built-in assembler whether you mean the contents
of the symbol or the address of the symbol,If you use just the
symbol name,the assembler treats it as an address,exactly as if
you had used the assembler OFFSET operator before it,If you put
the symbol inside brackets ([ ]),it becomes a memory reference.
For example,if your program contains the data definition
A DW 4
then "A" references the area of memory where A is stored.
When you assemble an instruction or evaluate an assembler
expression to refer to the contents of a variable,use the name of
the variable alone or between brackets:
mov dx,a
mov ax,[a]
To refer to the address of the variable,use the OFFSET operator:
mov ax,offset a
Operand data size overrides
===========================
For some instructions,you must specify the operand size using one
of the following expressions before the operand:
BYTE PTR
WORD PTR
Here are examples of instructions using these overrides:
add BYTE PTR[si],10
mov WORD PTR[bp+10],99
In addition to these size overrides,you can use the following
overrides to assemble 8087/80287/80387/80486 numeric processor
instructions:
DWORD PTR
QWORD PTR
TBYTE PTR
Here are some examples using these overrides:
fild QWORD PTR[bx]
stp TBYTE PTR[bp+4]
String instructions
===================
When you assemble a string instruction,you must include the size
(byte or word) as part of the instruction mnemonic,The assembler
does not accept the form of the string instructions that uses a
sizeless mnemonic with an operand that specifies the size,For
example,use STOSW rather than STOS WORD PTR[di].
=========================================
3,Assembler-specific bugs
=========================================
This section,which covers some of the common pitfalls of assembly
language programming,is intended for people who have Turbo Assembler
or use inline assembler in C++ programs,You should refer to the
Turbo Assembler User's Guide for a fuller explanation on these
often encountered errors--and tips on how to avoid them.
Forgetting to return to DOS
===========================
In C++,a program ends automatically when there is no more code to
execute,even if no explicit termination command was written into the
program,Not so in assembly language,where only those actions that
you explicitly request are performed,When you run a program that has
no command to return to DOS,execution simply continues right past the
end of the program's code and into whatever code happens to be in the
adjacent memory.
Forgetting a RET instruction
============================
The proper invocation of a subroutine consists of a call to the subroutine
from another section of code,execution of the subroutine,and a return
from the subroutine to the calling code,Remember to insert a RET
instruction in each subroutine,so that the RETurn to the calling code
occurs,When you're typing a program,it's easy to skip a RET and end
up with an error.
Generating the wrong type of return
===================================
The PROC directive has two effects,First,it defines a name by which a
procedure can be called,Second,it controls whether the procedure is a near
or far procedure.
The RET instructions in a procedure should match the type of the procedure,
shouldn't they?
Yes and no,The problem is that it's possible and often desirable to group
several subroutines in the same procedure,Since these subroutines lack an
associated PROC directive,their RET instructions take on the type of the
overall procedure,which is not necessarily the correct type for the
individual subroutines.
Reversing operands
==================
To many people,the order of instruction operands in 8086 assembly language
seems backward (and there is certainly some justification for this
viewpoint),If the line
mov ax,bx
meant "move AX to BX," the line would scan smoothly from left to right,and
this is exactly the way in which many microprocessor manufacturers have
designed their assembly languages.
However,Intel took a different approach with 8086 assembly language; for
us,the line means "move BX to AX," and that can sometimes cause confusion.
Forgetting the stack or reserving a too-small stack
===================================================
In most cases,you're treading on thin ice if you don't explicitly allocate
space for a stack,Programs without an allocated stack sometimes run,but
there is no assurance that these programs will run under all circumstances.
DOS programs can have a,STACK directive to reserve space for the stack.
For each program,you should reserve more than enough space for the
deepest stack the program can use.
Calling a subroutine that wipes out registers
=============================================
When you're writing assembler code,it's easy to think of the registers
as local variables,dedicated to the use of the procedure you're working
on at the moment,In particular,there's a tendency to assume that
registers are unchanged by calls to other procedures,It just isn't
so--the registers are global variables,and each procedure can preserve or
destroy any or all registers.
Using the wrong sense for a conditional jump
============================================
The profusion of conditional jumps in assembly language (JE,JNE,JC,
JNC,JA,JB,JG,and so on) allows tremendous flexibility in writing
code--and also makes it easy to select the wrong jump for a given purpose.
Moreover,since condition-handling in assembly language requires at least
two separate lines,one for the comparison and one for the conditional
jump (it requires many more lines for complex conditions),assembly
language condition-handling is less intuitive and more prone to errors than
condition-handling in C++.
Forgetting about REP string overrun
===================================
String instructions have a curious property,After they're executed,the
pointers they use wind up pointing to an address 1 byte away (or 2 bytes
for a word instruction) from the last address processed,This can cause
some confusion with repeated string instructions,especially REP SCAS and
REP CMPS.
Relying on a zero CX to cover a whole segment
=============================================
Any repeated string instruction executed with CX equal to zero does nothing.
This can be convenient in that there's no need to check for the zero
case before executing a repeated string instruction; on the other hand,
there's no way to access every byte in a segment with a byte-sized string
instruction.
Using incorrect direction flag settings
=======================================
When a string instruction is executed,its associated pointer or pointers--
SI or DI or both--increment or decrement,It all depends on the state of the
direction flag.
The direction flag can be cleared with CLD to cause string instructions to
increment (count up) and can be set with STD to cause string instructions to
decrement (count down),Once cleared or set,the direction flag stays in the
same state until either another CLD or STD is executed,or until the flags
are popped from the stack with POPF or IRET,While it's handy to be able to
program the direction flag once and then execute a series of string
instructions that all operate in the same direction,the direction flag can
also be responsible for intermittent and hard-to-find bugs by causing the
behavior of string instructions to depend on code that executed much earlier.
Using the wrong sense for a repeated string comparison
======================================================
The CMPS instruction compares two areas of memory; the SCAS instruction
compares the accumulator to an area of memory,Prefixed by REPE,either
of these instructions can perform a comparison until either CX becomes
zero or a not-equal comparison occurs,Unfortunately,it's easy to become
confused about which of the REP prefixes does what.
Forgetting about string segment defaults
========================================
Each of the string instructions defaults to using a source segment (if any)
of DS,and a destination segment (if any) of ES,It's easy to forget this
and try to perform,say,a STOSB to the data segment,since that's where
all the data you're processing with non-string instructions normally resides.
Converting incorrectly from byte to word operations
===================================================
In general,it's desirable to use the largest possible data size (usually
word,but dword on an 80386) for a string instruction,since string
instructions with larger data sizes often run faster.
There are a couple of potential pitfalls here,First,the conversion from a
byte count to a word count by a simple
shr cx,1
loses a byte if CX is odd,since the least-significant bit is shifted out.
Second,make sure you remember SHR divides the byte count by two,Using,
say,STOSW with a byte rather than a word count can wipe out other data
and cause problems of all sorts.
Using multiple prefixes
=======================
String instructions with multiple prefixes are error-prone and should
generally be avoided.
Relying on the operand(s) to a string instruction
=================================================
The optional operand or operands to a string instruction are used for data
sizing and segment overrides only,and do not guarantee that the memory
location referenced is accessed.
Wiping out a register with multiplication
=========================================
Multiplication--whether 8 bit by 8 bit,16 bit by 16 bit,or 32 bit by 32
bit--always destroys the contents of at least one register other than the
portion of the accumulator used as a source operand.
Forgetting that string instructions alter several registers
===========================================================
The string instructions,MOVS,STOS,LODS,CMPS,and SCAS,can affect several
of the flags and as many as three registers during execution of a single
instruction,When you use string instructions,remember that SI,DI,or
both either increment or decrement (depending on the state of the direction
flag) on each execution of a string instruction,CX is also decremented at
least once,and possibly as far as zero,each time a string instruction with
a REP prefix is used.
Expecting certain instructions to alter the carry flag
======================================================
While some instructions affect registers or flags unexpectedly,other
instructions don't even affect all the flags you might expect them to.
Waiting too long to use flags
=============================
Flags last only until the next instruction that alters them,which is
usually not very long,It's a good practice to act on flags as soon as
possible after they're set,thereby avoiding all sorts of potential bugs.
Confusing memory and immediate operands
=======================================
An assembler program may refer either to the offset of a memory variable or
to the value stored in that memory variable,Unfortunately,assembly language
is neither strict nor intuitive about the ways in which these two types of
references can be made,and as a result,offset and value references to a
memory variable are often confused.
Failing to preserve everything in an interrupt handler
======================================================
Every interrupt handler should explicitly preserve the contents of all
registers,While it is valid to preserve explicitly only those registers
that the handler modifies,it's good insurance just to push all registers
on entry to an interrupt handler and pop all registers on exit.
Forgetting group overrides in operands and data tables
======================================================
Segment groups let you partition data logically into a number of areas
without having to load a segment register every time you want to switch
from one of those logical data areas to another.
=========================================
4,Inline assembler tips
=========================================
Looking at raw hex data
=======================
You can use the Data|Add Watch and Data| Evaluate/Modify commands with
a format modifier to look at raw data dumps,For example,if your
language is Assembler,
[ES:DI],20m
specifies that you want to look at a raw hex memory dump of the 20 bytes
pointed to by the ES:DI register pair.
Source-level debugging
======================
You can step through your assembler code using a Module window just as
with any of the high-level languages,If you want to see the register
values,you can put a Registers window to the right of the Module window.
Sometimes,you may want to use a CPU window and see your source code as
well,To do this,open a CPU window and choose the Code pane's Mixed
command until it reads Both,That way you can see both your source code
and machine code bytes,Remember to zoom the CPU window (by pressing F5)
if you want to see the machine code bytes.
Examining and changing registers
================================
The obvious way to change registers is to highlight a register in either
a CPU window or Registers window,A quick way to change a register is to
choose Data|Evaluate/Modify,You can enter an assignment expression that
directly modifies a register's contents,For example,
SI = 99
loads the SI register with 99.
Likewise,you can examine registers using the same technique,For example,
Alt-D E AX
shows you the value of the AX register.
=========================================
5,Inline assembler keywords
=========================================
This section lists the instruction mnemonics and other special symbols that
you use when entering instructions with the inline assembler,The keywords
presented here are the same as those used by Turbo Assembler.
8086/80186/80286 instructional mnemonics
_________________________________________
AAA INC LIDT** REPNZ
AAD INSB* LLDT** REPZ
AAM INSW* LMSW** RET
AAS INT LOCK REFT
ADC INTO LODSB ROL
ADD IRET LODSW ROR
AND JB LOOP SAHF
ARPL** JBE LOOPNZ SAR
BOUND* JCXZ LOOPZ SBB
CALL JE LSL** SCASB
CLC JL LTR** SCASW
CLD JLE MOV SGDT**
CLI JMP MOVSB SHL
CLTS** JNB MOVSW SHR
CMC JNBE MUL SLDT**
CMP JNE NEG SMSW**
CMPSB JNLE NOP STC
CMPSW JNO NOT STD
CWD JNP OR STI
DAA JO OUT STOSB
DAS JP OUTSB STOSW
DEC JS OUTSW STR**
DIV LAHF POP SUB
ENTER* LAR** POPA* TEST
ESC LDS POPF WAIT
HLT LEA PUSH VERR**
IDIV LEAVE PUSHA* VERW**
IMUL LES PUSHF XCHG
IN LGDT** RCL XLAT
XOR
___________________________________________
* Available only when running on the 186 and 286 processor
** Available only when running on the 286 processor
Turbo Debugger supports all 80386 and 80387 instruction
mnemonics and registers:
80386 instruction mnemonics
_________________________________________
BSF LSS SETG SETS
BSR MOVSX SETL SHLD
BT MOVZX SETLE SHRD
BTC POPAD SETNB CMPSD
BTR POPFD SETNE STOSD
BTS PUSHAD SETNL LODSD
CDQ PUSHFD SETNO MOVSD
CWDE SETA SETNP SCASD
IRETD SETB SETNS INSD
LFS SETBE SETO OUTSD
LGS SETE SETP JECXZ
__________________________________________
80486 instruction mnemonics
_________________________________________
BSWAP INVLPG
CMPXCHG WBINVD
INVD XADD
_________________________________________
80386 registers
_________________________________________
EAX EDI
EBX EBP
ECX ESP
EDX FS
ESI GS
_________________________________________
CPU registers
__________________________________________________________________
Byte registers AH,AL,BH,BL,CH,CL,DH,DL
Word registers AX,BX,CX,DX,SI,DI,SP,BP,FLAGS
Segment registers CS,DS,ES,SS
Floating registers ST,ST(0),ST(1),ST(2),ST(3),ST(4),
ST(5),ST(6),ST(7)
___________________________________________________________________
Special keywords
_________________________________________
WORD PTR TBYTE PTR
BYTE PTR NEAR
DWORD PTR FAR
QWORD PTR SHORT
_________________________________________
8087/80287 numeric coprocessor instruction mnemonics
____________________________________________________
FABS FIADD FLDL2E FST
FADD FIACOM FLDL2T FSTCW
FADDP FIACOMP FLDPI FSTENV
FBLD FIDIV FLDZ FSTP
FBSTP FIDIVR FLD1 FSTSW**
FCHS FILD FMUL FSUB
FCLEX FIMUL FMULP FSUBP
FCOM FINCSTP FNOP FSUBR
FCOMP FINIT FNSTS** FSUBRP
FCOMPP FIST FPATAN FTST
FDECSTP FISTP FPREM FWAIT
FDISI FISUB FPTAN FXAM
FDIV FISUBR FRNDINT FXCH
FDIVP FLD FRSTOR FXTRACT
FDIVR FLDCWR FSAVENT FYL2X
FDIVRP FLDENV FSCALE FYL2XPI
FENI FLDLG2 FSETPM* F2XM1
FFREE FLDLN2 FSQRT
_____________________________________________________
* Available only when running on the 287 numeric coprocessor.
** On the 80287,the fstsw instruction can use the AX register as an
operand,as well as the normal memory operand.
80387 instruction mnemonics
_________________________________________
FCOS FUCOM
FSIN FUCOMP
FPREM1 FUCOMPP
FSINCOS
_________________________________________
The 80x87 coprocessor chip and emulator
=======================================
This section is for programmers who are familiar with the operation
if the 80x87 math coprocessor,If your program uses floating-point
numbers,Turbo Debugger lets you examine and change the state of the numeric
coprocessor or,if the coprocessor is emulated,examine the state of the
software emulator,(Windows permits you only to examine the state of the
emulator,not to change it.) You don't need to use the capabilities
described in this chapter to debug programs that use floating-point numbers,
although some very subtle bugs may be easier to find.
In this section,we discuss the differences between the 80x87 chip and
the software emulator,We also describe the Numeric Processor window and
show you how to examine and modify the floating-point registers,the status
bits,and the control bits.
The 80x87 chip vs,the emulator
===============================
TDW automatically detects whether your program is using the math chip or the
emulator and adjusts its behavior accordingly.
Note that most programs use either the emulator or the math chip,not both
within the same program,If you have written special assembler code that
uses both,TDW won't be able to show you the status of the math chip; it
reports on the emulator only.
=========================================
6,The Numeric Processor window
=========================================
You create a Numeric Processor window by choosing the View|Numeric Processor
command from the menu bar,The line at the top of the window shows the
current instruction pointer,opcode,and data pointer,The instruction
pointer is both shown as a 20-bit physical address,The data pointer is
either a 16-bit or a 20-bit address,depending on the memory model,You
can convert 20-bit addresses to segment and offset form by using the first
four digits as the segment value and the last digit as the offset value.
For example,if the top line shows IPTR=5A669,you can treat this as the
address 5a66:9 if you want to examine the current data and instruction in
a CPU window,This window has three panes,The left pane (Register pane)
shows the contents of the floating-point registers,the middle pane
(Control pane) shows the control flags,and the right pane (Status pane)
shows the status flags.
The top line shows you the following information about the last floating-
point operation that was executed:
o Emulator indicates that the numeric processor is being emulated,If there
were a numeric processor,8087,80287,80387,or 80486 would appear instead.
o The IPTR shows the 20-bit physical address from which the last floating-
point instruction was fetched.
o The OPCODE shows the instruction type that was fetched.
o The OPTR shows the 16-bit or 20-bit physical address of the memory address
that the instruction referenced,if any.
The Register pane
-----------------
The 80-bit floating-point registers
-----------------------------------
The Register pane shows each of the floating-point registers,ST(0) to
ST(7),along with its status (valid/zero/special/empty),The contents
are shown as an 80-bit floating-point number.
If you've zoomed the Numeric Processor window (by pressing F5) or made
it wider by using Window|Size/Move,you'll also see the floating-point
registers displayed as raw hex bytes.
The Register pane's local menu
------------------------------
___________
| Zero |
| Empty |
| Change..,|
|___________|
To bring up the Register pane local menu,press Alt-F10,or use the Ctrl
key with the first letter of the desired command to directly access the
command.
Zero
----
Sets the value of the currently highlighted register to zero.
Empty
-----
Sets the value of the currently highlighted register to empty,This is a
special status that indicates that the register no longer contains valid
data.
Change
------
Loads a new value into the currently highlighted register,You are
prompted for the value to load,You can enter an integer or floating-
point value,using the current language's expression parser,The value
you enter is automatically converted to the 80-bit temporary real format
used by the numeric coprocessor.
You can also invoke this command by simply starting to type the new value
for the floating-point register,A dialog box appears,exactly as if you
had specified the Change command.
The Control pane
----------------
The control bits
----------------
The following table lists the different control flags and how they
appear in the Control pane:
_________________________________________
Name in pane Flag description__
im Invalid operation mask
dm Denormalized operand mask
zm Zero divide mask
om Overflow mask
um Underflow mask
pm Precision mask
iem Interrupt enable mask (8087 only)
pc Precision control
rc Rounding control
ic Infinity control__
The Control pane's local menu
-----------------------------
________
| Toggle |
|________|
Press Tab to go to the Control pane,then press Alt-F10 to pop up the
local menu,(Alternatively,you can use the Ctrl key with the first letter
of the desired command to access it.)
Toggle
------
Cycles through the values that the currently highlighted control flag
can be set to,Most flags can only be set or cleared (0 or 1),so this
command just toggles the flag to the other value,Some other flags have
more than two values; for those flags,this command increments the flag
value until the maximum value is reached,and then sets it back to zero.
You can also toggle the control flag values by highlighting them and
pressing Enter.
The Status pane
---------------
The status bits
---------------
The following table lists the different status flags and how they appear
in the Status pane:
____________________________________
Name in pane Flag description__
ie Invalid operation
de Denormalized operand
ze Zero divide
oe Overflow
ue Underflow
pe Precision
ir Interrupt request
cc Condition code
st Stack top pointer_
The Status pane's local menu
----------------------------
________
| Toggle |
|________|
Press Tab to move to the Statuspane,then press Alt-F10 to pop up the
local menu,(You can also use the Ctrl key with the first letter of the
desired command to access the command directly.)
Toggle
------
Cycles through the values that the currently highlighted status flag
can be set to,Most flags can only be set or cleared (0 or 1),so this
command just toggles the flag to the other value,Some other flags have
more than two values; for those flags,this command increments the
flag value until the maximum value is reached,and then sets it back to
zero.
You can also toggle the status flag values by highlighting them and
pressing Enter.
/***************************** END OF FILE *******************************/