<> <> <> <<>> DIRECTORY Ascii, Basics, CardTab, Convert, FS, IO, PrincOps, PrincOpsUtils, RedBlackTree, RefText, Rope, SunADotOut, SunADotOutPrivate, VM; SunADotOutImpl: CEDAR MONITOR IMPORTS Basics, CardTab, Convert, FS, IO, PrincOpsUtils, RedBlackTree, RefText, Rope, VM EXPORTS SunADotOut ~ { OPEN SunADotOutPrivate; ROPE: TYPE ~ Rope.ROPE; Error: PUBLIC ERROR [ msg: ROPE ] ~ CODE; <> Module: TYPE ~ REF ModuleObject; ModuleObject: PUBLIC TYPE ~ SunADotOutPrivate.ModuleObject; ModuleFromFile: PUBLIC PROC [ filename: ROPE ] RETURNS [ m: Module _ NIL ] ~ { s: IO.STREAM ~ OpenFile[filename]; m _ ModuleFromStream[s]; m.filename _ filename; }; ModuleFromStream: PUBLIC PROC [ s: IO.STREAM ] RETURNS [ m: Module _ NIL ] ~ { m _ NEW[ModuleObject]; m.filename _ NIL; m.header _ ReadHeader[s]; { bytes: CARD32 ~ m.header.text; interval: VM.Interval ~ GetInterval[s, bytes]; block: Basics.UnsafeBlock ~ [VM.AddressForPageNumber[interval.page], 0, bytes]; m.programText _ [interval, block, 0]; }; { bytes: CARD32 ~ m.header.data; interval: VM.Interval ~ GetInterval[s, bytes]; block: Basics.UnsafeBlock ~ [VM.AddressForPageNumber[interval.page], 0, bytes]; m.programData _ [interval, block, 0]; }; { bytes: CARD32 ~ m.header.bss; interval: VM.Interval ~ VM.nullInterval; block: Basics.UnsafeBlock ~ [NIL, 0, bytes]; m.programBss _ [interval, block, 0]; }; m.textRelocation _ GetRelocationInfo[s, m.header.textRelocationSize]; m.dataRelocation _ GetRelocationInfo[s, m.header.dataRelocationSize]; { namelist: LIST OF Symbol; table: CardTab.Ref; symbolTableBase: CARD32 ~ SymbolTablePosition[m]; -- or s.GetIndex[]; [namelist, table] _ GetSymbolTable[s, m.header.symbolTableSize]; m.header.stringTableSize _ GrabSymbols[s, namelist]; m.symbolTable _ RecordInterestingSymbols[namelist]; TranslateSymbolIndex[table, m.textRelocation, symbolTableBase]; TranslateSymbolIndex[table, m.dataRelocation, symbolTableBase]; table.Erase[]; table _ NIL; -- throw it away now! }; }; WriteModule: PUBLIC PROC [ s: IO.STREAM, m: Module ] ~ { WriteHeader[s, m.header]; }; <> GetKey: RedBlackTree.GetKey ~ { symbol: Symbol ~ NARROW[data]; key: ROPE ~ symbol.text; RETURN[key]; }; Compare: RedBlackTree.Compare ~ { t1: ROPE ~ NARROW[k]; t2: ROPE ~ NARROW[GetKey[data]]; relation: Basics.Comparison ~ Rope.Compare[t1, t2, TRUE]; RETURN[relation]; }; CreateProc: TYPE ~ PROC [ key: ROPE ] RETURNS [ s: Symbol ]; FindEntry: PROC [ table: RedBlackTree.Table, key: ROPE, create: CreateProc _ NIL ] RETURNS [ symbol: Symbol _ NIL ] ~ { WITH table.Lookup[key] SELECT FROM s: Symbol => { symbol _ s }; ENDCASE => { IF ( create = NIL ) THEN ERROR; -- avoid procedure error runtime event! symbol _ create[key]; table.Insert[symbol, key] }; }; UndefinedNames: PUBLIC PROC [ m: Module, itemProc: SunADotOut.ItemProc ] ~ { table: RedBlackTree.Table ~ m.symbolTable; EachNode: RedBlackTree.EachNode ~ { s: Symbol ~ NARROW[data]; IF ( s.type = undefined ) THEN itemProc[s.text, s]; }; IF ( itemProc = NIL ) THEN ERROR; table.EnumerateIncreasing[EachNode]; }; RecordInterestingSymbols: PROC [ namelist: LIST OF Symbol, m: Module _ NIL ] RETURNS [ table: RedBlackTree.Table _ NIL ] ~ { table _ RedBlackTree.Create[getKey: GetKey, compare: Compare]; FOR item: LIST OF Symbol _ namelist, item.rest WHILE ( item # NIL ) DO s: Symbol ~ item.first; IF ( m # NIL ) THEN s.backlink _ m; SELECT TRUE FROM ( s.permanent ) => { NULL }; ( s.text = NIL ) => { ERROR }; <<( s.type # undefined ) => { NULL };>> ENDCASE => { table.Insert[s, s.text] }; ENDLOOP; }; <> HdrPtr: TYPE ~ LONG POINTER TO HdrObj; HdrPreamble: TYPE ~ MACHINE DEPENDENT RECORD [ dynamic(0:0..0): BOOL, toolVersion(0:1..7): [0..128), machineType(0:8..15): BYTE, magic(0:16..31): Basics.HWORD ]; HdrObj: TYPE ~ MACHINE DEPENDENT RECORD [ preamble: HdrPreamble, text: Basics.FWORD, data: Basics.FWORD, bss: Basics.FWORD, symbolTableSize: Basics.FWORD, entryPoint: Basics.FWORD, textRelocationSize: Basics.FWORD, dataRelocationSize: Basics.FWORD ]; Header: TYPE ~ REF HeaderObject; HeaderObject: PUBLIC TYPE ~ SunADotOutPrivate.HeaderObject; rawHdrSiz: NAT ~ BYTES[HdrObj]; ReadHeader: PROC [ s: IO.STREAM ] RETURNS [ h: Header _ NIL ] ~ TRUSTED { hdrobj: HdrObj; buf: Basics.UnsafeBlock ~ [@hdrobj, 0, rawHdrSiz]; IF ( s.UnsafeGetBlock[buf] # rawHdrSiz ) THEN ERROR; h _ NEW[HeaderObject]; h.dynamic _ hdrobj.preamble.dynamic; h.toolVersion _ hdrobj.preamble.toolVersion; h.machineType _ VAL[hdrobj.preamble.machineType]; h.magic _ VAL[Basics.Card16FromH[hdrobj.preamble.magic]]; h.text _ Basics.Card32FromF[hdrobj.text]; IF ( h.magic = ZMAGIC ) THEN h.text _ h.text - rawHdrSiz; -- silly rabbit! h.data _ Basics.Card32FromF[hdrobj.data]; h.bss _ Basics.Card32FromF[hdrobj.bss]; h.symbolTableSize _ Basics.Card32FromF[hdrobj.symbolTableSize]; h.entryPoint _ Basics.Card32FromF[hdrobj.entryPoint]; h.textRelocationSize _ Basics.Card32FromF[hdrobj.textRelocationSize]; h.dataRelocationSize _ Basics.Card32FromF[hdrobj.dataRelocationSize]; h.stringTableSize _ 0; }; WriteHeader: PROC [ s: IO.STREAM, h: Header ] ~ TRUSTED { hdrobj: HdrObj; buf: Basics.UnsafeBlock ~ [@hdrobj, 0, rawHdrSiz]; hdrobj.preamble.dynamic _ h.dynamic; hdrobj.preamble.toolVersion _ h.toolVersion; hdrobj.preamble.machineType _ ORD[h.machineType]; hdrobj.preamble.magic _ Basics.HFromCard16[ORD[h.magic]]; hdrobj.text _ Basics.FFromCard32[h.text]; hdrobj.data _ Basics.FFromCard32[h.data]; hdrobj.bss _ Basics.FFromCard32[h.bss]; hdrobj.symbolTableSize _ Basics.FFromCard32[h.symbolTableSize]; hdrobj.entryPoint _ Basics.FFromCard32[h.entryPoint]; hdrobj.textRelocationSize _ Basics.FFromCard32[h.textRelocationSize]; hdrobj.dataRelocationSize _ Basics.FFromCard32[h.dataRelocationSize]; s.UnsafePutBlock[buf]; }; <> RelocationInfo: TYPE ~ REF RelocationInfoObject; RelocationInfoObject: TYPE ~ SunADotOutPrivate.RelocationInfoObject; IndexFor: PROC [ table: CardTab.Ref, text: ROPE ] RETURNS [ index: CARD32 _ 0 ] ~ { EachPairAction: CardTab.EachPairAction ~ { s: Symbol ~ NARROW[val]; IF ( text.Equal[s.text] ) THEN { index _ key; quit _ TRUE }; }; [] _ table.Pairs[EachPairAction]; }; DumpIndexTable: PROC [ out: IO.STREAM, table: CardTab.Ref ] ~ { EachPairAction: CardTab.EachPairAction ~ { s: Symbol ~ NARROW[val]; out.PutF["\t%g: %g\n", IO.card[key], IO.rope[s.text]]; }; out.PutF["Index Table\n"]; [] _ table.Pairs[EachPairAction]; }; TranslateSymbolIndex: PROC [ table: CardTab.Ref, list: LIST OF RelocationInfo, symbolTableBase: CARD32 ] ~ { FOR item: LIST OF RelocationInfo _ list, item.rest WHILE ( item # NIL ) DO <> IF ( NOT item.first.extern ) THEN LOOP; WITH table.Fetch[item.first.index].val SELECT FROM s: Symbol => { item.first.symbol _ s }; ENDCASE => { ERROR }; ENDLOOP; }; InfoGetKey: RedBlackTree.GetKey ~ { RETURN[data]; }; InfoCompare: RedBlackTree.Compare ~ { t1: RelocationInfo ~ NARROW[k]; t2: RelocationInfo ~ NARROW[data]; SELECT ( t2.address ) FROM -- backward for CONS[item, list] < t1.address => { RETURN[less] }; = t1.address => { RETURN[equal] }; > t1.address => { RETURN[greater] }; ENDCASE => { ERROR }; }; RelObj: TYPE ~ MACHINE DEPENDENT RECORD [ addr : Basics.FWORD, oddBytes: PACKED ARRAY [0..BYTES[Basics.FWORD]) OF CHAR, addend: Basics.FWORD ]; BuildRelocationList: PROC [ interval: VM.Interval, bytes: CARD32 ] RETURNS [ list: LIST OF RelocationInfo _ NIL ] ~ { table: RedBlackTree.Table ~ RedBlackTree.Create[InfoGetKey, InfoCompare]; EachNode: RedBlackTree.EachNode ~ { info: RelocationInfo ~ NARROW[data]; list _ CONS[info, list]; }; SniffCard24: PROC [ buf: PACKED ARRAY [0..BYTES[Basics.FWORD]) OF CHAR ] RETURNS [ c: CARD32 ] ~ TRUSTED { long: Basics.LongNumber ~ [bytes[ lh: buf[1].ORD, ll: buf[2].ORD, hh: 0, hl: buf[0].ORD ]]; c _ long.lc; }; base: LONG POINTER ~ VM.AddressForPageNumber[interval.page]; entries: CARD32 ~ bytes / BYTES[RelObj]; IF ( entries = 0 ) THEN RETURN; FOR i: CARD32 IN [0..entries) DO info: RelocationInfo ~ NEW[RelocationInfoObject]; TRUSTED { finger: LONG POINTER TO RelObj ~ LOOPHOLE[base + ( i * SIZE[RelObj] )]; info.address _ Basics.Card32FromF[finger.addr]; info.index _ SniffCard24[finger.oddBytes]; info.extern _ ( Basics.BITAND[finger.oddBytes[3].ORD, 080H] # 0 ); IF ( NOT info.extern ) THEN info.basedUpon _ VAL[finger.oddBytes[2].ORD]; -- fishy! info.unused _ finger.oddBytes[3].ORD; info.type _ VAL[Basics.BITAND[finger.oddBytes[3].ORD, 01FH]]; info.addend _ Basics.Int32FromF[finger.addend]; }; table.Insert[info, info]; ENDLOOP; table.EnumerateIncreasing[EachNode]; }; GetRelocationInfo: PROC [ s: IO.STREAM, bytes: CARD32 ] RETURNS [ list: LIST OF RelocationInfo _ NIL ] ~ { interval: VM.Interval ~ GetInterval[s, bytes]; list _ BuildRelocationList[interval, bytes]; TRUSTED { VM.Free[interval] }; }; <> Symbol: TYPE ~ REF SymbolObject; SymbolObject: TYPE ~ SunADotOutPrivate.SymbolObject; SymObj: TYPE ~ MACHINE DEPENDENT RECORD [ stringTableIndex: Basics.FWORD, oddBytes: PACKED ARRAY [0..BYTES[Basics.FWORD]) OF CHAR, value: Basics.FWORD ]; BuildNameList: PROC [ interval: VM.Interval, bytes: CARD32 ] RETURNS [ list: LIST OF Symbol _ NIL, table: CardTab.Ref _ NIL ] ~ TRUSTED { base: LONG POINTER ~ VM.AddressForPageNumber[interval.page]; entries: CARD32 ~ bytes / BYTES[SymObj]; table _ CardTab.Create[]; IF ( entries = 0 ) THEN RETURN; FOR i: CARD32 IN [0..entries) DO symbol: Symbol ~ NEW[SymbolObject]; index: CARD32 ~ i * BYTES[SymObj]; finger: LONG POINTER TO SymObj ~ LOOPHOLE[base + ( i * SIZE[SymObj] )]; symbol.text _ NIL; symbol.stringTableIndex _ Basics.Card32FromF[finger.stringTableIndex]; symbol.type _ VAL[Basics.BITAND[finger.oddBytes[0].ORD, NAMETYPEMASK]]; symbol.description _ VAL[Basics.BITAND[finger.oddBytes[0].ORD, SYMDESCMASK]]; symbol.permanent _ ( symbol.description # notPermanent ); symbol.other _ finger.oddBytes[1].ORD; symbol.details _ Basics.Card16FromH[ [hi: finger.oddBytes[2].ORD, lo: finger.oddBytes[3].ORD] ]; symbol.value _ Basics.Card32FromF[finger.value]; IF ( NOT table.Insert[i, symbol] ) THEN ERROR; -- index list _ CONS[symbol, list]; ENDLOOP; }; <> GetSymbolTable: PROC [ s: IO.STREAM, bytes: CARD32 ] RETURNS [ namelist: LIST OF Symbol _ NIL, table: CardTab.Ref _ NIL ] ~ { interval: VM.Interval ~ GetInterval[s, bytes]; [namelist, table] _ BuildNameList[interval, bytes]; TRUSTED { VM.Free[interval] }; }; GetCard32: PROC [ s: IO.STREAM ] RETURNS [ c: CARD32 _ 0 ] ~ { buf: REF TEXT ~ RefText.ObtainScratch[BYTES[Basics.FWORD]]; IF ( CARD32[s.GetBlock[block: buf, count: BYTES[Basics.FWORD]]] # BYTES[Basics.FWORD] ) THEN ERROR; { long: Basics.LongNumber ~ [bytes[ lh: buf[2].ORD, ll: buf[3].ORD, hh: buf[0].ORD, hl: buf[1].ORD]]; c _ long.lc; }; RefText.ReleaseScratch[buf]; }; maxString: NAT ~ 512; GetStringFromTable: PROC [ stringBlock: Basics.UnsafeBlock, stringTableIndex: CARD32 ] RETURNS [ text: ROPE _ NIL ] ~ TRUSTED { GetByte: PROC [ i: CARD32 ] RETURNS [ ch: CHAR ] ~ TRUSTED { buf: PACKED ARRAY [0..BYTES[Basics.FWORD]) OF CHAR; to: PrincOps.ByteBltBlock ~ [@buf, 0, 1]; from: PrincOps.ByteBltBlock ~ [stringBlock.base, i, i.SUCC]; IF ( PrincOpsUtils.ByteBlt[to, from] # 1) THEN ERROR; ch _ buf[0]; }; IF ( stringTableIndex = 0 ) THEN RETURN; stringTableIndex _ stringTableIndex - BYTES[Basics.FWORD]; <> { buf: REF TEXT ~ RefText.ObtainScratch[maxString]; FOR i: NAT IN [0..maxString) DO buf[i] _ GetByte[stringTableIndex + i]; IF ( buf[i] = Ascii.NUL ) THEN { buf.length _ i; EXIT }; ENDLOOP; text _ Rope.FromRefText[buf]; RefText.ReleaseScratch[buf]; }; }; GrabSymbols: PROC [ s: IO.STREAM, namelist: LIST OF Symbol ] RETURNS [ size: CARD32 ] ~ { bytes: CARD32 ~ GetCard32[s] - BYTES[Basics.FWORD]; interval: VM.Interval ~ GetInterval[s, bytes]; stringBlock: Basics.UnsafeBlock ~ [VM.AddressForPageNumber[interval.page], 0, bytes - 4]; <> FOR item: LIST OF Symbol _ namelist, item.rest WHILE ( item # NIL ) DO item.first.text _ GetStringFromTable[stringBlock, item.first.stringTableIndex]; ENDLOOP; TRUSTED { VM.Free[interval] }; size _ bytes; }; <> OpenFile: PROC [ name: ROPE ] RETURNS [ s: IO.STREAM _ NIL ] ~ { ENABLE { UNWIND => { NULL }; FS.Error => { IF ( error.group = user ) THEN { Error[Rope.Concat["FS.Error: ", error.explanation]] } }; }; s _ FS.StreamOpen[name, $read]; }; bufSiz: NAT _ 8192; GetInterval: PROC [ s: IO.STREAM, bytes: CARD32 ] RETURNS [ interval: VM.Interval _ VM.nullInterval ] ~ { count: VM.PageCount ~ VM.PagesForBytes[bytes]; chunk: CARD32 ~ VM.PagesForBytes[bufSiz]; IF ( count <= 0 ) THEN RETURN; interval _ VM.SimpleAllocate[count]; FOR p: CARD32 _ 0, p + chunk WHILE ( p < CARD32[count] ) DO stuff: CARD32 ~ MIN[bytes, bufSiz]; finger: LONG POINTER ~ VM.AddressForPageNumber[interval.page + p]; block: Basics.UnsafeBlock ~ [finger, 0, stuff]; TRUSTED { IF ( CARD32[s.UnsafeGetBlock[block]] # stuff ) THEN ERROR }; bytes _ bytes - stuff; ENDLOOP; }; SymbolTablePosition: PROC [ m: Module ] RETURNS [ c: CARD32 ] ~ { h: Header ~ m.header; c _ rawHdrSiz + h.text + h.data + h.textRelocationSize + h.dataRelocationSize; }; ComputeSize: PROC [ m: Module ] RETURNS [ c: CARD32 ] ~ { h: Header ~ m.header; c _ rawHdrSiz + h.text + h.data + h.textRelocationSize + h.dataRelocationSize + h.symbolTableSize + BYTES[Basics.FWORD] + h.stringTableSize; }; <> DisplaySymbolDescription: PROC [ out: IO.STREAM, d: SymbolDescription ] ~ { name: ROPE ~ SELECT d FROM notPermanent => "notPermanent", GSYM => "GSYM", FNAME => "FNAME", FUN => "FUN", STSYM => "STSYM", LCSYM => "LCSYM", MAIN => "MAIN", PC => "PC", RSYM => "RSYM", M2C => "M2C", SLINE => "SLINE", SSYM => "SSYM", SO => "SO", LSYM => "LSYM", BINCL => "BINCL", SOL => "SOL", PSYM => "PSYM", EINCL => "EINCL", ALTENTRY => "ALTENTRY", LBRAC => "LBRAC", EXCL => "EXCL", SCOPE => "SCOPE", RBRAC => "RBRAC", BCOMM => "BCOMM", ECOMM => "ECOMM", ECOML => "ECOML", LENG => "LENG", ENDCASE => Rope.Concat["unknown - ", Convert.RopeFromCard[d.ORD]]; out.PutF[" desc: %g", IO.rope[name]]; }; DisplayNameType: PROC [ out: IO.STREAM, type: NameType ] ~ { name: ROPE ~ SELECT type FROM undefined => "undefined", absolute => "absolute", text => "text", data => "data", bss => "bss", common => "common", fileNameSymbol => "fileNameSymbol", ENDCASE => Rope.Concat["unknown - ", Convert.RopeFromCard[type.ORD]]; out.PutF[" nametype: %g", IO.rope[name]]; }; DisplayMachineType: PROC [ out: IO.STREAM, m: MachineType ] ~ { name: ROPE ~ SELECT m FROM oldSun2 => "oldSun2", mc68010 => "mc68010", mc68020 => "mc68020", sparc => "sparc", ENDCASE => Rope.Concat["unknown - ", Convert.RopeFromCard[m.ORD]]; out.PutF[" machineType: %g", IO.rope[name]]; }; DisplayMagic: PROC [ out: IO.STREAM, m: Magic ] ~ { name: ROPE ~ SELECT m FROM OMAGIC => "OMAGIC", NMAGIC => "NMAGIC", ZMAGIC => "ZMAGIC", ENDCASE => Rope.Concat["unknown - ", Convert.RopeFromCard[m.ORD]]; out.PutF[" magic: %g", IO.rope[name]]; }; DisplayHeader: PROC [ out: IO.STREAM, h: Header ] ~ { out.PutF[" dynamic: %g\n", IO.bool[h.dynamic]]; out.PutF[" toolVersion: %g\n", IO.card[h.toolVersion]]; DisplayMachineType[out, h.machineType]; out.PutF["\n"]; DisplayMagic[out, h.magic]; out.PutF["\n"]; out.PutF[" text segment size: %g\n", IO.card[h.text]]; out.PutF[" initialized data size: %g\n", IO.card[h.data]]; out.PutF[" un-initialized data size: %g\n", IO.card[h.bss]]; out.PutF[" symbolTableSize: %g\n", IO.card[h.symbolTableSize]]; out.PutF[" entryPoint: %g\n", IO.card[h.entryPoint]]; out.PutF[" textRelocationSize: %g\n", IO.card[h.textRelocationSize]]; out.PutF[" dataRelocationSize: %g\n", IO.card[h.dataRelocationSize]]; out.PutF[" stringTableSize: %g\n", IO.card[h.stringTableSize]]; }; DisplayNameList: PROC [ out: IO.STREAM, list: LIST OF Symbol ] ~ { FOR item: LIST OF Symbol _ list, item.rest WHILE ( item # NIL ) DO out.PutF[" text: %g\n", IO.rope[item.first.text]]; IF ( item.first.text = NIL ) THEN out.PutF[" stringTableIndex: %g\n", IO.card[item.first.stringTableIndex]]; DisplayNameType[out, item.first.type]; out.PutF["\n"]; out.PutF[" permanent: %g\n", IO.bool[item.first.permanent]]; <> DisplaySymbolDescription[out, item.first.description]; out.PutF["\n"]; out.PutF[" other: %g\n", IO.card[item.first.other]]; out.PutF[" details: %g\n", IO.card[item.first.details]]; out.PutF[" value: %g\n", IO.card[item.first.value]]; out.PutF["\n"]; ENDLOOP; }; DisplayTable: PROC [ out: IO.STREAM, table: RedBlackTree.Table ] ~ { EachNode: RedBlackTree.EachNode ~ { s: Symbol ~ NARROW[data]; out.PutF[" %g", IO.rope[s.text]]; out.PutF["\n\t"]; out.PutF["value: %g", IO.card[s.value]]; DisplayNameType[out, s.type]; out.PutF[" other: %g", IO.card[s.other]]; out.PutF[" details: %g", IO.card[s.details]]; out.PutF["\n"]; }; table.EnumerateIncreasing[EachNode]; }; DisplayUndefinedSymbols: PROC [ out: IO.STREAM, m: Module ] ~ { some: BOOL _ FALSE; DisplaySymbol: SunADotOut.ItemProc ~ { some _ TRUE; out.PutF["\t%g\n", IO.rope[text]]; }; out.PutF[" Unresolved symbols:\n"]; out.PutF["\t\n"]; UndefinedNames[m, DisplaySymbol]; IF ( some ) THEN { out.PutF["\t\n"]; out.PutF["\tdone.\n"] } ELSE { out.PutF["\tno unresolved symbols.\n"] }; }; DisplayModule: PROC [ out: IO.STREAM, m: Module ] ~ { out.PutF["log for filename: %g\n", IO.rope[m.filename]]; out.PutF["\n"]; DisplayUndefinedSymbols[out, m]; out.PutF["\n"]; DisplayHeader[out, m.header]; out.PutF[" computed file size: %g\n", IO.card[ComputeSize[m]]]; out.PutF["\n"]; DisplayTable[out, m.symbolTable]; out.PutF["\n"]; }; test: Module; TestLoader: PROC ~ { out: IO.STREAM ~ FS.StreamOpen["SparcLoader.log", $create]; test _ ModuleFromFile["Sun4>SparcLoaderTestCodeImpl.C2C.o"]; DisplayModule[out, test]; out.Close[]; }; }.