home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
QuickTime 2.0 Developer Kit
/
QuickTime 2.0 Developer Kit.iso
/
mac
/
MAC
/
Programming Stuff
/
Macintosh Debugging
/
Blat
/
Blat.p
< prev
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
NeXTSTEP
RISC OS/Acorn
Shift JIS
UTF-8
Wrap
Text File
|
1992-08-07
|
67.1 KB
|
1,363 lines
|
[
TEXT/MPS
]
{ Blat:
a dcmd to use the MMU to protect the low 256 bytes of RAM from stray use.
There is no data there, so any program writing (or reading) there is suspect. This is an
even better, better bus error, since it will catch writes to 0, as well as reads from
zero, and anything up to 255.
Down side: You lose a bit of performance when this is installed. I set the MMU table
size to be 256 bytes, and the extra overhead costs some performance. Overall, the
machine is not noticeably slower. I change VBR, install my own MMU tables. These
are in use even when Blat is turned off. I would leave it installed only when actively
testing, and remove it when not, just as a conservative approach. This is a fairly weird
hunk of code, because it is so close to the machine.
Caveats:
Sorry, but it won't work on a IIci, or IIsi. The onboard video causes grief, and the
Ram is wildly mapped, even when it is in use. Requires a special case to fix it.
The other bad thing is that it won't work on 040 machines. The MMU is so different
that my tables won't work, so it would require another special case.
Copyright © 1991 by Apple Computer, Inc., all rights reserved.
by Bo3b Johnson 8/28/91
MS 37-DS
for best viewing, use Palatino 12.
8/28/91: First version of the program started from a skanky prototype. Most
of the really hard problems were solved there.
10/4/91: First real release to people. Version 2. Includes the Friedlander
interface additions, and tested on an fx and IIx. Thanks, Jim, it's way
easier to use now.
10/16/91: Version 3. Fixed bug in 32 bit tables, that would map 1 Meg hunk at
8 Meg and 9 Meg to the same place.
Fixed macsbug not being able to set breakpoints because I left protection
on when I invoked it with DebugStr. Now leave protection off.
Changed table in 32 bit mode to have all DT=1 entries, since Microsoft Word
had a bus error that was being handled by the system. I had way high ram
marked as invalid dt=0, for $1000 0000, and that made my MMU check fail
in BusErrorGlue. Now set dt=1, it is handled by a system bus error handler.
Commented out starting ROM addresses, since they are fx specific.
10/23/91: Version 4: Fixed bug calling Gestalt early in boot, that would give
gestaltUndefSelectorErr, and I'd say VM was installed when it wasn't. Now
use extra AND to case on that error, and assume VM isn't installed if I get that.
Added code to protect the VBR page too, so that I can bust Macsbug from taking
over the bus error vector. Check for VBR page validity too.
Bumped up possible ignores to 200.
10/30/91: Version 5: Fixed bug introduced by locking the VBR page too, that of having
Macsbug not be able to install an A-trap handler. Now allows all through
except for writes to the bus error vector, which is saved in a global.
Finally forced to look for Macsbug active using the MacJmp flag, since at boot
time Macsbug does its init stuff without having NMI blocked.
Cleaned up a few variables and routines that weren't necessary anymore,
because of the new 24-bit table reusing 32-bit table, and VBR installing in
the system heap.
Killed the Get/Set VBR/TC routines, since I do it all in one routine now that
is called SetMMURegisters. This is superior, since I can change the CRP and
TC without turning off the MMU, which will be necessary for IIci someday.
}
UNIT Blat;
{
Build this dude using the Build Menu, it has a make file.
Things to do:
The registers are partly fried by the DebugStr for display. Fixes?
Clean it up a bit.
Make it work in IIsi, IIci case.
Bigger page size matter on speed?
040 case of walking the tables to find bits to hit, spiffy bus error handler.
Seem to have trouble with macsbug Log still, get a hang, waiting in syncwait.
Incompatible with DAL? Was problem on IIx, don't know why.
Not particularly pleased with the init code, it seems really ragged and should be
modularized in a nicer form.
Maybe not save the bus error handler in a global, use 8 instead? This would
require turning off protection to get it.
Is VBR based handlers not being a chain OK? ie. I force it to always be me.
Questions:
Do I need to have a semaphore to prevent reentry? I had one as an experiment in
the prototype, but the bug turned out to be something else. I've removed it here,
under the assumption that it is unnecessary. It was an attempt to prevent the
handler from freezing Macsbug, by trying to break when Macsbug was invoked
to handle a real bus error. I solved that by looking at NMIFlag instead.
Should I use the Macsbug flag instead of the NMIFlag to determine if Macsbug is
running? It's maybe more accurate, but it's in two places depending upon 24/32 bit
mode. Also, I'm pretty darn sure that NMIFlag is always set when Macsbug is
running, to prevent NMIs inside of Macsbug.
If I don't turn off the protection while you are in Macsbug, the user can't use or
see that memory, but nothing bad happens. Should I set it that way too, just
for safety? Currently it is always on when I invoke Macsbug. If you set a
breakpoint, or break in some other way, it is still active too.
When the TC register disables the MMU, RAM is mapped straight through. On a
IIci this would be bad for example, since the video is mapped at zero. This should
ensure that I won't get any bad jumps or anything while it is off.
On a IIci class machine, I could drive the installed table instead, and articulate the
branch if need be. This would enforce the system page size of 32K though, so
that needs some thought. In the 040 case, the same is true, but need a really
spiffy bus error handler.
Couldn't decide whether the bus error handler needs to check for the address
being larger than 255, and calling the old one if so. The way I fixed the Word
problem was to make all pages valid so that the Invalid bit in the MMSR would
not be set. Not sure if that was the right way.
General:
OK, one big problem here is that the dcmd cannot turn on the MMU stuff when
it is called from Macsbug. As a hang example, when the Bus Error handler calls
Macsbug via DebugStr, that starts up Macsbug. The first thing Macsbug does is
try to change the bus error vector in low memory, so that it can handle its own
bus errors. This invokes my bus error handler for a invalid write, where I call
Macsbug... and you get the big wedge. I fixed it partly by watching for the NMIFlag
to be on, which means that Macsbug is executing. When it is executing, I don't
report any references to the zero page, since any DebugStr while Macsbug is
executing will cause a hang. This was partly successful, but the next problem is
that Macsbug calls the video-driver to swap video depth. That driver installs
a bus error handler temporarily to catch it killing itself I guess. Anyway, this is
after Macsbug has installed its own bus error handler, so when the video driver
writes to $8, it invokes Macsbug's bus error handler, and that causes Macsbug to
hang again, as it tries to display a message before it is fully operational. So....
This leads to the use of the VBR register to remap the vectors to a different
location. I currently put them in a block in the dcmd code space, which is way
high in memory, and a smaller target. The trouble with this is that now any
of the system wide bus error handlers that are installed are being installed
zero based, not VBR based (the 68000 didn't have a VBR register, so the original
code had to use zero based, and no one has paid attention to VBR yet.) Installed
there, my bus error handler still takes precedence, and I can pass along any
non-MMU error to the bus error handler at $8. This is what VM does too.
I don't want to do it for all the vectors though, so any operations other than
bus errors won't get fed through.
Too bad I guess. There's nothing terribly important, but its something to note.
OK, the definitive answer on whether I need to use VBR or not. The answer is
yes, I must. The reason is that if I set the zero page (0..255) as invalid, with DT=0,
then the cpu will halt with a double bus fault. It takes an interrupt say, that
causes a fault, then it tries to get the bus error vector, which also causes a fault.
Biff, you've got a locked machine. Ergo, I must use VBR, and it doesn't have
anything to do with Macsbug weirdness. Currently, I shadow everthing up to the
VBR page, as the active vector page, and leave the zero page locked. Both the
zero page and the VBR page should be the same, except when some VBR
knowledgable code like Macsbug sneaks in and changes the VBR page.
And, definitively, I must check for Macsbug running and avoid calling DebugStr
while it is active. Macsbug itself is OK since it uses VBR based handlers. It calls
the video driver which tries to write to 8, which forces a bus error which makes
Macsbug freak out with 'macsbug caused the exception'. If Macsbug left it's handler
installed until after the video swap it would be OK, but it puts me back, then swaps
video, causing me to invoke macsbug while it is still active. Hence, I put in the
check for IsMacsbugActive to avoid the problem.
The other part of the story is that dcmd's can't set VBR, since Macsbug saves
and restores that value during it's switch. So the dcmd changes the real, active
VBR, which is then crushed at Macsbug exit by the one saved in Macsbug's
register file. I can get around this by installing all my stuff at dcmd init time,
during boot. This is not fully desireable since I really wanted to leave the
machine in a more pristine state when Blat wasn't turned on. The only real
difference will be that Blat installs it's tables and sets up VBR and the MMU
registers at dcmdInit, but leaves the protection itself turned off, so that the
0 page is marked as valid instead of write or read protected. This way the
computer will use my tables during normal use, and turned on only when told
so by the dcmd. The downside of this is that it will slow the computer by around
10%, since my tables are bigger than the systems (256 bytes/page, instead of 32k bytes).
This probably means you don't want to leave Blat installed when you aren't
actively using it, since it modifies the system at a fundamental level.
An experiment that I started was to try to see if I could make the system work
with a larger page size, like 4K. The idea was to set up the bus error handler to
allow anything above 255 to go through, and only stop the 0..255 references. The
test was to see if doing it that way with a bigger page would be slower or faster
than having the 256 byte page size. The experiment failed in this environment,
since Macsbug uses low memory globals, and would thus generate an internal
bus error while it was running. That would make it try to say 'Macsbug caused
the exception', which caused another bus error, and whammo you've got a well
hung machine. I don't currently see any way around this, so I'm bagging the
experiment for now.
The VBR based page is being used by the CPU while Blat is running. This disables
any short term bus error handlers that are installed at zero, which is bad because
they are there to catch and handle weird errors, and to keep the system running.
I want those to still be active so that Blat doesn't disable some of this error fixing
code while it is installed. Note this would happen even if Blat wasn't turned on,
since I have to install the VBR based table for reasons specified above (Macsbug
will hang). So to avoid that problem and to make it more generally friendly, I
will turn on write protection from the start, when it is installed, and any time I
get a bus error I'll do the EmulateAccess so that that access will install into the
0 based page, as well as the VBR page. By doing this I can keep the pages in sync,
so the system won't really notice that I'm doing a weird thing by moving VBR
from 0. This doesn't give any safety to things since a bad access down there can
smoke the valid VBR table now, but in general the system runs smoothly, and
this is an informant type of tool instead of trying to enforce system rules. In
other words, if it previously crashed the computer, it still will; but now you can use
Blat to stop at invalid references so you can find the crashing bug immediately.
When Blat is turned on, that will just set a global that decides whether I should
stop or not, since the protection will already be on. Blat is thus never really
off when it is installed. It will just not show bus errors that it catches. Another
side of this problem is that I cannot just copy up the temporary bus error handlers
that are installed while the system runs, since they would then be installed VBR
based, and when they went to de-install themselves at 0, an MMU induced bus
error would result, and their bus error handler would take it. I can't expect them
to handle a bus error of this form, so as a hack I don't shadow writes to 8 up to
VBR+8, since those are bus error handlers. This leaves my bus error handler in
charge up there at VBR+8. Macsbug doesn't have a problem with this, since it
always installs its handler at VBR+8, and thus avoids writing to 8 and catching
the spurious errors. When macsbug is executing, my bus error handler is
not installed, so you can't write to the zero page, even with macsbug.
In order to keep those temporary bus error handlers active and working like they
are supposed too, I need to pass along bus errors that aren't MMU related. This
is for normal crash and burn type of bugs, that may be caught by a handler. The
system is full of these temp install things, and I need to keep them working as
expected, or the stability of the system with blat installed would be worse. In order
to do this, when I take a bus error in my handler, I look to see if it is MMU related
by doing a PTEST instruction. If that says it's an MMU error, then I'll go ahead
and do the normal blat thing to see if I stop or let it go through. If it isn't an
MMU induced bus error, then I just feed the error on to the handler that is
installed at 8. That will be the current handler, and presumably knows how to
handle the error. It can also call up the debugger in the case where it shouldn't
have happened. Thus temporary handlers are available still, and the system
should run as it normally does, except a bit slower due to the table overhead and
the bus errors that don't cause stops.
An interesting problem I ran into on a different machine was that the IIfx will
run properly even if the RAM is in the wrong bank. The machine I looked at
had 16 Meg in 4 Meg simms, but they were installed in Bank B instead of in
Bank A as is customary. The ROM is smart enough to set up MMU page tables
to map all the RAM back to memory location 0 from 128Meg where Bank B
starts. This poses quite the problem for me however, since the RAM is
physically installed way up there. When I turn off the MMU to remap it for
my tables, the machine crashes, of course, since RAM is now 128 Meg away, and
the PC is executing funny space. So... in case it doesn't work on your IIfx, look
to see if your RAM is installed in Bank B instead, will make it impossible for
me to run. (I don't think there is a solution to the problem, other than having
real RAM in Bank A). As long as there is some RAM in Bank A, I should be fine.
SwapMMUMode will flush the cpu caches when it changes mode. Specifically I
don't need to do that, because the addresses and data that are cached are still
valid after I set a new CRP with new tables. I don't change the mapping like
SwapMMUMode, I only change the table types.
Wow, what a surprise, another totally gnarly problem to try to solve. The problem
occurs when protection is on, and you break into macsbug. Macsbug is then running
with protection (either read or write) turned on, and anything that happens will
make Macsbug barf. Macsbug has installed it's own bus error handler into the VBR
based table, upon entry, as a way of catching errors and restarting it's 'Main Event
Loop'. The trouble is, when you do something like Log, or even switching to the
regular mac screen, some system code runs, which tries to install another bus
error handler, zero based. That is busted by still on protection, and the current
bus error handler being Macsbug-- gives the technicolor yawn. This doesn't have
anything to do with my leaving the protection on from boot on up, since it will
happen whenever the protection is active, like when Blat is doing its job. Waah.
It's too hard. OK, so what I'm doing now is to try to lock down the bus error
handler so that only I have control over the handler, and can't be replaced by
other code, most specifically, Macsbug. I need to have a VBR based set of vectors,
since I still need to allow for read protection, and you can't do read protection on
the active vectors, as noted above. I can however do write protection on the VBR
based vectors, which is what I'm doing to prevent Macsbug from coming in and
stomping my vector. I need to take control first, and allow things to go on through
as if I wasn't there, whenever Macsbug is active. This will prevent the type of
bug where Macsbug catches a bus error that I should be handling. I'll still keep
track of the current secondary bus error handler, and call it if it isn't an MMU
protection error. Motorola really blew it by overloading the bus error vector. This
would have been easy if they'd made a specific MMU bus error vector. Now, in
order to protect the VBR vectors too, I need to have an entry in my tables to
set the WP bit on. I don't want to change the tables too much, since they are hard
to debug, so I'll make the restriction that the VBR page has to be allocated in the
system heap in the first 64K, which is how far my table stretches for the fourth
level in 32 bit mode. That's 256 entries of 256 bytes apiece. If I can allocate a block
of Ram there, then I can align it to a page boundary, and set the WP bit, preventing
anyone from changing it from underneath of me.
As a sidelight, I thought about a different way to solve that problem, which was to
try to catch whenever Macsbug would be activated, and turn off protection during
those times. Most things go through MacJmp, so I could theoretically install my
own MacJmp, turn it off, then fly on to macsbug, coming back, turn it back on, then
back to the rom. I chose not to do it this way, because Macsbug has more than one
entry vector. It will use the trace bit and the a-trap handler too, on occasion, making
it further difficult. If I didn't trap those and whatever else I don't know about, then
you could get into macsbug with the protection still active, and get the same problems
as before. It would work mostly, but still have problems.
A convenient modification I made to the tables made it unneccessary to have a full
4 levels on the 24-bit tables. I set up the third level in those tables to be the same as
the 4th level for the 32-bit tables. That set of tables is an 8-bit hunk, so there are 256
entries in the table, and that is mapping 256 byte page sizes so it works in both 24 and
32 bit mode. A side effect of this is that I no longer need to keep two places to turn
protection off or on, since the same table is used in both modes. Now I just save the
single address, and set the byte to yea/nay. I still have the VBR table too, since that
was a new addition, and it is in that 8-bit table as well, but wherever it was allocated
in Ram.
I realized that I had done a dumb thing here, which was not setting up a chain of
standard bus error handlers. I was saving the bus error handler off in a global, which
of course meant I could only save a single handler, and any subsequent reads of
memory location 8 would get the default Rom bomb handler. This was clearly a
mistake as I found on the emulator, and so now I go ahead and just write the value
on through to memory location 8, save it in a global, and any time I get a bus error
that needs to feed through, then I'll just pass it along to that address. This effectively
makes a chain of handlers, since the save/restore characteristic of other code in the
system will read what's there, save it, then set it's own, then restore the old. If
something in between does its own handler installation, it will read the prior level
and everything will be fine. It will be a chain maintained in the locals of the code
installing the temporary handlers.
Sorry about the kMaxIgnores hard coded number for the number of PCs to ignore.
Its one of those funny things that's hard to get around in a dcmd. You have to
assume that the dcmd is invoked later at interrupt time, since that is how an NMI
will get into macsbug. This means the heaps are in an unknown state, so you
cannot legitimately allocate memory. You can work most of the time, but that's not
good enough. This means it's really hard to have dynamic memory allocation in a
dcmd. The simplest answer is to have a constant and just allocate it upfront during
dcmdInit, when it IS safe to allocate memory. To be more polite, I should get a
constant from a mx?? macsbug resource and thus allow it to be changed using resedit.
Course, this is one of the main reasons I give out the source, so you can change the
numbers and recompile easily.
}
{$R-}
{$D+} { debug labels on. }
INTERFACE
USES MemTypes, Resources, Traps, Memory, ToolUtils, OSUtils, Events, SysEqu,
GestaltEqu, dcmd; { Macsbug interface routines. }
{ Lower case starting letters means I don't use that identifier in the record.
This frame is generated by the CPU on bus errors. }
TYPE BusErrorFrame = Record
SR: Integer;
PC: LongInt;
typeAndVector: Integer;
internal1: Integer;
SSW: Integer; { Special Status Word, info bits. }
pipeC: Integer;
pipeB: Integer;
FaultAddress: Ptr; { Address that caused bus error. }
internal2: Array [1..2] of Integer;
OutputBuffer: LongInt; { Get bytes from here, for a write. }
internal3: Array [1..4] of Integer;
stageBaddress: LongInt;
internal4: Array [1..2] of Integer;
InputBuffer: LongInt; { Stuff bytes here, on a read. }
internal5: Array [1..3] of Integer;
version: Integer;
internal6: Array [1..18] of Integer;
End;
BusErrorFramePtr = ^BusErrorFrame; { So I don't copy the record. }
{ For the CRP and TC combinations in both 24 and 32 bit mode, I make a record
so that I can explicitly define their order in RAM. This is necessary because
I install a pointer to these records (as global vars in the dcmd space), and that
pointer is used by the system during SwapMMUMode. It must be the
CRP register followed by the TC register. }
MMURegisters = Record
theCRP: Int64Bit;
theTC: LongInt;
End;
{ When I pass back hex numbers on the stack, I want to use small ones. }
Str8 = String[8];
{ Vars that are used in other units, or outside of this unit need to be declared in the
Interface section. This is a genuine global, so it is prefixed by the 'g'. The Z+ option
says to make these available to the Linker, so that the Asm side of the world can
see the global var. }
{$PUSH} {$Z+}
VAR { *** make access methods so I don't have to do this. }
gSavedSR: Integer; { Save place for TurnInterruptsOff. }
gStandardBusError: LongInt; { save off the currently installed bus error vector. }
gStandardVBRBusError: LongInt; { save off the currently installed VR bus error vector. }
{$POP}
{ Public declaration for dcmdGlue. Must be in every dcmd. The name cannot be changed. }
PROCEDURE CommandEntry (paramPtr: dcmdBlockPtr);
{ This must be exported so that the assembly side can find this routine. }
PROCEDURE BusError (theFrame: BusErrorFramePtr);
IMPLEMENTATION
CONST
kShortDT = 2; { For use as DT=2. }
kProtectionOff = $01; { DT=1 for valid page, no protection. }
kWriteProtect = $05; { DT=1, but the WP bit is set. }
kReadWriteProtect = $00; { DT=0, so the page is marked invalid. }
kMMU24Info = MMUTbl; { make the better name from include file. }
kMMU32Info = MMUTblSize; { 32 bit MMU table pointer in low memory.}
kMaxIgnores = 200; { Maximum number of addresses to ignore. }
kHexDigits = '0123456789ABCDEF'; { Digits in base 16, for hex conversion. }
kUseIL = False;
kUseIP = True;
kOn = True;
kOff = False;
{ Some don't
really have to be globals, but it is a convenience. I label them with a p, so that you can
immediately see they are private globals (to this unit), a quality MacApp convention.
I use more globals than normal in dcmd's since Macsbug has such a wimpy stack. }
VAR pDumpString: Str255; { To dump label info, from symbols in code. }
pRegisters24: MMURegisters; { 24 bit CRP and TC values I install. }
pRegisters32: MMURegisters; { 32 bit CRP and TC values I install. }
pProtectAddress: Ptr; { byte that turns protection on and off. }
pProtectValue: Byte; { current setting of Blat. }
pIgnoreArray: Array [1..kMaxIgnores] of LongInt; { pc addresses to ignore as valid bus errors. }
pIgnoreCount: Integer; { Number of valid ignore addresses. }
pLearn: Boolean; { is learn mode on, or not}
pAutoLearn: Boolean; {autolearn is silent, doesn't break into MacsBug}
pUseIP: Boolean; { do we do IL or IP when we break}
pDisplayError: Boolean; { Blat On/Off: to show bus errors or not. }
pErrorString: Str255; { Error string if I can't install on the machine. }
pVBRTable: Ptr; { table allocated in system heap for VBR vectors. }
pVBRProtectAddress: Ptr; { Entry in tables for VBR page in system heap. }
{---------------------------------------------------------------------------------------------------------------------------------}
{ Not really external as procedures, they are table data. This gives us a nice handy
Pascal interface to get the address of each table, without having to pretend that
the tables are code. }
FUNCTION GetStartSpace: Ptr; EXTERNAL;
FUNCTION GetFirstLevel32: Ptr; EXTERNAL;
FUNCTION GetSecondLevel32: Ptr; EXTERNAL;
FUNCTION GetThirdLevel32: Ptr; EXTERNAL;
FUNCTION GetFourthLevel32: Ptr; EXTERNAL;
FUNCTION GetFirstLevel24: Ptr; EXTERNAL;
FUNCTION GetSecondLevel24: Ptr; EXTERNAL;
FUNCTION GetEndOfTables: Ptr; EXTERNAL;
PROCEDURE SaveTheA5; EXTERNAL;
PROCEDURE SetVBR (vbrPointer: Ptr); EXTERNAL;
FUNCTION GetVBR: LongInt; EXTERNAL;
PROCEDURE FlushATC (address: Ptr); EXTERNAL;
PROCEDURE SetMMURegisters (theRegisters: MMURegisters); EXTERNAL;
PROCEDURE TurnInterruptsOff; EXTERNAL;
PROCEDURE TurnInterruptsOn; EXTERNAL;
PROCEDURE BusErrorGlue; EXTERNAL;
FUNCTION FindMMUEntry (address: Ptr): Ptr; EXTERNAL;
{---------------------------------------------------------------------------------------------------------------------------------}
{ Well, I stole this routine from MacApp utilities. I want to lower case the strings so I
don't have case sensitivities. This will do it, without using the toolbox. }
PROCEDURE LowerStr255(VAR s: Str255);
VAR i: INTEGER;
BEGIN
FOR i := 1 TO LENGTH(s) DO
IF (s[i] IN ['A'..'Z']) THEN
s[i] := CHR(Ord(s[i]) + 32)
END; { LowerStr255 }
{---------------------------------------------------------------------------------------------------------------------------------}
{ Another handy routine stolen from MacApp to do the conversion on the dang strings. I
only pass back Str8, since that is the maximum length, and stack space is limited in
Macsbug, and I don't want to waste it needlessly.
Notably, this one handles negative LongInts properly, unlike the one distributed with
the dcmd samples. }
FUNCTION NumberToHex(decNumber: UNIV LongInt): Str8;
VAR i: Integer;
hexNumber: Str8;
BEGIN
hexNumber[0] := CHR(8);
FOR i := 8 DOWNTO 1 DO
BEGIN
hexNumber[i] := kHexDigits[BAND(decNumber, 15) + 1];
decNumber := BSR(decNumber, 4);
END;
NumberToHex := hexNumber;
END;
{---------------------------------------------------------------------------------------------------------------------------------}
{ A utility routine to decide if I'm in 32 bit mode or not. This has to check the low
memory global to see if the bit is set. }
FUNCTION Is32BitMode: Boolean;
VAR bitMode: Byte; { For convenient bit testing. }
BEGIN
bitMode := Ptr(MMU32Bit)^; { Get MMU32bit value. }
IF BTst (bitMode , 0) THEN
Is32BitMode := True
ELSE
Is32BitMode := False;
END;
{---------------------------------------------------------------------------------------------------------------------------------}
{ A utility routine to decide if Macsbug is running or not. If it is, I can't call things like
DebugStr. This checks the low memory global NMIValue, since Macsbug sets it when
it is entered. A potentially better choice is the MacJmp top byte which has flags to say
a similar thing, but it is in two places, depending on bit mode.
I've got the sort of reverse logic here because I wanted to make it easier to read
where I use it.
I have to set it up the way Macsbug turns itself on, since NMIFlag by itself is not
good enough. At boot, Macsbug is active, but hasn't set it. There may be other times
as well. I going to be conservative here, and just check both fields to see if it might
active, on the assumption that it is safer, and has no drawback. }
FUNCTION MacsbugIsNotActive: Boolean;
CONST MacJmpFlag = $BFF; { Flags only defined in assembly. }
MacJmp = $120; { Used in different bit mode. }
kMacsbugActive = 7; { Bit in flags as debugger running. }
VAR NMIValue: Byte;
MacsbugFlags: Byte;
Rom24Only: Boolean;
BEGIN
NMIValue := Ptr(NMIFlag)^;
IF BTST (NMIValue, 7) THEN { Bit set means Macsbug is live. }
MacsbugIsNotActive := False { And only do that check. }
ELSE BEGIN
{ The NMIFlag is not set, so check the secondary characteristic of the MacJmp flags.
If the machine is in 32 bit mode, use the MacJmpFlag to see. If it is in 24 bit mode,
the flags are stored in the high byte of the MacJmp vector. God I love this compatibility
programming. The MacJmpFlag is worse than that even. It turns out that it is $FF
if the ROM is not capable of 32 bit mode. If it is capable, it is used as the flags,
regardless of the bit mode. Ugh. This is how Macsbug checks, and since this is here
strictly for Macsbug compatibility, I am doing it the same way. Sorry about the
-1 as a magic number, but it isn't defined as a constant, it IS a magic number as
used by Macsbug and the header files. }
Rom24Only := Ptr(MacJmpFlag)^ = -1;
IF Rom24Only THEN
MacsbugFlags := Ptr(MacJmp)^
ELSE
MacsbugFlags := Ptr(MacJmpFlag)^;
IF NOT BTST (MacsbugFlags, kMacsbugActive) THEN
MacsbugIsNotActive := True
ELSE
MacsbugIsNotActive := False;
END;
END;
{---------------------------------------------------------------------------------------------------------------------------------}
{ A routine to set the bytes for protection. This one byte is found in the 24 bit and 32
bit MMU page tables. The same set of tables are used in both bit modes, with different
TC values. This just sets the DT byte of that short format entries to be the
protection specified in the global. The actual memory location is set up when the tables
are initialized at InstallTables. The FlushATC is necessarly since the Address Translation
Cache in the MMU will have cached the protection state for that page, and I just changed
it. For the VBR page, I want to turn it back on, but it can never be kReadWrite or it's death.}
PROCEDURE TurnProtectionOn;
BEGIN
pProtectAddress^ := pProtectValue; { restore the protection value. }
pVBRProtectAddress^ := kWriteProtect; { Keep it from changing in VBR. }
FlushATC (NIL);
FlushATC (pVBRTable); { Flush zero page, and VBR page. }
END;
{---------------------------------------------------------------------------------------------------------------------------------}
{ A routine to set the bytes for protection. This does the counterpart of TurnProtectionOn,
but of course disables the MMU protection for the benefit of my routines, primarily the
EmulateAccess routine. I have to turn it off during my writes, to avoid getting a
reentrant bus error. Naturally this just sets the DT=01 as a valid descriptor with no
write protect. Turn off protection for the VBR based page too. }
PROCEDURE TurnProtectionOff;
BEGIN
pProtectAddress^ := kProtectionOff;
pVBRProtectAddress^ := kProtectionOff;
FlushATC (NIL);
FlushATC (pVBRTable); { Flush zero page, and VBR page. }
END;
{---------------------------------------------------------------------------------------------------------------------------------}
{ When called upon by the user, this routine will ignore the address that is passed in, by
adding it to the ignores list; then bumping the ignore count. These addresses are
skipped during bus error handling, as valid accesses. }
PROCEDURE AddToIgnores (ignoreAddress: LongInt);
BEGIN
pIgnoreCount := pIgnoreCount + 1;
IF pIgnoreCount > kMaxIgnores Then
dcmdDrawLine (ConCat(' too many addresses to ignore, max: ', NumberToHex(kMaxIgnores)))
ELSE BEGIN
pIgnoreArray [pIgnoreCount] := ignoreAddress;
IF pAutoLearn = kOn THEN
dcmdDrawLine (ConCat(' Auto-learning address: ', NumberToHex (ignoreAddress)))
ELSE IF pLearn = kOn THEN
dcmdDrawLine (ConCat(' Learning address: ', NumberToHex (ignoreAddress)))
ELSE
dcmdDrawLine (ConCat(' Ignoring address: ', NumberToHex (ignoreAddress)));
END;
END;
{---------------------------------------------------------------------------------------------------------------------------------}
{ Dump the list of ignored values, so they can see what all is being ignored. }
PROCEDURE DumpIgnoresList;
Var I: Integer;
BEGIN
dcmdDrawLine (' Blat list of ignored addresses: ');
For I := 1 to pIgnoreCount Do
dcmdDrawLine (ConCat(' ', NumberToHex (pIgnoreArray[I])));
End;
{---------------------------------------------------------------------------------------------------------------------------------}
{ When set up and running, I take bus errors that are due to the memory protection
I've installed. This routine is the actual bus error handler, that is installed. It does
several things, since Motorola blew it and overloaded the bus error routine. (eg. page
faults should go through a different vector.) It will examine the frame to see if it is one
generated by the MMU, and if so, will clear the re-run bit, to say I'll handle it. The
page protection is turned off, the memory access is emulated, then the protection is
turned back on, and the routine returns with an RTE to continue. }
{ EmulateAccess is to run the memory operation the CPU was trying to do when I busted
it's chops by having the page protected. This is presumably a legitimate access to the
zero page vectors, like you might see when the ROM installs a one-shot bus error
handler. Emulate the access by figuring out the address, the data to move, the size,
and which way to move it. All this info is in the bus error stack frame. The
faultAddress is the memory location that caused the bus error, either as a read or
as a write. theSSW is the Special Status Word, which has the flags describing the
access. The outputBuffer is the right aligned data that is being written. The inputBuffer
is only used when it is a read operation, and is a pointer to the location in the stack
frame that needs the data.
As part of making the machine run properly, since this has to be installed at boot, I
want to allow the access to go through to both the 0 based access, as well as the VBR
based table, so that short term bus error handlers are installed in the active place off
of VBR. Nearly all the accesses are valid, and in any case, this is just trying to be a
spy tool, not a police tool. Doing it this way makes ADB key work again, and any
other funky system junk that need the vector page (like sound, via hardware). I
could do a PTEST and pass the bus error on to the 0 based page, but that would
special case the bus errors, and my goal is really just to inform the programmer,
which will have already been done. For writes then, I'll write the data to both
the zero based access that I caught, and will write it to VBR+offset. For reads, I'll
just let it do the normal thing of getting it from zero, since VBR based vectors and
0 based vectors should always be in sync.
More complication, of course. Since I now protect the VBR based page from writes,
I can take bus errors there too, primarily from Macsbug. In fact, anything other than
a write to vbr+8 is probably a bug, so I'll not even emulate those. If it is VBR+8, then
I need to save the address, for passing along in case of a real bus error, but I still won't
write it to the VBR page. }
PROCEDURE EmulateAccess (theSSW: Integer; faultAddress: Ptr;
outputBuffer: Ptr; inputBuffer: Ptr);
CONST kReadBit = 6;
kByteBit = 4; { If set, always a byte access. }
kWordBit = 5; { If both 4&5 are clear, it's a long access. }
kLong = 0;
kWord = 1;
kByte = 2; { constants for a Case. }
VAR source, destination: Ptr;
vbrDestination: Ptr; { for copying it there too. }
doRead: Boolean;
theSize: Integer; { Set up for a Case. }
shadow: Boolean;
BEGIN
{ Right up top, if this is an access above the 0..255, then it must be the VBR page only,
since I don't want to shadow backwards from vbr to 0 based. }
IF ORD(faultAddress) > 255 THEN
shadow := False
ELSE
shadow := True;
{ Set up a couple of local vars that are easier to deal with than the BitTest stuff. }
doRead := BTst (theSSW, kReadBit);
IF BTst (theSSW, kByteBit) Then { Set up theSize. }
theSize := kByte
ELSE IF BTst (theSSW, kWordBit) Then
theSize := kWord
ELSE theSize := kLong; { I'm ignoring the 3-byte case. }
{ Now decide which of the two types it is, read or write. For Reads, I need to move
the data from the fault address to the InputBuffer, which is in the stack frame itself.
I have to move the data in a right-aligned fashion though, so for anything less
than a long word, I need to bump up the destination pointer so it will be right
aligned data. Then I need to move only the amount of data specified, since I
could bus error again, for addresses on the edge. }
IF doRead THEN BEGIN
source := faultAddress; { For reads, go from fault to inputBuffer. }
destination := inputBuffer;
Case theSize Of
kByte: BEGIN
destination := Ptr(ORD(destination) + 3); { Bump pointer by 3 for right aligned. }
destination^ := source^; { Move it only as a byte. }
END;
kWord: BEGIN
destination := Ptr(ORD(destination) + 2); { Bump pointer by 2 for right aligned. }
IntegerPtr(destination)^ := IntegerPtr(source)^;
END;
kLong: BEGIN
LongIntPtr(destination)^ := LongIntPtr(source)^; { Move full 4 bytes across. }
END;
END; { Case theSize }
END
{ For Write operations, I have the source data in the output buffer that is on the
stack frame. That data needs to go to the fault address. The data in the output buffer
is right aligned, so I need to bump the source pointer for everything except the
long word access. Also, only move the exact size of data specified.
I added the write to VBR based page too. }
ELSE BEGIN
source := outputBuffer; { For writes, move it from output to fault. }
destination := faultAddress;
vbrDestination := Ptr(ORD(faultAddress) + GetVBR); { make address in VBR based page. }
Case theSize Of
kByte: BEGIN
source := Ptr(ORD(source) + 3); { Bump pointer by 3 for right aligned. }
destination^ := source^; { Move it only as a byte. }
IF shadow THEN vbrDestination^ := source^; { Move byte to VBR page too. }
END;
kWord: BEGIN
source := Ptr(ORD(source) + 2); { Bump pointer by 2 for right aligned. }
IntegerPtr(destination)^ := IntegerPtr(source)^;
IF shadow THEN IntegerPtr(vbrDestination)^ := IntegerPtr(source)^;
END;
kLong: BEGIN
{ Don't copy new bus error handlers up there. }
IF ORD(destination) = ORD(pVBRTable)+BusErrVct THEN
gStandardVBRBusError := LongIntPtr(source)^
ELSE IF ORD(destination) = BusErrVct THEN BEGIN
gStandardBusError := LongIntPtr(source)^;
LongIntPtr(destination)^ := LongIntPtr(source)^; { and into low memory too. }
END
ELSE BEGIN
LongIntPtr(destination)^ := LongIntPtr(source)^; { Move full 4 bytes across. }
IF shadow THEN LongIntPtr(vbrDestination)^ := LongIntPtr(source)^;
END;
END;
END; { Case theSize }
END;
END; { EmulateAccess }
{---------------------------------------------------------------------------------------------------------------------------------}
{ This bus error handler will take the interrupts as they come in, and the procedure
definition is set up to use the stack frame left on the stack, as it is entered.
This is fairly weird, but I wanted to
see how far I could push the high-level language use. There are actually two frame
types I am concerned about, the $A and $B frames, and they are different lengths.
I've defined the stack parameters as the larger frame, and then when the smaller
frame is there, I won't use the extended fields. The routine exits by an assembly
routine anyway, so the frame won't need to get stripped off the stack. I have to
do an RTE to restart the CPU, and that's just too dang hard to do from up here.
I put the small routines into the .a file instead of doing Inline code, even though
I get extra JSRes. This isn't run often so the easier to read Asm file is the higher
priority. }
{ Since theFrame is bigger than 4 bytes, it will be passed by address, and the assembly
glue set it up that way. When this routine is executed, the BusErrorGlue has been
run, which has saved the scratch registers, turned off interrupts, and set up A5 to
be the dcmd's A5. theFrame is passed in as a pointer, so that Pascal won't make a
local copy of the entire record as a local. The BusErrorGlue just passes the pointer.
Can't just save and restore the protection byte, it may be changed while in the Debug
Str, to a new value. }
PROCEDURE BusError (theFrame: BusErrorFramePtr);
VAR doDisplay: Boolean;
I: Integer;
BEGIN
{ Got here from BusErrorGlue, and have saved registers, restored A5, and bailed
if it wasn't an MMU related bus error. }
{ If Macsbug is currently executing, then I don't want to call DebugStr, since that will
cause a hang; Macsbug isn't reentrant. Also, since I don't want to stop if they
haven't turned Blat on, I need to check that global. I will still take bus errors
in order to keep the VBR page up to date. }
IF MacsbugIsNotActive & pDisplayError THEN BEGIN
{ Loop for all the ignore addresses, to see if any match. }
doDisplay := True;
FOR I := 1 TO pIgnoreCount DO
IF pIgnoreArray[I] = theFrame^.PC THEN BEGIN
doDisplay := False;
Leave; { If one matched, skip out of loop. }
END;
{ If there was no matching address, this one needs to be displayed. }
IF doDisplay THEN BEGIN
{always draw stuff, even if in Auto-learn mode, we will only break if not in auto mode}
dcmdDrawLine('===========================');
dcmdDrawLine ('--Blat Bus Error: ');
dcmdDrawLine (ConCat('--address: ', NumberToHex (theFrame^.FaultAddress)));
{jf - if learn or auto-learn is on, add it to the learn list, let the user know if autoLearn is on}
IF ((pLearn = kOn) OR (pAutoLearn = kOn)) THEN
AddToIgnores(theFrame^.PC);
{only break into MacsBug if auto-learn is off}
IF pAutoLearn = kOff THEN BEGIN
IF pUseIP = kUseIP THEN
DebugStr (ConCat(';ip ', NumberToHex (theFrame^.PC)))
ELSE
DebugStr (ConCat(';il ', NumberToHex (theFrame^.PC)));
END;
End;
End; { Not in Macsbug and needed to stop. }
{ Clear the rerun bit in the Special Status Word, so that when the RTE is hit, it
won't try to rerun the bus access. Since I'm emulating the access, I have already
cleared the bus error condition, so there's no need to rerun it. This mask is just
to clear the 8th bit in the word. }
theFrame^.SSW := BAnd (theFrame^.SSW, $FEFF);
{ Turn off the memory protection since I'm going to do the emulate access anyway,
and it has to be off to keep this from bus erroring. }
TurnProtectionOff;
With theFrame^ DO
EmulateAccess(SSW, FaultAddress, @OutputBuffer, @InputBuffer);
TurnProtectionOn; { Restore to write protect, or read protect. }
End; { BusError }
{---------------------------------------------------------------------------------------------------------------------------------}
{ Verify that the machine is one that I can run on without wedging. For example, I don't
run on IIci, or 040 machines, so I gotta say no here, instead of pounding.
Checks for IIci on board video class machine,
no 040 machines,
no VM running. }
FUNCTION VerifyMachine: Boolean;
VAR err: Integer;
response: LongInt;
{ Sure, this is like a Goto, but it should be obvious how it works. This is the way
out of here when there is an error, and sets the error string in a common way. I
prefer this error routine style to massively nested If Then Else structures. }
PROCEDURE ErrorExit (theErr: Str255);
BEGIN
VerifyMachine := False; { Return failing result. }
pErrorString := theErr;
Exit (VerifyMachine); { Jump out of enclosing routine. }
END;
BEGIN
{ See if the VBR vector page made it inside of the 64K max that my tables allow. }
IF (pVBRTable = NIL) | (ORD(pVBRTable) > $10000) THEN
ErrorExit (' System heap too large, VBR allocation failed.');
{ If VM is installed, or we can't get info on it, set that as an error string. }
err := Gestalt(gestaltVMAttr, response);
{ Allow through gestaltUndefSelectorErr, as not a real Gestalt error. }
IF ((err <> noErr) & (err <> gestaltUndefSelectorErr)) THEN
ErrorExit (' Gestalt error');
{ If gestaltUndefSelectorErr Then VM isn't installed. Test bit if valid result. }
IF ((err = noErr) & BTst(response, gestaltVMPresent)) THEN
ErrorExit (' Blat is incompatible with VM');
{ If this is an 040 machine, then complain about that. If the selector is undefined,
I have to assume this isn't an 040. }
err := Gestalt(gestaltProcessorType, response);
IF ((err <> noErr) & (err <> gestaltUndefSelectorErr)) THEN
ErrorExit (' Gestalt error');
IF ((err = noErr) & (response = gestalt68040)) THEN
ErrorExit (' Blat is incompatible with the 68040 MMU.');
{ If you don't have an MMU, clearly this isn't going to work. If I get an undefined
selector error, I have to assume one isn't installed. }
err := Gestalt(gestaltMMUType, response);
IF ((err <> noErr) & (err <> gestaltUndefSelectorErr)) THEN
ErrorExit (' Gestalt error');
IF ((err = gestaltUndefSelectorErr) | (response = gestaltNoMMU)) THEN
ErrorExit (' Blat requires an MMU in order to function.');
{ If this is one of the IIci class machines, with onboard video, I can't run, since their
tables are too complicated for me. If I get an undefined gestalt selector here,
something is really screwed up. }
err := Gestalt(gestaltMachineType, response);
IF (err <> noErr) THEN
ErrorExit (' Gestalt error');
IF ((response = gestaltMacIIci) | (response = gestaltMacIIsi) | (response = gestaltMacLC)) THEN
ErrorExit (' Blat cannot run on machines that support onboard video (IIci, IIsi, LC).');
{ Finally, if I exit normally, then the string will be emptied, since it looks like we I run. }
pErrorString := '';
VerifyMachine := True;
END; { VerifyMachine }
{---------------------------------------------------------------------------------------------------------------------------------}
{ Save and install the new bus error handler for the system. This is my bus error handler
which will handle all the MMU faults, and feed through the errors to the old handler.
The old handler is maintained in the gStandardBusError as I take writes to 8. }
PROCEDURE InstallBusErrorHandler;
VAR vbrLand: LongIntPtr;
BEGIN
{ Save off the current bus error handler in 8, to init our var. Init the
VBR based one to point at my handler, since that is the normal case. }
gStandardBusError := LongIntPtr(BusErrVct)^;
gStandardVBRBusError := ORD(@BusErrorGlue);
{ Install a new bus error handler into the vector page, to point to my routine instead.
By installing after the VBR is set up to high dcmd area, I install my bus error
handler only at the VBR vector table. If a bus error comes through that isn't
related to memory protection, it gets fed on through to the vector that is 0 based. }
vbrLand := LongIntPtr(ORD(GetVBR)+BusErrVct);
LongIntPtr(vbrLand)^ := ORD(@BusErrorGlue);
END; { InstallBusErrorHandler }
{---------------------------------------------------------------------------------------------------------------------------------}
{ Set up the VBR based vector table, and set that table as the new VBR in active use.
The fundamental reason for doing this is so that I can set Read protection on the 0
based version of the vectors. VBR use makes it possible to survive that. }
PROCEDURE InstallVBR;
BEGIN
{ Round up the address in pVBRTable so that it is page aligned (low byte zero), and thus
will be at a 256 byte page boundary. Copy the vectors from zero up to there, to
start the shadow copy of vectors. This is where I will set the
VBR register to point, so it will be the real vectors in use. The bytes at zero will
thus no longer be used for the vectors. }
pVBRTable := Ptr(ORD(pVBRTable) + 256);
pVBRTable := Ptr(BAND (ORD(pVBRTable), $FFFFFF00));
BlockMove (NIL, pVBRTable, 256);
SetVBR (pVBRTable);
END; { InstallVBR }
{---------------------------------------------------------------------------------------------------------------------------------}
{ When the user asks to install, this routine will take the globals saved off, and install
them as the current MMU tables. By setting up the CRP register to be my tables, that
installs them and the proper entry is marked as invalid. }
PROCEDURE InstallTables;
TYPE Int64BitPtr = ^Int64Bit;
VAR crpPointer: Int64BitPtr;
tcPointer: LongIntPtr;
VAR xx: SignedByte;
BEGIN
{ While fooling with the MMU registers, I want to avoid screwing up any interrupt
routines that expect Ram to be mapped when they run. No interrupts while changing. }
{ Set the Translation Control register to 0, to disable the MMU. This has to be done to
allow a change to the CRP. Next change the Cpu Root Pointer to be our new tables,
that are installed in the code. After that, restore the TC to its new value, that matches
the new tables, and is enabled. When it turns back on the Address Translation Cache
will be flushed automatically by the MMU. }
IF Is32BitMode Then SetMMURegisters (pRegisters32)
ELSE SetMMURegisters (pRegisters24);
{ Install the two register values at the MMU24Info in low memory, for SwapMMUMode.
MMUTbl is the old name for MMU24Info, but is the only one defined in the interface.
I made my own constant that is the better name.
This just installs a new pointer to the variables I use, since on a IIx, they point into
ROM to start with, and I can't just change the values. By setting them to point to
me in RAM, the IIx will just use those numbers and not reset them. The order
of them is important, which is why I made them into a record. }
LongIntPtr(kMMU24Info)^ := ORD(@pRegisters24);
LongIntPtr(kMMU32Info)^ := ORD(@pRegisters32);
{ *** debug operation to find it easily on emulator. }
xx := Ptr(5)^;
{ Find the protection address for the VBR table itself. Bump up by three, to make it a
byte access for the TurnProtectionOff/On. }
{*** should do a FindMMUEntry for the zero page too, to be symmetric. }
pVBRProtectAddress := FindMMUEntry (pVBRTable);
pVBRProtectAddress := Ptr(ORD(pVBRProtectAddress) + 3);
END; { InstallTables }
{---------------------------------------------------------------------------------------------------------------------------------}
{ Patch up the MMU tables, to make them fit for MMU use. They have to be quad-long
word aligned, and the various stages of the tables have to point to the next lower
leaves in the tree. The 3rd level of the 24 bit table will use the 4th level of the 32
bit table, since it is specifiying the same Ram. }
PROCEDURE PatchupTables;
VAR adjustment: LongInt;
count: LongInt;
tableLand: LongIntPtr;
firstLevel: Ptr; { pointer to the FirstLevel table in dcmd code space. }
BEGIN
{ A block in the assembly file has the MMU tables. This code will move the tables
in memory lower by however many bytes it takes to make it quad-long word aligned. It
does this by starting at the StartSpace and fudging that address higher by however many
bytes it takes to make a quad-long aligned address. I do this by adding 16 to the number,
then clearing the low nibble with the AND. This is rounding up to the next highest
quad-word boundary. A 16 byte section of zeros is at the start of the tables, to absorb
the move. }
count := ORD(GetEndOfTables) - ORD(GetFirstLevel32);
firstLevel := Ptr(ORD(GetStartSpace) + 16);
firstLevel := Ptr(BAND (ORD(firstLevel), $FFFFFFF0));
BlockMove (GetFirstLevel32, firstLevel, count);
{ Now screw around with the tables, making them all point to the successive levels,
as the MMU requires. The bottom nibble of each entry is the DT field as specified
by the MMU stuff, and I always make it DT=2, for short entries. This makes the
first entry of the FirstLevel table point to the SecondLevel, and the first entry of
the SecondLevel table point to the ThirdLevel, and so on. I have to subtract out
the adjustment for each, since I moved all the tables with BlockMove above. }
adjustment := ORD(GetFirstLevel32) - ORD(firstLevel); { how far each table moved. }
tableLand := LongIntPtr(ORD(GetFirstLevel32) - adjustment);
tableLand^ := BOR(ORD(GetSecondLevel32) - adjustment, kShortDT);
tableLand := LongIntPtr(ORD(GetSecondLevel32) - adjustment);
tableLand^ := BOR(ORD(GetThirdLevel32) - adjustment, kShortDT);
tableLand := LongIntPtr(ORD(GetThirdLevel32) - adjustment);
tableLand^ := BOR(ORD(GetFourthLevel32) - adjustment, kShortDT);
{ Modify the tables for 24-bit mode too, so that they are correct. }
tableLand := LongIntPtr(ORD(GetFirstLevel24) - adjustment);
tableLand^ := BOR(ORD(GetSecondLevel24) - adjustment, kShortDT);
{ Set the pointer to the third level to point to the same table that is used for
the fourth level of the 32-bit mode tables. }
tableLand := LongIntPtr(ORD(GetSecondLevel24) - adjustment);
tableLand^ := BOR(ORD(GetFourthLevel32) - adjustment, kShortDT);
{ Save off the address of the low byte of the first longInt entry for the FourthLevel,
since that is the spot that turns the protection on and off. This byte is the DT value
for that page, and with the WP set, will write protect that first page in memory. }
pProtectAddress := Ptr(ORD(GetFourthLevel32) - adjustment + 3);
{ Set up our new CRP number to match that table address. This means writing in the
write number for the top half which is the table index stuff, which is disabled in my
case. The low word of that is DT=2 which means short entries for the next level,
which is pointed at by the low long. The CRP is a double longint. }
firstLevel := Ptr(ORD(GetFirstLevel24) - adjustment);
pRegisters24.theCRP.hiLong := $7FFF0002; { index limit turned off, DT=2 }
pRegisters24.theCRP.loLong := ORD(firstLevel); { pointer to first level tables. }
firstLevel := Ptr(ORD(GetFirstLevel32) - adjustment);
pRegisters32.theCRP.hiLong := $7FFF0002; { index limit turned off, DT=2 }
pRegisters32.theCRP.loLong := ORD(firstLevel); { pointer to first level tables. }
{ Set up the new TC register, that will be used when my tables are installed. For
24-bit mode it's like: 8=enabled, 0=supervisor root pointer and function code
lookup are disabled, 8=256 byte page size, 8=initial shift of 8 bits, 4=TIA for 4
bits in the first table, 4=TIB, 8=TIC, 0=TID.
32-bit mode: the top 8=enabled, 0=supervisor root pointer and function
code lookup are disabled, 8=256 byte page size, 0=initial shift is zero bits (32-bit
mode), 5=TIA bits for first level table, 7=TIB, 4=TIC, 8=TID. }
pRegisters24.theTC := $80884480;
pRegisters32.theTC := $80805748;
END; { PatchupTables }
{---------------------------------------------------------------------------------------------------------------------------------}
{ This installation routine will allocate a block in the system heap for the MMU tables that
I use to replace the system tables. It will install a bus error handler, and allocate a new
block for the actual vectors, making them VBR based instead of zero based. This last
bit is to make it work with Macsbug, and make things more reliable. }
PROCEDURE CreateBlat;
BEGIN
{ Allocate a VBR table in the system heap. It has to be within the first 64K or my
tables won't map to it nicely. If it doesn't show up in that range, then barf. The
+256 is to allow for the roundoff operation of making sure it is allocated on an
exact 256 byte page boundary. VBR doesn't have to be that way, but I'm allocating
it there so I can protect the page, and that makes it necessary to align it to a page. }
pVBRTable := NewPtrSys(256+256);
{ Just to be nice, make sure that we can run on this machine, without wedging. If it
isn't going to work, then skip the install. Looks at pVBRTable validity too. }
IF NOT VerifyMachine THEN Exit (CreateBlat);
{ Make the tables all cool. Fix up the base address, patch in the multiple levels. }
PatchupTables;
{ Save off the current A5, so that it can be used when the bus error handler is called. }
SaveTheA5;
pIgnoreCount := 0;
{ Addresses are only good on IIfx. }
(* Only good on fx, and you can get them with Auto anyway.
{ *** set up a hack list of memory addresses to ignore. }
pIgnoreArray [1] := $4080DB76; { Heapguts }
pIgnoreArray [2] := $4080DB98; { Heapguts }
pIgnoreArray [3] := $4080DC04; { Heapguts }
pIgnoreArray [4] := $4080DC26; { Heapguts }
pIgnoreArray [5] := $4080DBF8; { Heapguts, read }
pIgnoreArray [6] := $4080DB6A; { Heapguts, read }
pIgnoreArray [7] := $40808D14;
pIgnoreArray [8] := $40808D20;
pIgnoreArray [9] := $40808D30;
pIgnoreArray [10] := $4080C982;
pIgnoreCount := 10;
*)
{jf stuff follows}
pLearn := kOff;
pAutoLearn := kOff;
pUseIP := kUseIP;
{ Install the new VBR tables, my bus error handle, and my tables as the active MMU tables. }
InstallVBR;
InstallBusErrorHandler;
InstallTables;
{ Stuff the starting value for the protection. This is normally set to kWriteProtect, so
that all writes can be shadowed in the VBR table to keep things running. Start out
with not displaying the bus errors. }
pProtectValue := kWriteProtect;
pDisplayError := False;
TurnProtectionOn; { Turn on protect bytes. }
END; { CreateBlat }
{---------------------------------------------------------------------------------------------------------------------------------}
{ When debugging is required, as it often seems to be, maybe this will help, by
coughing up all the global variables, and some addresses for runtime inspection.
This particular dcmd cannot be run inside the TestDcmd world. }
PROCEDURE BlatDebug;
BEGIN
dcmdDrawLine ('--Blat Debugging Info--');
dcmdDrawLine (ConCat(' Code Start->: ', NumberToHex (@LowerStr255)));
dcmdDrawLine (ConCat(' gStandardBusError: ', NumberToHex (gStandardBusError)));
dcmdDrawLine (ConCat(' gStandardVBRBusError: ', NumberToHex (gStandardVBRBusError)));
dcmdDrawLine (ConCat(' gSavedSR: ', NumberToHex (gSavedSR)));
dcmdDrawLine (ConCat(' pRegisters24.theCRP: ', NumberToHex (pRegisters24.theCRP.hiLong), NumberToHex (pRegisters24.theCRP.loLong)));
dcmdDrawLine (ConCat(' pRegisters24.theTC: ', NumberToHex (pRegisters24.theTC)));
dcmdDrawLine (ConCat(' pRegisters32.theCRP: ', NumberToHex (pRegisters32.theCRP.hiLong), NumberToHex (pRegisters32.theCRP.loLong)));
dcmdDrawLine (ConCat(' pRegisters32.theTC: ', NumberToHex (pRegisters32.theTC)));
dcmdDrawLine (ConCat(' pProtectAddress: ', NumberToHex (ORD(pProtectAddress))));
dcmdDrawLine (ConCat(' pProtectValue: ', NumberToHex (pProtectValue)));
dcmdDrawLine (ConCat(' pVBRTable: ', NumberToHex (ORD(pVBRTable))));
dcmdDrawLine (ConCat(' pVBRProtectAddress: ', NumberToHex (pVBRProtectAddress)));
dcmdDrawLine (ConCat(' pErrorString: ', pErrorString));
dcmdDrawLine (ConCat(' pIgnoreCount: ', NumberToHex (pIgnoreCount)));
DumpIgnoresList;
IF pDisplayError THEN
dcmdDrawLine (' pDisplayError: True')
ELSE
dcmdDrawLine (' pDisplayError: False');
IF pLearn THEN
dcmdDrawLine (' pLearn: True')
ELSE
dcmdDrawLine (' pLearn: False');
IF pAutoLearn THEN
dcmdDrawLine (' pAutoLearn: True')
ELSE
dcmdDrawLine (' pAutoLearn: False');
IF pUseIP THEN
dcmdDrawLine (' pUseIP: True')
ELSE
dcmdDrawLine (' pUseIP: False');
IF Is32BitMode THEN
dcmdDrawLine (' In 32 Bit Mode')
ELSE
dcmdDrawLine (' In 24 Bit Mode');
IF MacsbugIsNotActive THEN
dcmdDrawLine (' Macsbug is Not Active')
ELSE
dcmdDrawLine (' Macsbug is Active');
END;
{---------------------------------------------------------------------------------------------------------------------------------}
{ dump the current settings of blat. }
PROCEDURE DumpStatusInfo;
BEGIN
dcmdDrawLine('-------------------------------------');
IF NOT pDisplayError THEN
dcmdDrawLine (' Blat is OFF')
ELSE IF pProtectValue = kWriteProtect THEN
dcmdDrawLine (' Blat is catching WRITES only')
ELSE IF pProtectValue = kReadWriteProtect THEN
dcmdDrawLine (' Blat is catching both READS and WRITES');
IF pLearn = kOn THEN
dcmdDrawLine (' Blat learning mode is ON')
ELSE
dcmdDrawLine (' Blat learning mode is OFF');
IF pAutoLearn = kOn THEN
dcmdDrawLine (' Blat auto-learning mode is ON')
ELSE
dcmdDrawLine (' Blat auto-learning mode is OFF');
IF pUseIP = kUseIP THEN
dcmdDrawLine (' Blat is using IP')
ELSE
dcmdDrawLine (' Blat is using IL');
dcmdDrawLine('-------------------------------------');
END;
{---------------------------------------------------------------------------------------------------------------------------------}
{ If something was not understood, dump the info for how this works. If I couldn't install
because of an incompatibility, then say that instead, and include the error message so
they know why. }
PROCEDURE DumpHelpInfo;
BEGIN
IF pErrorString = '' THEN BEGIN
dcmdDrawLine ('Blat Off | Writes | Both | Dump | Ignore $xxx ... | IP | IL | Learn | Auto | Status');
dcmdDrawLine (' Uses the MMU to catch accesses to bytes 0..255. (Version 5)');
DumpStatusInfo;
END
ELSE BEGIN
dcmdDrawLine ('Blat is NOT installed.');
dcmdDrawLine (pErrorString); { Error line if not installed. }
END;
END;
{---------------------------------------------------------------------------------------------------------------------------------}
{---------------------------------------------------------------------------------------------------------------------------------}
{ This fine fellow is the main entry point for the dcmd. It is the hook by which I get called
by MacsBug to do my thing. It is basically the chance to key off the command line and do
what they request.
Change the version number in the help, whenever it is re-released. This is the only
version number in the program. }
PROCEDURE CommandEntry (paramPtr: DCmdBlockPtr);
VAR parseChar: CHAR;
ignoreDude: LongInt;
okParse: Boolean;
BEGIN
CASE paramPtr^.request OF
{ When I'm called to Init, do the init code of setting up the MMU tables. }
dcmdInit:
CreateBlat;
{ I can get various DoIt commands, so parse out the options. If I don't get anything,
do the standard dump help info. I lowercase the string so I can avoid any case sensitivity
on options passed in. }
dcmdDoIt:
BEGIN
parseChar := dcmdGetNextParameter (pDumpString);
LowerStr255 (pDumpString);
IF pDumpString = 'writes' THEN BEGIN
pDisplayError := True; { Stop on errors, to show them. }
pProtectValue := kWriteProtect;
TurnProtectionOn; { Set bytes to Write Protect. }
dcmdDrawLine (' Blat will catch all writes to memory from 0..255.');
END
ELSE IF pDumpString = 'both' THEN BEGIN
pDisplayError := True; { Stop on errors, to show them. }
pProtectValue := kReadWriteProtect;
TurnProtectionOn; { Set bytes to Invalid (R/W protect). }
dcmdDrawLine (' Blat will catch both reads and writes to memory from 0..255.');
END
ELSE IF pDumpString = 'off' THEN BEGIN
pDisplayError := False; { don't stop on bus errors. }
pProtectValue := kWriteProtect;
TurnProtectionOn; { Protection still on, to maintain VBR.}
dcmdDrawLine (' Blat is off');
END
ELSE IF pDumpString = 'dump' THEN BEGIN
DumpIgnoresList;
END
{jf stuff follows}
ELSE IF pDumpString = 'ip' THEN BEGIN
dcmdDrawLine (' Blat will use IP');
pUseIP := kUseIP;
END
ELSE IF pDumpString = 'il' THEN BEGIN
dcmdDrawLine (' Blat will use IL');
pUseIP := kUseIL;
END
ELSE IF pDumpString = 'learn' THEN BEGIN
IF pLearn = kOn THEN BEGIN
pLearn := kOff;
dcmdDrawLine(' Blat learn mode OFF');
END
ELSE BEGIN
pLearn := kOn;
pAutoLearn := kOff;
dcmdDrawLine(' Blat learn mode ON (auto-learn is OFF)');
END;
END
ELSE IF pDumpString = 'auto' THEN BEGIN
IF pAutoLearn = kOn THEN BEGIN
pAutoLearn := kOff;
dcmdDrawLine(' Blat auto-learn mode OFF');
END
ELSE BEGIN
pAutoLearn := kOn;
pLearn:= kOff;
dcmdDrawLine(' Blat auto-learn mode ON (learn is OFF)');
END;
END
ELSE IF pDumpString = 'ignore' THEN BEGIN
REPEAT {loop through and get all of the addresses to ignore}
parseChar := dcmdGetNextExpression (ignoreDude, okParse);
IF okParse THEN AddToIgnores (ignoreDude);
UNTIL not okParse;
END
ELSE IF pDumpString = 'status' THEN
DumpHelpInfo
ELSE IF pDumpString = 'debug' THEN BEGIN
BlatDebug;
END
ELSE DumpHelpInfo;
END;
{ Give them the obvious help info. }
OTHERWISE
DumpHelpInfo;
END; { End of case paramPtr^.request. }
END; { CommandEntry }
END.