<> <> <> <> <> <> <> DIRECTORY AllocatorOps USING [BlockSize, REFToHP], Basics USING [BITAND], BasicTime USING [GetClockPulses, Pulses], DebuggerSwap USING [CallDebugger], FastBreak USING [FastBreakProc], IntervalTimer USING [Wait], IntervalTimerFace USING [exists], PrincOps, PrincOpsUtils USING [DisableInterrupts, EnableInterrupts, LongNotify, LongReEnter, LongWait, PsbIndexToHandle, PsbHandleToIndex, ReadPSB, ReadPTC], Process USING [Detach, GetCurrent, GetPriority, InitializeCondition, Priority, priorityFaultHandlers, priorityRealTime, priorityNormal, SecondsToTicks, SetPriority, TicksToMsec], Rope USING [ROPE], SafeStorage USING [NWordsAllocated, NWordsReclaimed], Loader USING [MakeProcedureResident, MakeGlobalFrameResident], SpyClient USING [DataType, StackType], SpyLog USING [active, Here, InvisibleProcesses, OpenForWrite, WriteData], SpyOps USING [Count, DataType, Frame, SetAllocationBreak, SpyState, Stack, stackHeader, stackLength, StackType], SystemVersion USING [machineType], VM USING [AddressFault, PageNumberForAddress], VMStatistics USING [pageFaults]; SpyKernelImpl: MONITOR IMPORTS AllocatorOps, Basics, BasicTime, DebuggerSwap, IntervalTimer, IntervalTimerFace, Loader, PrincOpsUtils, Process, SafeStorage, SpyLog, SpyOps, SystemVersion, VM, VMStatistics EXPORTS SpyClient, SpyOps = { OPEN PrincOps, Rope; PsbIndex: TYPE = PrincOps.PsbIndex; Ticks: TYPE = PrincOps.Ticks; <> spyState: PUBLIC SpyOps.SpyState _ off; watching: PUBLIC SpyOps.DataType _ CPU; justMe: PUBLIC PsbIndex _ 0; freqDivisor: PUBLIC NAT _ 0; <> runningTime: PUBLIC BasicTime.Pulses; -- total time running pageFaults: PUBLIC CARD; active, starts, stops: PUBLIC INTEGER _ 0; wordsAllocated: PUBLIC CARD; wordsReclaimed: PUBLIC CARD; 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, frequencyDivisor: NAT _ 1] RETURNS [errorMsg: ROPE] = { DisableSpy[]; IF dataType IN [allocations..wordsAllocated] THEN errorMsg _ SpyOps.SetAllocationBreak[]; IF errorMsg # NIL THEN RETURN [errorMsg]; IF frequencyDivisor < 1 THEN RETURN ["0 is a stupid frequency divisor"]; InitializeTables[]; watching _ dataType; justMe _ IF dataType = process THEN process ELSE 0; freqDivisor _ frequencyDivisor; allocWait _ allocDivisorMinusOne _ freqDivisor - 1; SpyLog.OpenForWrite[spyOnSpyLog]; EnableSpy[]; }; InitializeTables: PROC = { runningTime _ 0; code _ data _ 0; wordsAllocated _ wordsReclaimed _ 0; }; Initialize: PROC = { 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: BOOL; frame: PrincOps.FrameHandle; IF IntervalTimerFace.exists THEN RETURN; IF index = maxIndex THEN RETURN; cutoff _ Process.SecondsToTicks[10]; <> ticks _ PrincOpsUtils.ReadPTC[]; FOR psbi: PsbIndex IN [PrincOps.StartPsb..PrincOps.StartPsb+PrincOps.PDA.count) DO sample: CARDINAL _ PrincOps.PDA.block[psbi].mds; IF sample = 0 THEN LOOP; IF sample > cutoff + ticks 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: CARD; startWordsAllocated: CARD; startWordsReclaimed: CARD; StartCounting: PUBLIC ENTRY FastBreak.FastBreakProc = { priority: Process.Priority; starts _ starts + 1; WHILE active = 0 AND 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; baseWait: REAL _ SELECT SystemVersion.machineType FROM dorado => Process.TicksToMsec[1]*1.3e-4, ENDCASE => (Process.TicksToMsec[1]-1)*2.0e-3; priorityLimit: Process.Priority _ Process.priorityFaultHandlers; monitor: BOOL _ FALSE; -- measure performance of spy maxStackDepth: CARDINAL = 200; timeout: ARRAY [0..maxIndex) OF RECORD [gfh: PrincOps.GlobalFrameHandle, pc: CARDINAL]; maxIndex: CARDINAL = 25; index: CARDINAL _ 0; sampleInterval: INT _ 10*1000; -- measured in microseconds searchReadyList: BOOL _ TRUE; <> Spy: PROC = { top: PsbIndex; frame: FrameHandle; myPrincOps: PsbHandle = PrincOpsUtils.ReadPSB[]; handleMask: PsbLink = [ failed: FALSE, priority: 0, next: LAST[PsbIndex], reserved: 0, vector: FALSE]; waitSeconds: REAL = freqDivisor * baseWait; NextHandle: PROC [link: CARDINAL] RETURNS [PsbHandle] = INLINE { RETURN [LOOPHOLE[Basics.BITAND[link, LOOPHOLE[handleMask]]]]; }; spyState _ on; <
> DO IntervalTimer.Wait[waitSeconds]; IF active <= 0 THEN EXIT; IF spyState = disabled THEN LOOP; IF monitor THEN SpyLog.Here[]; IF justMe = 0 OR searchReadyList THEN { <> top _ PrincOps.PsbNull; PrincOpsUtils.DisableInterrupts[]; { once: BOOL _ FALSE; tailOfReadyList: PsbHandle _ NextHandle[LOOPHOLE[PDA.ready]]; headOfReadyList: PsbHandle _ NextHandle[LOOPHOLE[PDA[tailOfReadyList].link]]; <> FOR current: PsbHandle _ 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 OR level > priorityLimit THEN LOOP; <> IF current = myPrincOps THEN LOOP; IF ~link.vector AND PDA.state[level] = NullStateVectorHandle THEN LOOP; <> 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, it will run first. All of the other timeouts will appear on the ready list. Most likely, they will just check some condition and then go back to sleep. This will mask the more interesting processes.)>> FOR i: CARDINAL IN [0..maxIndex) DO IF timeout[i] = [NIL, 0] THEN EXIT; IF timeout[i] = [frame.accesslink, frame.pc] THEN GO TO skip; ENDLOOP; <> top _ PrincOpsUtils.PsbHandleToIndex[current]; EXIT; EXITS skip => {}; }; ENDLOOP; }; PrincOpsUtils.EnableInterrupts[]; }; SELECT TRUE FROM justMe = 0 => Record[top, frame, ready]; justMePrincOps.link.failed => Record[justMe, NIL, waitingML]; -- waiting ML justMePrincOps.flags.waiting => Record[justMe, NIL, waitingCV]; -- waiting CV OnQueue[justMe, @PDA.fault[PrincOps.qPageFault].queue] => Record[justMe, NIL, pageFault]; -- waiting pagefault OnQueue[justMe, @PDA.fault[PrincOps.qPageFault+4].queue] => Record[justMe, NIL, pageFault]; -- waiting pagefault OnQueue[justMe, @PrincOps.PDA.ready] => SELECT TRUE FROM PDA.state[justMePrincOps.link.priority] = NullStateVectorHandle AND ~justMePrincOps.link.vector => <> Record[justMe, NIL, waitingSV]; searchReadyList AND justMe # top => <> Record[justMe, NIL, preempted]; ENDCASE => Record[justMe, NIL, ready]; -- ready ENDCASE => Record[justMe, NIL, unknown]; -- 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] 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: BOOL _ 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: SpyClient.StackType; 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; shouldNotifyKernel: BOOL _ FALSE; <> spyState _ on; DO <> PrincOpsUtils.LongWait[@recorderLock, pPageFaultCONDITION, 1]; UNTIL PrincOpsUtils.LongReEnter[@recorderLock, pPageFaultCONDITION] DO ENDLOOP; IF pda.fault[qPageFault].queue.tail = PrincOps.PsbNull THEN { <> IF shouldNotifyKernel AND pda.fault[qPageFault].condition.tail ~= PrincOps.PsbNull THEN { LongNakedNotify[pPageFaultCONDITION]; shouldNotifyKernel _ FALSE; }; IF active <= 0 THEN EXIT; LOOP; -- go back and wait again. }; <
> fault.process _ pda.block[pda.fault[qPageFault].queue.tail].link.next; <> 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 _ VM.PageNumberForAddress[frame.accesslink.code.longbase+frame.pc/2]; SELECT TRUE FROM ABS[codePage - fault.page] <= 1 => { code _ code + 1; type _ pageFaultCode; }; Xfer[frame.accesslink.code.longbase, frame.pc] => { code _ code + 1; type _ pageFaultXfer; }; ENDCASE => { data _ data + 1; type _ pageFaultData; }; <> PrincOpsUtils.DisableInterrupts[]; IF pPageFaultCondition^.tail = PrincOps.PsbNull THEN shouldNotifyKernel _ TRUE -- 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 [BOOL] = 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: BOOL _ FALSE; breakAtom: ATOM _ NIL; allocDivisorMinusOne: CARDINAL _ 0; allocWait: CARDINAL _ 0; AllocationBreak: PUBLIC FastBreak.FastBreakProc = { <> words: CARDINAL _ 1; type: SpyOps.StackType _ allocSafe; GetWordsFromStack: PROC = INLINE { blockSize: INT = AllocatorOps.BlockSize[AllocatorOps.REFToHP[LOOPHOLE[@sv.stk[0], POINTER TO REF ANY]^]]; words _ MAX[MIN[blockSize, CARDINAL.LAST], 0]; }; IF active = 0 THEN RETURN; IF watching NOT IN [allocations..wordsAllocated] THEN RETURN; IF allocWait > 0 THEN {allocWait _ allocWait - 1; RETURN}; allocWait _ allocDivisorMinusOne; SELECT data FROM NIL => IF watching = wordsAllocated THEN GetWordsFromStack[]; <> LOOPHOLE[$Permanent, LONG POINTER] => { type _ allocPerm; IF watching = wordsAllocated THEN GetWordsFromStack[]; }; LOOPHOLE[$Unsafe, LONG POINTER] => { type _ allocUnsafe; IF watching = wordsAllocated THEN GetWordsFromStack[]; }; ENDCASE => NULL; IF break AND data = LOOPHOLE[breakAtom, LONG POINTER] THEN DebuggerSwap.CallDebugger["Allocation break"L]; Record[psbi: LOOPHOLE[Process.GetCurrent[]], type: type, frame: frame, count: words]; }; UserBreak: PUBLIC FastBreak.FastBreakProc = TRUSTED { count: CARDINAL _ 1; type: SpyOps.StackType _ userBreak; 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 PROC [psbi: PsbIndex, frame: FrameHandle, type: SpyOps.StackType, count: CARDINAL _ 1] = { recordInner: ENTRY PROC = { level: Process.Priority; psb: LONG POINTER TO ProcessStateBlock; IF spyState = disabled OR 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, ready] ELSE LogStack[psbi, 0, NIL, count, pageFault]; -- waiting for a page fault RETURN; }; 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]; }; writeProcess: PROCESS; extendProcess: PROCESS; [writeProcess, extendProcess] _ SpyLog.InvisibleProcesses[]; IF (writeProcess # NIL AND LOOPHOLE[writeProcess, PsbIndex] = psbi) OR (extendProcess # NIL AND LOOPHOLE[extendProcess, PsbIndex] = psbi) THEN RETURN; recordInner[]; }; LogStack: INTERNAL PROC [process: PsbIndex, level: Process.Priority, frame: FrameHandle, count: CARDINAL, type: SpyClient.StackType] = { <> sdLength: CARDINAL = PrincOps.SD[PrincOps.sGFTLength]; 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 (LOOPHOLE[gf, CARDINAL] MOD 4) # 0 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] _ [gf, 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>> <> <> <> <> <> <> <> <> <> <<>>