(c)  Copyright 1989 Commodore-Amiga, Inc.   All rights reserved.
The information contained herein is subject to change without notice, and 
is provided "as is" without warranty of any kind, either express or implied.  
The entire risk as to the use of this information is assumed by the user.



Troubleshooting Your Software

by Carolyn Scheppner


Many Amiga programming errors have classic symptoms.  This guide
gives some tips to help you find and eliminate these problems in
your software.


CLI Error Messages

This is caused by calling exit(n) with an invalid or missing
return value.  Assembler programmers using startup code should
jump to the startup code's  _exit with a valid return value on
the stack.  Programs without startup code should return with a
valid value in D0.  Valid return values are defined in
libraries/dos.h and i.  Other values (-1 for instance) can cause
CLI error  messages such as "not an object module".  


CLI Won't Close on RUN  

A CLI can't close if a program has a lock on the CLI input or
output stream  ("*").  If your program is RUN >NIL: from a CLI,
that CLI should be able to close unless your code or your
compiler's startup code explicitly opens "*".


Crashes and Memory Corruption at Run Time

Memory corruption, address errors, and illegal instruction errors
are generally caused by wild pointers - pointers which are
uninitialized,  incorrectly initialized, or point to memory
oresources which have already been freed or closed.  You may be
accidently modifying or incrementing a pointer later used to free
memory or close a resource.  The pointer may be one that you use
directly, or indirectly, such as in a structure which is  passed
to a system routine. 

Amiga functions which open system resources or allocate memory
typically return a pointer; if the open or allocate fails, zero
is usually returned.   So you must test the return value of a
system call for success before using  it as a pointer.

Test utilities such as MemWatch and MemMung can help catch the
use of  uninitialized pointers or freed memory.  MemMung is a
torture test which  sets freed memory areas and location $0 to
odd values (if your program is  written correctly, this will have
no effect; if not, your program is more likely to fail). 
MemWatch is a watchdog utility which reports modification of low 
memory (wild pointers often point to low memory, especially $0).

Memory corruption and crashes can also be caused by calling
functions with  the wrong arguments or missing arguments (for
example SetAPen(3) or  SetAPen(win,3), instead of SetAPen(rp,3)
).  Another possibility is that you might be overflowing  your
stack.  The compiler's stack checking option may be able to catch
this  (with Lattice, -v disables stack checking).  Cut stack
usage by dynamically allocating large structures, buffers, and
arrays if they are currently defined inside main() or your other
functions.  If you are using short integers be sure to explicitly
type any long constants (e.g. 42L).  For example, with short
integers, the expression 1 << 17 may become zero.  If corruption
is occurring during exit, use printf (or kprintf, etc.) with
Delay(n) to slow down your cleanup and broadcast each step.

A bad pointer which causes a system crash will often be reported
as a guru meditation 00000003 or 4.  Numbers in the range
00000006 - B may also indicate a problem with pointers.  These
numbers correspond to the hardware defined CPU exceptions of
Motorola's 68000-family of processors.  Generally these occur 
when the CPU tries to access a non-existent memory location or
execute an illegal instruction.  Other guru meditation numbers
are Amiga-specific, but may be caused by wild pointers; for the
meaning of these codes, refer to the include file exec/alerts.h.


Crashes - After Exit  

If your program crashes after exiting only when your program is
started from the Workbench, then you are probably UnLock()ing one
of the WBStartup message  wa_Locks, or UnLock()ing the Lock
returned from an initial CurrentDir() call.  If you call
CurrentDir() in your application, you should save the first lock
it returns and then call CurrentDir() on that lock before you
exit.

If you are crashing from both Workbench and CLI and you are only
crashing after exit, then you may be freeing or closing something
twice.  Also, you may be freeing or closing something that you
did not allocate or open.

A crash after your program exits can also be caused by leaving an
outstanding device IO request or other wakeup request.  If you
send an IO request and then exit, Exec, upon completion of that
IO request, will send a reply message to a port that no longer
exists.  You must abort and then WaitIO() on any pending IO
requests before you free things and exit.  See the autodocs for 
your device and for Exec AbortIO() and WaitIO().  Similar
problems can be caused by deleting a subtask that is in a wait 
loop such as WaitTOF().  Only delete subtasks when you are sure
they are in a safe state such as Wait(0L).


Crashes - Subtasks, Interrupts 

