mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
669 lines
16 KiB
669 lines
16 KiB
//////////////////////////////////////////////////////////////////////////////
|
|
// PDB Debug Information API Mod Implementation
|
|
|
|
#include "pdbimpl.h"
|
|
#include "dbiimpl.h"
|
|
#include <stdio.h>
|
|
|
|
Mod1::Mod1(PDB1* ppdb1_, DBI1* pdbi1_, IMOD imod_)
|
|
: ppdb1(ppdb1_), pdbi1(pdbi1_), ptm(0), imod(imod_), pmli(0), fSymsAdded(FALSE)
|
|
{
|
|
sc.isect = isectNil;
|
|
instrumentation(pdbi1->info.cModules++);
|
|
}
|
|
|
|
BOOL Mod1::fInit()
|
|
{
|
|
if (pdbi1->fWrite) {
|
|
MODI* pmodi = pdbi1->pmodiForImod(imod);
|
|
if (pmodi) {
|
|
// invalidate the section contribution for this module
|
|
pmodi->sc.isect = isectNil;
|
|
if (!pdbi1->invalidateSCforMod(imod))
|
|
return FALSE
|
|
;
|
|
// We anticipate the new group of symbols will be the same
|
|
// size as last time.
|
|
expect(fAlign(cbSyms()));
|
|
if (cbSyms() > 0 && !bufSyms.SetInitAlloc(cbSyms())) {
|
|
ppdb1->setOOMError();
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
Mod1::~Mod1()
|
|
{
|
|
if (ptm)
|
|
ptm->endMod();
|
|
if (pmli)
|
|
delete pmli;
|
|
}
|
|
|
|
INTV Mod1::QueryInterfaceVersion()
|
|
{
|
|
return intv;
|
|
};
|
|
|
|
IMPV Mod1::QueryImplementationVersion()
|
|
{
|
|
return impv;
|
|
};
|
|
|
|
BOOL Mod1::AddTypes(PB pbTypes, CB cb)
|
|
{
|
|
dassert(pbTypes);
|
|
|
|
if (fSymsAdded){
|
|
ppdb1->setUsageError();
|
|
return FALSE;
|
|
}
|
|
|
|
// check for c7 signature - cannot handle pre c7
|
|
if (*(ULONG*)pbTypes != CV_SIGNATURE_C7) {
|
|
ppdb1->setLastError(EC_CORRUPT);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
pbTypes += sizeof(ULONG);
|
|
cb -= sizeof(ULONG);
|
|
|
|
if (!cb) {
|
|
// If there are no types, bail now... The compiler sometimes emits
|
|
// just the CV_SIGNATURE_C7 DWORD.
|
|
return TRUE;
|
|
}
|
|
|
|
PTYPE ptype = (PTYPE)pbTypes;
|
|
if (ptype->leaf == LF_TYPESERVER) {
|
|
lfTypeServer* pts = (lfTypeServer*)&ptype->leaf;
|
|
return pdbi1->fGetTmts(pts, szObjFile(), &ptm);
|
|
}
|
|
else {
|
|
TPI* ptpi;
|
|
return ppdb1->OpenTpi(pdbWrite, &ptpi) &&
|
|
(ptm = new (ppdb1) TMR(ppdb1, pdbi1, ptpi)) &&
|
|
((TMR*)ptm)->fInit(pbTypes, cb, szModule());
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
// For each symbol in the group of symbols in the buffer,
|
|
// ensure any TIs within the symbol properly refer to type records
|
|
// in the project PDB.
|
|
//
|
|
// Note: the symbol buffer is modified in place, as TIs are updated.
|
|
//
|
|
BOOL Mod1::AddSymbols(PB pbSym, CB cb)
|
|
{
|
|
dassert(pbSym);
|
|
|
|
PSYM psymMac = (PSYM)(pbSym + cb);
|
|
|
|
if (*(ULONG*)pbSym == CV_SIGNATURE_C7) {
|
|
if (!fSymsAdded && !bufSyms.Append(pbSym, sizeof(ULONG))) {
|
|
ppdb1->setOOMError();
|
|
return FALSE;
|
|
}
|
|
pbSym += sizeof(ULONG);
|
|
}
|
|
else if (!fSymsAdded) {
|
|
ppdb1->setUsageError();
|
|
return FALSE;
|
|
}
|
|
fSymsAdded = TRUE;
|
|
|
|
// make pass thru incoming records and perform alignment if necessary and copy to
|
|
// local syms buffer
|
|
for (PSYM psym = (PSYM)pbSym; psym < psymMac; psym = (PSYM)pbEndSym(psym)) {
|
|
PSYM pbLastWrite;
|
|
if (!bufSyms.Append((PB) psym, cbForSym(psym), (PB*) &pbLastWrite)) {
|
|
ppdb1->setOOMError();
|
|
return FALSE;
|
|
}
|
|
|
|
#if defined(_DEBUG)
|
|
expect(fAlign(pbLastWrite));
|
|
#endif
|
|
|
|
if (!fAlign(cbForSym(psym))) {
|
|
// need alignment - adjust reclen in the local sym buffer and append the
|
|
// adjustment
|
|
pbLastWrite->reclen += (USHORT) dcbAlign(cbForSym(psym));
|
|
if (!bufSyms.AppendFmt("f", dcbAlign(cbForSym(psym)))) {
|
|
ppdb1->setOOMError();
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL Mod1::AddPublic(SZ_CONST szPublic, ISECT isect, OFF off)
|
|
{
|
|
MP* pmp = (MP*) new (ppdb1, szPublic) MP(szPublic, isect, off);
|
|
if (!pmp)
|
|
return FALSE;
|
|
|
|
BOOL fOK = pdbi1->packSymToPS((PSYM)pmp);
|
|
delete pmp;
|
|
return fOK;
|
|
}
|
|
|
|
BOOL Mod1::AddLines(SZ_CONST szSrc, ISECT isect, OFF offCon, CB cbCon, OFF doff, LINE lineStart, PB pbCoff, CB cbCoff)
|
|
{
|
|
dassert(szSrc);
|
|
dassert(pbCoff);
|
|
|
|
if (!pmli && !(pmli = new (ppdb1) MLI))
|
|
return FALSE;
|
|
|
|
if (pmli->AddLines(szSrc, isect, offCon, cbCon, doff, lineStart, (IMAGE_LINENUMBER*)pbCoff, cbCoff))
|
|
return TRUE;
|
|
else {
|
|
ppdb1->setOOMError();
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
BOOL Mod1::fUpdateLines()
|
|
{
|
|
return !pmli || pmli->Emit(bufLines);
|
|
}
|
|
|
|
BOOL Mod1::QuerySecContrib(OUT ISECT* pisect, OUT OFF* poff, OUT CB* pcb, OUT DWORD* pdwCharacteristics)
|
|
{
|
|
MODI* pmodi = pdbi1->pmodiForImod(imod);
|
|
|
|
if (!pmodi) {
|
|
ppdb1->setUsageError();
|
|
return FALSE;
|
|
}
|
|
|
|
if (pisect) *pisect = pmodi->sc.isect;
|
|
if (poff) *poff = pmodi->sc.off;
|
|
if (pcb) *pcb = pmodi->sc.cb;
|
|
if (pdwCharacteristics) *pdwCharacteristics = pmodi->sc.dwCharacteristics;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL Mod1::AddSecContrib(ISECT isect, OFF off, CB cb, DWORD dwCharacteristics)
|
|
{
|
|
if (fUpdateSecContrib()) {
|
|
|
|
sc.isect = isect;
|
|
sc.off = off;
|
|
sc.cb = cb;
|
|
sc.dwCharacteristics = dwCharacteristics;
|
|
sc.imod = imod;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL Mod1::fUpdateSecContrib() {
|
|
if (sc.isect == isectNil)
|
|
return TRUE;
|
|
|
|
if (!pdbi1->addSecContrib(sc))
|
|
return FALSE;
|
|
|
|
MODI* pmodi = pdbi1->pmodiForImod(imod);
|
|
if (pmodi->sc.isect == isectNil) {
|
|
//fill in first sect contribution
|
|
|
|
// UNDONE: SAPI expects this to be the first code CON not first CON
|
|
|
|
pmodi->sc = sc;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
BOOL Mod1::fUpdateFileInfo()
|
|
{
|
|
return pmli ? pmli->EmitFileInfo(this) : initFileInfo(0);
|
|
}
|
|
|
|
BOOL Mod1::QueryCBName(OUT CB* pcb)
|
|
{
|
|
SZ sz = szModule();
|
|
|
|
if (!sz)
|
|
return FALSE;
|
|
|
|
*pcb = strlen(sz) + 1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL Mod1::QueryName(OUT char szName[_MAX_PATH], OUT CB* pcb)
|
|
{
|
|
SZ sz = szModule();
|
|
|
|
if (!sz)
|
|
return FALSE;
|
|
|
|
*pcb = strlen(sz) + 1;
|
|
|
|
if (szName) {
|
|
memcpy (szName, sz, *pcb);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL Mod1::QueryCBFile(OUT CB* pcb)
|
|
{
|
|
SZ sz = szObjFile();
|
|
|
|
if (!sz)
|
|
return FALSE;
|
|
|
|
*pcb = strlen(sz) + 1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL Mod1::QueryFile(OUT char szFile[_MAX_PATH], OUT CB* pcb)
|
|
{
|
|
SZ sz = szObjFile();
|
|
|
|
if (!sz)
|
|
return FALSE;
|
|
|
|
*pcb = strlen(sz) + 1;
|
|
|
|
if (szFile) {
|
|
memcpy (szFile, sz, *pcb);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL Mod1::QuerySymbols(PB pbSym, CB* pcb)
|
|
{
|
|
return fReadPbCb(pbSym, pcb, 0, cbSyms());
|
|
}
|
|
|
|
BOOL Mod1::QueryLines(PB pbLines, CB* pcb)
|
|
{
|
|
return fReadPbCb(pbLines, pcb, cbSyms(), cbLines());
|
|
}
|
|
|
|
CB Mod1::cbGlobalRefs()
|
|
{
|
|
MODI* pmodi = pdbi1->pmodiForImod(imod);
|
|
dassert(pmodi);
|
|
if (pmodi->sn == snNil) {
|
|
return 0;
|
|
}
|
|
|
|
CB cbRet;
|
|
CB cb;
|
|
|
|
if (fReadPbCb((PB) &cbRet, &cb, pmodi->cbSyms + pmodi->cbLines + pmodi->cbFpo, sizeof(OFF)) &&
|
|
cb == sizeof (OFF))
|
|
return cbRet;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
BOOL Mod1::queryGlobalRefs(PB pb, CB cb)
|
|
{
|
|
dassert(pb);
|
|
dassert(cb);
|
|
CB cbRead;
|
|
return
|
|
fReadPbCb(pb, &cbRead, cbSyms() + cbLines() + cbFpo() + sizeof(CB), cb) &&
|
|
cbRead == cb;
|
|
}
|
|
|
|
|
|
BOOL Mod1::fReadPbCb(PB pb, CB* pcb, OFF off, CB cb)
|
|
{
|
|
// empty if no stream
|
|
MODI* pmodi = pdbi1->pmodiForImod(imod);
|
|
dassert(pmodi);
|
|
if (pmodi->sn == snNil) {
|
|
dassert(cb == 0);
|
|
*pcb = cb;
|
|
return TRUE;
|
|
}
|
|
|
|
if (pb) {
|
|
CB cbT = cb = *pcb = min(*pcb, cb);
|
|
if (!(MSFReadStream2(ppdb1->pmsf, pmodi->sn, off, pb, &cb) && cb == cbT)){
|
|
ppdb1->setReadError();
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
else {
|
|
// if !pb, we were called to set *pcb to the stream size
|
|
*pcb = cb;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
BOOL Mod1::Close()
|
|
{
|
|
BOOL fOK = !pdbi1->fWrite ||
|
|
(fUpdateSyms() &&
|
|
fUpdateLines() &&
|
|
fUpdateFileInfo() &&
|
|
fUpdateSecContrib() &&
|
|
fCommit());
|
|
|
|
pdbi1->NoteModCloseForImod(imod);
|
|
|
|
delete this;
|
|
return fOK;
|
|
}
|
|
|
|
BOOL Mod1::fCommit()
|
|
{
|
|
dassert(pdbi1->fWrite);
|
|
|
|
MODI* pmodi = pdbi1->pmodiForImod(imod);
|
|
pmodi->cbSyms = bufSymsOut.Size();
|
|
pmodi->cbLines = bufLines.Size();
|
|
pmodi->cbFpo = bufFpo.Size();
|
|
CB cbGlobalRefs = bufGlobalRefs.Size();
|
|
expect(fAlign(pmodi->cbSyms));
|
|
expect(fAlign(pmodi->cbLines));
|
|
expect(fAlign(pmodi->cbFpo));
|
|
|
|
if (pmodi->cbSyms + pmodi->cbLines + pmodi->cbFpo + cbGlobalRefs == 0)
|
|
return fEnsureNoSn(&pmodi->sn);
|
|
|
|
if (!fEnsureSn(&pmodi->sn))
|
|
return FALSE;
|
|
|
|
if (!MSFReplaceStream(ppdb1->pmsf, pmodi->sn, bufSymsOut.Start(), pmodi->cbSyms) ||
|
|
!MSFAppendStream (ppdb1->pmsf, pmodi->sn, bufLines.Start(), pmodi->cbLines) ||
|
|
!MSFAppendStream (ppdb1->pmsf, pmodi->sn, bufFpo.Start(), pmodi->cbFpo)||
|
|
!MSFAppendStream (ppdb1->pmsf, pmodi->sn, &cbGlobalRefs, sizeof(CB))||
|
|
!MSFAppendStream (ppdb1->pmsf, pmodi->sn, bufGlobalRefs.Start(), cbGlobalRefs)) {
|
|
ppdb1->setWriteError();
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
// MOD1::fUpdateSyms
|
|
// final process of a modules local syms. at this point we will make a pass thru the
|
|
// local syms kept in bufSyms. we will
|
|
// resolve any S_UDT that point to a forward refs to point to the defining type
|
|
// record if possible
|
|
// link up matching scope records for PROC/WITH/BEGIN with their matching end records
|
|
// add and delete the appropriate entries to the Globals and Statics symbol tables.
|
|
// copy the resultant locals to the appropriate MSF in the PDB
|
|
|
|
BOOL Mod1::fUpdateSyms()
|
|
{
|
|
return(fProcessSyms() && fProcessGlobalRefs());
|
|
}
|
|
|
|
static int iLevel = 0;
|
|
static ULONG offParent = 0;
|
|
|
|
BOOL Mod1::fCopySymOut(PSYM psym)
|
|
{
|
|
return bufSymsOut.Append((PB) psym, cbForSym(psym), 0);
|
|
}
|
|
|
|
BOOL Mod1::fCopySymOut(PSYM psym, PSYM *ppsymOut)
|
|
{
|
|
return bufSymsOut.Append((PB) psym, cbForSym(psym), (PB *)ppsymOut);
|
|
}
|
|
|
|
BOOL Mod1::fCopyGlobalRef(OFF off)
|
|
{
|
|
return bufGlobalRefs.Append((PB) &off, sizeof (OFF));
|
|
}
|
|
|
|
BOOL Mod1::fUdtIsDefn(PSYM psym)
|
|
{
|
|
dassert(psym->rectyp == S_UDT);
|
|
|
|
UDTSYM* psymUdt = (UDTSYM*) psym;
|
|
|
|
if (CV_IS_PRIMITIVE(psymUdt->typind))
|
|
return TRUE;
|
|
|
|
PTYPE ptype;
|
|
if (ptm)
|
|
ptype = ptm->ptypeForTi(psymUdt->typind);
|
|
else
|
|
if (!ppdb1->ptpi1->QueryPbCVRecordForTi(psymUdt->typind, (PB*)&ptype))
|
|
return TRUE; // scalar types are considered definitions
|
|
|
|
dassert(ptype);
|
|
|
|
switch (ptype->leaf) {
|
|
case LF_CLASS:
|
|
case LF_STRUCTURE:
|
|
{
|
|
lfClass* pClass = (lfClass*) &(ptype->leaf);
|
|
return !(pClass->property.fwdref);
|
|
}
|
|
|
|
case LF_UNION:
|
|
{
|
|
lfUnion* pUnion = (lfUnion*) &(ptype->leaf);
|
|
return !(pUnion->property.fwdref);
|
|
}
|
|
|
|
case LF_ENUM:
|
|
{
|
|
lfEnum* pEnum = (lfEnum*) &(ptype->leaf);
|
|
return !(pEnum->property.fwdref);
|
|
}
|
|
|
|
default:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
BOOL Mod1::packType(PSYM psym)
|
|
{
|
|
if (ptm) {
|
|
instrumentation(pdbi1->info.cSymbols++);
|
|
for (SymTiIter tii(psym); tii.next(); )
|
|
if (!ptm->fMapRti(tii.rti()))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL Mod1::fProcessSyms()
|
|
{
|
|
if (!bufSyms.Start() || bufSyms.Start() == bufSyms.End())
|
|
return TRUE; // no syms were added for this module
|
|
|
|
dassert(bufSyms.End());
|
|
|
|
offParent = 0;
|
|
iLevel = 0;
|
|
|
|
// copy the ever-lovin' signature
|
|
if (*(ULONG*)bufSyms.Start() != CV_SIGNATURE_C7 ||
|
|
!bufSymsOut.Append(bufSyms.Start(), sizeof(ULONG)))
|
|
return FALSE;
|
|
|
|
for (PSYM psym = (PSYM)(bufSyms.Start() + sizeof(ULONG));
|
|
(PB) psym < bufSyms.End();
|
|
psym = (PSYM)pbEndSym(psym)) {
|
|
OFF offSym;
|
|
PSYM psymOut;
|
|
|
|
expect(fAlign(psym));
|
|
switch(psym->rectyp) {
|
|
case S_GPROC16:
|
|
case S_GPROC32:
|
|
case S_GPROCMIPS:
|
|
case S_LPROC16:
|
|
case S_LPROC32:
|
|
case S_LPROCMIPS:
|
|
if (!packType(psym) ||
|
|
!pdbi1->packProcRefToGS(psym, imod, (OFF)bufSymsOut.Size(), &offSym) ||
|
|
// copy full sym to output syms
|
|
!fCopySymOut(psym, &psymOut) ||
|
|
// copy offset of procref to tables of globals ref'd
|
|
!fCopyGlobalRef(offSym))
|
|
return FALSE;
|
|
|
|
EnterLevel(psymOut);
|
|
break;
|
|
|
|
case S_UDT:
|
|
// if we have a udt decl (forward ref only) simply throw it out
|
|
// doesn't help us
|
|
if (!fUdtIsDefn(psym)) {
|
|
if (!packType(psym))
|
|
return FALSE;
|
|
break;
|
|
}
|
|
|
|
case S_LDATA16:
|
|
case S_LDATA32:
|
|
case S_LTHREAD32:
|
|
case S_CONSTANT:
|
|
if (iLevel) {
|
|
// simply copy to local sym
|
|
if (!packType(psym) ||
|
|
!fCopySymOut(psym))
|
|
return FALSE;
|
|
break;
|
|
}
|
|
|
|
// if we have a 4.1 source tpi (target tpi must be 4.1) we can toss S_UDTs
|
|
// and we will defer packing its udt defn until the end of link when we
|
|
// close the dbi
|
|
if ((psym->rectyp == S_UDT) &&
|
|
!CV_IS_PRIMITIVE(((UDTSYM *)psym)->typind) &&
|
|
(!ptm || ptm->fEliminateUDTs()))
|
|
break;
|
|
|
|
// if at global scope fall thru and pack it
|
|
|
|
case S_GDATA16:
|
|
case S_GDATA32:
|
|
case S_GTHREAD32:
|
|
if (!packType(psym) ||
|
|
!pdbi1->packSymToGS(psym, &offSym) ||
|
|
!fCopyGlobalRef(offSym))
|
|
return FALSE;
|
|
|
|
break;
|
|
|
|
case S_THUNK16:
|
|
case S_BLOCK16:
|
|
case S_WITH16:
|
|
case S_THUNK32:
|
|
case S_BLOCK32:
|
|
case S_WITH32:
|
|
if (!fCopySymOut(psym, &psymOut))
|
|
return FALSE;
|
|
EnterLevel(psymOut);
|
|
break;
|
|
|
|
case S_END:
|
|
if (!fCopySymOut(psym, &psymOut))
|
|
return FALSE;
|
|
ExitLevel(psymOut);
|
|
break;
|
|
|
|
case S_OBJNAME:
|
|
if (ptm && ptm->IsTMPCT() &&
|
|
!pdbi1->fAddTmpct(szModule(),
|
|
szCopySt((ST)&((OBJNAMESYM *)psym)->name[0]), ptm)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
if (!fCopySymOut(psym))
|
|
return FALSE;
|
|
break;
|
|
|
|
default:
|
|
if (!packType(psym) ||
|
|
!fCopySymOut(psym))
|
|
return FALSE;
|
|
break;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
// check to see here that we have run out of type indecies during the pack of
|
|
// this module
|
|
if (ptm)
|
|
if (!ptm->fNotOutOfTIs()) {
|
|
ppdb1->setLastError(EC_OUT_OF_TI);
|
|
return FALSE;
|
|
}
|
|
|
|
if (iLevel) {
|
|
ppdb1->setLastError(EC_CORRUPT);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE; //iLevel better be zero or we had bad scoping
|
|
}
|
|
|
|
BOOL Mod1::fProcessGlobalRefs()
|
|
{
|
|
CB cb;
|
|
if (cb = cbGlobalRefs()) {
|
|
PB pb = new (ppdb1) BYTE[cb];
|
|
if (!pb || !queryGlobalRefs(pb, cb)) {
|
|
ppdb1->setLastError(EC_CORRUPT);
|
|
return FALSE;
|
|
}
|
|
|
|
for (PB pbEnd = pb + cb; pb < pbEnd; pb += sizeof(OFF)) {
|
|
if (!pdbi1->decRefCntGS(*(OFF *)pb)) {
|
|
ppdb1->setLastError(EC_CORRUPT);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// EnterLevel/ExitLevel - fill in the scope link fields and bump the level indicator
|
|
|
|
void Mod1::EnterLevel(PSYM psym)
|
|
{
|
|
// note that this works because all of these symbols
|
|
// have a common format for the first fields. The
|
|
// address variants follow the link fields.
|
|
|
|
// put in the parent
|
|
((BLOCKSYM *)psym)->pParent = offParent;
|
|
offParent = (PB)psym - bufSymsOut.Start();
|
|
iLevel++;
|
|
}
|
|
|
|
void Mod1::ExitLevel(PSYM psym)
|
|
{
|
|
// fill in the end record to the parent
|
|
((BLOCKSYM *)(bufSymsOut.Start() + offParent))->pEnd =
|
|
(ULONG)((PB)psym - bufSymsOut.Start());
|
|
|
|
// reclaim his parent as the parent
|
|
offParent = ((BLOCKSYM *)(bufSymsOut.Start() + offParent))->pParent;
|
|
iLevel--;
|
|
}
|