DIRECTORY Basics USING [BITAND, BITSHIFT, Card16FromH, Card32FromF, Comparison, FWORD, HWORD], BasicTime USING [GMT, Now], CCTypes USING[CCError, CCErrorCase], CirioTypes USING [zeroBA], Commander USING [Register, CommandProc], CommanderOps USING [ArgumentVector, Parse], Convert USING [IntFromRope], IO, List USING[LORA, CompareProc, Sort], ObjectFiles USING[BracketPairKind, CNameOfStab, CreateParsed, DescribeModule, FileSegmentPC, GlobalVarLoc, MakeUnknownVarLoc, ReadInstruction, RopeForStabType, SimpleSeg, Stab, StabBody, StabType, UnreadableObjectFile, unspecdBitSize, VarLoc, VarLocBody, VersionStampInfo], ObjectFilesPrivate USING[AlterFunStabType, BracketConsumer, BracketPair, BracketPairBody, CheckStaticVar, FnConsumer, FunStabSetBody, GetSPOffsetType, GetTypeRefProcType, HeaderBody, InstallStaticVarsType, LineNumToPCMapBody, MemorySegmentInfo, ModuleBody, ModuleFromParsedAndPCProcType, ObjectFileFlavorBody, Parsed, ParsedBody, PCtoLineNumMapBody, ReadHeaderProcType, ReadInitialDataAsRope, ReadStab, RegisterObjectFileFlavor, SLineData, StabList, StabRange, StabSet, TranslationTable, TranslationTableBody, VarLocFromStabProcType], PFS USING [PathFromRope, StreamFromOpenFile, OpenFileFromStream], PFSNames USING [PATH], Random USING[ChooseInt, Create, RandomStream], Rope, SunADotOut, SystemInterface USING[CirioFile, CloseFileSet, CreateFileSet, FileSet, GetCirioFile, GetStreamForFile, ReleaseStreamForFile, ShowReport]; SunADotOutFiles: CEDAR MONITOR IMPORTS Basics, BasicTime, CCTypes, Commander, CommanderOps, Convert, IO, List, ObjectFiles, ObjectFilesPrivate, PFS, Random, Rope, SystemInterface EXPORTS ObjectFiles, SunADotOut = BEGIN CCError: ERROR[case: CCTypes.CCErrorCase, msg: Rope.ROPE ¬ NIL] ¬ CCTypes.CCError; PATH: TYPE ~ PFSNames.PATH; Stab: TYPE = ObjectFiles.Stab; StabList: TYPE = ObjectFilesPrivate.StabList; StabBody: TYPE = ObjectFiles.StabBody; StabType: TYPE = ObjectFiles.StabType; StabRange: TYPE = ObjectFilesPrivate.StabRange; FileSegmentPC: TYPE = ObjectFiles.FileSegmentPC; VersionStampInfo: TYPE = ObjectFiles.VersionStampInfo; LBrac: BYTE = 0c0H; RBrac: BYTE = 0e0H; SLine: BYTE = 044H; Fun: BYTE = 024H; PSym: BYTE = 0a0H; LSym: BYTE = 080H; RSym: BYTE = 040H; STSym: BYTE = 026H; LCSym: BYTE = 028H; GSym: BYTE = 020H; Main: BYTE = 02aH; SO: BYTE = 064H; BIncl: BYTE = 082H; EIncl: BYTE = 0a2H; Excl: BYTE = 0c2H; SOL: BYTE = 084H; Parsed: TYPE = REF ParsedBody; ParsedBody: PUBLIC TYPE = ObjectFilesPrivate.ParsedBody; Module: TYPE = REF ModuleBody; ModuleBody: PUBLIC TYPE = ObjectFilesPrivate.ModuleBody; Header: TYPE = REF HeaderBody; HeaderBody: TYPE = ObjectFilesPrivate.HeaderBody; MemorySegmentInfo: TYPE ~ ObjectFilesPrivate.MemorySegmentInfo; StabSet: TYPE = ObjectFilesPrivate.StabSet; BracketPair: TYPE ~ ObjectFilesPrivate.BracketPair; BracketPairBody: TYPE ~ ObjectFilesPrivate.BracketPairBody; unspecdBitSize: CARD ~ ObjectFiles.unspecdBitSize; MOldsun2: CARD = 0; M68010: CARD = 1; M68020: CARD = 2; MSparc: CARD = 3; OMagic: CARD16 = 0407B; NMagic: CARD16 = 0410B; ZMagic: CARD16 = 0413B; PageSize: CARD32 = 02000H; OldPageSize: CARD32 = 00800H; WireHeader: TYPE = REF WireHeaderBody; WireHeaderBody: TYPE = MACHINE DEPENDENT RECORD[ dynamic: BYTE[0..0], toolversion: BYTE[0..127], machtype: BYTE, magic: Basics.HWORD, textSize: Basics.FWORD, iDataSize: Basics.FWORD, bssSize: Basics.FWORD, symsSize: Basics.FWORD, entryPoint: Basics.FWORD, trSize: Basics.FWORD, drSize: Basics.FWORD]; WireSTEntry: TYPE = REF WireSTEntryBody; WireSTEntryBody: TYPE = MACHINE DEPENDENT RECORD[ stringX(0: 0..31): Basics.FWORD, type(0: 32..39): BYTE, other(0: 40..47): BYTE, desc(0: 48..63): Basics.HWORD, value(0: 64..95): Basics.FWORD]; ReadHeader: ObjectFilesPrivate.ReadHeaderProcType ~ TRUSTED { wHeader: WireHeader ¬ NEW[WireHeaderBody]; nBytes: INT ¬ IO.UnsafeGetBlock[stream, [LOOPHOLE[@wHeader­], 0, BYTES[WireHeaderBody]]]; magic: CARD ¬ Basics.Card16FromH[wHeader.magic]; nBadMagic: BOOLEAN ¬ (magic # OMagic) AND (magic # NMagic)AND (magic # ZMagic); nPageSize: CARD ¬ IF wHeader.machtype = MOldsun2 THEN OldPageSize ELSE PageSize; textOffset: CARD ¬ IF wHeader.machtype = MOldsun2 THEN (IF magic = ZMagic THEN nPageSize ELSE BYTES[WireHeaderBody]) ELSE (IF magic = ZMagic THEN 0 ELSE BYTES[WireHeaderBody]); textSize: CARD ¬ Basics.Card32FromF[wHeader.textSize]; iDataSeg: MemorySegmentInfo ¬ [textOffset + textSize, Basics.Card32FromF[wHeader.iDataSize]]; textRelocSeg: MemorySegmentInfo ¬ [iDataSeg.byteOffset+iDataSeg.byteLength, Basics.Card32FromF[wHeader.trSize]]; dataRelocSeg: MemorySegmentInfo ¬ [textRelocSeg.byteOffset+textRelocSeg.byteLength, Basics.Card32FromF[wHeader.drSize]]; symsSeg: MemorySegmentInfo ¬ [dataRelocSeg.byteOffset+dataRelocSeg.byteLength, Basics.Card32FromF[wHeader.symsSize]]; textLoadOffset: CARD32 ¬ IF magic = ZMagic THEN 02000H ELSE 0; header: Header ¬ NEW[HeaderBody ¬ [ dynamic: wHeader.dynamic # 0, toolversion: wHeader.toolversion, machtype: wHeader.machtype, magic: magic, text: [textOffset, textSize], iData: iDataSeg, textReloc: textRelocSeg, dataReloc: dataRelocSeg, syms: symsSeg, debug: [0, 0], linno: [0, 0], textLoadOffset: textLoadOffset, bssSize: Basics.Card32FromF[wHeader.bssSize], entryPoint: Basics.Card32FromF[wHeader.entryPoint], nPageSize: nPageSize, nEntries: symsSeg.byteLength/BYTES[WireSTEntryBody], stringOffset: symsSeg.byteOffset+symsSeg.byteLength ]]; RETURN[header]; }; SunADotOutModuleFromParsedAndPC: ObjectFilesPrivate.ModuleFromParsedAndPCProcType ~ { IF whole = NIL THEN RETURN[NIL] ELSE { stabRange: StabRange ¬ AlternativeFindModuleStabRange[whole, spc.relPC]; RETURN [ModuleFromParsedInner[whole, stabRange]]; }; }; LineNumList: TYPE = REF LineNumListBody; LineNumListBody: TYPE = RECORD [nLines: CARD, lines: LIST OF REF ANY ¬ NIL]; CreateModule: PROC[whole: Parsed, stream: IO.STREAM, stabRange: StabRange ¬ [0, 0]] RETURNS[Module] = BEGIN module: Module ¬ NEW[ModuleBody]; firstSO: Stab; nStabs: CARD; lineNums: LineNumList; lineNums2: LineNumList; [firstSO, ] ¬ BasicReadStab[whole, stream, stabRange.first]; nStabs ¬ stabRange.count; lineNums ¬ NEW [LineNumListBody ¬ [0, NIL]]; lineNums2 ¬ NEW [LineNumListBody ¬ [0, NIL]]; module.whole ¬ whole; module.firstStabX ¬ stabRange.first; module.limitStabX ¬ stabRange.first+stabRange.count; module.firstPC ¬ firstSO.value; module.limitPC ¬ whole.header.text.byteLength; -- (bogus, but it will suffice for reasons given earlier) module.stabs ¬ NEW[StabSet[nStabs]]; {I: CARD ¬ 0; WHILE I y ¬ mid; pc < midStab.value => y ¬ mid; midStab.value = pc => RETURN[midStab.stabX]; midStab.value < pc => x ¬ mid; ENDCASE => ERROR; ENDLOOP; RETURN[x]; END; SearchFwdForPCStab: PROC[whole: Parsed, start: CARD, limit: CARD] RETURNS[StabIndexTypeAndValue] = BEGIN dbxLimit: CARD ¬ 0; -- tentative FOR x: CARD ¬ start, x+1 WHILE x < limit DO info: StabIndexTypeAndValue ¬ CheckStab[whole, x]; IF info.stabType = SO OR info.stabType = Fun OR info.stabType = SLine THEN RETURN[info]; IF info.stabType # Unspecified AND info.stabType # Invalid THEN dbxLimit ¬ x+1; ENDLOOP; IF limit = whole.stabLimit AND dbxLimit > start THEN whole.stabLimit ¬ dbxLimit; RETURN[[limit, Invalid, 0]]; END; CheckStab: ENTRY PROC[whole: Parsed, stabX: CARD] RETURNS[StabIndexTypeAndValue] = BEGIN ENABLE UNWIND => NULL; stream: IO.STREAM ¬ SystemInterface.GetStreamForFile[whole.file]; IO.SetIndex[stream, whole.header.syms.byteOffset+stabX*BYTES[WireSTEntryBody]]; TRUSTED{[] ¬ IO.UnsafeGetBlock[stream, [LOOPHOLE[@WireStabBuffer­], 0, BYTES[WireSTEntryBody]]]}; SystemInterface.ReleaseStreamForFile[whole.file, stream]; RETURN[[stabX, TypeFromData[WireStabBuffer.type], Basics.Card32FromF[WireStabBuffer.value]]]; END; SunADotOutVarLocFromStab: ObjectFilesPrivate.VarLocFromStabProcType ~ { header: Header ~ stab.module.whole.header; MkSegment: PROC [kind: ObjectFiles.SimpleSeg] RETURNS [ObjectFiles.VarLoc] ~ { segRope:Rope.ROPE; segBase: CARD ~ SELECT kind FROM text => 0, data => header.text.byteLength, bss => header.text.byteLength + header.iData.byteLength, ENDCASE => ERROR; IF stab.value < segBase THEN ObjectFiles.UnreadableObjectFile[IO.PutFLR["value (%xH) less than segment base (%xH) for symbol %g in %g", LIST[[cardinal[stab.value]], [cardinal[segBase]], [rope[ObjectFiles.CNameOfStab[stab]]], [rope[ObjectFiles.DescribeModule[stab.module]]]] ]]; SELECT kind FROM text => segRope ¬ "text"; data => segRope ¬ "data"; bss => segRope ¬ "bss"; ENDCASE => ERROR; RETURN [NEW [ObjectFiles.VarLocBody ¬ [ bitSize: IF stab.size#0 THEN stab.size*8 ELSE unspecdBitSize, where: fSegment[ [0, segRope], (stab.value - segBase)*8, stab.value + header.text.byteOffset - header.textLoadOffset] ]]]}; SELECT stab.stabType FROM Fun => RETURN MkSegment[text]; STSym => RETURN MkSegment[data]; LCSym => RETURN MkSegment[bss]; LSym, PSym => { byteOffset: INT ~ LOOPHOLE[stab.value]; vLB: ObjectFiles.VarLoc; IF stab.stabType=PSym AND stab.size>4 AND stab.size # LAST[CARD] THEN vLB ¬ NEW[ObjectFiles.VarLocBody ¬ [ bitSize: stab.size*8, where: indirect[ base: NEW[ObjectFiles.VarLocBody ¬ [ bitSize: 32, where: frame[bitOffset: 8*byteOffset]]], offset: CirioTypes.zeroBA]]] ELSE vLB ¬ NEW[ObjectFiles.VarLocBody ¬ [ bitSize: IF stab.size#0 THEN stab.size*8 ELSE unspecdBitSize, where: frame[bitOffset: 8*byteOffset]]]; RETURN[vLB]}; RSym => { sz: CARD ~ IF stab.size#LAST[CARD] AND stab.size#0 THEN stab.size*8 ELSE unspecdBitSize; vLB: ObjectFiles.VarLoc; IF stab.size>4 AND stab.size # LAST[CARD] THEN vLB ¬ NEW[ObjectFiles.VarLocBody ¬ [ bitSize: sz, where: indirect[ base: NEW[ObjectFiles.VarLocBody ¬ [ bitSize: 32, where: register[regNum: stab.value]]], offset: CirioTypes.zeroBA]]] ELSE vLB ¬ NEW[ObjectFiles.VarLocBody ¬ [ bitSize: sz, where: register[regNum: stab.value]]]; RETURN[vLB] }; GSym => RETURN [NEW [ObjectFiles.VarLocBody ¬ [ bitSize: IF stab.size#0 THEN stab.size*8 ELSE unspecdBitSize, where: namedCommon[Rope.Concat["_", ObjectFiles.CNameOfStab[stab]], 0, 0, FALSE, FALSE] ]]]; ENDCASE => RETURN ObjectFiles.MakeUnknownVarLoc[IO.PutFR["unrecognized stabType (%02xH) for %g", [cardinal[ORD[stab.stabType]]], [rope[ObjectFiles.CNameOfStab[stab]]] ]]; }; PCtoLineNumMap: TYPE = REF PCtoLineNumMapBody; PCtoLineNumMapBody: TYPE = ObjectFilesPrivate.PCtoLineNumMapBody; LineNumToPCMap: TYPE = REF LineNumToPCMapBody; LineNumToPCMapBody: TYPE = ObjectFilesPrivate.LineNumToPCMapBody; SLineData: TYPE = ObjectFilesPrivate.SLineData; InstallPCLineNumMaps: PROC[module: Module, lineNums, lineNums2: LineNumList] = { CompareByCLineNum: PROC[ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison] = { info1: REF SLineData ¬ NARROW[ref1]; info2: REF SLineData ¬ NARROW[ref2]; IF info1.cLineNum < info2.cLineNum THEN RETURN[less]; IF info1.cLineNum > info2.cLineNum THEN RETURN[greater]; IF info1.parsedRelPC.relPC < info2.parsedRelPC.relPC THEN RETURN[less]; IF info1.parsedRelPC.relPC > info2.parsedRelPC.relPC THEN RETURN[greater]; RETURN[equal]; }; CompareByPC: PROC[ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison] = { info1: REF SLineData ¬ NARROW[ref1]; info2: REF SLineData ¬ NARROW[ref2]; IF info1.parsedRelPC.relPC < info2.parsedRelPC.relPC THEN RETURN[less]; IF info1.parsedRelPC.relPC > info2.parsedRelPC.relPC THEN RETURN[greater]; IF info1.cLineNum < info2.cLineNum THEN RETURN[less]; IF info1.cLineNum > info2.cLineNum THEN RETURN[greater]; RETURN[equal]; }; sortedList: LIST OF REF ANY ¬ NIL; IF module.pcToLineNum # NIL OR module.lineNumToPC # NIL THEN { IF module.pcToLineNum = NIL OR module.lineNumToPC = NIL THEN ERROR; RETURN; }; sortedList ¬ List.Sort[lineNums.lines, CompareByCLineNum]; module.lineNumToPC ¬ NEW[LineNumToPCMapBody[lineNums.nLines]]; FOR I: INTEGER IN [0..lineNums.nLines) DO info: REF SLineData ¬ NARROW[sortedList.first]; module.lineNumToPC[I] ¬ info­; sortedList ¬ sortedList.rest; ENDLOOP; sortedList ¬ List.Sort[lineNums2.lines, CompareByPC]; module.pcToLineNum ¬ NEW[PCtoLineNumMapBody[lineNums2.nLines]]; FOR I: INTEGER IN [0..lineNums2.nLines) DO info: REF SLineData ¬ NARROW[sortedList.first]; module.pcToLineNum[I] ¬ info­; sortedList ¬ sortedList.rest; ENDLOOP; }; TypeFromData: PROC [data: BYTE] RETURNS [stabType: ObjectFiles.StabType] ~ { val: CARD ¬ data; stabType ¬ SELECT val FROM LBrac => LBrac, RBrac => RBrac, SLine => SLine, Fun => Fun, PSym => PSym, LSym => LSym, RSym => RSym, STSym => STSym, LCSym => LCSym, GSym => GSym, Main => Main, SO => SO, BIncl => BIncl, EIncl => EIncl, Excl => Excl, SOL => SOL, ENDCASE => Unspecified }; BasicReadStab: ENTRY PROC[whole: Parsed, stream: IO.STREAM, stabX: CARD, lineNum, lineNum2: LineNumList ¬ NIL] RETURNS[stab: Stab, numStabsRead: CARD] ~ { ENABLE UNWIND => NULL; stab ¬ NEW [StabBody]; [stab­, numStabsRead] ¬ ReadStabBody[whole, stream, stabX, lineNum, lineNum2]; RETURN }; previousStream: IO.STREAM ¬ NIL; cloneStream: IO.STREAM ¬ NIL; ReadStabBody: PROC [whole: Parsed, stream: IO.STREAM, stabX: CARD, lineNum, lineNum2: LineNumList ¬ NIL] RETURNS [stab: StabBody, numStabsRead: CARD] ~ { rope: Rope.ROPE ¬ "\\"; size, rLen, offset: CARD16 ¬ 0; value: CARD32; stabType: ObjectFiles.StabType; IO.SetIndex[stream, whole.header.syms.byteOffset+stabX*BYTES[WireSTEntryBody]]; TRUSTED{[] ¬ IO.UnsafeGetBlock[stream, [LOOPHOLE[@WireStabBuffer­], 0, BYTES[WireSTEntryBody]]]}; stabType ¬ TypeFromData[WireStabBuffer.type]; size ¬ Basics.Card16FromH[WireStabBuffer.desc]; value ¬ Basics.Card32FromF[WireStabBuffer.value]; IF stream#previousStream THEN { cloneStream¬PFS.StreamFromOpenFile[openFile~PFS.OpenFileFromStream[stream]]; previousStream ¬ stream; }; rope ¬ ReadStringX[cloneStream, whole, Basics.Card32FromF[WireStabBuffer.stringX]]; rLen ¬ rope.Length[]; numStabsRead ¬ 1; IF ~rope.IsEmpty[] THEN WHILE rope.Fetch[rLen-1] = '\\ DO offset ¬ offset + 1; IO.SetIndex[stream, whole.header.syms.byteOffset+(stabX + offset)*BYTES[WireSTEntryBody]]; TRUSTED{[] ¬ IO.UnsafeGetBlock[stream, [LOOPHOLE[@WireStabBuffer­], 0, BYTES[WireSTEntryBody]]]}; rope ¬ rope.Substr[len:rLen-1].Concat[ReadStringX[cloneStream, whole, Basics.Card32FromF[WireStabBuffer.stringX]]]; rLen ¬ rope.Length[]; numStabsRead ¬ numStabsRead + 1; ENDLOOP; stab ¬ [ module: NIL, stabX: stabX, stabType: stabType, size: size, value: value, rope: rope ]; IF stab.stabType = SLine AND lineNum # NIL THEN { info: REF SLineData ¬ NEW[SLineData¬[cLineNum: Basics.Card16FromH[WireStabBuffer.desc], parsedRelPC: [[1, ".text"], stab.value]]]; lineNum.lines ¬ CONS[info, lineNum.lines]; lineNum.nLines ¬ lineNum.nLines + 1; lineNum2.lines ¬ CONS[info, lineNum2.lines]; lineNum2.nLines ¬ lineNum2.nLines + 1; }; RETURN}; WireStabBuffer: WireSTEntry ¬ NEW[WireSTEntryBody]; ReadStringX: PROC[stream: IO.STREAM, whole: Parsed, stringX: CARD] RETURNS[rope: Rope.ROPE] ~ { IO.SetIndex[stream, whole.header.stringOffset+stringX]; rope ¬ ReadRope[stream]}; RopeBufferSize: CARD = 100; RopeBuffer: REF PACKED ARRAY [0..RopeBufferSize) OF CHAR ¬ NEW[PACKED ARRAY [0..RopeBufferSize) OF CHAR]; ReadRope: PROC[s: IO.STREAM] RETURNS[rope: Rope.ROPE] = BEGIN ENABLE UNWIND => NULL; rope: Rope.ROPE ¬ NIL; WHILE TRUE DO nChars: CARD ¬ RopeBufferSize; -- tentative rt: Rope.Text; TRUSTED{[] ¬ IO.UnsafeGetBlock[s, [LOOPHOLE[@RopeBuffer­], 0, RopeBufferSize]]}; FOR I: CARD IN [0..RopeBufferSize) DO IF RopeBuffer[I] = '\000 THEN {nChars ¬ I; EXIT}; ENDLOOP; rt ¬ Rope.NewText[nChars]; FOR I: CARD IN [0..nChars) DO rt[I] ¬ RopeBuffer[I] ENDLOOP; rope ¬ IF rope = NIL THEN (IF nChars = 0 THEN "" ELSE rt) ELSE (IF nChars = 0 THEN rope ELSE Rope.Concat[rope, rt]); IF nChars < RopeBufferSize THEN EXIT; ENDLOOP; RETURN[rope]; END; RandomTestFindStabRange: Commander.CommandProc = { args: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd]; path: PATH ¬ PFS.PathFromRope[args[1]]; fileSet: SystemInterface.FileSet ¬ SystemInterface.CreateFileSet[]; { ENABLE UNWIND => SystemInterface.CloseFileSet[fileSet]; file: SystemInterface.CirioFile ¬ SystemInterface.GetCirioFile[fileSet, path]; nextSeed: INT ¬ IF args.argc < 3 THEN 4466 ELSE Convert.IntFromRope[args[2]]; start: BasicTime.GMT ¬ BasicTime.Now[]; WHILE TRUE DO rs: Random.RandomStream ¬ Random.Create[seed: nextSeed]; whole: Parsed ¬ ObjectFiles.CreateParsed[file, "SunADotOut"]; IO.PutF[cmd.out, "beginning seed = %g at %g\N", IO.card[nextSeed], IO.time[start]]; FOR I: INT IN [0..100) DO pc: CARD ¬ Random.ChooseInt[rs, 0, whole.header.text.byteLength]; range: StabRange ¬ AlternativeFindModuleStabRange[whole, pc]; IO.PutF[cmd.out, "\Tpc = %g gives firstX: %g count: %g\N", IO.card[pc], IO.card[range.first], IO.card[range.count]]; ENDLOOP; ENDLOOP; }; SystemInterface.CloseFileSet[fileSet]; }; SADOScanModuleStructure: PROC [module: Module, perFn: ObjectFilesPrivate.FnConsumer] ~ { I: CARD ¬ 0; WHILE I < module.stabs.nStabs DO stab: Stab ¬ module.stabs[I]; IF stab.module = NIL THEN I ¬ I+1 ELSE SELECT stab.stabType FROM GSym, LCSym, STSym, LSym => I ¬ I + 1; Fun => {funStab: Stab ~ stab; firstLocal: Stab ¬ NIL; WHILE I+1 < module.stabs.nStabs DO I ¬ I + 1; stab ¬ module.stabs[I]; IF stab.module = NIL THEN LOOP; SELECT stab.stabType FROM PSym, RSym => NULL; STSym, LSym => IF BracketFollows[module, I] THEN {firstLocal ¬ stab; EXIT} ELSE {firstLocal ¬ NIL; EXIT}; LBrac => {firstLocal ¬ stab; EXIT}; Fun, GSym, LCSym => {firstLocal ¬ NIL; EXIT}; SLine, Invalid, Unspecified, Main, SO, BIncl, EIncl, Excl, SOL => NULL; ENDCASE => SystemInterface.ShowReport[IO.PutFR["Found %g stab while scanning the arguments for function %g in %g", [rope[ObjectFiles.RopeForStabType[stab.stabType]]], [rope[ObjectFiles.CNameOfStab[funStab]]], [rope[ObjectFiles.DescribeModule[module]]] ], $normal]; ENDLOOP; I ¬ perFn[funStab, firstLocal, I+module.firstStabX] - module.firstStabX}; SLine, Invalid, Unspecified, Main, SO, BIncl, EIncl, Excl, SOL => I ¬ I + 1; ENDCASE => { SystemInterface.ShowReport[IO.PutFR["Found %g stab while scanning in the global space of %g", [rope[ObjectFiles.RopeForStabType[stab.stabType]]], [rope[ObjectFiles.DescribeModule[module]]] ], $normal]; I ¬ I + 1}; ENDLOOP; RETURN}; BracketFollows: PROC [module: Module, J: CARD] RETURNS [BOOL] ~ { FOR J ¬ J+1, J+1 WHILE J < module.stabs.nStabs DO stab: Stab ¬ module.stabs[J]; IF stab.module = NIL THEN LOOP; SELECT stab.stabType FROM Fun, GSym, LCSym => RETURN [FALSE]; LBrac => RETURN [TRUE]; ENDCASE => NULL; ENDLOOP; RETURN [FALSE]}; SADOScanFnStructure: PROC [module: Module, funStab: Stab, firstLocal: Stab ¬ NIL, nextX: CARD, perParm: PROC [Stab] ¬ NIL, perBracket: ObjectFilesPrivate.BracketConsumer ¬ NIL] RETURNS [limitX, limitPc: CARD] ~ { K: CARD; more: BOOL ¬ TRUE; IF perParm#NIL THEN PassLocals[module, funStab, funStab.stabX+1, nextX, perParm]; IF firstLocal=NIL THEN limitX ¬ nextX ELSE limitX ¬ DoBrack[module, funStab, firstLocal, perBracket]; K ¬ limitX-module.firstStabX; WHILE K < module.stabs.nStabs DO --seek limitPC, and more bracket pairs. stab: Stab ¬ module.stabs[K]; IF stab.module = NIL THEN K ¬ K + 1 ELSE SELECT stab.stabType FROM Fun => RETURN [limitX, stab.value]; LBrac, RSym => IF more THEN {limitX ¬ DoBrack[module, funStab, stab, perBracket]; K ¬ limitX-module.firstStabX} ELSE K ¬ K + 1; STSym, LSym => IF more AND BracketFollows[module, K] THEN {limitX ¬ DoBrack[module, funStab, stab, perBracket]; K ¬ limitX-module.firstStabX} ELSE {K ¬ K + 1; more ¬ FALSE}; ENDCASE => K ¬ K + 1; ENDLOOP; RETURN [limitX, module.limitPC]}; DoBrack: PROC [module: Module, funStab, first: Stab, perSubBracket: ObjectFilesPrivate.BracketConsumer] RETURNS [limitX: CARD] ~ { IF perSubBracket#NIL THEN limitX ¬ perSubBracket[first] ELSE limitX ¬ SADOScanBktStructure[module, funStab, first, NIL, NIL].limitX; RETURN}; SADOScanBktStructure: PROC [module: Module, funStab, first: Stab, perLocal: PROC [Stab] ¬ NIL, perSubBracket: ObjectFilesPrivate.BracketConsumer ¬ NIL] RETURNS [limitX, firstPc, limitPc: CARD] ~ { I: CARD ¬ first.stabX - module.firstStabX; stab: Stab; FOR I ¬ I, I+1 WHILE I < module.stabs.nStabs DO stab ¬ module.stabs[I]; IF stab.module = NIL THEN LOOP; SELECT stab.stabType FROM STSym, LSym, RSym => IF perLocal#NIL THEN perLocal[stab]; LBrac => { J: CARD ¬ I+1; firstPc ¬ stab.value; WHILE J < module.stabs.nStabs DO stab ¬ module.stabs[J]; IF stab.module = NIL THEN J ¬ J+1 ELSE SELECT stab.stabType FROM RBrac => RETURN [J+1+module.firstStabX, firstPc, stab.value]; STSym, LSym, RSym, LBrac => J ¬ DoBrack[module, funStab, stab, perSubBracket] - module.firstStabX; SLine, Invalid, Unspecified, Main, SO, BIncl, EIncl, Excl, SOL => J ¬ J +1; Fun => {--Since we're debugging C, which has no nested functions, this shouldn't happen; if it does, we assume the bracket parsing has failed and re-synch with top level enumeration of functions SystemInterface.ShowReport[IO.PutFR["Found FUN stab after an LBRAC in function %g in %g", [rope[ObjectFiles.CNameOfStab[funStab]]], [rope[ObjectFiles.DescribeModule[module]]] ], $normal]; RETURN [J+module.firstStabX, firstPc, stab.value]}; ENDCASE => SystemInterface.ShowReport[IO.PutFR["Found %g stab after an LBRAC in function %g in %g", [rope[ObjectFiles.RopeForStabType[stab.stabType]]], [rope[ObjectFiles.CNameOfStab[funStab]]], [rope[ObjectFiles.DescribeModule[module]]] ], $normal]; ENDLOOP; SystemInterface.ShowReport[IO.PutFR["Missed an RBRAC in function %g in %g", [rope[ObjectFiles.CNameOfStab[funStab]]], [rope[ObjectFiles.DescribeModule[module]]] ], $normal]; RETURN[J+module.firstStabX, firstPc, module.limitPC]}; Fun => {--shouldn't happen, but ... SystemInterface.ShowReport[IO.PutFR["Found FUN stab before an LBRAC in function %g in %g", [rope[ObjectFiles.CNameOfStab[funStab]]], [rope[ObjectFiles.DescribeModule[module]]] ], $normal]; RETURN [I+module.firstStabX, stab.value, stab.value]}; SLine, Invalid, Unspecified, Main, SO, BIncl, EIncl, Excl, SOL => NULL; ENDCASE => SystemInterface.ShowReport[IO.PutFR["Found %g stab before an LBRAC in function %g in %g", [rope[ObjectFiles.RopeForStabType[stab.stabType]]], [rope[ObjectFiles.CNameOfStab[funStab]]], [rope[ObjectFiles.DescribeModule[module]]] ], $normal]; ENDLOOP; SystemInterface.ShowReport[IO.PutFR["Missed an LBRAC in function %g in %g", [rope[ObjectFiles.CNameOfStab[funStab]]], [rope[ObjectFiles.DescribeModule[module]]] ], $normal]; RETURN[I+module.firstStabX, module.limitPC, module.limitPC]}; PassLocals: PROC [module: Module, funStab: Stab, firstX, limitX: CARD, to: PROC [Stab]] ~ { FOR I: CARD IN [firstX - module.firstStabX .. limitX-module.firstStabX) DO stab: Stab ¬ module.stabs[I]; IF stab.module#NIL THEN SELECT stab.stabType FROM PSym, RSym, LSym, STSym => to[stab]; SLine, Invalid, Unspecified, Main, SO, BIncl, EIncl, Excl, SOL => NULL; ENDCASE => SystemInterface.ShowReport[IO.PutFR["Found %g stab in parms/locals of %g in %g", [rope[ObjectFiles.RopeForStabType[stab.stabType]]], [rope[ObjectFiles.CNameOfStab[funStab]]], [rope[ObjectFiles.DescribeModule[module]]] ], $normal]; ENDLOOP; RETURN}; SunADotOutGetTypeRef: ObjectFilesPrivate.GetTypeRefProcType ~ { typeRef: Rope.ROPE; lastChar: CHAR ¬ IO.PeekChar[sourceStream]; IF IsDigit[lastChar] THEN { typeRef ¬ IO.GetTokenRope[sourceStream, NumTok].token; RETURN[typeRef]} ELSE IF lastChar='( THEN DO lastChar ¬ IO.GetChar[sourceStream]; typeRef ¬ Rope.Concat[typeRef, Rope.FromChar[lastChar]]; IF lastChar = ') THEN RETURN[typeRef] ENDLOOP ELSE CCError[cirioError, IO.PutFR1["malformed type rep (starts with %q)", [character[lastChar]] ]]; }; IsDigit: PROC [c:CHAR] RETURNS [BOOL] = INLINE { RETURN [c IN ['0 .. '9]] }; NumTok: PROC [char: CHAR] RETURNS [IO.CharClass] ~ { SELECT char FROM IN ['0..'9] => RETURN [other]; ENDCASE => RETURN [break]}; ModuleFromParsedAndStabRange: PUBLIC PROC[whole: Parsed, stabRange: StabRange] RETURNS[Module] = { RETURN [ModuleFromParsedInner[whole, stabRange]]; }; ModuleFromParsedInner: PROC[whole: Parsed, stabRange: StabRange] RETURNS[Module] = { firstX, limitX: CARD; stream: IO.STREAM; IF stabRange.count = 0 THEN RETURN[NIL]; firstX ¬ stabRange.first; limitX ¬ stabRange.first+stabRange.count; stream ¬ SystemInterface.GetStreamForFile[whole.file]; { ENABLE UNWIND => { SystemInterface.ReleaseStreamForFile[whole.file, stream]; }; firstSO: Stab; firstPC: CARD; lag: LIST OF Module ¬ NIL; [firstSO, ] ¬ BasicReadStab[whole, stream, firstX]; -- rope field not filled firstPC ¬ firstSO.value; FOR modules: LIST OF Module ¬ whole.modules, modules.rest WHILE modules # NIL DO IF modules.first.firstPC = firstPC THEN -- we already have it { SystemInterface.ReleaseStreamForFile[whole.file, stream]; RETURN[modules.first] }; IF firstPC < modules.first.firstPC THEN -- we should have found it by now { newModule: Module ¬ CreateModule[whole, stream, stabRange]; cell: LIST OF Module ¬ LIST[newModule]; cell.rest ¬ modules; IF lag # NIL THEN lag.rest ¬ cell ELSE whole.modules ¬ cell; SystemInterface.ReleaseStreamForFile[whole.file, stream]; RETURN[newModule]; }; lag ¬ modules; ENDLOOP; { newModule: Module ¬ CreateModule[whole, stream, stabRange]; cell: LIST OF Module ¬ LIST[newModule]; IF lag # NIL THEN lag.rest ¬ cell ELSE whole.modules ¬ cell; SystemInterface.ReleaseStreamForFile[whole.file, stream]; RETURN[newModule]; }; }; }; SADOInstallStaticVars: ObjectFilesPrivate.InstallStaticVarsType ~ { IF NOT module.staticVarsInstalled THEN FOR x: CARD IN [module.firstStabX..module.limitStabX) DO stab: Stab ¬ ObjectFilesPrivate.ReadStab[module, x]; IF stab = NIL THEN LOOP; IF stab.stabType = Fun THEN EXIT; IF stab.stabType = STSym OR stab.stabType = LCSym THEN { IF module.versionStampStab = NIL THEN { gvi: ObjectFiles.GlobalVarLoc ¬ NIL; gvi ¬ NARROW[ObjectFilesPrivate.CheckStaticVar[stab, "versionStamp", TRUE]]; IF gvi # NIL THEN { dataRope: Rope.ROPE ¬ ObjectFilesPrivate.ReadInitialDataAsRope[module, gvi.fileByteOffset]; module.versionStampStab ¬ stab; module.versionStampInfo ¬ NEW[ObjectFiles.VersionStampInfo¬[gvi, dataRope]]; LOOP; }; }; IF module.globalFrameStab = NIL THEN { gvi: ObjectFiles.GlobalVarLoc ¬ NIL; gvi ¬ NARROW[ObjectFilesPrivate.CheckStaticVar[stab, "globalframe", TRUE]]; IF gvi # NIL THEN { module.globalFrameStab ¬ stab; module.globalFrameGvl ¬ gvi; LOOP; }; }; }; ENDLOOP; module.staticVarsInstalled ¬ TRUE; }; SADOAlterFunStab: ObjectFilesPrivate.AlterFunStabType ~ {RETURN [module.funStabs[funStabX].stab]; }; SADOGetSPOffset: ObjectFilesPrivate.GetSPOffsetType ~ { entryRelativePC: CARD ¬ spc.relPC; inst0: CARD ¬ ObjectFiles.ReadInstruction[module, [spc.fSeg, spc.relPC]]; inst1: CARD ¬ ObjectFiles.ReadInstruction[module, [spc.fSeg, spc.relPC+4]]; inst2: CARD ¬ ObjectFiles.ReadInstruction[module, [spc.fSeg, spc.relPC+8]]; spOffset: INT ¬ SaveProtocol1[inst0, inst1, inst2]; IF spOffset = 0 THEN spOffset ¬ SaveProtocol2[inst0]; RETURN[spOffset]; }; SaveProtocol1: PROC[inst0, inst1, inst2: CARD] RETURNS[spOffset: INT] ~ { decoded0: DecodedSparcInstruction ¬ DecodeSparcInstruction[inst0]; decoded1: DecodedSparcInstruction ¬ DecodeSparcInstruction[inst1]; decoded2: DecodedSparcInstruction ¬ DecodeSparcInstruction[inst2]; IF decoded0.op # 0 THEN RETURN[0]; IF decoded0.op2 # 4 THEN RETURN[0]; IF decoded0.rd # 1 THEN RETURN[0]; -- this is %g1 IF decoded1.op # 2 THEN RETURN[0]; IF decoded1.op3 # 0 THEN RETURN[0]; IF decoded1.i # 1 THEN RETURN[0]; IF decoded1.rs1 # 1 THEN RETURN[0]; -- this is %g1 IF decoded1.rd # 1 THEN RETURN[0]; -- this is %g1 IF decoded2.op # 2 THEN RETURN[0]; IF decoded2.op3 # 74B THEN RETURN[0]; IF decoded2.i # 0 THEN RETURN[0]; IF decoded2.rs1 # 16B THEN RETURN[0]; -- this is %sp IF decoded2.rs2 # 1 THEN RETURN[0]; -- this is %g1 IF decoded2.rd # 16B THEN RETURN[0]; -- this is %sp spOffset ¬ LOOPHOLE[Basics.BITSHIFT[decoded0.imm22, 10] + decoded1.simm13]; }; SaveProtocol2: PROC[inst0: CARD] RETURNS[spOffset: INT] ~ { decoded0: DecodedSparcInstruction ¬ DecodeSparcInstruction[inst0]; IF decoded0.op # 2 THEN RETURN[0]; IF decoded0.op3 # 74B THEN RETURN[0]; IF decoded0.i # 0 THEN RETURN[0]; IF decoded0.rs1 # 16B THEN RETURN[0]; -- this is %sp IF decoded0.rd # 16B THEN RETURN[0]; -- this is %sp spOffset ¬ decoded0.simm13; }; DecodedSparcInstruction: TYPE = RECORD[ op, disp30, rd, a, cond, op2, imm22, disp22, op3, rs1, i, asi, rs2, simm13, opf: CARD]; DecodeSparcInstruction: PROC[inst: CARD] RETURNS[DecodedSparcInstruction] ~ { RETURN[[ op: Basics.BITAND[3, Basics.BITSHIFT[inst, -30]], disp30: Basics.BITAND[7777777777B, inst], rd: Basics.BITAND[37B, Basics.BITSHIFT[inst, -25]], a: Basics.BITAND[1, Basics.BITSHIFT[inst, -29]], cond: Basics.BITAND[17B, Basics.BITSHIFT[inst, -25]], op2: Basics.BITAND[7, Basics.BITSHIFT[inst, -22]], imm22: Basics.BITAND[17777777B, inst], disp22: Basics.BITAND[17777777B, inst], op3: Basics.BITAND[77B, Basics.BITSHIFT[inst, -19]], rs1: Basics.BITAND[37B, Basics.BITSHIFT[inst, -14]], i: Basics.BITAND[1, Basics.BITSHIFT[inst, -13]], asi: Basics.BITAND[377B, Basics.BITSHIFT[inst, -5]], rs2: Basics.BITAND[37B, inst], simm13: Basics.BITAND[17777B, inst], opf: Basics.BITAND[777B, Basics.BITSHIFT[inst, -5]]]]; }; transTable: ObjectFilesPrivate.TranslationTable ¬ NEW[ObjectFilesPrivate.TranslationTableBody[3]]; Commander.Register["sunadoRandomTestFindStabRange", RandomTestFindStabRange]; transTable[0] ¬ [2, 2, 263, "SunADotOut"]; transTable[1] ¬ [2, 2, 264, "SunADotOut"]; transTable[2] ¬ [2, 2, 267, "SunADotOut"]; ObjectFilesPrivate.RegisterObjectFileFlavor[NEW[ObjectFilesPrivate.ObjectFileFlavorBody ¬ [ "SunADotOut", SunADotOut, ReadHeader, SunADotOutModuleFromParsedAndPC, SunADotOutVarLocFromStab, SunADotOutGetTypeRef, NIL, SADOScanModuleStructure, SADOScanFnStructure, SADOScanBktStructure, TRUE, SADOInstallStaticVars, SADOAlterFunStab, SADOGetSPOffset ]], transTable]; END.. š SunADotOutFiles.mesa Copyright Ó 1990, 1991, 1992, 1993 by Xerox Corporation. All rights reserved. Modified from DotOAccessImpl.mesa Laurie Horton, June 24, 1992 6:10 pm PDT Philip James, February 10, 1992 1:42 pm PST Last tweaked by Mike Spreitzer January 21, 1993 5:33 pm PST Chauser, March 24, 1992 10:36 am PST Katsuyuki Komatsu April 1, 1992 3:32 pm PST Willie-s, January 22, 1993 2:18 pm PST Useful types Following types are defined in ObjectFiles some Stab types Following types are defined in ObjectFilesPrivate Following values are of global interest theContStab: Stab ~ NEW [StabBody _ [module: NIL, stabX: CARD.LAST, stringX: CARD32.LAST, type: BYTE.LAST, other: BYTE.LAST, desc: CARD16.LAST, value: CARD32.LAST, rope: "don't look at me, I'm a continuation of an earlier stab", dx: CARD.LAST]]; This stab is put in module.stabs[i] wherever continuation stabs would appear. The following computations are adapted from /usr/include/sys/exec.h, obtained by: REdit cartiff -f /usr/include/sys/exec.h The following computations are adapted from /usr/include/sun4/a.out.h, obtained by: REdit cartiff -f /usr/include/sun4/a.out.h The following record type was constructed based on page 1339 of the Sun Release 4.0 documentation (A.OUT documentation) PROC[stream: IO.STREAM] RETURNS[Header] = TRUSTED The following computations are adapted from /usr/include/sun4/a.out.h, obtained by: REdit cartiff -f /usr/include/sun4/a.out.h text segment is loaded into memory at nominal 0 + textLoadOffset the 02000H is the largest page size on certain machines, and allows the first page of memory to remain unmapped. Module We assume that there are dbx stabs located in the whole in the symbol table entry range [firstX..limitX). We assume that the first stab is a SO stab whose value is the first pc of the module. We assume that this or the next stab is a SO stab containing the file name. The first and limitX have presumably been obtained by other means. For example, at this writing the CirioNub will provide access to a table of pc ranges located in the target world. (In addition, we provide a rather crude mechanism below to use when the CirioNub approach is not available. (e.g., when examining a file that is not part of a PCedar load state.) We are unable to define a legitimate limitPC. We assume that when, later, an attempt is made to look up a pc in the bracket structure, we have already determined that that pc definitely belongs to this Module. Thus, we can use header.textSize as a (bogus) limitPC. PROC [whole: Parsed, spc: FileSegmentPC, moduleRope: ROPE _ NIL] RETURNS [Module] ~ { prepare the Module for a call on BuildBrackets lets read the all stabs now (WITH their ropes) (I did some timing tests on a Dorado for RopeImpl. It took about 6 seconds to perform this loop, including the allocates. On the other hand, it takes about 4 seconds to spin through the entries without allocating the stab records. So, since at some point I have to traverse all the entries to find the Fun stabs, I might as well allocate them.) (we also relocate the pc value of bracket stabs.) now lets finish up Finding Modules This procedure should only be used when examining a whole which is not in the load state. If we are given a pc in a running PCR, it is far faster to use procedures in LoadStateAccess (if supported!) to obtain the stab range for a Module. Those procedures use a table available from the nub to find the stab range, then produce the Module through a call on CreateModule. foo: IO.STREAM _ NIL; ENABLE PFS.Error => { foo _ PFS.StreamOpen[PFS.PathFromRope["/tmp/PJ.out"], create]; CONTINUE}; foo _ PFS.StreamOpen[PFS.PathFromRope["/tmp/PJ.out"], append]; IO.PutF[foo, "downLooping [%g .. 0]\n", IO.card[nominalX]]; IO.PutF[foo, "(%g)\tstabType:\t%g\n", IO.card[x], IO.rope[ObjectFiles.RopeForStabType[info.stabType]]]; IO.PutF[foo, "Bailing out because baseSO.stabType # SO\n"]; IO.Close[foo]; foo _ NIL; IO.PutF[foo, "upLooping [%g .. %g]\n", IO.card[nominalX], IO.card[whole.stabLimit]]; IO.PutF[foo, "(%g)\tstabType:\t%g\n", IO.card[x], IO.rope[ObjectFiles.RopeForStabType[info.stabType]]]; IO.PutF[foo, "good answer: [%g, %g]\n", IO.card[baseSO.stabX], IO.card[highDbx-baseSO.stabX+1]]; IO.Close[foo]; foo _ NIL; IO.PutF[foo, "bad answer [0, 0] because\n\tbaseSO.value\t%g\n\trelativePC\t%g\n\tmaxPC\t%g\nThese values aren't in descending order.", IO.card[baseSO.value], IO.card[relativePC], IO.card[maxPC]]; IO.Close[foo]; foo _ NIL; if successful, the value returned is somewhere within the range of stabXs for the desired Module. If unsuccessful, the value returned is whole.header.nEntries. We depend upon the fact that all pc containing dbx stabs for a given whole file occur before or after any other dbx stab. We do not require that the pc values be sorted within the block of stabs for a given whole. However, we require that all our pc values are greater than or less than a pc value from some other block of dbx stabs. now, we assert an invariant all SO dbx stabs to the left of or at x have a pc value < pc all SO dbx stabs to the right of or at y have a pc value > pc We start looking at start, and stop just before limit. Hence, if we fail to find a pc stab, then we return [limit, 0, 0] IF info.stabType > 1fH THEN dbxLimit _ x+1; PROC [stab: Stab] RETURNS [ObjectFiles.VarLoc] ~ { C Line-Number to Relative-PC maps sorted by PC sorted by Line Num Stabs MergeContinuationStabs: ENTRY PROC[module: Module, stabX: CARD] ~ { ENABLE UNWIND => NULL; stab: Stab _ module.stabs[stabX]; DO rlen: INT ~ stab.rope.Length[]; IF rlen>0 AND stab.rope.Fetch[rlen-1]='\\ THEN { s2: Stab ~ module.stabs[stabX+stab.dx]; IF s2.stabType=stab.stabType AND s2.value=stab.value AND s2.other=stab.other AND s2.desc=stab.desc THEN { stab.dx _ stab.dx.SUCC; stab.rope _ stab.rope.Substr[len: rlen-1].Concat[s2.rope]; } ELSE EXIT} ELSE EXIT; ENDLOOP; RETURN}; warning: does NOT relocate LBrac or RBrac stabs. Also: the module field is set to NIL; hack to avoid messing up the stream buffering all users should be in the monitor WARNING: the rope reading code causes random repositioning of the stream. That is CRUDE and expensive. Simply for this experiment. I need to do something better. Read in the whole string table? read in the symbols, sort them, then read in string table and pick out the interesting strings? What? symbol ropes we assume that the stream has been positioned at the beginning of a sequence of chars terminated by a '\000 char. usage: RandomTestFindStabRange /dir/.../dir/[sun4/]foo[.c2c].o Stab Structure PROC [sourceStream:IO.STREAM] RETURNS [Rope.ROPE] nest so that we can catch unwinds and release the stream first, see if we already have it PROC[module: Module] = this procedure examines all stabs up to the first Fun stab, looking for certain expected stabs. These are recorded in the whole. Following procedures compute the SP offset from FP by examining the entry protocol to a procedure. SPoffset = 0 means that the instructions do not form the expected protocol. protocol 1 is sethi %hi(Offset),%g1 add %g1,%lo(Offset),%g1 save %sp,%g1,%sp check for sethi %hi(spOffset),%g1 check for add %g1,%lo(spOffset),%g1 check for save %sp,%g1,%sp protocol 2 is save %sp, spOffset,%sp check for save %sp, spOffset, %sp Main code Ê(Ô•NewlineDelimiter –"cedarcode" style™codešœ™Kšœ ÏeœC™NK™!K™(K™+K™;K™$K™+K™&—K˜šÏk ˜ Kš œžœžœžœ(žœžœ˜TKšœ žœžœ˜Kšœžœ˜$Kšœ žœ ˜Kšœ žœ˜(Kšœ žœ˜+Kšœžœ˜Kšžœ˜Kšœžœžœ˜$Kšœ žœ€˜‘Kšœžœþ˜–Kšžœžœ8˜AKšœ žœžœ˜Kšœžœ"˜.Kšœ˜K˜ Kšœžœt˜‰—K˜K˜K˜šÏnœžœž˜Kšžœ?žœ)žœ˜“Kšžœ˜—Kšœž˜K˜KšŸœžœ&žœžœ˜RK˜™ Kšžœžœ žœ˜—K˜™*K˜Kšœžœ˜Kšœ žœ˜-Kšœ žœ˜&Kšœ žœ˜&Kšœ žœ ˜/Kšœžœ˜0Kšœžœ ˜6K˜Kšœ™K™KšŸœžœ˜KšŸœžœ˜KšŸœžœ˜KšŸœžœ˜KšŸœžœ˜KšŸœžœ˜KšŸœžœ˜KšŸœžœ˜KšŸœžœ˜KšŸœžœ˜KšŸœžœ˜KšÐbkœžœ˜KšŸœžœ˜KšŸœžœ˜KšŸœžœ˜Kš œžœ˜K˜—K˜™1K™Kšœžœžœ ˜Kšœ žœžœ!˜8K˜Kšœžœžœ ˜Kšœ žœžœ!˜8K™Kšœžœžœ ˜Kšœ žœ!˜1K˜Kšœžœ(˜?K˜Kšœ žœ˜+K˜Kšœ žœ"˜3Kšœžœ&˜;K˜—™'K™Kšœžœ˜2K˜š!œžœžœ žœžœ žœžœžœžœ žœžœžœžœ žœžœGžœžœ™õK™M—K˜šœD™DKšœ5™5—K˜KšŸœžœ˜Kšžœžœ˜Kšžœžœ˜KšŸœžœ˜K˜KšŸœžœ ˜KšŸœžœ ˜KšŸœžœ ˜K˜šœF™FKšœ7™7—K˜KšŸœžœ ˜KšŸ œžœ ˜˜Kšœfžœ™x—Kšœ žœžœ˜&š œžœžœž œžœ˜0Kšœ žœ˜Kšœ žœ ˜Kšœ žœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜—K˜Kšœ žœžœ˜(š œžœžœž œžœ˜1Kšœžœ˜ Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜ —K˜K˜šŸ œ*žœ˜>Kš žœ žœžœžœ ž™1Kšœžœ˜*Kš œžœžœžœžœ˜YKšœžœ%˜0Kšœ žœžœžœ˜OšœF™FKšœ7™7—Kš œ žœžœžœ žœ ˜Pšœ žœžœž˜6Kš œžœžœ žœžœ˜=—šœž˜Kš œžœžœžœžœ˜6—Kšœ žœ(˜6K˜]K˜pK˜xK˜uš œžœžœžœžœ˜>Kšœ@™@Kšœp™p—šœžœ˜#K˜K˜!K˜Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜Kšœ-˜-Kšœ3˜3Kšœ˜Kšœžœ˜4Kšœ3˜3Kšœ˜—Kšžœ ˜Kšœ˜——K˜K™™™K™iK™UK™KK™ëK™ŠK™—šŸœ6˜UKšžœ1žœžœžœ ™UKš žœ žœžœžœžœž˜$šœ˜K˜HKšžœ+˜1—Kšœ˜—K˜K˜K˜Kšœ žœžœ˜(Kšœžœžœ žœ žœžœžœžœžœ˜LK˜š Ÿ œžœžœžœ!žœ ˜eKšž˜Kšœžœ ˜!Kšœ˜Kšœžœ˜ Kšœ˜Kšœ˜K˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ%˜%Kšœ˜Kšœ˜Kšœ žœ˜ K˜ Kšœ žœ˜ Kšœžœ˜K˜—Kšžœ ˜Kšžœ˜K™K˜——™K™Kš œžœžœžœžœ˜UKšŸœ*˜C˜Kšœó™ó—Kšœžœžœžœ™šŸœžœžœžœ˜cKšž˜Kšœ˜šžœžœ ™Kšœžœ žœ&™>Kšžœ™ —Kšœžœ žœ&™>š žœ žœžœžœ ž˜'Kšž˜Kšœ žœ"˜0Kšœ;¡ ˜GKšœžœ˜Kšœ žœ˜Kšžœ"žœžœ ˜8Kšžœ&žœ™;š žœžœž œžœž˜*K˜2Kšžœ$žœ žœ3™gš žœžœžœžœž˜JKšœžœ˜—Kšžœžœžœ˜+Kšžœžœžœžœ˜DKšœ žœ˜#Kšžœ˜—šžœžœžœ˜Kšžœ9™;Kšžœ ™Kšœžœ™ Kšžœ ˜—Kšžœ%žœžœ™Tšžœžœžœž˜-K˜2Kšžœ$žœ žœ3™gšžœžœž˜4Kšœžœ˜—Kšžœžœžœžœ˜DKšœ žœ˜#Kšžœ˜—šžœžœžœ˜š žœŸœžœžœž˜)Kšœžœ žœ˜/K˜K˜Kšžœ˜—K˜K˜5Kšœžœ'˜?š žœŸœžœžœž˜*Kšœžœ žœ˜/K˜K˜Kšžœ˜—Kšœ˜——K™™K™šŸœžœžœžœ™CKšžœžœžœ™K™!šž™Kšœžœ™šžœžœžœ™0Kšœ'™'š žœžœžœžœžœ™iKšœžœ™Kšœ:™:Kšœ™—Kšžœžœ™ —Kšžœžœ™ Kšžœ™—Kšžœ™K™—šŸ œžœžœžœ%˜LKšœžœ˜šœ žœžœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ˜Kšœ ˜ Kšœ ˜ Kšžœžœ˜ Kšœ˜Kšœ˜Kšœ ˜ Kšžœžœ˜ Kšžœ˜—K˜K˜—šŸ œžœžœžœžœ žœ#žœžœžœ˜šK˜Kšœ0™0K™%K™Kšžœžœžœ˜Kšœžœ ˜K˜NKšž˜Kšœ˜K˜—K™-Kšœžœžœžœ˜ Kšœ žœžœžœ˜K˜šŸ œžœžœžœ žœ#žœžœ žœ˜™Kšœ žœ˜Kšœžœ˜Kšœžœ˜Kšœ˜Kšžœ5žœ˜OKšžœžœžœžœ˜aK˜-K˜/K˜1šžœžœ˜Kšœ žœžœ˜LK˜K˜—K˜SK˜K˜šžœž˜šžœž˜!K˜šžœ@žœ˜ZKšžœžœžœžœ˜aK˜sK˜K˜ Kšžœ˜———˜Kšœžœ˜ Kšœ ˜ Kšœ˜K˜ Kšœ ˜ Kšœ ˜ Kšœ˜—šžœžœ žœžœ˜1Kšœžœ žœi˜‚Kšœžœ˜*K˜$Kšœžœ˜,K˜&K˜—Kšžœ˜—K˜šŸœžœ˜3KšÏt"™"—K˜K˜šŸ œžœ žœžœžœžœ žœ˜_Kšžœ5˜7K˜KšžœLžœÕ™­—K˜K˜—™ K™KšŸœžœ˜KšŸ œžœžœžœžœžœžœžœžœžœžœ˜iK˜˜Kšœq™q—š Ÿœžœžœžœžœ žœ˜7Kšž˜Kšžœžœžœ˜Kšœ žœžœ˜šžœžœž˜ Kšœžœ¡ ˜+K˜K˜Kšžœžœžœ%˜PK˜š žœŸœžœžœž˜%Kšžœžœžœ˜1Kšžœ˜K˜—K˜Kš žœŸœžœžœ žœžœ˜<š œžœžœžœžœ žœžœ˜9Kšžœžœ žœžœ˜:—Kšžœžœžœ˜%Kšžœ˜—Kšžœ˜ Kšžœ˜—K™K™>šŸœ˜1Kšœ˜K˜˜QKšžœ žœžœ˜%Kšžœ;˜?K˜šžœžœ¡'˜HK˜Kšžœžœžœ ˜#šžœžœž˜Kšœžœ˜#šœžœ˜šžœ6˜:K˜—Kšžœ ˜—šœžœžœ˜4šžœ6˜:K˜—Kšžœžœ˜—Kšžœ˜—Kšžœ˜—Kšžœ˜!K˜—šŸœžœ[žœ žœ˜‚Kšžœžœžœ˜7Kšžœ7žœžœ ˜LKšžœ˜K˜—šŸœžœ2žœ žœ6žœžœžœ˜ÄKšŸœžœ#˜*K˜ šžœ žœž˜/K˜Kšžœžœžœžœ˜šžœž˜Kšœžœ žœžœ˜9šœ ˜ KšŸœžœ˜K˜šžœž˜ K˜Kšžœžœžœ˜!šžœžœž˜Kšœ žœ.˜=K˜bKšœ#žœžœ ˜Kšœ¡º˜ÂKšœžœž˜»Kšžœ-˜3—KšžœžœÑ˜ù—Kšžœ˜—Kšœžœ˜­Kšžœ0˜6—šœ¡˜#KšœžœŸ˜¼Kšžœ0˜6—Kšœ#žœžœžœ˜GKšžœžœÒ˜ú—Kšžœ˜—Kšœžœ˜­Kšžœ7˜=K˜—šŸ œžœ1žœžœ ˜[š žœŸœžœžœ:ž˜JK˜š žœ žœžœžœž˜1Kšœ$˜$Kšœ#žœžœžœ˜GKšžœžœÉ˜ñ—Kšžœ˜—Kšžœ˜K˜—šŸœ+˜?Kš žœžœžœžœžœ™1K˜K˜Kšœ žœžœ˜+šžœžœ˜Kšœ žœ*˜6Kšžœ ˜—šžœžœ žœž˜Kšœ žœ˜$K˜8Kšžœžœžœ ˜%Kšž˜—KšžœžœH˜cK˜K˜š Ðbnœžœžœžœžœ˜%Kšœžœžœžœ˜&K˜—š Ÿœžœžœžœžœ˜4šžœž˜Kšžœ žœ ˜Kšžœžœ ˜——K˜——˜šŸœžœžœ&žœ ˜`šœ˜Kšžœ+˜1—Kšœ˜——˜K˜šŸœžœ&žœ ˜Sšœ˜Kšœžœ˜Kšœžœžœ˜K˜Kšžœžœžœžœ˜(K˜K˜)K˜6K˜™8Kšœ˜šžœžœ˜Kšœ9˜9K˜K˜—Kšœ˜Kšœ žœ˜Kšœžœžœ žœ˜Kšœ4¡˜LK˜K™ š žœ žœžœ&žœ žœž˜Pšžœ!žœ¡˜=šœ˜Kšœ:˜:Kšžœ˜Kšœ˜——šžœ!žœ¡!˜IKšœ˜K˜;Kšœžœžœ žœ ˜'K˜Kšžœžœžœžœ˜˜IKšœžœ@˜KKšœžœ@˜KKšœ žœ&˜3Kšžœžœ!˜5Kšžœ ˜Kšœ˜—˜Kšœ!žœ žœ0™bK™K—™™ K™K™K™——š Ÿ œžœžœžœ žœ˜IK˜BK˜BK˜B˜Kšœ#™#—Kšžœžœžœ˜"Kšžœžœžœ˜#KšžœžœžœÐci˜2˜Kšœ%™%—Kšžœžœžœ˜"Kšžœžœžœ˜#Kšžœžœžœ˜!Kšžœžœžœ¤˜2Kšžœžœžœ¤˜1˜K™—Kšžœžœžœ˜"Kšžœžœžœ˜%Kšžœžœžœ˜!KšžœžœžœÏi¤˜4Kšžœžœžœ¤˜2Kšžœžœžœ¥¤˜3K˜Kšœ žœžœ(˜KKšœ˜—˜™ Kšœ™——š Ÿ œžœžœžœ žœ˜;K˜B˜Kšœ$™$—Kšžœžœžœ˜"Kšžœžœžœ˜%Kšžœžœžœ˜!Kšžœžœžœ¥¤˜4Kšžœžœžœ¥¤˜3K˜K˜Kšœ˜—K˜šœžœžœ˜'KšœQžœ˜XK˜—šŸœžœžœžœ˜Mšžœ˜Kšœ žœ žœ ˜1Kšœžœ˜)Kšœ žœ žœ ˜3Kšœ žœ žœ ˜0Kšœ žœ žœ ˜5Kšœ žœ žœ ˜2Kšœžœ˜&Kšœžœ˜'Kšœ žœ žœ ˜4Kšœ žœ žœ ˜4Kšœ žœ žœ ˜0Kšœ žœžœ ˜4Kšœ žœ ˜Kšœžœ˜$Kšœ žœžœ˜6—Kšœ˜K˜———K˜K™ ™Kšœ2žœ-˜bKšœM˜MK˜*K˜*K˜*šœ,žœ,˜[Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ˜Kšžœ˜K˜K˜K˜Kšžœ˜K˜Kšœ˜K˜Kšœ˜Kšœ ˜ ——K™Kšžœ˜—…—~Ãt