-- CheckFrames.mesa -- Last edited by Daniels on 10-Sep-82 15:48:48 -- Last edited by Sweet on 8-Jan-83 19:39:58 DIRECTORY BcdDefs USING [Base, FTSelf, MTIndex, VersionID], BcdOps USING [BcdBase, MTHandle, SGHandle], CatchFormat USING [CatchEV, CatchEVHandle, defaultFsi, nullCatchEV], Environment USING [Byte, bytesPerPage, PageCount, wordsPerPage], Exec USING [ AddCommand, CheckForAbort, EndOfCommandLine, ExecProc, FreeTokenString, GetNameandPassword, GetToken, Handle, OutputProc], FileTransfer USING [ ClientProc, Close, Connection, Create, Destroy, Error, FileInfo, GetStreamInfo, MessageProc, ReadNextStream, ReadStream, ResetVFN, SetPrimaryCredentials, SetProcs, VirtualFilename, VirtualFilenameObject], Format USING [ Char, CR, Decimal, LongDecimal, Number, NumberFormat, StringProc, SubString], Heap USING [systemZone], Mopcodes USING [zJ2], PrincOps USING [FrameSizeIndex, FrameVec, FSIndex, MaxFrameSize, PrefixHandle], Profile USING [userName, userPassword], Space USING [ Create, CreateUniformSwapUnits, Delete, Handle, LongPointer, Map, nullHandle, virtualMemory], Storage USING [CopyString], Stream USING [EndOfStream, GetBlock, Handle], String USING [InvalidNumber, StringToDecimal, SubString, SubStringDescriptor], Symbols, SymbolSegment USING [STHeader, VersionID]; CheckFrames: PROGRAM IMPORTS Exec, Heap, FileTransfer, Format, Profile, Space, Storage, Stream, String = BEGIN OPEN Symbols; z: UNCOUNTED ZONE ← Heap.systemZone; BYTE: TYPE = Environment.Byte; FrameSizeIndex: TYPE = PrincOps.FrameSizeIndex; exec: Exec.Handle ← NIL; conn: FileTransfer.Connection ← NIL; vfn: FileTransfer.VirtualFilename ← @vfnObject; vfnObject: FileTransfer.VirtualFilenameObject; buffer: LONG POINTER ← NIL; bufferPages: Environment.PageCount ← 100; bufferSpace: Space.Handle ← Space.nullHandle; last: CARDINAL = bufferPages*Environment.bytesPerPage; localFsi: FrameSizeIndex ← LAST[FrameSizeIndex]; globalSize: [0..PrincOps.MaxFrameSize] ← PrincOps.MaxFrameSize; bcd: BcdOps.BcdBase ← NIL; mtb, sgb: BcdDefs.Base ← NIL; mth: BcdOps.MTHandle ← NIL; sgh: BcdOps.SGHandle ← NIL; symHeader: LONG POINTER TO SymbolSegment.STHeader ← NIL; ht: LONG POINTER TO ARRAY HTIndex OF HTRecord ← NIL; ssb: LONG STRING ← NIL; seb: Symbols.Base ← NIL; ctxb: Symbols.Base ← NIL; bb: Symbols.Base ← NIL; bbSize: CARDINAL ← 0; bti: Symbols.BTIndex; codebase: PrincOps.PrefixHandle; catchEV: CatchFormat.CatchEV; catchEntry: CatchFormat.CatchEVHandle ← NIL; FSSequence: TYPE = RECORD [ firstCatch: CARDINAL, seq: SEQUENCE max: CARDINAL OF PrincOps.FSIndex]; frameSizes: LONG POINTER TO FSSequence ← NIL; countProblems: CARDINAL ← 0; totalProblems, totalFiles, totalBad: LONG CARDINAL ← 0; showLocals: BOOLEAN ← FALSE; -- Exec window output execProc: Format.StringProc; PutCR: PROC = {Format.CR[execProc]}; PutString: PROC [s: LONG STRING] = {execProc[s]}; PutSubString: PROC [ss: String.SubString] = {Format.SubString[execProc, ss]}; PutLine: PROC [s: LONG STRING] = {execProc[s]; Format.CR[execProc]}; PutChar: PROC [c: CHARACTER] = {Format.Char[execProc, c]}; PutDecimal: PROC [n: INTEGER] = {Format.Decimal[execProc, n]}; PutLongDecimal: PROC [n: LONG INTEGER] = {Format.LongDecimal[execProc, n]}; PutNumber: PROC [n: INTEGER, f: Format.NumberFormat] = { Format.Number[execProc, n, f]}; LocalDatum: TYPE = RECORD[ offset, length, nesting: CARDINAL, hti: Symbols.HTIndex]; LocalDataSeq: TYPE = RECORD [length: CARDINAL, data: SEQUENCE maxLength: CARDINAL OF LocalDatum]; LocalData: TYPE = LONG POINTER TO LocalDataSeq; localData: LocalData ← NIL; -- a few procs stolen from SymbolPack FirstCtxSe: PROC [ctx: Symbols.CTXIndex] RETURNS [Symbols.ISEIndex] = { RETURN [IF ctx = Symbols.CTXNull THEN Symbols.ISENull ELSE ctxb[ctx].seList]}; NextSe: PROC [sei: Symbols.ISEIndex] RETURNS [Symbols.ISEIndex] = { OPEN Symbols; RETURN [ IF sei = SENull THEN ISENull ELSE WITH id: seb[sei] SELECT FROM terminal => ISENull, sequential => sei + SIZE[sequential id SERecord], linked => id.link, ENDCASE => ISENull]}; ArgRecord: PROC [type: CSEIndex] RETURNS [RecordSEIndex] = { RETURN [IF type = SENull THEN RecordSENull ELSE WITH seb[type] SELECT FROM record => LOOPHOLE[type, RecordSEIndex], ENDCASE => RecordSENull]}; TransferTypes: PROC [type: SEIndex] RETURNS [typeIn, typeOut: RecordSEIndex] = { sei: CSEIndex = UnderType[type]; WITH t: seb[sei] SELECT FROM transfer => RETURN [typeIn: t.inRecord, typeOut: t.outRecord]; ENDCASE; RETURN [RecordSENull, RecordSENull]}; UnderType: PROC [type: SEIndex] RETURNS [CSEIndex] = { sei: SEIndex ← type; WHILE sei # SENull DO WITH se: seb[sei] SELECT FROM id => {IF se.idType # typeTYPE THEN ERROR; sei ← se.idInfo}; ENDCASE => EXIT; ENDLOOP; RETURN [LOOPHOLE[sei, CSEIndex]]}; -- end of SymbolPack stuff PutLocals: PROC [root: Symbols.CBTIndex] = { OPEN Symbols; in, out: RecordSEIndex; nesting: CARDINAL ← 0; AddLocal: PROC [d: LocalDatum] = { j: CARDINAL; IF localData.length = localData.maxLength THEN { new: LocalData ← z.NEW[LocalDataSeq[localData.length + 30]]; FOR i: CARDINAL IN [0..localData.length) DO new[i] ← localData[i]; ENDLOOP; new.length ← localData.length; z.FREE[@localData]; localData ← new}; FOR j ← localData.length, j-1 WHILE j > 0 DO IF localData[j-1].offset <= d.offset THEN EXIT; localData[j] ← localData[j-1]; ENDLOOP; localData[j] ← d; localData.length ← localData.length + 1}; AddContext: PROC [ctx: Symbols.CTXIndex, nesting: CARDINAL ← 0] = { FOR sei: Symbols.ISEIndex ← FirstCtxSe[ctx], NextSe[sei] UNTIL sei = Symbols.ISENull DO IF ~seb[sei].constant THEN AddLocal[[ offset: seb[sei].idValue/16, length: seb[sei].idInfo/16, nesting: nesting, hti: seb[sei].hash]]; ENDLOOP}; EnumerateBodies: PROC [bti: BTIndex, nesting: CARDINAL ← 0] = { DO WITH b: bb[bti] SELECT FROM Callable => WITH bi: b SELECT FROM Inner => AddLocal[[ offset: bi.frameOffset, length: 1, nesting: nesting, hti: seb[bi.id].hash]]; ENDCASE; Other => { AddContext[b.localCtx, nesting]; IF b.firstSon # BTNull THEN EnumerateBodies[b.firstSon, nesting+1]}; ENDCASE; IF bb[bti].link.which = parent THEN EXIT; bti ← bb[bti].link.index; ENDLOOP}; localData.length ← 0; [in, out] ← TransferTypes[bb[root].ioType]; IF in # SENull THEN AddContext[seb[in].fieldCtx]; IF out # SENull THEN AddContext[seb[out].fieldCtx]; IF bb[root].localCtx # CTXNull THEN AddContext[bb[root].localCtx]; IF bb[root].firstSon # BTNull THEN EnumerateBodies[bb[root].firstSon]; FOR i: CARDINAL IN [0..localData.length) DO d: LocalDatum = localData[i]; PutNumber[d.offset, Octal4]; PutNumber[d.length, Octal6]; THROUGH [0..d.nesting] DO PutChar[' ] ENDLOOP; PutHash[d.hti]; PutCR[]; ENDLOOP; }; Octal4: Format.NumberFormat = [ base: 8, unsigned: TRUE, zerofill: FALSE, columns: 4]; Octal6: Format.NumberFormat = [ base: 8, unsigned: TRUE, zerofill: FALSE, columns: 6]; PutHash: PROC [hti: Symbols.HTIndex] = { ss: String.SubStringDescriptor; SubStringForHash[@ss, hti]; PutSubString[@ss]}; CheckFrames: Exec.ExecProc = { ENABLE { ABORTED => GO TO aborted; FileTransfer.Error --[code]-- => SELECT code FROM login => {LoginUser[clientData: NIL]; RETRY}; retry => GOTO timedOut; unknown => GOTO fileTransferProblem; ENDCASE; UNWIND => Finalize[]}; exec ← h; execProc ← exec.OutputProc[]; Initialize[]; OpenConnection[]; Check[]; Finalize[]; outcome ← normal; EXITS aborted => {outcome ← abort; Finalize[]; PutCR[]; PutLine["...aborted"L]}; timedOut => { outcome ← error; Finalize[]; PutCR[]; PutLine["...connection timed out!"L]}; fileTransferProblem => { outcome ← error; Finalize[]; PutCR[]; PutLine["...unknown FileTransfer problem!"L]}}; Initialize: PROC = { PutHeading[]; localFsi ← LAST[FrameSizeIndex]; globalSize ← PrincOps.MaxFrameSize; conn ← NIL; vfnObject ← FileTransfer.VirtualFilenameObject[ host: NIL, directory: NIL, name: NIL, version: NIL]; vfn ← @vfnObject; bufferSpace ← Space.Create[size: bufferPages, parent: Space.virtualMemory]; totalFiles ← totalProblems ← totalBad ← 0; Space.Map[bufferSpace]; Space.CreateUniformSwapUnits[parent: bufferSpace, size: 4]; buffer ← Space.LongPointer[bufferSpace]; localData ← z.NEW[LocalDataSeq[100]]}; Finalize: PROC = { IF bufferSpace # Space.nullHandle THEN { Space.Delete[bufferSpace]; bufferSpace ← Space.nullHandle; buffer ← NIL}; IF localData # NIL THEN z.FREE[@localData]; IF frameSizes # NIL THEN Heap.systemZone.FREE[@frameSizes]; FileTransfer.ResetVFN[vfn: vfn, h: TRUE, d: TRUE, n: TRUE, v: TRUE]; CloseConnection[]}; PutHeading: PROC = {PutCR[]; PutLine["Frame Size Checker"L]; PutCR[]}; OpenConnection: PROC = { conn ← FileTransfer.Create[]; conn.SetProcs[clientData: NIL, messages: PutMessages, login: LoginUser]; conn.SetPrimaryCredentials[ user: Profile.userName, password: Profile.userPassword]; }; PutMessages: FileTransfer.MessageProc = { IF level = fatal THEN { PutString["Fatal error: "L]; IF s1 # NIL THEN PutString[s1]; IF s2 # NIL THEN PutString[s2]; IF s3 # NIL THEN PutString[s3]; IF s4 # NIL THEN PutString[s4]}}; LoginUser: FileTransfer.ClientProc --[clientData: LONG POINTER]-- = { user: STRING = [40]; password: STRING = [40]; exec.GetNameandPassword[user, password]; conn.SetPrimaryCredentials[user: user, password: password]}; CloseConnection: PROC = { IF conn # NIL THEN {conn.Close[]; conn.Destroy[]; conn ← NIL}}; verbose: BOOLEAN ← FALSE; Check: PROC = { ENABLE FileTransfer.Error => IF code = skip THEN CONTINUE; stream: Stream.Handle ← NIL; token, switches: LONG STRING; FileTransfer.ResetVFN[vfn: vfn, h: TRUE, d: TRUE, n: TRUE, v: FALSE]; [token: token, switches: switches] ← exec.GetToken[]; vfn.host ← Storage.CopyString[token]; [] ← Exec.FreeTokenString[token]; [] ← Exec.FreeTokenString[switches]; [token: token, switches: switches] ← exec.GetToken[]; vfn.directory ← Storage.CopyString[token]; verbose ← FALSE; IF switches # NIL AND switches.length # 0 THEN FOR n: CARDINAL IN [0..switches.length) DO SELECT switches[n] FROM 'v, 'V => verbose ← TRUE; 'a, 'A => showLocals ← TRUE; ENDCASE; ENDLOOP; [] ← Exec.FreeTokenString[token]; [] ← Exec.FreeTokenString[switches]; vfn.name ← Storage.CopyString["*.bcd"L]; UNTIL exec.EndOfCommandLine[] DO valid: BOOLEAN ← TRUE; num: CARDINAL; [token, switches] ← exec.GetToken[]; IF token # NIL THEN num ← String.StringToDecimal[ token ! String.InvalidNumber => {valid ← FALSE; CONTINUE}]; IF valid AND switches # NIL AND switches.length # 0 THEN SELECT switches[0] FROM 'l, 'L => localFsi ← GetFsi[num]; 'g, 'G => globalSize ← num; ENDCASE; [] ← Exec.FreeTokenString[token]; [] ← Exec.FreeTokenString[switches]; ENDLOOP; PutString["Checking for local frames >= "L]; PutDecimal[PrincOps.FrameVec[localFsi]]; PutString[", global frames >= "L]; PutDecimal[globalSize]; PutLine["..."L]; stream ← conn.ReadStream[vfn, NIL, FALSE, remote]; WHILE stream # NIL DO stream.options.signalEndOfStream ← TRUE; CheckFile[stream]; stream ← FileTransfer.ReadNextStream[stream] ENDLOOP; FileTransfer.ResetVFN[vfn: vfn, h: TRUE, d: TRUE, n: TRUE, v: TRUE]; PutCR[]; PutCR[]; PutLongDecimal[totalBad]; PutString[" files out of "L]; PutLongDecimal[totalFiles]; PutString[" had "L]; PutLongDecimal[totalProblems]; PutLine[" problems"L]}; CheckFile: PROC [stream: Stream.Handle] = { source: FileTransfer.FileInfo; tooLong: BOOLEAN ← TRUE; IF exec.CheckForAbort[] THEN ERROR ABORTED; source ← FileTransfer.GetStreamInfo[stream]; IF (((totalFiles ← totalFiles + 1) MOD 10) = 0) OR verbose THEN { IF ~verbose THEN { PutString[" Checking file "L]; PutLongDecimal[totalFiles]; PutString[": "L]}; PutLine[source.body]}; [] ← stream.GetBlock[ [buffer, 0, last] ! Stream.EndOfStream => {tooLong ← FALSE; CONTINUE}]; IF ~tooLong THEN { bcd ← LOOPHOLE[buffer, BcdOps.BcdBase]; IF bcd.versionIdent # BcdDefs.VersionID THEN GOTO obsoleteBcd; IF bcd.nConfigs # 0 THEN GOTO binderBcd; IF bcd.nPages > bufferPages THEN GOTO tooLong; mtb ← LOOPHOLE[bcd + bcd.mtOffset]; mth ← @mtb[FIRST[BcdDefs.MTIndex]]; sgb ← LOOPHOLE[bcd + bcd.sgOffset]; sgh ← @sgb[mth.code.sgi]; -- Bcd's code segment table entry IF sgh.pages > bufferPages THEN GOTO tooLong; IF mth.tableCompiled OR sgh.file # BcdDefs.FTSelf THEN GOTO punt; -- tablecompiled, or ... codebase ← LOOPHOLE[buffer + (sgh.base - 1)*Environment.wordsPerPage]; codebase ← codebase + mth.code.offset; catchEV ← LOOPHOLE[codebase.entry[codebase.header.nEntries]/2]; catchEntry ← @codebase[catchEV]; sgh ← @sgb[mth.sseg]; -- Bcd's symbol segment table entry IF sgh.base + sgh.pages > bufferPages THEN GOTO tooLong; IF sgh.file # BcdDefs.FTSelf THEN GOTO punt; -- tablecompiled, or ... symHeader ← LOOPHOLE[buffer + (sgh.base - 1)*Environment.wordsPerPage]; IF symHeader.versionIdent # SymbolSegment.VersionID THEN GOTO badSymbols; bb ← LOOPHOLE[symHeader + symHeader.bodyBlock.offset]; IF (bbSize ← symHeader.bodyBlock.size) = 0 THEN GOTO punt; ht ← LOOPHOLE[symHeader + symHeader.htBlock.offset]; ssb ← LOOPHOLE[symHeader + symHeader.ssBlock.offset]; seb ← LOOPHOLE[symHeader + symHeader.seBlock.offset]; ctxb ← LOOPHOLE[symHeader + symHeader.ctxBlock.offset]; IF symHeader.seBlock.size = 0 THEN GOTO punt; GetFrameSizes[]; bti ← FIRST[Symbols.BTIndex]; countProblems ← 0; IF mth.framesize >= globalSize THEN { IF verbose THEN PutString[" "L] ELSE {PutString[source.body]; PutString[", "L]}; PutString["gf size = "L]; PutDecimal[mth.framesize]; PutCR[]; countProblems ← 1}; UNTIL LOOPHOLE[bti, CARDINAL] >= bbSize DO ok: BOOLEAN; ss: String.SubStringDescriptor; WITH b: bb[bti] SELECT FROM Callable => { WITH bi: b.info SELECT FROM External => ok ← ~b.inline; ENDCASE => ok ← FALSE; IF ok THEN WITH b SELECT FROM Outer, Inner => IF frameSizes[b.entryIndex] > localFsi THEN { IF countProblems = 0 AND ~verbose THEN PutLine[source.body]; countProblems ← countProblems + 1; IF b.entryIndex = 0 THEN ss ← ["MAIN"L, 0, ("MAIN"L).length] ELSE SubStringForHash[@ss, seb[b.id].hash]; PutString[" "]; PutSubString[@ss]; PutString[", frame size = "]; PutDecimal[PrincOps.FrameVec[frameSizes[b.entryIndex]]]; PutCR[]; IF showLocals THEN PutLocals[LOOPHOLE[bti]]}; Catch => { IF catchEV = CatchFormat.nullCatchEV THEN GOTO punt; IF frameSizes[index + frameSizes.firstCatch] > localFsi THEN { IF countProblems = 0 AND ~verbose THEN PutLine[source.body]; countProblems ← countProblems + 1; PutString[" CATCH["L]; PutDecimal[index]; PutString["], frame size = "L]; PutDecimal[ PrincOps.FrameVec[ frameSizes[index + frameSizes.firstCatch]]]; PutCR[]}}; ENDCASE; WITH b SELECT FROM Outer => bti ← bti + SIZE[Outer Callable BodyRecord]; Inner => bti ← bti + SIZE[Inner Callable BodyRecord]; Catch => bti ← bti + SIZE[Catch Callable BodyRecord]; ENDCASE}; Other => bti ← bti + SIZE[Other BodyRecord]; ENDCASE; ENDLOOP; IF frameSizes # NIL THEN Heap.systemZone.FREE[@frameSizes]; IF countProblems # 0 THEN { totalProblems ← totalProblems + countProblems; totalBad ← totalBad + 1}}; EXITS tooLong => {}; obsoleteBcd => {}; binderBcd => {}; punt => {}; badSymbols => {}}; GetFsi: PROC [frameSize: CARDINAL] RETURNS [FrameSizeIndex] = { FOR fsi: FrameSizeIndex DECREASING IN FrameSizeIndex DO IF frameSize >= PrincOps.FrameVec[fsi] THEN RETURN[fsi]; REPEAT FINISHED => RETURN[0] ENDLOOP}; GetFrameSizes: PROC = { nEntries: CARDINAL = codebase.header.nEntries; code: LONG POINTER TO PACKED ARRAY [0..0) OF BYTE = LOOPHOLE[codebase]; frameSizes ← Heap.systemZone.NEW[ FSSequence [nEntries + catchEntry.count] ← [ firstCatch: nEntries, seq: NULL]]; FOR i: CARDINAL IN [0..nEntries) DO frameSizes[i] ← code[codebase.entry[i].pc] ENDLOOP; IF catchEV # CatchFormat.nullCatchEV THEN FOR i: CARDINAL IN [0..catchEntry.count) DO frameSizes[i + frameSizes.firstCatch] ← IF code[catchEntry[i]] = Mopcodes.zJ2 THEN code[catchEntry[i] + 1] ELSE CatchFormat.defaultFsi; ENDLOOP; }; SubStringForHash: PROC [s: String.SubString, hti: HTIndex] = { s.base ← ssb; IF hti = HTNull THEN s.offset ← s.length ← 0 ELSE s.length ← ht[hti].ssIndex - (s.offset ← ht[hti - 1].ssIndex)}; -- User niceness CheckHelp: Exec.ExecProc = { h.OutputProc[][ "This command takes all bcds on a remote directory and looks for procedures with a local frame size greater than some given size. It can also do the same for global frames. The command line format is CheckFrames.~ host dir localSize/l globalSize/g"L]}; -- MAIN BODY CODE RegisterSelf: PROC = { Exec.AddCommand[name: "CheckFrames.~"L, proc: CheckFrames, help: CheckHelp]}; RegisterSelf[]; END.