BBObjectLocationImpl.mesa
Russ Atkinson, June 30, 1983 5:33 pm
DIRECTORY
AMBridge USING
[ContextPC, GetWorld, GetWorldIncarnation, GFHFromTV, IsRemote, RemoteGFHFromTV],
AMEvents USING [BreakID, Event, EventRec, SetBreak, ClearBreak],
AMModel USING
[Context, ContextChildren, ContextSection, ParentSection, Section, SectionClass, SectionSource, Source, SourceObj, SourceSection],
AMModelBridge USING
[LoadedSection, LoadedSectionForProc, LoadedSectionForProgPC, ProcFromLoadedSection],
AMModelLocation USING [CodeLocation, EntryLocations, ExitLocations],
AMTypes USING [Error, GlobalParent, TVType, UnderClass],
BBAction USING [Action, NewAction, WaitForDataTaken],
BBDisableBreaks USING [Enabled],
BBObjectLocation USING [BreakpointHandler, LocationErrorCode, siNull],
BBZones USING [GetQuantizedZone],
PrincOps USING [BYTE, BytePC, FrameCodeBase, InstWord],
RTBasic USING [TV],
WorldVM USING
[CurrentIncarnation, Incarnation, Long, LongRead, Read, World];
BBObjectLocationImpl: CEDAR MONITOR
IMPORTS AMBridge, AMEvents, AMModel, AMModelBridge, AMModelLocation, AMTypes,
BBAction, BBDisableBreaks, BBZones, WorldVM
EXPORTS BBObjectLocation
= BEGIN OPEN BBObjectLocation, RTBasic;
BreakID: TYPE = AMEvents.BreakID;
BYTE: TYPE = PrincOps.BYTE;
BytePC: TYPE = PrincOps.BytePC;
CodeLocation: TYPE = AMModelLocation.CodeLocation;
Event: TYPE = AMEvents.Event;
EventRec: TYPE = AMEvents.EventRec;
FrameCodeBase: TYPE = PrincOps.FrameCodeBase;
EmptyCodeBase: FrameCodeBase = LOOPHOLE[LONG[0], FrameCodeBase];
LoadedSection: TYPE = AMModelBridge.LoadedSection;
EmptyLoadedSection: LoadedSection = [NIL, NIL, [0]];
Section: TYPE = AMModel.Section;
Source: TYPE = AMModel.Source;
World: TYPE = WorldVM.World;
Location: TYPE = REF LocationRep;
LocationRep: PUBLIC TYPE = RECORD
[gf: TVNIL, pc: BytePC ← [0], section: AMModel.Section ← NIL];
BreakpointId: TYPE = REF BreakpointIdRep;
BreakpointIdRep: PUBLIC TYPE = BreakBlockRep;
The break block is used as our callback data to AMEvents.SetBreak. We can get back to our code head, and to our level of handler proc & data. By convention, the break block is no longer active when the location = NIL
BreakBlock: TYPE = REF BreakBlockRep;
BreakBlockRep: TYPE = RECORD [
next: BreakBlock ← NIL,
location: Location ← NIL,
head: CodeHead ← NIL,
handler: BreakpointHandler ← NIL, -- client's handler
data: REFNIL -- client's data
];
The code head is present to allow multiple breakpoints at a single location. Note that the physical location (CodeLocation) is stored here, while the logical location is in the break block to allow multiple logical locations for a given physical location.
CodeHeadArray: TYPE = ARRAY [0..CodeHeadMod) OF CodeHead;
CodeHeadMod: CARDINAL = 32;
CodeHead: TYPE = REF CodeHeadRep;
CodeHeadRep: TYPE = RECORD [
next: CodeHead ← NIL,
list: BreakBlock ← NIL,
cid: AMEvents.BreakID ← NIL,
codeLoc: CodeLocation ← [EmptyCodeBase, [0]],
incarnation: WorldVM.Incarnation ← 0,
world: World ← NIL];
LocationError: PUBLIC ERROR [code: LocationErrorCode] = CODE;
LocationErrorCode: TYPE = {duplicateId, notALink, cantUseGlobalFrame, badGFandPC};
SourceIndex: TYPE = CARDINAL;
siNull: SourceIndex=LAST[SourceIndex];
EntryLocation: PUBLIC PROC [self: Location] RETURNS [newLoc: Location] = TRUSTED {
returns the entry location for self
tv: TV ← self.gf;
section: Section ← self.section;
pc: BytePC ← self.pc;
loadSect: LoadedSection ← [self.section, self.gf, pc];
list: LIST OF CodeLocation ← NIL;
SELECT AMModel.SectionClass[section] FROM
statement => {section ← AMModel.ParentSection[section]};
ENDCASE => RETURN [self];
list ← AMModelLocation.EntryLocations[section].list;
IF list # NIL THEN pc ← list.first.pc;
RETURN [LocationFromGFandPC[tv, pc]];
};
ExitLocation: PUBLIC PROC [self: Location] RETURNS [newLoc: Location] = TRUSTED {
returns the exit location for self
tv: TV ← self.gf;
section: Section ← self.section;
pc: BytePC ← self.pc;
loadSect: LoadedSection ← [self.section, self.gf, pc];
list: LIST OF CodeLocation ← NIL;
SELECT AMModel.SectionClass[section] FROM
statement => {section ← AMModel.ParentSection[section]};
ENDCASE => RETURN [self];
list ← AMModelLocation.ExitLocations[section].list;
IF list # NIL THEN pc ← list.first.pc;
RETURN [LocationFromGFandPC[tv, pc]];
};
SetBreakpoint: PUBLIC UNSAFE PROC
[self: Location, handler: BreakpointHandler, data: REFNIL]
RETURNS [rId: BreakpointId] = TRUSTED {
IF self = NIL THEN RETURN [NIL];
rId ← NewBreakBlock[self];
rId.data ← data;
rId.handler ← handler;
};
ClearBreakpoint: PUBLIC UNSAFE PROC
[self: Location, id: BreakpointId] RETURNS [rId: BreakpointId] = TRUSTED {
head: CodeHead ← NIL;
IF id = NIL OR self = NIL THEN RETURN [NIL];
head ← FindCodeHead[self, none];
IF head = NIL OR id.head # head THEN RETURN [NIL];
rId ← FlushBreakBlock[head, id];
};
IsLocationAtLocation: PUBLIC PROC
[self: Location, loc: Location] RETURNS [yes: BOOL] = TRUSTED {
IF self = loc THEN RETURN [TRUE];
IF self = NIL OR loc = NIL THEN RETURN [FALSE];
IF self.pc # loc.pc THEN RETURN [FALSE];
RETURN [SameGlobalFrames[self.gf, loc.gf]];
};
IsFrameAtLocation: PUBLIC PROC
[self: Location, frame: TV -- Local frame --] RETURNS [yes: BOOL] = TRUSTED {
IF self = NIL THEN RETURN [frame = NIL];
IF frame = NIL THEN RETURN [FALSE];
IF AMBridge.ContextPC[frame] # self.pc THEN RETURN [FALSE];
RETURN [SameGlobalFrames[AMTypes.GlobalParent[frame], self.gf]];
};
LocationFromGFandPC: PUBLIC PROC
[gf: TV, pc: BytePC] RETURNS [new: Location] = TRUSTED {
returns the location corresponding to the given gf and pc
LocationError[badGFandPC] occurs if illegal arguments are given
section: Section ← NIL;
IF gf = NIL THEN RETURN [NIL];
IF pc = 0
THEN {
just the global frame, not a procedure
section ← AMModel.ContextSection[gf];
}
ELSE {
gosh, we sure hope that this is a procedure
section ← AMModelBridge.LoadedSectionForProgPC
[gf, pc ! AMTypes.Error => CONTINUE].section;
};
new ← LocationFromSection[section];
new.gf ← gf;
new.pc ← pc;
};
GFandPCFromLocation: PUBLIC PROC
[loc: Location] RETURNS [gf: TV, pc: BytePC] = TRUSTED {
returns the GF and PC for the given location
RETURN [loc.gf, loc.pc];
};
TVFromLocation: PUBLIC PROC [loc: Location] RETURNS [tv: TVNIL] = TRUSTED {
returns the TV (globalFrame or procedure) for the given location
IF loc # NIL THEN {
section: Section ← loc.section;
gf: TV ← loc.gf;
pc: BytePC ← loc.pc;
IF pc = 0 THEN RETURN [gf];
SELECT AMModel.SectionClass[section] FROM
statement => section ← AMModel.ParentSection[section];
proc => {};
ENDCASE => RETURN [loc.gf];
tv ← AMModelBridge.ProcFromLoadedSection[[section, gf, pc]];
};
};
ByteAtGFandPC: PUBLIC PROC [gf: TV, pc: BytePC] RETURNS [BYTE] = TRUSTED {
returns exact byte currently at given gf and pc
addr: LONG CARDINALLOOPHOLE[GetCodeBase[gf]];
world: World ← AMBridge.GetWorld[gf];
pair: PrincOps.InstWord ← LOOPHOLE[WorldVM.Read[world, addr], PrincOps.InstWord];
addr ← addr + pc / 2;
RETURN [IF pc MOD 2 = 0 THEN pair.evenbyte ELSE pair.oddbyte];
};
Non-operations
CatchBreakpoint: PUBLIC UNSAFE PROC
[event: Event] RETURNS [handled: BOOLTRUE] = TRUSTED {
for use by BBNubImpl in handling breakpoints; WE MUST BE ABLE TO DO SOMETHING REASONABLE WITH BREAKPOINTS NOT IN OUR LIST, SINCE AMEvents ALLOWS SETTING OF BREAKPOINTS IN FUNNY WAYS.
ev: REF EventRec[break] ← NARROW[event];
head: CodeHead ← NIL;
cdata: REF = ev.clientData;
IF cdata = NIL THEN RETURN [TRUE]; -- just a little racy, here
WITH cdata SELECT FROM
myHead: CodeHead => head ← myHead;
ENDCASE => {
This is a breakpoint created by someone other than us, so make a new event that conveys this non-information!
action: BBAction.Action ← BBAction.NewAction[event, other];
BBAction.WaitForDataTaken[action, "Whose break is this?"];
RETURN;
};
At this point the head is definitiely a non-NIL CodeHead.
IF head.list # NIL THEN {
cloc: CodeLocation ← head.codeLoc;
addr: LONG CARDINALLOOPHOLE[cloc.codeBase];
IF BBDisableBreaks.Enabled[] THEN
FOR bb: BreakBlock ← head.list, bb.next UNTIL bb = NIL DO
IF head.list = NIL
THEN EXIT; -- somebody cleared everything!!!
IF bb.head = NIL OR bb.handler = NIL
THEN LOOP; -- break deleted during enumeration
[] ← bb.handler[event, bb, bb.data];
ENDLOOP;
The following line is a hack that should go away when breakpoints really work! It is intended to reduce the number of times that proceeding a breakpoint causes a page fault on the instruction fetch, which then causes a spurious break to take place.
[] ← WorldVM.Read
[head.world, LOOPHOLE[cloc.codeBase.longbase, LONG CARDINAL] + cloc.pc/2];
};
};
TVToLocation: PUBLIC PROC [tv: TV] RETURNS [l: Location] = TRUSTED {
gf: TVNIL;
pc: BytePC ← [0];
IF tv = NIL THEN RETURN [NIL];
SELECT AMTypes.UnderClass[AMTypes.TVType[tv]] FROM
localFrame => {
gf ← AMTypes.GlobalParent[tv];
pc ← AMBridge.ContextPC[tv]};
globalFrame => gf ← tv;
procedure => {
loadSect: LoadedSection ← AMModelBridge.LoadedSectionForProc[tv];
list: LIST OF CodeLocation ← AMModelLocation.EntryLocations[loadSect.section].list;
IF list = NIL THEN RETURN [NIL];
pc ← list.first.pc;
gf ← AMTypes.GlobalParent[tv];
};
ENDCASE => RETURN [NIL];
RETURN [LocationFromGFandPC[gf, pc]];
};
SourceToLocation: PUBLIC PROC
[tv: TV, sourceIndex: CARDINAL] RETURNS [l: Location] = TRUSTED {
section: Section ← AMModel.ContextSection[tv];
source: Source ← AMModel.SectionSource[section];
pc: BytePC ← [0];
list: LIST OF CodeLocation ← NIL;
model: AMModel.Context ← NIL;
IF source = NIL THEN RETURN [NIL];
source ←
z.NEW[AMModel.SourceObj ←
[source.fileName, statement, source.versionStamp, field[sourceIndex, sourceIndex]]];
section ← AMModel.SourceSection[source, tv].section;
list ← AMModelLocation.EntryLocations[section].list;
IF list # NIL THEN pc ← list.first.pc;
RETURN [LocationFromGFandPC[tv, pc]];
};
LocationToSource: PUBLIC PROC
[loc: Location] RETURNS [gf: TVNIL, sourceIndex: CARDINAL ← 0] = TRUSTED {
section: Section ← IF loc = NIL THEN NIL ELSE loc.section;
source: Source ← NIL;
index: INT ← 0;
IF section = NIL THEN RETURN [NIL, siNull];
source ← AMModel.SectionSource[section];
gf ← loc.gf;
WITH s: source SELECT FROM
field => index ← s.firstCharIndex;
ENDCASE;
IF index < 0 THEN sourceIndex ← siNull ELSE sourceIndex ← index;
};
UTILITY ROUTINES
NewBreakBlock: PROC
[loc: Location] RETURNS [bb: BreakBlock ← NIL] = TRUSTED {
make a new break block for the given location
make sure that the breakpoint is set, of course
head: CodeHead ← FindCodeHead[loc, add];
IF head = NIL THEN RETURN [NIL];
bb ← z.NEW[BreakBlockRep ← [next: head.list, head: head]];
head.list ← bb;
bb.location ← loc;
};
FindAction: TYPE = {none, add};
FindCodeHead: ENTRY PROC
[loc: Location, action: FindAction ← none] RETURNS [head: CodeHead] = TRUSTED {
ENABLE UNWIND => NULL;
mod: CARDINAL = loc.pc MOD CodeHeadMod;
cloc: CodeLocation = GetCodeLocation[loc];
world: World = AMBridge.GetWorld[loc.gf];
incarnation: WorldVM.Incarnation = WorldVM.CurrentIncarnation[world];
scan for an existing head for this location
head ← codeHeadTab[mod];
WHILE head # NIL DO
IF world = head.world AND incarnation = head.incarnation AND cloc = head.codeLoc
THEN EXIT;
head ← head.next;
ENDLOOP;
IF action = none THEN RETURN [head];
IF head = NIL THEN {
make a new head for this location & put it on the list
head ← z.NEW[CodeHeadRep ← [
next: codeHeadTab[mod],
codeLoc: cloc,
incarnation: incarnation,
world: world]];
codeHeadTab[mod] ← head;
};
IF head.cid = NIL THEN {
we need to set the breakpoint
head.cid ← AMEvents.SetBreak[
world, LOOPHOLE[cloc.codeBase], loc.pc, head
! ABORTED => GO TO abort; ANY => GO TO oops];
};
RETURN [head];
EXITS
abort => RETURN WITH ERROR ABORTED;
oops => RETURN WITH ERROR LocationError[duplicateId];
};
FirstChild: PROC
[parent: AMModel.Context] RETURNS [child: AMModel.Context] = TRUSTED {
visit: PROC [context: AMModel.Context] RETURNS[stop: BOOL] = TRUSTED {
RETURN [TRUE]
};
RETURN [AMModel.ContextChildren[parent, visit]];
};
LocationFromSection: PROC
[section: Section] RETURNS [Location] = TRUSTED {
RETURN [z.NEW[LocationRep ← [section: section]]];
};
SameGlobalFrames: PROC [gf1,gf2: TV] RETURNS [BOOL] = TRUSTED {
IF gf1 = gf2 THEN RETURN [TRUE];
IF gf1 = NIL OR gf2 = NIL THEN RETURN [FALSE];
IF NOT InSameWorld[gf1,gf2] THEN RETURN [FALSE];
RETURN [GetCodeBase[gf1].longbase = GetCodeBase[gf2].longbase];
};
GetCodeBase: PROC [gf: TV] RETURNS [base: FrameCodeBase] = TRUSTED {
world: World ← AMBridge.GetWorld[gf];
short: CARDINAL;
addr: LONG CARDINAL;
IF gf = NIL THEN RETURN [EmptyCodeBase];
IF AMBridge.IsRemote[gf]
THEN short ← LOOPHOLE[AMBridge.RemoteGFHFromTV[gf].gfh, CARDINAL]
ELSE short ← LOOPHOLE[AMBridge.GFHFromTV[gf], CARDINAL];
addr ← WorldVM.Long[world, short + 1];
base ← LOOPHOLE[WorldVM.LongRead[world, addr], FrameCodeBase];
base.out ← FALSE;
};
GetCodeLocation: PROC [loc: Location] RETURNS [CodeLocation] = TRUSTED {
RETURN [[codeBase: GetCodeBase[loc.gf], pc: loc.pc]];
};
InSameWorld: PROC [tv1,tv2: TV] RETURNS [BOOL] = TRUSTED {
RETURN [
AMBridge.GetWorld[tv1] = AMBridge.GetWorld[tv2]
AND AMBridge.GetWorldIncarnation[tv1] = AMBridge.GetWorldIncarnation[tv2]];
};
FlushBreakBlock: ENTRY PROC
[head: CodeHead, id: BreakpointId] RETURNS [rId: BreakpointId ← NIL] = TRUSTED {
ENABLE UNWIND => NULL;
lag: BreakBlock ← NIL;
FOR bb: BreakBlock ← head.list, bb.next UNTIL bb = NIL DO
IF bb = id THEN {
IF lag = NIL THEN head.list ← bb.next ELSE lag.next ← bb.next;
bb.head ← NIL;
EXIT};
lag ← bb;
ENDLOOP;
IF head.cid # NIL AND head.list = NIL THEN {
time to really clear the break
cid: AMEvents.BreakID ← head.cid;
head.cid ← NIL;
AMEvents.ClearBreak[cid];
};
};
z: ZONE ← BBZones.GetQuantizedZone[];
codeHeadTab: REF CodeHeadArray ← z.NEW[CodeHeadArray ← ALL[NIL]];
END.