Topic : The GFA-Basic Compendium
Author : GFA Systemtechnik GmbH
Version : GFABasic.HYP v2.98 (12/31/2023)
Subject : Documentation/Programming
Nodes : 899
Index Size : 28056
HCP-Version : 3
Compiled on : Atari
@charset : atarist
@lang :
@default : Document not found
@help : Help
@options : +g -i -s +z
@width : 75
@hostname : STRNGSRV
@hostname : CAB
@hostname : HIGHWIRE
@hostname : THING
View Ref-FileDebugging with and MiNT
by
Revision 0.10b
May 4, 2006
Introduction
This document describes a method for debugging when all else fails and all you
have to go on is the crash log MiNT dumps in the console. Sometimes you can
simply add PRINT statements at various points in the program in order to narrow
your search. However sometimes this becomes rather tedious, especially in very
large programs. In some extreme cases I've actually seen programs suddenly stop
crashing simply by adding PRINT statements. Needless to say that is a very
frustrating situation if that happens. The method described here will help you
locate the exact PROCEDURE or FUNCTION in a more precise manner. Hopefully
saving you time which can be put to better uses, like coding more applications.
:o)
Note: The particular example presented here is not an actual crash log from a
compiled program, however all the same principles apply.
Understanding the crash log
Firstly one needs to understand the information MiNT is trying to provide when
an application crashes. A typical crash log might look like this:
PROCESS "make_ndx" KILLED: MEMORY VIOLATION. (PID 021) Type: free
PC: 0050A128 Addr: 0090BFD4 BP: 0050A000
Note: The example used here is a an application that crashes only when MiNT's
memory protection (MP) is enabled. All values are in hex notation.
The log entry breaks down like this:
Entry Description
PROCESS "make_ndx" name of the program that crashed
KILLED: MEMORY VIOLATION the reason the program was killed
(PID 021) GEMDOS Process ID of the program
Type: free type of memory block accessed
PC: 0050A128 address of Program Counter
Addr: 0090BFD4 address it attempted to access
BP: 0050A000 BASEPAGE address of the program
Type can have one of the following entries:
Type Description
private owned by another application
global remapped to 'hardware' for violation reports
super only accessible in supervisor mode only
readable read only, writing not allowed
free free memory, has no owner
hardware memory not controlled by MiNT
This represents the type of memory block the application attempted to illegally
access. This can be an important clue.
Note: Information taken from file mprot.x on the MiNT CVS server.
Interpreting the crash log
The most interesting pieces of information here are the PC and BP values. With
a little math we can determine the exact offset into the binary where the crash
occurred.
OFFSET = PC - (BP + 256)
This is accomplished by subtracting the BASEPAGE + 256 from the Program Counter.
Adding 256 to the BASEPAGE accounts for the size of the BASEPAGE itself. We are
not interested in the BASEPAGE because it will not be included in the
disassembly.
In the above example we get this:
$0050A128 - ($0050A000 + $100) = $28 !hex
5284136 - (5283840 + 256) = 40 !decimal
We now know that at offset $28 (40) in the binary is where the crash actually
occurred.
I know the offset, now what?
The next step would be to recompile the application with the Symbol Table option
set to on. This allows TTDigger (TTD) to show much more detailed information
when disassembling a program. All the PROCEDURE and FUNCTION names in the
program will be visible in TTD starting with an underscore. Some examples:
TTD
FUNCTION test _TEST:
FUNCTION dump_tree(addr%) _DUMP_TR:
PROCEDURE init_system _INIT_SY:
PROCEDURE go(x&,y&) _GO:
Only the first 7 characters of PROCEDURE and FUNCTION names will be shown.
Entry points (labels) for PROCEDURE and FUNCTION names are always aligned to
the left edge of the window and followed by a colon.
Note: The 7 character limitation within the Symbol Table is actually part of
the DRI Object File Specification.
Symbol Table warning
The Symbol Table will make the binary larger. It is also recommended you do
not leave this option on all the time. Distributing binaries with the Symbol
Table serves only hackers, and it makes reverse engineering your application
quite a bit easier. So always remember to disable the Symbol Table option when
you are done debugging!
Using TTD
Now start TTD and load your application. Scroll down to the offset which you
calculated earlier ($28). Once you locate the offset, you can see the assembler
instruction that actually caused the program to crash. Now scroll up to the
first label that begins with an underscore "_". This should be the PROCEDURE
or FUNCTION your program was executing when it suddenly came to a grinding halt.
Note: TTD can be set to work in Hex or Decimal mode.
It's possible the offset does not lie in the range of your application. This
can happen if you are loading and executing external modules or overlays. If
this happens you can apply the same techniques to the module or overlay.
However if you are not the author of the module or overlay, you probably won't
have access to the source code and thus cannot resolve the problem yourself.
Another tip that might help
If you suspect your program is crashing outside of a PROCEDURE or FUNCTION you
can add some markers that are easy to spot in TTD.
Examples: dummy$="XXXX"
dummy$=">>>>"
The Compiler converts 4 character strings directly into code instead
of placing them in the DATA segment of the binary. Thus they will appear in the
TTD listing exactly where you place them in your source code.
Note: Turn on the HEX/ASC option in TTD.
TTD output (further analysis)
Here's the actual code segment from the offending application:
* Created by TT-Digger v7.0
* Mon Apr 24 07:54:20 2006
* TEXT $0004FC bytes, segment starts at $00000000
* DATA $00005E bytes, segment starts at $000004FC
* BSS $00014E bytes, segment starts at $0000055A
* SYMBOLS $000000 bytes
* FLAG $0000
000: movea.l $0004(a7),a0 ; a0 = basepage addr
004: move.l $000C(a0),d0 ; d0 = length of text segment
008: add.l $0014(a0),d0 ; add d0,length of data segment
00C: add.l $001C(a0),d0 ; add d0,length of bss segment
010: addi.l #$00000100,d0 ; add d0,256
016: move.l d0,-(a7) ; push new size on to the stack
018: move.l a0,-(a7) ; push block addr on to the stack
01A: clr -(a7) ; push 0 on to the stack
01C: move #$004A,-(a7) ; push Mshrink opcode on to the stack
020: trap #1 ; Gemdos (make the call)
022: adda.l #$0000000C,a7 ; restore stack to previous state
028: move.l #$FFFFFFFF,-(a7) ; push -1 on to the stack
02E: move #$0048,-(a7) ; push Malloc opcode on to the stack
032: trap #1 ; Gemdos (make the call)
034: addq.l #6,a7 ; restore stack to previous state
Note: This is only a partial listing. The actual listing is much larger, but
its not really needed to illustrate the problem. Comments added for clarity.
Program breakdown:
$000 - $010 computes the actual program size, result in d0
$016 - $022 MSHRINK call -> status%=MSHRINK(addr%,size%)
$028 - $034 MALLOC call -> free_ram%=MALLOC(-1)
Understanding the problem
From the listing we can determine the crash occurs very early on in the programs
start up phase. We can see at offset $28 we have this:
move.l #$FFFFFFFF,-(a7)
This one line of code simply tries to move (LPOKE if you will) the value -1 to
where ever the stack pointer is currently pointing. Why does this one line of
code bring our program to a grinding halt?
The answer in this case requires some basic knowledge of how MiNT starts up an
application. The first thing MiNT does is allocate all available ram (largest
available block). It then creates a BASEPAGE for the application in the first
256 bytes and following that is the actual code of the application. By default
MiNT sets the user stack pointer to the end of the memory block.
This line of code is actually the program preparing for a MALLOC call. It is
attempting to inquire (-1) the largest free memory block.
What happens in this particular case is the program attempts to do the right
thing and give back memory it does not need with MSHRINK. However, before doing
so it fails to move the stack pointer to a safe location inside it's own memory
space. Remember the stack pointer by default is pointing to the end of the
memory block. After the MSHRINK call the stack pointer is literally left
pointing out into free memory! As soon as the MSHRINK succeeds the memory is
no longer owned by the application and under no circumstances will MiNT allow
you to access memory you do not own when MP is enabled. The program is
instantly killed as a result. This is a huge mistake on the part of the
programmer, if not downright sloppy programming.
There you have it, a way to let MiNT help you debug your programs.
Contact
Hopefully the information presented here is useful. To make suggestions or
report a typo please visit:
http://www.bright.net/~gfabasic/
eof