This article is designed to demonstrate how you can use Fantasm's more powerful features to achieve a goal that at first seems either impossible or expensive. It explains how you can use Fantasm to automatically verify memory writes at run time from your code and stop if the write would write outside of your application heap. This will catch many bugs that can be extremely difficult to find.

Over the last couple of weeks I have had spurious crashes in various applications, including things like the Finder, whilst running some development software written with Fantasm. I finally realized that my App must be writing out of it's heap and
corrupting other applications.

The Mac (currently) does not have "protected memory", which would have helped me find the fault. There are utilities available for about 200 bucks that will try to spot writes out of your allocated space, or failing that I could write a "padded cell" for my app that would run the instructions via emulation, checking where a given instruction was about to write to. Neither of these appealed to me very much, but the second was looking more and more likely.

A few days back, I was working on something completely different (as you do when faced with a big problem) when it suddenly dawned on me how to find my fault and provide some excellent future debugging aids into the bargain. It was going to cost me zero bucks and about an hour of my time. How?

Two of Fantasm's many directives, macs_first and macs_last, allow one to tell Fantasm how to search for a given item. For example, when faced with "stw", it would normally search the directives first, then the instruction list and finally the macros (there are good reasons for this seemingly non-efficient search order that I won't go into here). Now, macs_first and last allow you change this behaviour; specifically macs_first causes Fantasm to search in the defined macros list first. This means we can override instructions with macros.

Back to my writing out of my heap problem. All I did was defined a macro called "stw" and used the macs_first directive at the top of my source files(see note at end). This causes Fantasm to execute the "stw" macro rather than assemble "stw" instructions, which was the most likely candidate that was writing out of my heap with a bad address.

My stw macro read something like this:
if the address we are about to store to is not in our heap, then cause an
illegal instruction.


Simple and incredibly effective 'cause we get to stop right before the instruction that would cause our Mac to go all wobbly. This found the fault within 3 seconds of booting the reassembled app up! What had happened was I had erronously used a volatile register to hold a pointer over an OS call, which was trashing the volatile register.

I could have found it by eyeballing the code, but 110,000 lines of code is a lot to eyeball!

OK, so this mod added on about 200K to my executable size, and cut execution speed by 20%, but it's only debugging code that can be switched off simply by setting a global definition.

Here's the details.
1. Add two global variables to your project:

**test.globs
heap_low:	rs.w	1
heap_high:	rs.w	1	*range of valid addresses for our app

2. Execute this code immediately your app starts up:

	import	GetZone
	Xcall	GetZone	*bottom of heap
	stw	r3,heap_low(`bss)
	stw	sp,heap_high(`bss)

3. The macro to check the address looks like this:

check_addr:	macro	*needs an offset in \1 and a register in \2
**E.G.  check_addr 32,r3
		addi	`temp_reg1,\2,\1	*temp_reg1=the address
		lwz	`temp_reg2,heap_low(`bss)
		cmpw	`temp_reg1,`temp_reg2
		bgt	low_ok\@
		illegal
low_ok\@:
		lwz	`temp_reg2,heap_high(`bss)
		cmpw	`temp_reg1,`temp_reg2
		ble	high_ok\@
		illegal
high_ok\@:
		endm
	

The actual macro that replaces stw receives two parameters separated by a comma, as per a normal stw instruction. The first parameter is rS, which is the general purpose register (GPR) that contains the data we are storing, the second has the form of d(rA), where d is an optional offset, and rA is the GPR containing an address.
What we want to do is check the address held in rA+d against our heap bounds. Hence we need to decode two items - d and rA and pass them to check_addr. This will involve some string manipulation, but Fantasm is reasonably good at that.
1. Find the open brackets. If there is no open brackets, it's a syntax error.
2. The text to the left of the open brackets is the offset.
3. Find the close brackets. If there is no close brackets it's a syntax error.
4. The text between the open and close brackets is rA.

safe_store:	macro
open_p:	fndc	\2,"("	*the position in \2 of the open brackets
		iflt	open_p	*if fndc fails, it returns -1
			fail	Syntax 
		endif
length:	len	\2
**get offset
[o$]:		left$	\2,open_p
**get register
[r$]:		right$	\2,length-open_p+1	*get text between open parenthesis and end of string
close_p:	fndc	[r$],")"
		iflt	close_p	*if fndc fails, it returns -1
			fail	Syntax
		endif
[r$]:	left$	[r$],close_p			*get register
		check_addr	[o$],[r$]		*generate code to check the address
		endm	

This macro is called from a macro called stw which reads as follows:

**stw rs,d(ra)
stw:	macro	*Ensure macs_first is in-force to ensure this macro overrides stw.
	safe_store	\1,\2
	macs_last	*without this we would recurse in the stw instruction
	stw	\1,\2	*The actual instruction
	macs_first
	endm

 

Here's a complete program that demonstrates this:

The program starts at main. The first thing it does is load up the bss pointer and heap_low and heap_high variables. Then it overrides normal assembly with the macs_first directive. For this example we will purposly make the mistake of storing the data in var1(r3) rather than var1(`bss). We load up r4 with the data "1" and the address 0 in r3. Then we make the (deliberate) mistake of storing r4 in var1(r3) rather than var1(`bss). When we run the program we end up in Macsbug:

PowerPC illegal instruction at 0497F7E4 main+00054
 Disassembling PowerPC code from 0497F7BC
  main
     +0003C 0497F7CC   li         r3,0x0000                               | 38600000
     +00040 0497F7D0   li         r4,0x0001                               | 38800001
     +00044 0497F7D4   addi       r20,r3,0x0008                           | 3A830008
     +00048 0497F7D8   lwz        r21,0x0000(r30)                         | 82BE0000
     +0004C 0497F7DC   cmpw       r20,r21                                 | 7C14A800
     +00050 0497F7E0   bgt        main+00058                 ; 0x0497F7E8 | 41810008
     +00054 0497F7E4  *dc.l       0x7C00012A                              | 7C00012A
     +00058 0497F7E8   lwz        r21,0x0004(r30)                         | 82BE0004
     +0005C 0497F7EC   cmpw       r20,r21                                 | 7C14A800
     +00060 0497F7F0   ble        main+00068                 ; 0x0497F7F8 | 40810008
     +00064 0497F7F4   dc.l       0x7C00012A                              | 7C00012A
     +00068 0497F7F8   stw        r4,0x0008(r3)                           | 90830008
  Closing log

Which is exactly what we wanted. We have stopped before the stw r4,8(r3) instruction which would have made our machine unsafe. For comprehensive checking you'd need macros for other stores and possibly reads as well; you may also want to allow writing to the System heap which would mean modifications to check_addr.

 

©Lightsoft 1998.

We always enjoy feedback from articles, so if you have any constructive criticism, corrections or wish to clarify something, please email us or post to the Fantasm mailing list.

 

*Actually, what I did was include the macs_first directive in one of my globally included files.


Other articles in this series: Fantasm and C