If part of your code runs on a different stack or on the system
stack, you  must turn off compiler stack-checking options
(Lattice uses the -v flag to  disable stack checking).  If part
of your code is called directly by the system or by other tasks,
you must use the large code / large data model, or use compiler
functions or options to assure that the correct base registers
are set up for  your subtask or interrupt code.


Crashes - Window Related  

Be careful not to call CloseWindow() during a
while(msg=GetMsg(...)) loop on that window's port because the
next GetMsg() will be on a freed pointer.   Also, use
ModifyIDCMP(NULL) with care, especially if you are using one port
with multiple windows.  Be sure to ClearMenuStrip() any menus
before closing a window, and do not free items such as
dynamically allocated gadgets and menus while they are attached
to a window.


Crashes - Workbench Only  

If you are crashing near the first DOS call, either your stack is
too small or your startup code does not GetMsg() the WBStartup
message from the process message port.  If your program crashes
only when started from Workbench and your startup code opens no
stdio window or NIL: file handles for Workbench programs, then
make  sure you are not writing anything to stdout (e.g. printf()
) when started  from Workbench (argc==0).  See also ``Crashes -
After Exit'' above.


Disk Icon Won't Go Away  

This occurs when a program leaves a lock on one or more of a
disk's files or directories.  


Fails Only On the 68020/30  

In general this occurs whenever an application inadvertently
contains a CPU dependency.  The following programming practices
will lead to programs which fail on the Motorola 68020/030 (but
run OK on the 68000):

 o	Using the upper byte of addresses for flags.
 o	Doing signed math on addresses.
 o	Writing self-modifying code.
 o	Using the MOVE SR assembler instruction (use Exec GetCC()
	instead).
 o	Using software delay or timing loops.
 o	Making assumptions about the order in which asynchronous
	tasks will finish.

Special features of the 68020/30 processors can also cause
problems for  programs written on the 68000.  For example, an
invalid cache entry due to DMA or other non-processor
modification of data which has already been cached;  a different
exception stack frame; interrupt auto-vectors moved by VBR; the 
68020/30 CLR instruction which does a single write access unlike
the 68000 CLR instruction which does a separate read and write
access (this might effect a read-triggered register in IO space -
use MOVE instead).


Fails Only On the 68000  

Again, a program which fails only on certain processors contains
a CPU dependency.  The following programming practices can cause
this problem:

 o	Software delay loops.
 o	Word or longword access of an odd address (illegal on 	the
	68000).
 o	Assumptions about the order in which asynchronous tasks will 
	finish.
 o	Using compiler flags which have generated inline 68881/68882
	math coprocessor instructions or 68020/30 specific code.
 o	Using the CLR instruction on a hardware register (it's
	behavior on the 68000 differs from the 68020/030 (use
	MOVE	instead) ).


Fails Only on Older ROMs or Older Workbench  

This can be caused by calling functions or using structures which
do not  exist in the older versions of the operating system.  Or
you may be asking for a library version higher than you need. 
Ask for the lowest version  which provides the functions your
application 

requires (usually 33).  You should not use the #define
LIBRARY_VERSION from the include files when you open a library. 
Also make sure you check OpenLibrary() calls for  success (a
non-zero return value).  If the library you request is not
available, exit gracefully and informatively. 


Fails only on Newer ROMs or Newer Workbench  

This should not happen with proper programming.  Possible causes
are:

 o	Running too close to your stack limits or the memory limits
	of a base machine (newer versions of the operating system
	may use slightly more stack in system calls, and usually use
	more free memory).
 o	Using system functions improperly.
 o	Not testing function return values.
 o	Using improperly initialized pointers
 o	Assuming that a system variable (such as a Flags field) is B
	if it is not A.
 o	Failing to initialize formerly reserved structure fields to
	zero.
 o	Violating Amiga programming guidelines (for example:
	depending on or poking private  system structures, jumping
	into ROM, depending on undocumented or unsupported 
	behaviors).
 o	Failing to read the function autodocs.


Fails On CHIP-RAM-Only Machines  

This is caused by specifically asking for or requiring MEMF_FAST
memory.   If you don't need chip memory, ask for memory type 0L,
or MEMF_CLEAR, or  MEMF_PUBLIC|MEMF_CLEAR as applicable.  If
there is fast memory available,  you will be given fast memory. 
If not, you will get chip memory.


Fails Only on Machines with FAST RAM  

Data and buffers which will be accessed directly by the custom
chips must be in chip memory.  This includes bitplanes (use
OpenScreen() or AllocRaster() ), audio samples, trackdisk
buffers, and the graphic image data for sprites,  pointers, bobs,
images, gadgets, etc.  Use compiler or linker flags to force chip
memory loading of any initialized data that needs to be in chip
memory.  You could also dynamically allocate chip memory and copy
the initialized data there.


