-- Cedar Debugger: global control and access to process facilities -- DebugTool.mesa -- Andrew Birrell, April 22, 1983 10:40 am -- Russ Atkinson, April 6, 1983 11:54 pm -- Paul Rovner, April 14, 1983 3:53 pm DIRECTORY AMEventBooted USING[ BootedNotifier, RegisterBootedNotifier, UnRegisterBootedNotifier ], AMEvents USING[ Kill, StopEvents, GetEvents ], AMEventsExtra USING[ Screen ], AMModel USING[ Class, Context, ContextClass, ContextChildren, ContextName, ContextWorld, MostRecentNamedContext, RootContext], AMProcess USING[ Adjust, CallDebugger, Freeze, GetProcesses, GetState, Name, Process, PSBIToTV, State, Thaw, TVToPSBI], AMTypes USING[ DynamicParent, EnclosingBody, Error, Locals], Atom USING[ GetPName ], Buttons USING[ Button, ButtonProc, Create, Destroy, ReLabel, SetDisplayStyle ], Commander USING[ CommandProc, Register ], Containers USING[ ChildXBound, ChildYBound, Create ], IO USING[ Close, CreateInputStreamFromRope, CreateOutputStreamToRope, EndOf, EndOfStream, GetInt, GetOutputStreamRope, GetToken, PutChar, PutF, PutRope, STREAM, SyntaxError, Value ], Labels USING[ Create ], MBQueue USING[ Create, CreateButton, Flush, Queue, QueueClientAction ], Process USING[ Abort, Detach ], PSB USING[ PsbIndex ], Rope USING[ Cat, Equal, Length, ROPE ], RTBasic USING [TV], Rules USING[ Create ], TypeScript USING[ Create ], ViewerClasses USING[ Viewer ], ViewerEvents USING[ EventProc, RegisterEventProc ], ViewerIO USING[ CreateViewerStreams ], ViewerOps USING[ DestroyViewer, OpenIcon, PaintViewer, SetOpenHeight ], ViewerTools USING[ GetContents, MakeNewTextViewer, SetContents, SetSelection], Volume USING[ GetType, systemID ], WorldVM USING[ CurrentIncarnation, Incarnation, LocalWorld, World, LookupFailed, BadWorld, GetWorld ]; DebugTool: CEDAR MONITOR IMPORTS AMEventBooted, AMEvents, AMEventsExtra, AMModel, AMProcess, Atom, Buttons, Commander, Containers, IO, Labels, MBQueue, Process, Rope, AMTypes, Rules, TypeScript, Volume, ViewerEvents, ViewerIO, ViewerOps, ViewerTools, WorldVM = BEGIN OPEN RTBasic; -- ******** Creation/Finding viewer for particular world ******** -- -- Maintains at most one viewer for any world (destroying extras). There are two interfaces: -- buttons in an existing viewer, or userExec command line. -- The synchronization is messy - it needs a rework someday. local: ATOM = $Local; outload: ATOM = $Outload; remote: ATOM = $Remote; Debug: Commander.CommandProc = { -- [cmd: Commander.Handle] arg: Rope.ROPE = cmd.commandLine; in: IO.STREAM = IO.CreateInputStreamFromRope[arg]; token: Rope.ROPE _ in.GetToken[]; IF token.Length[] = 0 THEN token _ Atom.GetPName[local]; GetWorldViewer[ class: SELECT TRUE FROM token.Equal[Atom.GetPName[local],FALSE] => local, token.Equal[Atom.GetPName[outload],FALSE] => outload, ENDCASE => remote, worldName: token, prev: NIL]; }; ChangeWorld: SelectorNotifier = TRUSTED { d: MyData = NARROW[clientData]; IF d.processes # NIL AND NOT DoThaw[d] THEN RETURN[FALSE] ELSE BEGIN UnWorld[d]; -- so nobody else expects us to have that world -- GetWorldViewer[ class: value, worldName: SELECT value FROM local => Atom.GetPName[local], outload => Atom.GetPName[outload], ENDCASE => ViewerTools.GetContents[d.hostT], prev: d]; RETURN[TRUE] END; }; GetWorldViewer: PROC[class: ATOM, worldName: Rope.ROPE, prev: MyData] = { d: MyData; new: BOOL; [d, new] _ CheckExisting[worldName, prev]; d.class _ class; IF new THEN IF d = prev THEN SetWorld[d] ELSE CreateForWorld[d] ELSE ViewerOps.OpenIcon[d.self]; }; viewers: LIST OF MyData _ NIL; FindViewer: ENTRY PROC[viewer: ViewerClasses.Viewer] RETURNS[d: MyData] = BEGIN FOR old: LIST OF MyData _ viewers, old.rest UNTIL old = NIL DO IF old.first.self = viewer THEN RETURN[old.first] ENDLOOP; RETURN[NIL] END; CheckExisting: ENTRY PROC [worldName: Rope.ROPE, prev: MyData] RETURNS[found: MyData, new: BOOL] = TRUSTED { FOR old: LIST OF MyData _ viewers, old.rest UNTIL old = NIL DO IF NOT old.first.self.destroyed AND worldName.Equal[old.first.worldName, FALSE] THEN { found _ old.first; new _ FALSE; EXIT }; REPEAT FINISHED => BEGIN IF prev # NIL THEN { found _ prev; found.worldName _ worldName; found.world _ NIL } ELSE { found _ NEW[MyDataObject _ [worldName: worldName]]; viewers _ CONS[first: found, rest: viewers]; }; new _ TRUE; END ENDLOOP; IF prev # NIL AND found # prev THEN Process.Detach[FORK ViewerOps.DestroyViewer[prev.self]]; }; DestroyProc: ViewerEvents.EventProc = TRUSTED { -- PROC [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent] d: MyData = FindViewer[viewer]; IF d # NIL THEN BEGIN MBQueue.Flush[d.mbQueue]; StopFinding[d]; MBQueue.QueueClientAction[d.mbQueue, UnWorld, d]; END; }; GiveUpFinding: Buttons.ButtonProc = TRUSTED -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL -- NOTE: this button is not serialized with d.mbQueue, but with the Viewers notifier -- BEGIN d: MyData = NARROW[clientData]; MBQueue.Flush[d.mbQueue]; StopFinding[d]; MBQueue.QueueClientAction[d.mbQueue, UnWorld, d]; END; UnWorld: PROC[clientData: REF ANY] = TRUSTED BEGIN d: MyData = NARROW[clientData]; d.worldName _ NIL; -- should be inside the monitor, really -- IF d.finding THEN ERROR; AMEventBooted.UnRegisterBootedNotifier[BootedNotifier, d.world, d]; IF d.class = remote THEN AMEvents.StopEvents[d.world]; END; SetWorld: PROC[d: MyData] = TRUSTED { worldName: Rope.ROPE = d.worldName; d.self.name _ Rope.Cat["Debug ", worldName]; ViewerOps.PaintViewer[d.self, caption]; d.out.PutF["\n\nConnecting to \"%g\" ... ", [rope[worldName]] ]; IF StartFinding[d] -- Fork it, so that it can be aborted -- THEN BEGIN IF d.class = remote THEN BEGIN giveUpButton: ViewerClasses.Viewer = Buttons.Create[ info: [name: "Give up connection attempt!", parent: d.self, border: TRUE, wy: d.kidsY+d.buttH, wx: d.maxW], proc: GiveUpFinding, clientData: d, fork: FALSE]; d.world _ JOIN d.finder; ViewerOps.DestroyViewer[giveUpButton]; END ELSE d.world _ JOIN d.finder; END; IF NOT d.self.destroyed THEN { IF d.world = NIL THEN { SELECT d.class FROM local => d.out.PutF["failed!"]; outload => d.out.PutF["there is no outloaded debuggee"]; remote => d.out.PutF["can't contact \"%g\"", [rope[worldName]] ]; ENDCASE => ERROR; } ELSE { d.out.PutF["ok"]; d.rootContext _ AMModel.RootContext[d.world]; AMEventBooted.RegisterBootedNotifier[proc: BootedNotifier, world: d.world, clientData: d]; }; }; }; StartFinding: ENTRY PROC[d: MyData] RETURNS[BOOL] = TRUSTED BEGIN IF d.self.destroyed THEN RETURN[FALSE]; -- else don't do it: we wouldn't get aborted -- d.finder _ FORK Finder[d]; d.finding _ TRUE; RETURN[TRUE] END; StopFinding: ENTRY PROC[d: MyData] = TRUSTED { IF d.finding THEN Process.Abort[d.finder] }; Found: ENTRY PROC[d: MyData] = { d.finding _ FALSE }; Finder: PROC[d: MyData] RETURNS[world: WorldVM.World _ NIL] = TRUSTED{ world _ WorldVM.GetWorld[d.worldName ! WorldVM.LookupFailed, WorldVM.BadWorld => CONTINUE]; IF world # NIL THEN AMEvents.GetEvents[world, NIL, NIL ! ABORTED => { world _ NIL; CONTINUE }]; Found[d]; }; -- ******** Commands for viewer watching particular world ******** -- FreezeAll: Buttons.ButtonProc = TRUSTED { -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL d: MyData = NARROW[clientData]; IF d.rootContext # NIL AND d.world # WorldVM.LocalWorld[] THEN Merge[d, AMProcess.GetProcesses[LIST[d.rootContext]]] ELSE d.out.PutRope["\n\nYou don't really want to freeze the entire local world!"]; }; FreezeReady: Buttons.ButtonProc = TRUSTED { -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL d: MyData = NARROW[clientData]; states: LIST OF AMProcess.State; SELECT TRUE FROM shift, control => states _ LIST[waitingML, frameFault, pageFault, writeProtectFault, unknownFault]; ENDCASE => states _ LIST[ready]; IF d.rootContext # NIL AND d.world # WorldVM.LocalWorld[] THEN Merge[d, AMProcess.GetProcesses[LIST[d.rootContext], states]] ELSE d.out.PutRope["\n\nYou don't really want to freeze the entire local world!"]; }; FreezePSBI: Buttons.ButtonProc = TRUSTED { -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL d: MyData = NARROW[clientData]; IF d.rootContext # NIL THEN BEGIN psbiRope: Rope.ROPE = ViewerTools.GetContents[d.psbiT]; in: IO.STREAM = IO.CreateInputStreamFromRope[psbiRope]; psbi: INT = in.GetInt[! IO.EndOfStream, IO.SyntaxError => GOTO bad]; IF NOT in.EndOf[] OR psbi NOT IN [ FIRST[PSB.PsbIndex] .. LAST[PSB.PsbIndex] ] THEN GOTO bad ELSE BEGIN p: AMProcess.Process = AMProcess.PSBIToTV[d.world, psbi]; AMProcess.Freeze[LIST[p]]; Merge[d, LIST[p]]; END; EXITS bad => d.out.PutRope["\n\nNot a valid PSB index!"] END ELSE d.out.PutRope["\n\nNo world!"]; }; FreezeContext: Buttons.ButtonProc = TRUSTED { -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL d: MyData = NARROW[clientData]; CheckContext[d]; IF d.context # NIL THEN BEGIN IF (d.world # WorldVM.LocalWorld[] OR AMModel.ContextClass[d.context.first] # world) THEN BEGIN new: LIST OF AMProcess.Process = AMProcess.GetProcesses[d.context]; IF mouseButton = blue THEN AMProcess.Adjust[new, NIL]; Merge[d, new]; END ELSE d.out.PutRope["\n\nYou don't really want to freeze the entire local world!"]; END; }; Merge: PROC[d: MyData, new: LIST OF AMProcess.Process] = TRUSTED BEGIN newOne: LIST OF AMProcess.Process _ new; oldOne: LIST OF AMProcess.Process _ d.processes; prevOld: LIST OF AMProcess.Process _ NIL; first: BOOL _ TRUE; d.out.PutRope["\n\nAdditional frozen processes: "]; -- Merge in new processes. Assumes both lists are sorted -- DO oldBits: CARDINAL = IF oldOne = NIL THEN LAST[CARDINAL] ELSE AMProcess.TVToPSBI[oldOne.first].psbi; newBits: CARDINAL; IF newOne = NIL THEN EXIT; newBits _ AMProcess.TVToPSBI[newOne.first].psbi; SELECT TRUE FROM newBits < oldBits => { this: LIST OF AMProcess.Process = newOne; IF first THEN first _ FALSE ELSE d.out.PutRope[", "]; d.out.PutF["%bB", [integer[newBits]] ]; newOne _ newOne.rest; this.rest _ oldOne; IF prevOld = NIL THEN d.processes _ this ELSE prevOld.rest _ this; prevOld _ this; }; newBits = oldBits => newOne _ newOne.rest; newBits > oldBits => { prevOld _ oldOne; oldOne _ oldOne.rest }; ENDCASE => ERROR; ENDLOOP; IF first THEN d.out.PutRope["none"]; CreateButtons[d]; END; BootedNotifier: AMEventBooted.BootedNotifier = { --PROC[world: WorldVM.World, clientData: REF] d: MyData = NARROW[clientData]; MBQueue.Flush[d.mbQueue]; StopFinding[d]; MBQueue.QueueClientAction[d.mbQueue, CleanupButtons, d]; }; CleanupButtons: PROC[clientData: REF ANY] = TRUSTED BEGIN d: MyData = NARROW[clientData]; d.out.PutRope["\n\n~~~~~~ End of session ~~~~~~"]; d.processes _ NIL; CreateButtons[d]; END; ThawAll: Buttons.ButtonProc = -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL { [] _ DoThaw[clientData] }; DoThaw: PROC[clientData: REF ANY] RETURNS[BOOL] = TRUSTED { d: MyData = NARROW[clientData]; count: INT = CountActions[d]; d.out.PutRope["\n\nThaw all processes"]; IF count # 0 THEN d.out.PutF[" ... there are still %g action areas for frozen processes", [integer[count]] ] ELSE BEGIN AMProcess.Thaw[d.processes]; d.processes _ NIL; CreateButtons[d]; END; RETURN[count=0] }; ListLoadstate: Buttons.ButtonProc = TRUSTED -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL BEGIN d: MyData = NARROW[clientData]; level: INT _ 0; Enum: PROC[c: AMModel.Context] RETURNS[stop: BOOL _ FALSE] = TRUSTED BEGIN class: AMModel.Class = AMModel.ContextClass[c]; IF class = prog AND level # 0 THEN RETURN; d.out.PutChar['\n]; THROUGH [1..level] DO d.out.PutRope[" "] ENDLOOP; d.out.PutRope[AMModel.ContextName[c]]; IF class = model THEN { level _ level+1; [] _ AMModel.ContextChildren[c, Enum]; level _ level-1 }; END; d.out.PutRope["\n\nLoadstate:"]; [] _ AMModel.ContextChildren[d.rootContext, Enum]; END; ListContext: Buttons.ButtonProc = TRUSTED -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL BEGIN d: MyData = NARROW[clientData]; level: INT _ 0; Enum: PROC[c: AMModel.Context] RETURNS[stop: BOOL _ FALSE] = TRUSTED BEGIN class: AMModel.Class = AMModel.ContextClass[c]; d.out.PutChar['\n]; THROUGH [1..level] DO d.out.PutRope[" "] ENDLOOP; d.out.PutRope[AMModel.ContextName[c]]; IF class = model THEN { level _ level+1; [] _ AMModel.ContextChildren[c, Enum]; level _ level-1 }; END; CheckContext[d]; IF d.context # NIL THEN BEGIN d.out.PutF["\n\nContext \"%g\":", [rope[d.contextName]] ]; IF AMModel.ContextClass[d.context.first] = prog THEN [] _ Enum[d.context.first] ELSE [] _ AMModel.ContextChildren[d.context.first, Enum]; END; END; UserScreen: Buttons.ButtonProc = TRUSTED -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL BEGIN d: MyData = NARROW[clientData]; Process .Detach[FORK AMEventsExtra.Screen[d.world] ]; END; Continue: Buttons.ButtonProc = TRUSTED -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL BEGIN d: MyData = NARROW[clientData]; d.out.PutRope["\n\nNot implemented"]; END; Kill: Buttons.ButtonProc = TRUSTED -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL BEGIN d: MyData = NARROW[clientData]; Process.Detach[FORK AMEvents.Kill[d.world] ]; END; Stop: Buttons.ButtonProc = TRUSTED -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL BEGIN d: MyData = NARROW[clientData]; d.stopCount _ d.stopCount + 1; END; ProcessRec: TYPE = RECORD[d: MyData, p: AMProcess.Process, button: Buttons.Button]; ThawThis: Buttons.ButtonProc = TRUSTED { -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL pData: REF ProcessRec = NARROW[clientData]; d: MyData = pData.d; IF CheckAction[d, pData] THEN BEGIN AMProcess.Thaw[LIST[pData.p]]; Buttons.ReLabel[pData.button, "thawed"]; EndAction[d, pData]; END ELSE d.out.PutF["\n\n%g still has an action area", [rope[AMProcess.Name[pData.p]]] ]; }; AdjustThis: Buttons.ButtonProc = TRUSTED { -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL pData: REF ProcessRec = NARROW[clientData]; d: MyData = pData.d; IF CheckAction[d, pData] THEN BEGIN ENABLE UNWIND => EndAction[d, pData]; IF mouseButton = blue THEN BEGIN AMProcess.Adjust[LIST[pData.p], NIL]; Buttons.ReLabel[pData.button, ProcessLabel[pData.p].label]; END ELSE BEGIN CheckContext[d]; IF d.context # NIL THEN BEGIN AMProcess.Adjust[LIST[pData.p], d.context]; Buttons.ReLabel[pData.button, ProcessLabel[pData.p].label]; END; END; EndAction[d, pData]; END ELSE d.out.PutF["\n\n%g still has an action area", [rope[AMProcess.Name[pData.p]]] ]; }; DebugThis: Buttons.ButtonProc = TRUSTED { -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL pData: REF ProcessRec = NARROW[clientData]; d: MyData = pData.d; label: Rope.ROPE; stack: TV; [label, stack] _ ProcessLabel[pData.p]; Buttons.ReLabel[pData.button, label]; IF mouseButton = blue THEN BEGIN IF CheckAction[d, pData] THEN Process.Detach[FORK DoAction[d, pData] ] ELSE d.out.PutF["\n\n%g already has an action area", [rope[AMProcess.Name[pData.p]]] ]; END ELSE { d.out.PutF["\n\n%g:", [rope[AMProcess.Name[pData.p]]] ]; Process.Detach[FORK PutStack[d, stack, FALSE --mouseButton=yellow??--] ]; }; }; CountActions: ENTRY PROC[d: MyData] RETURNS[count: INT _ 0] = BEGIN FOR psbi: PSB.PsbIndex IN PSB.PsbIndex DO IF d.action[psbi] THEN count _ count+1 ENDLOOP; END; CheckAction: ENTRY PROC[d: MyData, pData: REF ProcessRec] RETURNS[BOOL] = TRUSTED BEGIN psbi: PSB.PsbIndex = AMProcess.TVToPSBI[pData.p].psbi; IF d.action[psbi] THEN RETURN[FALSE] ELSE { d.action[psbi] _ TRUE; RETURN[TRUE] } END; EndAction: ENTRY PROC[d: MyData, pData: REF ProcessRec] = TRUSTED { d.action[AMProcess.TVToPSBI[pData.p].psbi] _ FALSE }; DoAction: PROC[d: MyData, pData: REF ProcessRec] = TRUSTED BEGIN ENABLE UNWIND => EndAction[d, pData]; Buttons.SetDisplayStyle[pData.button, $WhiteOnBlack]; AMProcess.CallDebugger[pData.p, ProcessLabel[pData.p].label]; Buttons.SetDisplayStyle[pData.button, $BlackOnWhite]; EndAction[d, pData]; END; ProcessLabel: PROC[p: AMProcess.Process] RETURNS[label: Rope.ROPE, stack: TV] = TRUSTED { str: IO.STREAM = IO.CreateOutputStreamToRope[]; state: AMProcess.State; faultData: LONG CARDINAL; topFrame: BOOL; [state, faultData, , stack, topFrame] _ AMProcess.GetState[p]; str.PutF["%g", IF stack = NIL THEN [rope["no frozen frame"]] ELSE [tv[stack]] ]; str.PutF[IF topFrame THEN ", %g" ELSE ", (%g)", [rope[StateRope[state]]] ]; IF state IN [frameFault..unknownFault] THEN str.PutF["[%bB]", [cardinal[faultData]] ]; label _ str.GetOutputStreamRope[]; str.Close[]; }; PutStack: PROC[d: MyData, top: TV, vars: BOOL] = { ENABLE UNWIND => d.out.PutRope["\n ~~~ Unwound ~~~"]; out: IO.STREAM _ d.out; initStopCount: INT _ d.stopCount; FOR this: TV _ top, AMTypes.DynamicParent[this] UNTIL this = NIL DO IF d.stopCount # initStopCount THEN { out.PutRope["\n ~~~ Display stopped ~~~"]; RETURN}; PutProc[d, this, vars] ENDLOOP; out.PutRope["\n ~~~ End of stack ~~~"]; }; PutProc: PROC[d: MyData, l: TV, vars: BOOL] = { ENABLE AMTypes.Error => { IO.PutF[d.out, "\nAMTypes.Error[%g]", [rope[msg]] ]; CONTINUE }; out: IO.STREAM _ d.out; initStopCount: INT _ d.stopCount; IO.PutF[out, "\n Procedure: %g", [tv[l]] ]; IF vars THEN FOR block: TV _ l, AMTypes.EnclosingBody[block] UNTIL block = NIL DO blockVars: TV = AMTypes.Locals[block]; IF d.stopCount # initStopCount THEN RETURN; IF blockVars # NIL THEN IO.PutF[out, "\n vars: %g", [tv[blockVars]] ]; ENDLOOP; }; StateRope: PROC[state: AMProcess.State] RETURNS[ Rope.ROPE ] = { RETURN[ SELECT state FROM ready => "ready", waitingCV => "waitingCV", waitingML => "waitingML", frameFault => "frameFault", pageFault => "pageFault", writeProtectFault => "writeProtectFault", unknownFault => "unknownFault", uncaughtSignal => "uncaughtSignal", breakpoint => "breakpoint", callDebugger => "callDebugger", dead => "dead", unknown => "unknown", ENDCASE => "illegal state!"] }; CheckContext: PROC[d: MyData] = TRUSTED { contextName: Rope.ROPE = ViewerTools.GetContents[d.contextT]; IF NOT Rope.Equal[contextName, d.contextName, FALSE] OR d.context = NIL OR d.world # AMModel.ContextWorld[d.context.first] OR d.contextIncarnation # WorldVM.CurrentIncarnation[d.world] THEN { new: AMModel.Context; d.context _ NIL; d.contextIncarnation _ WorldVM.CurrentIncarnation[d.world]; new _ SELECT TRUE FROM d.world = NIL => NIL, Rope.Length[contextName] = 0 => d.rootContext ENDCASE => AMModel.MostRecentNamedContext[contextName, d.rootContext]; IF new # NIL THEN { d.context _ LIST[new]; d.contextName _ contextName }; }; IF d.context = NIL THEN d.out.PutRope["\n\nInvalid context"]; }; -- ******** Viewer management ******** -- MyData: TYPE = REF MyDataObject; MyDataObject: TYPE = MONITORED RECORD[ in: IO.STREAM, out: IO.STREAM, mbQueue: MBQueue.Queue _ NIL, self, kids, hostT, contextT, psbiT, script: ViewerClasses.Viewer _ NIL, kidsY: INTEGER _ 0, finding: BOOL _ FALSE, stopCount: INT _ 0, finder: PROCESS RETURNS[WorldVM.World] _ NIL, worldName: Rope.ROPE, class: ATOM _ local, value: REF ATOM _ NIL, world: WorldVM.World _ NIL, rootContext: AMModel.Context _ NIL, contextName: Rope.ROPE _ NIL, contextIncarnation: WorldVM.Incarnation _ 0, context: LIST OF AMModel.Context _ NIL, processes: LIST OF AMProcess.Process _ NIL, action: PACKED ARRAY PSB.PsbIndex OF BOOL _ ALL[FALSE], pButtons: LIST OF REF ProcessRec _ NIL, maxW: INTEGER _ 0, buttH: INTEGER _ 0, kidsH: INTEGER _ 0]; CreateForWorld: PROC[d: MyData] = BEGIN d.mbQueue _ MBQueue.Create[]; MBQueue.QueueClientAction[d.mbQueue, ReallyCreate, d]; END; ReallyCreate: PROC[clientData: REF ANY] = -- This is a separate procedure so that it is synchronized with d.mbQueue -- -- Thus, the buttons can't be invoked until we've finished creating them, and called SetWorld. { d: MyData = NARROW[clientData]; v: ViewerClasses.Viewer = Containers.Create[ info: [name: "Debug processes", column: left, scrollable: FALSE, iconic: TRUE]]; child: ViewerClasses.Viewer _ NIL; x: INTEGER _ 1; y: INTEGER _ 0; CommandButton: PROC[name: Rope.ROPE, proc: Buttons.ButtonProc, data: REF ANY, newline: BOOL, guarded: BOOL _ FALSE] = { child _ MBQueue.CreateButton[ q: d.mbQueue, info: [name: name, parent: v, border: TRUE, wy: y, wx: x, ww: d.maxW], proc: proc, clientData: data, fork: TRUE, paint: TRUE, guarded: guarded]; x _ IF newline THEN 1 ELSE child.wx + d.maxW - 1; y _ IF newline THEN child.wy + child.wh - 1 ELSE child.wy; }; LabelText: PROC[name, data: Rope.ROPE, prev: ViewerClasses.Viewer] RETURNS[ViewerClasses.Viewer] = { child _ ViewerTools.MakeNewTextViewer[ info: [parent: v, wh: d.buttH, ww: 999, scrollable: FALSE, data: IF prev = NIL THEN data ELSE ViewerTools.GetContents[prev], border: FALSE, wx: x + d.maxW + 2, wy: y], paint: TRUE ]; Containers.ChildXBound[v, child]; [] _ Buttons.Create[ info: [name: name, parent: v, wh: d.buttH, border: FALSE, wx: x+1, wy: y], proc: TextLabelProc, clientData: child, fork: FALSE, paint: TRUE]; x _ 1; y _ child.wy + child.wh - 1; RETURN[child] }; Label: PROC[name: Rope.ROPE, newline: BOOL] = { child _ Labels.Create[ info: [name: name, parent: v, border: FALSE, wy: y, wx: x+1], paint: TRUE ]; x _ IF newline THEN 1 ELSE child.wx + d.maxW - 1; y _ IF newline THEN child.wy + child.wh - 1 ELSE child.wy; }; Rule: PROC = { child _ Rules.Create[ info: [parent: v, border: FALSE, wy: y, wx: 0, ww: v.ww, wh: 1], paint: TRUE ]; Containers.ChildXBound[v, child]; x _ 1; y _ child.wy + child.wh + 1; }; d.self _ v; { -- kludge to find max button size! -- temp: Buttons.Button = Buttons.Create[ info: [name: "Loadstate:", parent: v, border: FALSE, wx: 0, wy: 0], proc: NIL, clientData: d, fork: FALSE, paint: FALSE]; d.maxW _ temp.ww; d.buttH _ temp.wh; Buttons.Destroy[temp]; }; [child, d.value] _ CreateSelector[q: d.mbQueue, name: "World:", values: LIST[local, outload, remote], init: NEW[ATOM_d.class], change: ChangeWorld, clientData: d, viewer: v, x: x, y: y, w: d.maxW]; x _ child.wx + child.ww + 2; x _ 1 + 5*(d.maxW-1); d.hostT _ LabelText["Host:", IF d.class # remote THEN "Remote debuggee host name" ELSE d.worldName, d.hostT]; Label["Freeze:", FALSE]; CommandButton["All", FreezeAll, d, FALSE]; CommandButton["Ready", FreezeReady, d, FALSE] ; CommandButton["Process", FreezePSBI, d, FALSE]; CommandButton["Context", FreezeContext, d, FALSE]; d.contextT _ LabelText["Context:", "Module or config name", d.contextT]; Label["Thaw:", FALSE]; CommandButton["All", ThawAll, d, FALSE]; x _ 1 + 5*(d.maxW-1); d.psbiT _ LabelText["PsbIndex:", "Freezing an explicit process", d.psbiT]; Label["List:", FALSE]; CommandButton["Loadstate", ListLoadstate, d, FALSE]; CommandButton["Context", ListContext, d, FALSE]; x _ 1 + 5*(d.maxW-1); Label["Control:", FALSE]; CommandButton["Stop", Stop, d, FALSE]; CommandButton["Kill", Kill, d, FALSE, TRUE]; -- CommandButton["Continue", Continue, d, FALSE]; CommandButton["Screen", UserScreen, d, TRUE]; y _ y + 3; Rule[]; d.kidsY _ y; y _ y + (d.kidsH _ 10*(d.buttH-1) + d.buttH/2); Rule[]; d.script _ TypeScript.Create[ info: [parent: v, wh: v.ch-y, ww: v.cw, border: FALSE, wy: y, wx: 0] ]; Containers.ChildXBound[v, d.script]; Containers.ChildYBound[v, d.script]; [in: d.in, out: d.out] _ ViewerIO.CreateViewerStreams[NIL, d.script]; ViewerOps.SetOpenHeight[v, y + 10 * d.buttH]; ViewerOps.OpenIcon[v]; SetWorld[d]; }; CreateButtons: PROC[d: MyData] = TRUSTED { parent: ViewerClasses.Viewer = d.self; child: ViewerClasses.Viewer _ NIL; x: INTEGER _ 1; y: INTEGER _ 1; CommandButton: PROC[name: Rope.ROPE, proc: Buttons.ButtonProc, data: REF ANY, newline: BOOL] = TRUSTED { child _ MBQueue.CreateButton[ q: d.mbQueue, info: [name: name, parent: kids, border: TRUE, wy: y, wx: x, ww: IF newline THEN kids.cw - x - 2 ELSE d.maxW], proc: proc, clientData: data, fork: TRUE]; x _ IF newline THEN 1 ELSE child.wx + d.maxW - 1; y _ IF newline THEN child.wy + child.wh - 1 ELSE child.wy; }; Label: PROC[name: Rope.ROPE, newline: BOOL] = TRUSTED { child _ Labels.Create[ info: [name: name, parent: kids, border: FALSE, wy: y, wx: x+1] ]; x _ IF newline THEN 1 ELSE child.wx + d.maxW - 1; y _ IF newline THEN child.wy + child.wh - 1 ELSE child.wy; }; kids: ViewerClasses.Viewer = Containers.Create[ info: [parent: parent, border: FALSE, scrollable: TRUE, wx: 0, wy: d.kidsY, ww: parent.cw, wh: d.kidsH] ]; lastButton: LIST OF REF ProcessRec _ NIL; IF d.kids # NIL THEN ViewerOps.DestroyViewer[d.kids]; d.kids _ kids; Containers.ChildXBound[parent, kids]; d.pButtons _ NIL; FOR p: LIST OF AMProcess.Process _ d.processes, p.rest UNTIL p = NIL DO pData: REF ProcessRec = NEW[ProcessRec _ [d, p.first]]; Label[AMProcess.Name[p.first], FALSE]; CommandButton["Adjust", AdjustThis, pData, FALSE]; CommandButton["Thaw", ThawThis, pData, FALSE]; CommandButton[NIL, DebugThis, pData, TRUE]; Containers.ChildXBound[kids, child]; pData.button _ child; IF lastButton = NIL THEN d.pButtons _ lastButton _ CONS[first: pData, rest: NIL] ELSE { lastButton.rest _ CONS[first: pData, rest: NIL]; lastButton _ lastButton.rest }; ENDLOOP; FOR b: LIST OF REF ProcessRec _ d.pButtons, b.rest UNTIL b = NIL DO Buttons.ReLabel[b.first.button, ProcessLabel[b.first.p].label] ENDLOOP; }; TextLabelProc: Buttons.ButtonProc = { -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL text: ViewerClasses.Viewer = NARROW[clientData]; SELECT mouseButton FROM red => ViewerTools.SetSelection[text, NIL]; blue => { ViewerTools.SetContents[text, NIL]; ViewerTools.SetSelection[text, NIL] }; yellow => NULL; ENDCASE => ERROR; }; Selector: TYPE = REF SelectorRec; SelectorRec: TYPE = RECORD[ value: REF ATOM, change: SelectorNotifier, clientData: REF ANY, buttons: LIST OF Buttons.Button, values: LIST OF ATOM ]; SelectorNotifier: TYPE = PROC[parent: ViewerClasses.Viewer, clientData: REF ANY, value: ATOM] RETURNS[BOOL]; CreateSelector: PROC[q: MBQueue.Queue, name: Rope.ROPE, values: LIST OF ATOM, init: REF ATOM _ NIL, change: SelectorNotifier _ NIL, clientData: REF ANY _ NIL, viewer: ViewerClasses.Viewer, x, y: INTEGER, w: INTEGER _ 0] RETURNS[child: ViewerClasses.Viewer, value: REF ATOM] = { selector: Selector _ NEW[ SelectorRec _ [value: IF init # NIL THEN init ELSE NEW[ATOM_values.first], change: change, clientData: clientData, buttons: NIL, values: values ] ]; last: LIST OF Buttons.Button _ NIL; value _ selector.value; child _ Labels.Create[ info: [name: name, parent: viewer, border: FALSE, wx: x+1, wy: y, ww: w] ]; FOR a: LIST OF ATOM _ values, a.rest UNTIL a = NIL DO child _ MBQueue.CreateButton[ q: q, info: [name: Atom.GetPName[a.first], parent: viewer, border: TRUE, wy: child.wy, wx: child.wx + child.ww - 1, ww: w], proc: SelectorProc, clientData: selector, fork: TRUE, paint: TRUE]; IF last = NIL THEN last _ selector.buttons _ CONS[first: child, rest: NIL] ELSE { last.rest _ CONS[first: child, rest: NIL]; last _ last.rest }; IF a.first = selector.value^ THEN Buttons.SetDisplayStyle[child, $WhiteOnBlack]; ENDLOOP; }; SelectorProc: Buttons.ButtonProc = { -- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL self: Buttons.Button = NARROW[parent]; selector: Selector = NARROW[clientData]; buttons: LIST OF Buttons.Button _ selector.buttons; FOR a: LIST OF ATOM _ selector.values, a.rest UNTIL a = NIL DO IF self = buttons.first THEN BEGIN IF selector.change = NIL OR selector.change[self.parent, selector.clientData, a.first] THEN BEGIN selector.value^ _ a.first; Buttons.SetDisplayStyle[self, $WhiteOnBlack]; FOR others: LIST OF Buttons.Button _ selector.buttons, others.rest UNTIL others = NIL DO IF others.first # self THEN Buttons.SetDisplayStyle[others.first, $BlackOnWhite] ENDLOOP; END; EXIT END; buttons _ buttons.rest; ENDLOOP; }; -- START HERE [] _ ViewerEvents.RegisterEventProc[DestroyProc, destroy]; Commander.Register[ key: "Debug", proc: Debug, doc: "Tool for debugging processes and other worlds"]; IF Volume.GetType[Volume.systemID] # normal THEN GetWorldViewer[outload, Atom.GetPName[outload], NIL]; END.