Types
ROPE: TYPE ~ Rope.ROPE;
ProgramCounter: TYPE = CARD32;
PC: TYPE = CARD32;
WatchAllocationType:
TYPE ~ Spy.WatchAllocationType;
Ref: TYPE ~ REF SpyRep;
SpyRep:
PUBLIC
TYPE ~
RECORD [
missed: CARD32 ¬ 0,
startTime: BasicTime.GMT,
stopTime: BasicTime.GMT,
watchAllocations: WatchAllocationType ¬ none,
tree: Tree,
nAlloc: NAT,
limit: NAT,
pAlloc: POINTER TO MemUnit,
stackBuffer: ARRAY [0..stackLimit) OF ProgramCounter,
mem: SEQUENCE size: NAT OF MemUnit
];
Tree: TYPE ~ POINTER TO TreeRep;
TreeRep:
TYPE ~
RECORD [
children: OSet ¬ NIL,
pc: ProgramCounter ¬ 0,
count: CARD ¬ 0
];
OSet: TYPE = POINTER TO OSetNode; -- represents an pc-ordered collection of Tree objects
OSetNode:
TYPE =
RECORD [
left: OSet, -- smaller items
item: Tree, -- the child tree
right: OSet -- larger items
];
MemUnit:
TYPE ~
RECORD [
oSetNode: OSetNode,
treeRep: TreeRep
];
Starting and Stopping
Start:
PUBLIC
ENTRY
SAFE
PROC [watchThreadSwitches:
BOOL ¬
TRUE, watchAllocations: WatchAllocationType ¬ none, watchSignals:
BOOL ¬
FALSE, count:
NAT ¬ 10000]
RETURNS [
BOOL] ~
TRUSTED {
spy: Ref ¬ NIL;
IF running THEN RETURN [FALSE];
lock ¬ ALL[BYTE.LAST];
spy ¬ SafeStorage.GetUntracedZone[].NEW[SpyRep[count + 1]];
spy.nAlloc ¬ 0;
spy.limit ¬ spy.size-1;
spy.pAlloc ¬ @(spy.mem[0]);
spy.tree ¬ GetAvail[spy].item;
spy.tree ¬ [children: NIL, pc: 0, count: 0];
spy.startTime ¬ BasicTime.Now[];
spy.stopTime ¬ BasicTime.nullGMT;
spy.watchAllocations ¬ watchAllocations;
IF watchThreadSwitches
THEN {
RegisterSwitchCallback[ExamineNavel, NIL];
Process.Detach[FORK Preemptor];
};
SELECT watchAllocations
FROM
$count => RegisterAllocatorCallback[CountAllocations, NIL];
$words => RegisterAllocatorCallback[CountWordsAllocated, NIL];
ENDCASE;
IF watchSignals
THEN {
RegisterSignalCallback[ExamineNavel];
};
currentSpy ¬ spy;
running ¬ TRUE;
missed ¬ 0;
lock ¬ ALL[0];
RETURN[TRUE];
};
Stop:
PUBLIC
ENTRY
SAFE
PROC
RETURNS [Ref] ~
TRUSTED {
ENABLE UNWIND => NULL;
IF NOT running THEN RETURN [NIL];
RegisterSwitchCallback[NIL, NIL];
RegisterAllocatorCallback[NIL, NIL];
RegisterSignalCallback[NIL];
FOR i:
NAT
IN [0..100)
DO
spy: Ref ~ ObtainSpy[];
IF spy #
NIL
THEN {
spy.missed ¬ missed;
currentSpy ¬ NIL;
spy.stopTime ¬ BasicTime.Now[];
running ¬ FALSE;
RETURN[spy];
};
IF i MOD 16 = 0 THEN Process.Pause[1] ELSE Process.Yield[];
ENDLOOP;
currentSpy ¬ NIL;
running ¬ FALSE;
lock ¬ ALL[BYTE.LAST];
RETURN [NIL];
};
Stack sampling
JmpBufPtr: TYPE ~ POINTER TO JmpBuf;
JmpBuf:
TYPE ~ <<VERY>>
MACHINE
DEPENDENT
RECORD [
pc: PC, -- the program counter
fp: WORD, -- Not used on SPARCs
sp: SP -- the stack pointer
];
SP: TYPE = POINTER TO StackFrame;
StackFrame:
TYPE ~ <<VERY>>
MACHINE
DEPENDENT
RECORD [
local: ARRAY [0..8) OF CARD32,
in: ARRAY [0..6) OF CARD32,
callersSP: SP,
callersPC: PC
];
SigtrampStackFrame:
TYPE ~ <<
VERY>>
MACHINE
DEPENDENT
RECORD [
(c.f. .../SUNSRC/lib/libc/sys/common/sparc/sigtramp.s)
frame: StackFrame,
signalNumber: CARD,
signalCode: CARD,
sigcontextPtr: POINTER TO SigContext,
addr: CARD
];
ForceStackToMemory:
PROC [jb: JmpBufPtr]
~ TRUSTED MACHINE CODE { "XR𡤏orceStackToMemory" };
ProcessRep:
TYPE ~
MACHINE DEPENDENT RECORD [
thread: POINTER TO ThreadRep,
generationNumber: CARD
];
ThreadRep:
TYPE ~
UNSPECIFIED;
-- don't care about details.
SigContext:
TYPE ~
MACHINE <<SunOS>>
DEPENDENT
RECORD [
flag: CARD32, -- on signal stack flag
mask: CARD32, -- old signal mask
oldSP: SP, -- old sp
oldPC: PC, -- old pc
oldNPC: PC, --old npc
oldPSR: CARD32, -- old psr
oldG1: CARD32, -- old g1
oldO1: CARD32 -- old o0
];
ILSymEntryRep:
TYPE ~
MACHINE DEPENDENT RECORD [
--
IncrementalLoad.h
name: POINTER TO Basics.RawChars,
type: CARD32,
value: CARD32,
size: CARD32,
ilfe: POINTER TO ILFileEntryRep
];
ILFileEntryRep:
TYPE ~
MACHINE DEPENDENT RECORD [
--
IncrementalLoad.h
seqNum: CARD32,
commitPoint: CARD,
fName: POINTER TO Basics.RawChars
etc...
];
sigtrampName: PACKED ARRAY [0..12) OF CHAR ¬ ['←,'←,'s,'i,'g,'t,'r,'a,'m,'p,'\000,'\000];
sigtrampStart, sigtrampEnd: PC ¬ 0; -- init below
SigTrampStart: PROC RETURNS [PC] = {RETURN [sigtrampStart]};
SigTrampEnd: PROC RETURNS [PC] = {RETURN [sigtrampEnd]};
InitSigtramp:
PROC ~ {
ILGetMatchingSymEntryByValue:
PROC [ilse:
POINTER
TO ILSymEntryRep, val:
CARD, wantedTypes:
CARD, ignoredClasses:
CARD, numToSkip:
INT]
RETURNS [
POINTER
TO ILSymEntryRep] ~
MACHINE
CODE {
"XR←ILGetMatchingSymEntryByValue"
};
ILGetMatchingSymEntryByName:
PROC [ilse:
POINTER
TO ILSymEntryRep, pattern:
POINTER, caseSensitive:
BOOL, wantedTypes:
CARD, ignoredClasses:
CARD, numToSkip:
INT]
RETURNS [
POINTER
TO ILSymEntryRep] ~
MACHINE
CODE {
"XR←ILGetMatchingSymEntryByName"
};
e1:
POINTER
TO ILSymEntryRep ~ ILGetMatchingSymEntryByName[
ilse: NIL,
pattern: @sigtrampName,
caseSensitive: TRUE,
wantedTypes: 4, -- text
ignoredClasses: 0,
numToSkip: 1];
IF e1 #
NIL
THEN {
e2:
POINTER
TO ILSymEntryRep ~ ILGetMatchingSymEntryByValue[
ilse: e1,
val: 0,
wantedTypes: 4, -- text
ignoredClasses: 0,
numToSkip: 1
];
sigtrampStart ¬ e1.value;
sigtrampEnd ¬ e2.value;
};
};
debug:
RECORD [
badPCcount: CARD ¬ 0,
badPC: CARD ¬ 0,
badSPcount: CARD ¬ 0,
prevSP: CARD ¬ 0,
badSP: CARD ¬ 0,
sigtrampCount: CARD ¬ 0,
goodSigtrampCount: CARD ¬ 0,
zeroSP: CARD ¬ 0
];
SpyDebugData:
PROC
RETURNS [
POINTER] ~ {
RETURN [@debug]
};
SpyDebugReset:
PROC ~ {
debug ¬ [];
};
ValidSP:
PROC [sp:
SP, prevSP:
SP]
RETURNS [
BOOL] ~ {
After seeing some bogus backpointers that miraculously cure themselves by the time any kind of debugger can look at this stack, the check against prevSP was added.
IF sp = NIL THEN RETURN [FALSE]; -- the only invalid SP we really expect to see.
IF LOOPHOLE[sp, CARD] >= 8*1024 AND LOOPHOLE[sp, CARD] MOD 8 = 0 AND (prevSP = NIL OR LOOPHOLE[sp, CARD]-LOOPHOLE[prevSP, CARD] <=16*1024) THEN RETURN [TRUE];
This SP looks funny; squirrel it away to look at later.
debug.badSPcount ¬ debug.badSPcount + 1;
debug.badSP ¬ LOOPHOLE[sp];
debug.prevSP ¬ LOOPHOLE[prevSP];
RETURN [FALSE]
};
trapPC: PC ¬ 0;
SetTrapPC:
PROC [pc:
PC]
RETURNS [old:
PC] ~ {
old ¬ trapPC;
trapPC ¬ pc;
};
ValidPC:
PROC [pc:
PC]
RETURNS [
BOOL] ~
INLINE {
RETURN [pc >= 8*1024 AND pc MOD 4 = 0]
};
startProcess: PROCESS ¬ Process.GetCurrent[];
safeProcess: PROCESS ¬ NIL;
Debug: PROC ~ { safeProcess ¬ startProcess };
Help:
PROC = {
ENABLE ABORTED => CONTINUE;
safeProcess ¬ NIL;
ERROR;
};
GetThreadIndex:
UNSAFE
PROC [ct:
POINTER
TO ProcessRep]
RETURNS [[0..2**14)] ~
UNCHECKED {
CirioNubLocalGetThreadIndex: UNSAFE PROC [ct: POINTER] RETURNS [INT32] ~ MACHINE CODE {"<xr/CirioNubLocalProcs.h>.CirioNubLocalGetThreadIndex"};
ans: INT ¬ CirioNubLocalGetThreadIndex[ct];
RETURN [IF ans IN [0..2**14) THEN ans ELSE 2**14-1];
};
SampleMyStack:
PUBLIC
SAFE
PROC [ignoreHottest:
NAT, increment:
CARD] ~
TRUSTED {
self: ProcessRep ¬ LOOPHOLE[Process.GetCurrent[]];
selfID: CARD ~ (GetThreadIndex[@self]*32768+self.generationNumber)*4+3;
jb: JmpBuf;
k: NAT ¬ 0;
spy: Ref ¬ ObtainSpy[];
IF spy = NIL THEN {missed ¬ missed + 1; RETURN};
IF spy.tree #
NIL
THEN {
t: Tree ¬ spy.tree;
prevSP: SP ¬ NIL;
nextSP: SP ¬ NIL;
ForceStackToMemory[@jb];
FOR sp:
SP ¬ jb.sp, nextSP
WHILE ValidSP[sp, prevSP]
AND k < stackLimit
DO
pc: PC ¬ sp.callersPC;
spy.stackBuffer[k] ¬ pc;
k ¬ k + 1;
nextSP ¬ sp.callersSP;
IF trapPC # 0 AND pc = trapPC THEN { trapPC ¬ 0; ERROR}; -- hahahaha
IF pc
IN [sigtrampStart..sigtrampEnd)
AND ValidSP[nextSP, sp]
AND ValidSP[nextSP.callersSP, nextSP]
THEN {
This was called by sigtramp. But sigtramp does not return normally, so we need to poke around to find the actual pc at which the signal occured. HIGHLY SPARC AND SunOS DEPENDENT
(c.f. .../SUNSRC/lib/libc/sys/common/sparc/sigtramp.s)
Args to sigfunc are (sig, code, &sigcontext, addr)
sf: POINTER TO SigtrampStackFrame ~ LOOPHOLE[nextSP.callersSP];
sigcontextPtr: POINTER TO SigContext ~ sf.sigcontextPtr;
pc: PC ¬ sigcontextPtr.oldPC;
IF ValidPC[pc]
THEN {
debug.goodSigtrampCount ¬ debug.goodSigtrampCount + 1;
IF k < stackLimit
THEN {
IF ignoreHottest = 3 THEN ignoreHottest ¬ k;
spy.stackBuffer[k] ¬ pc;
k ¬ k + 1;
};
nextSP ¬ sigcontextPtr.oldSP;
} ELSE { debug.badPC ¬ pc; debug.badPCcount ¬ debug.badPCcount + 1 };
};
prevSP ¬ sp;
ENDLOOP;
IF k > 0 THEN { IF spy.stackBuffer[k-1] = 0 THEN k ¬ k - 1 ELSE increment ¬ 100000 };
t.count ¬ t.count + increment;
t ¬ Insert[t, selfID, spy];
IF t #
NIL
THEN {
t.count ¬ t.count + increment;
FOR i:
NAT
DECREASING
IN [ignoreHottest..k)
DO
t ¬ Insert[t, spy.stackBuffer[i], spy];
IF t = NIL THEN EXIT;
t.count ¬ t.count + increment;
ENDLOOP;
};
ReleaseSpy[spy];
};
};
Allocator Callback Registration
CProc: TYPE = POINTER TO INSTRUCTION;
INSTRUCTION: TYPE = WORD;
Proc: TYPE ~ POINTER TO ProcRep;
ProcRep: TYPE ~ MACHINE DEPENDENT RECORD [pcStart: CProc, staticLink: CARD32];
CProcFromProc:
PROC [p:
PROC
ANY
RETURNS
ANY]
RETURNS [CProc] = {
s: Proc = LOOPHOLE[p];
zero: [0..0] = s.staticLink; -- check for top-level.
RETURN [s.pcStart]
};
AllocatorCallbackType:
TYPE ~
PROC [bytesRequested:
CARD, isAtomic:
BOOL, clientData:
POINTER]
RETURNS [
POINTER ¬
NIL];
RegisterAllocatorCallback:
PROC [callback: AllocatorCallbackType, clientData:
POINTER ¬
NIL] ~ {
fn: CProc ¬ IF callback = NIL THEN NIL ELSE CProcFromProc[callback];
oldFn: CProc ¬ NIL;
oldClientData: POINTER ¬ NIL;
GCRegisterAllocCallback:
PROC [fn: CProc, clientData:
POINTER, pOfn:
POINTER
TO CProc, oOclientData:
POINTER
TO
POINTER] ~
MACHINE
CODE {
"GC←register𡤊llocllback"
};
GCRegisterAllocCallback[fn, clientData, @oldFn, @oldClientData];
};
Tree management
GetAvail:
PROC [spy: Ref]
RETURNS [oset: OSet ¬
NIL] = {
IF spy.nAlloc < spy.limit
THEN {
p: POINTER TO MemUnit ~ spy.pAlloc;
p.oSetNode.left ¬ NIL;
p.oSetNode.item ¬ @(p.treeRep);
p.oSetNode.right ¬ NIL;
p.treeRep.children ¬ NIL;
p.treeRep.pc ¬ 0;
p.treeRep.count ¬ 0;
spy.nAlloc ¬ spy.nAlloc + 1;
spy.pAlloc ¬ spy.pAlloc + SIZE[MemUnit];
RETURN [@(p.oSetNode)]
};
};
FreeAvail:
PROC [spy: Ref, oset: OSet] = {
p: POINTER TO MemUnit ~ spy.pAlloc - SIZE[MemUnit];
IF @(p.oSetNode) = oset
THEN {
spy.pAlloc ¬ p;
spy.nAlloc ¬ spy.nAlloc - 1;
};
};
Insert:
PROC [tree: Tree, pc: ProgramCounter, avail: Ref]
RETURNS [Tree] = {
new: OSet ¬ GetAvail[avail];
IF tree #
NIL
AND new #
NIL
THEN {
children: OSet ¬ Splay[tree.children, pc, new];
IF new # children THEN FreeAvail[avail, new];
tree.children ¬ children;
RETURN [children.item]
};
RETURN [NIL]
};
Abstraction for Splay operation
STTree: TYPE = OSet;
Key: TYPE = ProgramCounter;
KeyField: PROC [s: STTree] RETURNS [Key] = INLINE {RETURN [s.item.pc]};
Splay:
PROC [s: STTree, key: Key, dummy: STTree]
RETURNS [STTree] ~ {
The Sleator-Tarjan splay-tree operation; rebalances s so that nodes nearest key are near the root. Needs dummy as scratch.
state: {N, L, R} ¬ N;
l: STTree ¬ dummy;
r: STTree ¬ dummy;
p: STTree ¬ NIL;
dummy.left ¬ dummy.right ¬ NIL;
UNTIL s=
NIL
DO
SELECT KeyField[s]
FROM
< key => {
SELECT state
FROM
N, R => {l.right ¬ s; p ¬ l; l ¬ s; s ¬ s.right; state ¬ L};
L => {l.right ¬ s.left; p.right ¬ s; s.left ¬ l; p ¬ NIL; l ¬ s; s ¬ s.right; state ¬ N};
ENDCASE;
};
> key => {
SELECT state
FROM
N, L => {r.left ¬ s; p ¬ r; r ¬ s; s ¬ s.left; state ¬ R};
R => {r.left ¬ s.right; p.left ¬ s; s.right ¬ r; p ¬ NIL; r ¬ s; s ¬ s.left; state ¬ N};
ENDCASE;
};
ENDCASE => {
l.right ¬ s.left;
r.left ¬ s.right;
s.left ¬ dummy.right;
s.right ¬ dummy.left;
RETURN[s];
};
ENDLOOP;
l.right ¬ NIL;
r.left ¬ NIL;
{left: OSet ¬ dummy.right; right: OSet ¬ dummy.left;
s ¬ dummy;
s.left ¬ left;
s.right ¬ right;
IF s.item = NIL THEN DieAnytimeNow;
s.item.children ¬ NIL;
s.item.pc ¬ key;
s.item.count ¬ 0;
RETURN[s];
};
};
Output
WriteTree:
PUBLIC
SAFE
PROC [stream:
IO.
STREAM, ref: Ref] ~
TRUSTED {
indent: NAT ¬ 0;
Inner:
PROC [tree: Tree] ~ {
nest: INT ¬ 0; -- paren nest count for this invokation of Inner.
DO
-- loop back here if this has a singleton child.
offset: CARD ¬ 0;
IO.PutChar[stream, '(];
nest ¬ nest + 1;
IF tree = NIL THEN EXIT; -- should not happen, but what the hey?
IO.Put1[stream, [cardinal[tree.count]]];
IO.PutChar[stream, ' ];
offset ¬ PutPCInfo[stream, tree.pc];
IO.PutChar[stream, ' ];
IO.Put1[stream, [cardinal[tree.pc-offset]]];
IO.PutChar[stream, ' ];
IO.Put1[stream, [cardinal[offset]]];
IF tree.children #
NIL
AND tree.children.left =
NIL
AND tree.children.right =
NIL
THEN {
Only one child, so don't do line break or indent further.
IO.PutChar[stream, ' ];
tree ¬ tree.children.item;
LOOP;
}
ELSE {
indent ¬ indent + 1;
Uses the splay operation to enumerate the children, using a bounded stack.
IF tree.children #
NIL
THEN {
[] ¬ Insert[tree, FirstKey[tree.children], ref];
DO
IO.PutChar[stream, '\n];
IO.PutRope[stream, spaces, 0, indent];
Inner[tree.children.item];
IF tree.children.right = NIL THEN EXIT;
[] ¬ Insert[tree, FirstKey[tree.children.right], ref];
ENDLOOP;
};
indent ¬ indent - 1;
EXIT;
};
ENDLOOP;
UNTIL nest = 0
DO
IO.PutChar[stream, ')];
nest ¬ nest - 1;
ENDLOOP;
};
sav: NAT ~ ref.limit;
ref.limit ¬ ref.size;
WriteStats[stream, ref];
Inner[ref.tree];
IF ref.nAlloc > sav THEN ERROR;
ref.limit ¬ sav;
};
WriteStats:
PROC [stream:
IO.
STREAM, ref: Ref] ~ {
stream.PutF1[";;; Spy\tstart:\t%g\n", [time[ref.startTime]]];
stream.PutF1[";;; \tstop:\t%g\n", [time[ref.stopTime]]];
SELECT ref.watchAllocations
FROM
$count => stream.PutRope[";;; \twatching allocations\n"];
$words => stream.PutRope[";;; \twatching words allocated\n"];
ENDCASE;
stream.PutF1[";;; \tran for %g s\n", [integer[BasicTime.Period[from: ref.startTime, to: ref.stopTime]]]];
stream.PutF1[";;; \tused %g tree nodes ", [cardinal[ref.nAlloc]]];
stream.PutF1["(of %g)\n", [cardinal[ref.limit]]];
IF ref.missed # 0 THEN stream.PutF1[";;; \tmissed %g samples due to contention.\n", [cardinal[ref.missed]]];
};
FirstKey:
PROC [oSet: OSet]
RETURNS [
PC] ~ {
UNTIL oSet.left =
NIL
DO
oSet ¬ oSet.left;
ENDLOOP;
RETURN [oSet.item.pc]
};
Dbl: PROC [a: ROPE] RETURNS [ROPE] ~ INLINE {RETURN[Rope.Concat[a,a]]};
spaces: ROPE ~ Dbl[Dbl[Dbl[Dbl[Dbl[" "]]]]];
PutUXString:
PROC [stream:
IO.
STREAM, p:
POINTER
TO Basics.RawChars] ~ {
end: INT ¬ 200;
FOR i:
NAT
IN [0..200)
DO
IF p[i] = VAL[0] THEN {end ¬ i; EXIT};
ENDLOOP;
IO.UnsafePutBlock[stream, [LOOPHOLE[p], 0, end]];
};
PutBaseUXString:
PROC [stream:
IO.
STREAM, p:
POINTER
TO Basics.RawChars] ~ {
start: INT ¬ 0;
end: INT ¬ 0;
FOR i:
NAT
IN [0..200)
DO
c: CHAR ~ p[i];
SELECT c
FROM
'/ => start ¬ i+1;
'. => IF end <= start THEN end ¬ i;
VAL[0] => {
IF end <= start THEN end ¬ i;
EXIT;
};
ENDCASE;
ENDLOOP;
IO.UnsafePutBlock[stream, [base: LOOPHOLE[p], startIndex: start, count: end-start]];
};
CirioNubSymEntryRep:
TYPE ~
MACHINE DEPENDENT RECORD [
--
CirioNubTypes.h
symID: CARD32,
name: POINTER TO Basics.RawChars,
type: CARD32,
value: CARD32,
size: CARD32,
fileSeqNum: CARD32
];
PutPCInfo:
PROC [stream:
IO.
STREAM, pc:
PC]
RETURNS [offset:
CARD ¬ 0]= {
IO.PutChar[stream, '\"];
IF pc
MOD 4 = 3
THEN {
Fake PC encodes a thread identifier.
w: CARD ~ (pc-3)/4;
g: CARD ~ w MOD 32768;
t: CARD ~ (w - g) / 32768;
IO.PutRope[stream, "T"];
IO.Put1[stream, [cardinal[t]]];
IO.PutRope[stream, ".G"];
IO.Put1[stream, [cardinal[g]]];
}
ELSE {
PCtoInfoInner: PROC [pc: CARD32, buf: POINTER TO SWPCInfo] RETURNS [INT32] ~
MACHINE CODE { "CirioNubLocalPCtoInfo" };
SWPCInfo:
TYPE ~
MACHINE
DEPENDENT
RECORD [
overlays the CirioNubPCInfo structure of CirioNubTypes.h.
procName: POINTER TO Basics.RawChars,
procSymID: CARD32,
fileName: POINTER TO Basics.RawChars,
fileSeqNum: CARD32,
guessedEmbeddedFileName: POINTER TO Basics.RawChars,
guessedEmbeddedFileSymID: CARD32
];
info: SWPCInfo;
res: INT32 ¬ PCtoInfoInner[pc, @info];
IF res=0
THEN {
wantAllTypes: CARD ~ CARD.LAST; -- IncrementalLoad.h
ignoreNone: CARD ~ 0; -- IncrementalLoad.h
CirioNubLocalLookupSymEntryByID:
PROC [symID:
CARD32, buf:
POINTER
TO CirioNubSymEntryRep]
RETURNS [
INT] ~
MACHINE
CODE {
"CirioNubLocalLookupSymEntryByID"
};
buffer: CirioNubSymEntryRep;
res: INT32 ¬ CirioNubLocalLookupSymEntryByID[symID: info.procSymID, buf: @buffer];
PutBaseUXString[stream, info.guessedEmbeddedFileName];
IO.PutChar[stream, '.];
IF res=0
THEN {
PutUXString[stream, buffer.name];
offset ¬ pc-buffer.value;
};
};
};
IO.PutChar[stream, '\"];
};
InitSigtramp[];