Fails Only with Enhanced Chips  

This is usually caused by writing or reading addresses past the
end of  register space on older custom chips, or writing a
non-zero value to bits which are undefined in older chip
registers, or failing to mask out undefined bits when
interpreting the value read from a chip register.  


Fireworks  

A dazzling pyrotechnic video display is caused by trashing or
freeing a copper list which is in use, or trashing the pointers
to the copper list.  If you aren't messing with copper lists, see
``Crashes and Memory Corruption''. 


Graphics - Corrupted Images  

The bit data for graphic images such as sprites, pointers, bobs,
and gadgets  must be in chip memory.  Check your compiler manual
for directives or flags  which will place your graphic image data
in chip memory.  Alternately you could allocate chip memory and
copy the graphic image there.


Hang - Single Program Only  

Program hangs are generally caused by Wait()ing on the wrong
signal bits,  on the wrong port, on the wrong message, or on some
other event that will  never occur.  They can also be caused by
verify deadlocks.  Be sure to turn off all Intuition VERIFY
messages (such as MENUVERIFY) before calling  AutoRequest() or
doing disk access. 


Hang - Whole System  

This is generally caused by a Disable() without a corresponding
Enable(). It can also be caused by memory corruption, especially
corruption of low memory.  See ``Crashes and Memory Corruption''
above.


Memory Loss  

First, make sure that your program is actually causing the memory
loss.   Boot with a normal Workbench disk whose
s:startup-sequence  LoadWB command line has been changed to
LoadWB -debug.  It is important to  boot with a standard
Workbench because some third party applications  such as
background utilities, shells, and network handlers dynamically 
allocate and free memory.  Arrange all windows so that part of
the  Workbench backdrop window is accessible and so that no
window rearrangement  will be needed to run your program.  Select
flushlibs from the rightmost  Workbench menu.  Any disk-loaded
fonts, libraries, devices, etc. that are  not currently open will
be flushed from memory.  Wait a few seconds, then  click on the
Workbench backdrop.  Write down the amount of free memory 
displayed in the Workbench title bar.  Now without rearranging
any windows,  run your program and use all of the program
features.  Exit your program,  wait a few seconds, then click on
the Workbench backdrop.  Now select  flushlibs, wait a few
seconds and write down this final free amount.   If this matches
the first value you wrote down, then your program is fine and is
not causing a memory loss.

If memory was actually lost and your program can be run from CLI
or Workbench, then try the above procedure with both methods of
starting your program.  See ``Memory Loss - CLI Only'' and 

``Memory Loss - Workbench Only'' as  appropriate.

If you lose memory from both Workbench and CLI, then make sure
all calls to functions which open/allocate/create/lock have a
matching call to the corresponding close/free/delete/unlock
function  (there are a few  system calls that do not require a
corresponding free - check the autodocs).   Generally, the
close/free/delete/unlock calls should be in the opposite order of
the allocations.

If you are losing a small, fixed amount of memory, look for a
structure of that  size in the Structure Offsets listing in the
Includes and Autodocs manual. For example, a loss of exactly 24
bytes is probably a Lock which has not been UnLock()ed.  If you
are using ScrollRaster(), be aware that ScrollRaster() left or
right in a SUPERBITMAP window with no TmpRas will currently lose 
memory (workaround - attach a TmpRas).  If you lose much more
memory when  started from Workbench than from the CLI, make sure
your program is not using  Exit(n).  This would bypass startup
code cleanups and prevent a Workbench-loaded program from being
unloaded. Use exit(n) instead.


Memory Loss - CLI Only  

Some third-party shells dynamically allocate history buffers, or
cause other  memory fluctuations.  Also, if your program executes
different code when  started from CLI, check that code and its
cleanup.  And check your startup.asm if you wrote your own.


Memory Loss - Ctrl-C Exit Only  

This occurs when you have Amiga-specific resources allocated and
you have not disabled your compiler's automatic Ctrl-C handling
(causing all of  your program clean-ups to be skipped).  Disable
the compiler's Ctrl-C  handling and handle Ctrl-C yourself.


Memory Loss - During Execution  

A continuing memory loss during execution can be caused by
failure to keep up with all of the IDCMP messages (such as
MOUSEMOVE) that you request from Intuition.  Intuition can not
reuse IDCMP message blocks until you call ReplyMsg() on  them. 
If your window's allotted message blocks are all in use, new ones
will be allocated and not freed until the window is closed. 
Continuing  memory losses can also be caused by a program loop
containing an  allocation/open type call without a corresponding
free.


