<> <> <> <> <> <> <> DIRECTORY Basics USING [BITAND], BasicTime USING [GetClockPulses, Pulses, PulsesToMicroseconds], DebuggerSwap USING [CallDebugger], FastBreak USING [FastBreakProc], <> <> PrincOps, PrincOpsUtils USING [DisableInterrupts, EnableInterrupts, LongNotify, LongReEnter, LongWait, PsbIndexToHandle, PsbHandleToIndex, ReadPSB, ReadPTC], <> Process USING [Detach, GetCurrent, GetPriority, InitializeCondition, Priority, priorityRealTime, priorityNormal, SecondsToTicks, SetPriority], Rope USING [ROPE], SafeStorage USING [NWordsAllocated, NWordsReclaimed], Loader USING [MakeProcedureResident, MakeGlobalFrameResident], SpyClient USING [DataType], SpyLog USING [active, Here, OpenForWrite, WriteData], SpyOps USING [Count, DataType, Frame, SetAllocationBreak, SpyState, Stack, stackHeader, stackLength, StackType], Terminal USING [Current, Virtual, WaitForBWVerticalRetrace], VM USING [AddressFault, PageNumberForAddress], VMStatistics USING [pageFaults]; SpyKernelImpl: MONITOR IMPORTS Basics, BasicTime, DebuggerSwap, Loader, PrincOpsUtils, Process, SafeStorage, SpyLog, SpyOps, Terminal, VM, VMStatistics EXPORTS SpyClient, SpyOps = { OPEN PrincOps, Rope; PsbIndex: TYPE = PrincOps.PsbIndex; Ticks: TYPE = PrincOps.Ticks; -- SpyKernel parameters -- spyState: PUBLIC SpyOps.SpyState _ off; watching: PUBLIC SpyOps.DataType _ CPU; justMe: PUBLIC PsbIndex _ 0; <> runningTime: PUBLIC BasicTime.Pulses; -- total time running pageFaults: PUBLIC LONG CARDINAL; active, starts, stops: PUBLIC INTEGER _ 0; wordsAllocated: PUBLIC LONG CARDINAL; wordsReclaimed: PUBLIC LONG CARDINAL; code: PUBLIC SpyOps.Count _ 0; -- page faults on code data: PUBLIC SpyOps.Count _ 0; -- page faults on data <<****************************************************************************>> <> <<****************************************************************************>> InitializeSpy: PUBLIC ENTRY PROC[ dataType: SpyClient.DataType _ CPU, process: PsbIndex _ PrincOps.PsbNull, spyOnSpyLog: BOOL _ FALSE] RETURNS[errorMsg: ROPE] = { DisableSpy[]; IF dataType IN [allocations..wordsAllocated] THEN errorMsg _ SpyOps.SetAllocationBreak[]; IF errorMsg # NIL THEN RETURN[errorMsg]; InitializeTables[]; watching _ dataType; justMe _ IF dataType = process THEN process ELSE 0; SpyLog.OpenForWrite[spyOnSpyLog]; EnableSpy[]; }; InitializeTables: PROC = { runningTime _ 0; code _ data _ 0; wordsAllocated _ wordsReclaimed _ 0; }; Initialize: PROC = { dolphin _ BasicTime.PulsesToMicroseconds[1]#32; -- hack Loader.MakeProcedureResident[Spy]; Loader.MakeProcedureResident[Record]; Loader.MakeProcedureResident[UserBreak]; Loader.MakeProcedureResident[PageFaultRecorder]; Loader.MakeGlobalFrameResident[Initialize]; Process.InitializeCondition[@processDead, 10000]; SetTimeouts[]; }; SetTimeouts: PROC = { ticks: Ticks; cutoff: Ticks; found: BOOLEAN; frame: PrincOps.FrameHandle; <> cutoff _ Process.SecondsToTicks[10]; <> ticks _ PrincOpsUtils.ReadPTC[]; FOR psbi: PsbIndex IN [PrincOps.StartPsb..PrincOps.StartPsb+PrincOps.PDA.count) DO IF PrincOps.PDA.block[psbi].mds = 0 THEN LOOP; IF PrincOps.PDA.block[psbi].mds - ticks > cutoff THEN LOOP; -- not significant IF PrincOps.PDA.block[psbi].link.vector THEN frame _ PDA[PrincOps.PDA.block[psbi].context.state].frame ELSE frame _ PrincOps.PDA.block[psbi].context.frame; found _ FALSE; FOR i: CARDINAL IN [0..index) DO IF timeout[i] = [frame.accesslink, frame.pc] THEN {found _ TRUE; EXIT}; ENDLOOP; IF found THEN LOOP; timeout[index] _ [frame.accesslink, frame.pc]; index _ index + 1; IF index = maxIndex THEN EXIT; ENDLOOP; <> }; <> <> <> <<****************************************************************************>> <> <<****************************************************************************>> <> processDead: CONDITION; spyProcess: PROCESS _ NIL; oldSpyState: SpyOps.SpyState _ off; startTime: BasicTime.Pulses; startPageFaults: LONG CARDINAL; startWordsAllocated: LONG CARDINAL; startWordsReclaimed: LONG CARDINAL; StartCounting: PUBLIC ENTRY FastBreak.FastBreakProc = { priority: Process.Priority; starts _ starts + 1; IF active = 0 THEN WHILE spyProcess # NIL DO WAIT processDead; ENDLOOP; IF (active _ active + 1) > 1 THEN RETURN[useOldBreak: FALSE]; -- a psuedo start startPageFaults _ VMStatistics.pageFaults; startWordsAllocated _ SafeStorage.NWordsAllocated[]; startWordsReclaimed _ SafeStorage.NWordsReclaimed[]; startTime _ BasicTime.GetClockPulses[]; IF watching = breakProcess THEN justMe _ LOOPHOLE[Process.GetCurrent[]]; IF justMe # 0 THEN justMePrincOps _ @PrincOps.PDA[PrincOpsUtils.PsbIndexToHandle[justMe]]; SetTimeouts[]; -- do it a second time in case the first time missed some priority _ Process.GetPriority[]; Process.SetPriority[Process.priorityRealTime]; SELECT watching FROM CPU, process, breakProcess => Process.Detach[spyProcess _ FORK Spy[]]; pagefaults => Process.Detach[spyProcess _ FORK PageFaultRecorder[]]; allocations, wordsAllocated => { <> spyState _ on}; ENDCASE => spyState _ on; Process.SetPriority[priority]; RETURN[useOldBreak: FALSE]; }; StopCounting: PUBLIC ENTRY FastBreak.FastBreakProc = { IF active = 0 THEN RETURN[useOldBreak: FALSE]; -- already stopped stops _ stops + 1; IF (active _ active - 1) > 0 THEN RETURN; -- a psuedo stop spyState _ off; runningTime _ runningTime + BasicTime.GetClockPulses[] - startTime; pageFaults _ pageFaults + VMStatistics.pageFaults - startPageFaults; wordsAllocated _ wordsAllocated + SafeStorage.NWordsAllocated[] - startWordsAllocated; wordsReclaimed _ wordsReclaimed + SafeStorage.NWordsReclaimed[] - startWordsReclaimed; RETURN[useOldBreak: FALSE]; }; DisableSpy: PROC = { IF spyState = disabled THEN RETURN; oldSpyState _ spyState; spyState _ disabled; }; EnableSpy: PROC = {spyState _ oldSpyState}; <<*********************************************************************>> <> <<*********************************************************************>> <> justMePrincOps: LONG POINTER TO ProcessStateBlock _ NIL; wait: CARDINAL _ 0; -- used with dolphins monitor: BOOLEAN _ FALSE; -- measure performance of spy dolphin: BOOLEAN _ FALSE; maxStackDepth: CARDINAL = 200; timeout: ARRAY [0..maxIndex) OF RECORD[gfh: PrincOps.GlobalFrameHandle, pc: CARDINAL]; maxIndex: CARDINAL = 20; index: CARDINAL_ 0; sampleInterval: INT _ 10*1000; -- measured in microseconds screen: Terminal.Virtual = Terminal.Current[]; searchReadyList: BOOLEAN _ TRUE; -- sometimes the user gets the machine gets into a state where searching the ready list is a bad idea. This boolean allows the user to stop the Spy from searching the ready list. Spy: PROC = { top: PsbIndex; frame: FrameHandle; myPrincOps: PsbHandle = PrincOpsUtils.ReadPSB[]; handleMask: PsbLink = [ failed: FALSE, priority: 0, next: LAST[PsbIndex], reserved: 0, vector: FALSE]; NextHandle: PROC [link: CARDINAL] RETURNS [PsbHandle] = INLINE { RETURN[LOOPHOLE[Basics.BITAND[link, LOOPHOLE[handleMask]]]]}; SearchReadyList: PROC = INLINE { skip, once: BOOLEAN _ FALSE; headOfReadyList, current: PsbHandle; PrincOpsUtils.DisableInterrupts[]; top _ PrincOps.PsbNull; headOfReadyList _ NextHandle[LOOPHOLE[PDA.ready]]; headOfReadyList _ NextHandle[LOOPHOLE[PDA[headOfReadyList].link]]; -- want the SECOND psb. FOR current _ headOfReadyList, NextHandle[LOOPHOLE[PDA[current].link]] DO psb: LONG POINTER TO ProcessStateBlock = @PDA[current]; link: PsbLink = psb.link; level: Process.Priority = link.priority; IF current = headOfReadyList THEN IF once THEN EXIT ELSE once _ TRUE; IF level = 0 THEN LOOP; IF current = myPrincOps THEN LOOP; IF ~link.vector AND PDA.state[level] = NullStateVectorHandle THEN LOOP; -- no SV. frame _ IF link.vector THEN PDA[psb.context.state].frame ELSE psb.context.frame; <> <<(The Spy wakes up with all of the other timeouts. Since it is the highest priority,>> <> <> <> skip _ FALSE; FOR i: CARDINAL IN [0..maxIndex) DO IF timeout[i] = [NIL, 0] THEN EXIT; IF timeout[i] # [frame.accesslink, frame.pc] THEN LOOP; skip _ TRUE; EXIT; ENDLOOP; IF skip THEN LOOP; <> top _ PrincOpsUtils.PsbHandleToIndex[current]; EXIT; ENDLOOP; PrincOpsUtils.EnableInterrupts[]; }; spyState _ on; -- MAIN LOOP -- DO -- IF IntervalTimerFace.exists -- THEN IntervalTimer.WaitForExpirationInterval[sampleInterval] ELSE Terminal.WaitForBWVerticalRetrace[screen]; IF active <= 0 THEN EXIT; IF spyState = disabled THEN LOOP; IF dolphin AND wait > 0 THEN {wait _ wait - 1; LOOP} ELSE wait _ 8; IF monitor THEN SpyLog.Here[]; IF justMe = 0 OR searchReadyList THEN SearchReadyList[]; SELECT TRUE FROM justMe = 0 => Record[top, frame]; justMePrincOps.link.failed => Record[justMe, NIL, 2]; -- waiting ML justMePrincOps.flags.waiting => Record[justMe, NIL, 3]; -- waiting CV OnQueue[justMe, @PDA.fault[PrincOps.qPageFault].queue] => Record[justMe, NIL, 1]; -- waiting pagefault OnQueue[justMe, @PDA.fault[PrincOps.qPageFault+4].queue] => Record[justMe, NIL, 1]; -- waiting pagefault OnQueue[justMe, @PrincOps.PDA.ready] => SELECT TRUE FROM PDA.state[justMePrincOps.link.priority] = NullStateVectorHandle AND ~justMePrincOps.link.vector => Record[justMe, NIL, 5]; -- waiting SV searchReadyList AND justMe # top => Record[justMe, NIL, 4]; -- prempted by a higher priority process ENDCASE => Record[justMe]; -- ready ENDCASE => Record[justMe, NIL, 6]; -- in some unknown state IF monitor THEN SpyLog.Here[]; ENDLOOP; spyProcess _ NIL; spyState _ off; NotifyProcessDead[]; Process.SetPriority[Process.priorityNormal]; }; OnQueue: PROC[psbi: PsbIndex, queueHandle: PrincOps.QueueHandle] RETURNS[BOOL] = INLINE { tail, prev: PsbIndex; IF queueHandle^ = PrincOps.QueueEmpty THEN RETURN[FALSE]; prev _ tail _ queueHandle.tail; THROUGH [FIRST[PsbIndex]..LAST[PsbIndex]+1] -- garbage protection -- DO next: PsbIndex = PrincOps.PDA.block[prev].link.next; IF next = psbi THEN RETURN[TRUE]; prev _ next; IF prev = tail THEN RETURN[FALSE]; ENDLOOP; RETURN[FALSE] -- actually, the queue is thoroughly mangled! -- }; <<*********************************************************************>> <> <<*********************************************************************>> recordPageFaulted: BOOLEAN _ FALSE; Data: TYPE = RECORD[process: CARDINAL, page: INTEGER]; CodeBase: TYPE = LONG POINTER TO PACKED ARRAY [0..0) OF PrincOps.op; PageFaultRecorder: PROC = { <> fault: Data; type: CARDINAL; codePage: INTEGER; handle: PrincOps.PsbHandle; frame: PrincOps.FrameHandle; pda: PrincOps.PDABase = PrincOps.PDA; qPageFault: PrincOps.FaultIndex = PrincOps.qPageFault; pPageFaultCondition: LONG POINTER TO PrincOps.Condition = @pda.fault[qPageFault].condition; pPageFaultCONDITION: LONG POINTER TO CONDITION = LOOPHOLE[pPageFaultCondition]; recorderLock: MONITORLOCK; shouldNotifyPilot: BOOLEAN _ FALSE; <> spyState _ on; DO <> PrincOpsUtils.LongWait[@recorderLock, pPageFaultCONDITION, 1]; UNTIL PrincOpsUtils.LongReEnter[@recorderLock, pPageFaultCONDITION] DO NULL ENDLOOP; IF pda.fault[qPageFault].queue.tail = PrincOps.PsbNull THEN { -- timed out IF shouldNotifyPilot AND pda.fault[qPageFault].condition.tail ~= PrincOps.PsbNull THEN { LongNakedNotify[pPageFaultCONDITION]; shouldNotifyPilot _ FALSE}; IF active <= 0 THEN EXIT; LOOP}; -- go back and wait again. <
> fault.process _ pda.block[pda.fault[qPageFault].queue.tail].link.next; -- walk to tail, then to head. handle _ PrincOpsUtils.PsbIndexToHandle[fault.process]; fault.page _ VM.PageNumberForAddress[pda[pda[handle].context.state].memPointer]; frame _ IF pda[handle].link.vector THEN pda[pda[handle].context.state].frame ELSE pda[handle].context.frame; codePage _ LOOPHOLE[frame.accesslink.code.longbase+frame.pc/2, INT]/256; SELECT TRUE FROM ABS[codePage - fault.page] <= 1 => {code _ code + 1; type _ 2}; Xfer[frame.accesslink.code.longbase, frame.pc] => {code _ code + 1; type _ 3}; ENDCASE => {data _ data + 1; type _ 1}; <> PrincOpsUtils.DisableInterrupts[]; IF pPageFaultCondition^.tail = PrincOps.PsbNull THEN shouldNotifyPilot _ TRUE -- Pilot not ready for this fault yet... ELSE LongNakedNotify[pPageFaultCONDITION]; PrincOpsUtils.EnableInterrupts[]; <> IF active <= 0 THEN EXIT; IF recordPageFaulted THEN SpyLog.WriteData[@fault, SIZE[Data], CODE[Data]]; Record[fault.process, NIL, type]; ENDLOOP; spyProcess _ NIL; spyState _ off; NotifyProcessDead[]; Process.SetPriority[Process.priorityNormal]; }; Xfer: PROC[base: CodeBase, pc: PrincOps.BytePC] RETURNS[BOOLEAN] = INLINE { <> IF base[pc] IN [PrincOps.zEFC0..PrincOps.zKFCB] THEN RETURN[TRUE]; RETURN[FALSE]; }; LongNakedNotify: PROC [pCondition: LONG POINTER TO CONDITION] = INLINE { <> pCond: LONG POINTER TO PrincOps.Condition = LOOPHOLE[pCondition]; PrincOpsUtils.DisableInterrupts[]; IF pCond^.tail=PrincOps.PsbNull THEN {pCond^.wakeup _ TRUE; PrincOpsUtils.EnableInterrupts[]} ELSE {PrincOpsUtils.EnableInterrupts[]; PrincOpsUtils.LongNotify[pCondition]}}; NotifyProcessDead: ENTRY PROC = INLINE {NOTIFY processDead}; <<***************************************************************************>> <> <<***************************************************************************>> stack: SpyOps.Stack; break: BOOLEAN _ FALSE; breakAtom: ATOM _ NIL; AllocationBreak: PUBLIC FastBreak.FastBreakProc = { <> words: CARDINAL; type: SpyOps.StackType; local: POINTER TO ARRAY [0..7] OF CARDINAL; IF active = 0 THEN RETURN; IF watching NOT IN [allocations..wordsAllocated] THEN RETURN; words _ 1; type _ 0; IF watching = wordsAllocated THEN local _ @frame.local[0]; IF data = NIL AND watching = wordsAllocated THEN words _ local[0]; IF data = LOOPHOLE[$Permanent, LONG POINTER] THEN { type _ 1; IF watching = wordsAllocated THEN words _ local[2]}; IF data = LOOPHOLE[$Unsafe, LONG POINTER] THEN { type _ 2; IF watching = wordsAllocated THEN words _ local[4]}; IF break AND data = LOOPHOLE[breakAtom, LONG POINTER] THEN DebuggerSwap.CallDebugger["Allocation break"]; Record[ psbi: LOOPHOLE[Process.GetCurrent[]], type: type, frame: frame, count: words]; }; UserBreak: PUBLIC FastBreak.FastBreakProc = TRUSTED { count: CARDINAL _ 1; type: SpyOps.StackType _ 1; IF spyState # on THEN RETURN[useOldBreak: FALSE]; IF watching # userDefined THEN RETURN[useOldBreak: FALSE]; IF data # NIL THEN type _ LOOPHOLE[data, LONG POINTER TO SpyOps.StackType]^; Record[LOOPHOLE[Process.GetCurrent[]], frame, type, count]; RETURN[useOldBreak: FALSE]; }; Record: PUBLIC ENTRY PROC[psbi: PsbIndex, frame: FrameHandle _ NIL, type: SpyOps.StackType _ 0, count: CARDINAL _ 1] = { level: Process.Priority; psb: LONG POINTER TO ProcessStateBlock; IF spyState = disabled THEN RETURN; IF active <= 0 THEN RETURN; IF psbi = PrincOps.PsbNull THEN IF PDA.fault[qPageFault].queue.tail = PrincOps.PsbNull AND PDA.fault[qPageFault+4].queue.tail = PrincOps.PsbNull THEN {LogStack[psbi, 0, NIL, count, 0]; RETURN} ELSE {LogStack[psbi, 0, NIL, count, 1]; RETURN}; -- waiting for a page fault psb _ @PDA[PrincOpsUtils.PsbIndexToHandle[psbi]]; level _ psb.link.priority; IF frame = NIL THEN frame _ IF psb.link.vector THEN PDA[psb.context.state].frame ELSE psb.context.frame; LogStack[psbi, level, frame, count, type] }; LogStack: INTERNAL PROC [process: PsbIndex, level: Process.Priority, frame: FrameHandle, count, type: CARDINAL] = { <> sdLength: CARDINAL = PrincOps.SD[PrincOps.sGFTLength]; gfi: GFTIndex _ 0; gf: GlobalFrameHandle _ NIL; length: CARDINAL _ 0; ValidFrame: PROC [f: ControlLink] RETURNS [FrameHandle] = { <> OPEN PrincOps; <> <> IF f.proc OR f.indirect OR f.frame =NIL OR (gf _ f.frame.accesslink) = NIL OR <> (gfi _ gf.gfi) > sdLength OR PrincOps.GFT[gfi].framePtr ~= gf THEN RETURN[NIL]; RETURN[f.frame] }; IF ~SpyLog.active THEN RETURN; stack.type _ type; stack.count _ count; stack.level _ level; stack.process _ process; frame _ ValidFrame[LOOPHOLE[frame] ! VM.AddressFault => {frame _ NIL; CONTINUE}]; THROUGH [0..maxStackDepth) UNTIL frame = NIL DO <> <> IF length >= SpyOps.stackLength THEN {stack.level _ 7; length _ 0; EXIT}; stack.frame[length] _ [0, gfi, frame.pc]; IF length > 0 AND stack.frame[length] = stack.frame[length-1] THEN length _ length - 1; -- skip recursive frames length _ length + 1; frame _ ValidFrame[frame.returnlink ! VM.AddressFault => EXIT]; ENDLOOP; SpyLog.WriteData[@stack, SpyOps.stackHeader + length*SpyOps.Frame.SIZE, SpyOps.Stack.CODE]; }; Initialize[]; <<>> }. <<>> <<22-Jan-82 Maxwell: Removed cross-partition code; converted to Cedar>> <<4-Feb-82 Maxwell: Added stack active option>> <<11-Mar-82 Maxwell: Added page fault recorder>> <> <> <> <> <<>>