Memory Loss - Workbench Only  

This is often caused by the failure of your code to unload after
you exit.  Make sure that your code is being linked with a
correct, standard startup module and do not use the Exit(n)
function to exit your program.  The Exit(n) function will bypass
your startup code's cleanup, including its ReplyMsg() of the
WorkbenchStartup message (this signals Workbench to unload your 
program from memory).  You should exit via exit(n) where n is a
valid DOS  error code such as RETURN_OK (libraries/dos.h).  You
may also exit with a final closing brace "}" or with the return
statement.  Assembler  programmers using startup code can JMP to
_exit with a long return value on the stack or use the RTS
instruction.


Menu Problems  

A flickering menu is caused by leaving a pixel or more space
between menu subitems in your menu structures.  Crashing after
browsing a menu (looking at menu without selecting any items) is
caused by not properly handling MENUNULL select messages. 
Multiple selection not working is caused by improper handling of
the NextSelect field properly. See the Menus chapter from the
Intuition  manual for more details.


Out-of-Sync Response to Input  

This is caused by failing to handle all received signals or
messages after waking up from a Wait() or WaitPort() call.  More
than one event or message may have caused your program to be
awakened.  Check the signals returned by Wait() and act on every
one that is set.  At ports which may have more than one message
(such as a window's IDCMP port) you must handle the messages in a
while(msg=GetMsg(...)) loop.


Performance Loss in Other Processes  

This is often caused by a program doing one of the following:
Busy waiting or polling.
Running at a higher priority.
Doing lengthy Forbid()s, Disable()s, or interrupt handling.


Sound Samples Won't Play Correctly

The data for audio samples must be in chip memory.  Check your
compiler  manual for directives or flags which will place your
audio sample data in chip memory.  Also, you can dynamically
allocate chip memory and copy or load the audio sample there.


Trackdisk Data Not Transferred  

This may occur if your trackdisk buffers are not in chip memory.


Windows - Borders Flicker after Resize  

Set the NOCAREREFRESH flag.  Even SMART_REFRESH windows can
generate refresh  events if there is a sizing gadget, so if you
don't have specific code to handle this, you must set the
NOCAREREFRESH flag.  If you do have refresh code, be sure to use
the Begin/EndRefresh() calls.  Failure to do one or the other
will leave Intuition in an intermediate state and slow down
operation for all  windows on the screen.



 GENERAL DEBUGGING TECHNIQUES 

Isolate the problem by using printf() to find the section of code
in which the problem occurs.  If you cannot display messages on
the screen, use kprintf() to send messages to the serial port or
dprintf() for the parallel port (see Linker Library
documentation).  Check the initial values, allocation, use, and
freeing of all pointers and structures used in the problem area. 
Also make sure that all of your system and internal function
calls pass correct initialized arguments and that all possible
error returns are checked for and handled.



Use Debugging Tools  

A variety of debugging tools are available to help locate faulty
code.  There are source level debuggers (such as Lattice's
CodePRobe), crash interceptors (such as GOMF), memory watchdogs
like MemWatch and WatchMem, and other helpful tools like MemMung,
Avail, WBFrags, etc.  


Test With Different Configurations

Test your program on a wide variety of systems and
configurations.  Programs with coding errors may appear to work
properly on one configuration but may fail or cause fatal
problems on another.  Make sure that your code is tested on both
the 68000 and the 68020/30, on machines with and without fast
memory, and on machines with and without enhanced chips.  Test
all of your program  functions on every machine.


Test All Error and Abort Code

A program with missing error checks or unsafe cleanup might work
fine when  all of the items it opens or allocates are available,
but may fail fatally when an error or problem is encountered. 
Try your code with missing files, filenames with spaces,
incorrect filenames, cancelled requesters, Ctrl-C, missing
libraries or devices, low memory, missing hardware, etc.

Test all of your text input functions with international ASCII
characters (such as the character produced by pressing ALT-F then
A).  Rawkey codes  produce different keyboard characters on the
various national keyboards  (higher levels of keyboard input are
automatically translated to the proper  characters).  If your
program will be distributed internationally, support  and take
advantage of the additional screen lines available on a PAL
system.  On A2000s with the enhanced Agnus chip, a PAL display
can be selected via  motherboard jumper J102.  Note that a base
PAL machine will have less memory free due to the larger display
size.
