2020-09-30 17:12:29 +02:00

2011 lines
48 KiB
C++

// SH.CXX - Symbol Handler: Low level management
//
// Copyright (C)1991-94, Microsoft Corporation
//
// Purpose: Provide layer between SH functions and linked list manager.
//
// Notes: Also included are fixup/unfixup functions until OSDEBUG is on-line.
//
// DESCRIPTION OF INITIALIZATION CALLING SEQUENCE
// ----------------------------------------------
// During startup of debugging a user application, a large number of
// Symbol Handler functions need to be called. Here is the order in
// which it makes sense to call them, and what they do:
//
// (1) SHCreateProcess
// To create a handle for the new debuggee process which
// is being debugged.
// (2) SHSetHpid
// This doesn't have to be called right now, but it should
// be called as soon as the HPID of the debuggee is known.
// (3) SHAddDll
// Call this a number of times: once for the EXE which is
// being debugged, and once for each DLL being debugged
// (e.g., in CodeView, for all the /L xxxx.DLL files).
// This doesn't load the symbolic information off disk;
// it just lets the SH know that these files are being
// debugged.
// (4) SHAddDllsToProcess
// This associates all added DLLs with the added EXE, so
// the SH knows that they are all being debugged as part
// of the same process.
// (5) SHLoadDll
// Call this once for the EXE and each DLL, passing FALSE
// for the fLoading parameter. This actually loads the
// symbolic information off disk.
// (6) Start debuggee running
// (7) SHLoadDll
// Call this for EXEs/DLLs as notifications are received
// indicating that they have been loaded into memory.
// This time, pass TRUE for the fLoading parameter.
// (8) SHUnloadDll
// Call this for EXEs/DLLs as notifications are received
// indicating that they have been unloaded from memory.
// This does not actually unload the symbolic information.
//
//
// Revision History:
//
// 07-Nov-94 BryanT
// Merge in NT changes. Changes include: removing PASCAL/LOADDS/FAR/NEAR
// references. Calls to STRxxx and MEMxxx functions (use strxxx/memxxx)
// Replace calls to SHSplitPath with splitpath. Remove non-WIN32 host
// support. Change EXS structure to EXE. LLLock(hexe) yields a lpexe like
// all other Handles do. Eliminate need for EXR structure. Consolidate
// several static locals into global xxxCache variables so they can be
// cleared when a process terminates (for multiprocess support).
// Remove single user for SHHexeRemove (it can be done in place).
// Add a vldChk arg to SHAddDllExt so one can pass a timestamp/checksum
// to be used for image lookup.
// Store just the name in exg.lszModule (SHAddDllExt). The full path is
// still stored in exg.lszName. Needed for cmd window support.
// Remove hlliMds references. Rather than use the LL code, change
// loadomf.cpp to keep an array with the index stored in each node
// (no need to iterate to find, etc).
// Define SHGetModule (used for ntsd syntax support)
// Remove the dual global Exg lists and replace with a single one
// Rewrite LoadDll to handle symbol sharing and multiple processes.
// Add SHLszGetErrorText (to return the error message for an SH error code)
// Add SHWantSymbols (To allow other parts of the debugger to indicate
// symbols s/b loaded for a module).
// Change SHChangeProcess to return the old HPDS.
//
// [untagged] 21-Oct-93 MarkBro
//
// Added SHUnloadSymbolHandler for NB10 notifications. Can
// also be used in the future to free up memory so symbol
// handler doesn't need to be free'd and reloaded.
//
// [02] 05-Mar-93 DanS
//
// Added critical section for win32 build.
//
// [01] 31-dec-91 DavidGra
//
// Fix bug with far addresses being unfixed up in the disassembler
// when passed through the symbol handler.
//
// [00] 11-dec-91 DavidGra
//
// Make SHAddDll return an she indicating file not found or
// out of memory or success. Create SHAddDllExt to handle
// the internal support and thunk it with SHAddDll to keep
// the API the same.
#include "shinc.hpp"
#pragma hdrstop
#include "strings.h"
HLLI HlliPds; // List of processes
HPDS hpdsCur; // Current process which is being debugged
HPID hpidCurr; // Current PID
MODCACHE ModCache; // Last module returned from SHHextFromHmod
LINECACHE LineCache; // Last Linenumber from SLFLineToAddr
HSFCACHE HsfCache; // Last Hsf returned from SLHmodFromHsf
CXTCACHE CxtCache; // Last Context info from SHSetCxtMod
SLCACHE SlCache; // Last Addr info from SLCAddrFromLine
ADDRCACHE AddrCache; // Last Line info from SLLineFromAddr
static HLLI HlliExgExe;
//static char rgchFile [_MAX_CVPATH];
//static INT fhCur = -1 ;
#define MATCH_NAMEONLY 1
#define AllocAlign(cb) ( (PVOID) (((DWORD) MHAlloc(cb+1) + 1) & 0xFFFFFFFE) )
// UNDONE: Investigate moving the cache data into the exg, exe, or mds struct.
static VOID VoidCaches(VOID);
// VoidCaches
//
// Purpose: Reinitialized the various caches to a known state
static VOID
VoidCaches(
VOID
)
{
memset(&LineCache, 0, sizeof(LineCache));
memset(&AddrCache, 0, sizeof(AddrCache));
memset(&ModCache, 0, sizeof(ModCache));
memset(&HsfCache, 0, sizeof(HsfCache));
memset(&CxtCache, 0, sizeof(CxtCache));
if (SlCache.lpslp) {
MHFree(SlCache.lpslp);
}
memset(&SlCache, 0, sizeof(SlCache));
SlCache.cslp = -1;
}
// SHCreateProcess
//
// Purpose: Create a handle for a new debuggee process. The debuggee
// process doesn't actually have to be running yet; this is
// just an abstract handle for the Symbol Handler's use, so
// that it can keep track of symbols for multiple debuggee
// processes at the same time.
//
// Output:
// Returns an HPDS, a handle to the new process, or 0 for failure.
HPDS
SHCreateProcess (
VOID
)
{
HPDS hpds = SHFAddNewPds();
SHChangeProcess(hpds);
return hpds;
}
// SHSetHpid
//
// Purpose: Tell the SH what HPID to assign to the current process.
// Each debuggee process has an HPID, and this call associates
// an HPID with a HPDS in the SH.
// Input: hpid The HPID to make current.
VOID
SHSetHpid (
HPID hpid
)
{
LPPDS lppds = (LPPDS) LLLock(hpdsCur);
lppds->hpid = hpidCurr = hpid;
LLUnlock(hpdsCur);
}
// SHDeleteProcess
//
// Purpose: Delete a debuggee process handle (HPDS). Removes it from
// the SH's internal list of HPDS's.
//
// Input: hpds The HPDS to delete.
//
// Output:
// TRUE for success, FALSE for failure.
BOOL
SHDeleteProcess (
HPDS hpds
)
{
HPDS hpdsT = hpdsCur;
HPID hpidT = hpidCurr;
SHChangeProcess (hpds);
LLDelete(HlliPds, hpdsCur);
if (hpdsT != hpdsCur) {
hpdsCur = hpdsT;
hpidCurr = hpidT;
} else {
hpdsCur = LLNext (HlliPds, NULL);
if ( hpdsCur != 0 ) {
LPPDS lppds = (LPPDS) LLLock (hpdsCur);
hpidCurr = lppds->hpid;
LLUnlock (hpdsCur);
}
}
return TRUE;
}
// SHChangeProcess
//
// Purpose: Change the current debuggee process handle (HPDS). The SH
// can maintain symbols for multiple processes; this sets which
// one is current, so that symbol lookups will be done on the
// right set of symbolic information.
//
// Input: hpds The HPDS to make current.
HPDS
SHChangeProcess(
HPDS hpds
)
{
LPPDS lppds;
HPDS hpdsLast = hpdsCur;
hpdsCur = hpds;
lppds = (LPPDS) LLLock (hpdsCur);
hpidCurr = lppds->hpid;
LLUnlock (hpdsCur);
return(hpdsLast);
}
BOOL
FInitLists (
VOID
)
{
// Create the pds list
HlliPds = LLInit ( sizeof ( PDS ), 0, KillPdsNode, CmpPdsNode );
HlliExgExe = LLInit ( sizeof ( EXG ), 0, KillExgNode, CmpExgNode );
return HlliPds && HlliExgExe;
}
// KillPdsNode
//
// Purpose: Destroy private contents of a process node
//
// Input: pointer to node data
//
// Notes: Only data in the pds structure to destroy is a list of exe's.
VOID
KillPdsNode (
PVOID lpvPdsNode
)
{
LPPDS lppds = (LPPDS) lpvPdsNode;
LLDestroy ( lppds->hlliExe );
}
int
CmpPdsNode (
PVOID lpv1,
PVOID lpv2,
LONG lParam
)
{
LPPDS lppds = (LPPDS) lpv1;
HPID *lphpid = (HPID *) lpv2;
Unreferenced (lParam);
return !(lppds->hpid == *lphpid);
}
VOID
KillAlm(
LPALM lpalm
)
{
if (lpalm) {
MHFree ( lpalm );
}
}
VOID
KillSht(
LPSHT lpsht
)
{
if (lpsht) {
if (lpsht->rgib) {
lpsht->rgib = 0;
}
if (lpsht->rgcib) {
lpsht->rgcib = 0;
}
KillAlm (lpsht->lpalm);
lpsht->lpalm = 0;
}
}
VOID
KillGst(
LPGST lpgst
)
{
if (lpgst) {
KillSht ( &lpgst->shtName );
KillSht ( &lpgst->shtAddr );
KillAlm ( lpgst->lpalm );
lpgst->lpalm = 0;
}
}
// KillExgNode
//
// Purpose: Destroy information contained in an exe node
//
// Input: pointer to node data
VOID
KillExgNode (
PVOID lpvExgNode
)
{
LPEXG lpexg = (LPEXG) lpvExgNode;
DWORD i;
// Debug info file name
if (lpexg->lszDebug) {
MHFree (lpexg->lszDebug);
lpexg->lszDebug = 0;
}
// .exe/.com file name
if (lpexg->lszName) {
MHFree (lpexg->lszName);
lpexg->lszName = 0;
}
// AltName
if (lpexg->lszAltName) {
MHFree(lpexg->lszAltName);
lpexg->lszAltName = 0;
}
// Pdb name
if (lpexg->lszPdbName) {
MHFree (lpexg->lszPdbName);
lpexg->lszPdbName = 0;
}
// Type table
if (lpexg->lpalmTypes) {
KillAlm(lpexg->lpalmTypes);
lpexg->lpalmTypes = 0;
}
// Free up memory associated with the dll name
if (lpexg->lszModule) {
MHFree ( lpexg->lszModule );
lpexg->lszModule = 0;
}
// Nuke the OMF symbolic.
OLUnloadOmf(lpexg);
// And any possible cache references to this module.
VoidCaches();
}
LSZ
NameOnly(
LSZ lsz
)
{
LSZ p;
p = lsz + _tcslen(lsz);
// Search from the end back for the path delimiter.
while ( p > lsz && *p != '\\' && *p != ':' ) {
p = _tcsdec(lsz, p);
}
if (p > lsz) {
// We're pointing at the delimiter. Move forward one.
p = _tcsinc(p);
}
return p;
}
// CmpExgNode
//
// Purpose: Compare global exe nodes
//
// Input: pointer to node data
int
CmpExgNode (
PVOID lpv1,
PVOID lpv2,
LONG lParam
)
{
LPEXG lpexg1 = (LPEXG) lpv1;
LSZ lsz1 = lpexg1->lszName;
LSZ lsz2 = (LSZ) lpv2;
if (lParam == MATCH_NAMEONLY) {
lsz1 = NameOnly(lsz1);
lsz2 = NameOnly(lsz2);
}
return _tcsicmp(lsz1, lsz2);
}
// KillExeNode
//
// Purpose: Destroy information contained in an exe node
//
// Input: pointer to node data
VOID
KillExeNode(
PVOID lpvExeNode
)
{
assert (((LPEXE)lpvExeNode)->pDebugData == NULL);
// All we need to do is flush the global cache (in case we're still in it)
VoidCaches();
}
int
CmpExeNode(
PVOID lpv1,
PVOID lpv2,
LONG l
)
{
LPEXE lpexe1 = (LPEXE) lpv1;
Unreferenced(l);
return !(lpexe1->hexg == (HEXG) lpv2);
}
// KillMdsNode
//
// Purpose: Free up memory allocations associated with node
//
// Input: pointer to the mds node
VOID
KillMdsNode (
PVOID lpvMdsNode
)
{
LPMDS lpmds = (LPMDS)lpvMdsNode;
if (lpmds->name) {
MHFree (lpmds->name);
lpmds->name = 0;
}
if (lpmds->lpsgc) {
MHFree (lpmds->lpsgc);
lpmds->lpsgc = 0;
}
if (lpmds->symbols) {
if (lpmds->pmod) {
// For PDB, symbols is a copy.
MHFree(lpmds->symbols);
}
lpmds->symbols = 0;
lpmds->cbSymbols = 0;
}
if (lpmds->hst) {
// Just a pointer to the cv data
lpmds->hst = 0;
}
if (lpmds->pmod) {
if (!ModClose(lpmds->pmod)) {
assert (FALSE);
}
lpmds->pmod = 0;
}
}
// CmpMdsNode
//
// Purpose: To compare two mds nodes.
//
// Input:
// lpv1 far pointer to first node
// lpv2 far pointer to second node
// lParam comparison type ( MDS_INDEX is only valid one, for now)
//
// Output: Returns zero if imds are equal, else non-zero
int
CmpMdsNode (
PVOID lpv1,
PVOID lpv2,
LONG lParam
)
{
LPMDS lpmds1 = (LPMDS) lpv1;
LPMDS lpmds2 = (LPMDS) lpv2;
assert (lParam == MDS_INDEX);
return lpmds1->imds != lpmds2->imds;
}
// SHHexgFromHmod
//
// Purpose: Get the hexg from the specified mds handle
//
// Input: handle to a VALID mds node
//
// Output: handle to the hmod's parent (hexe)
HEXG
SHHexgFromHmod (
HMOD hmod
)
{
HEXG hexg;
assert(hmod);
hexg = ((LPMDS)hmod)->hexg;
return hexg;
}
// SHHexeFromHmod
//
// Purpose: Get the hexe from the specified module handle
//
// Input: handle to a VALID mds node
//
// Output: handle to the hmod's parent (hexe)
HEXE
SHHexeFromHmod (
HMOD hmod
)
{
HEXG hexg;
LPEXG lpexg;
LPPDS lppds;
if (hmod == NULL) {
return hexeNull;
}
// If this isn't the same hmod or hpds as last time, look up
// the hexe in current process list and return.
if ((hmod != ModCache.hmod) || (hpdsCur != ModCache.hpds)) {
hexg = ((LPMDS) hmod)->hexg;
if (hexg != NULL) {
ModCache.hmod = hmod;
ModCache.hpds = hpdsCur;
lppds = (LPPDS) LLLock(hpdsCur);
ModCache.hexe = LLFind(lppds->hlliExe, 0, (PVOID) hexg, 0L);
LLUnlock(hpdsCur);
}
}
return ModCache.hexe;
}
// SHGetExeName
//
// Purpose: Get the exe name for a specified hexe
//
// Input: handle to the exe node
//
// Output: pointer to the exe's full path-name file
LSZ
SHGetExeName (
HEXE hexe
)
{
LSZ lsz;
HEXG hexg;
LPEXG lpexg;
assert(hpdsCur && hexe);
if (!hexe) {
return(NULL);
}
hexg = ((LPEXE) LLLock (hexe))->hexg;
lpexg = (LPEXG) LLLock(hexg);
// If there's an alternate name (as in kernel debugging when hal.dll
// may really be halmp or halncr or...), use it.
if (lpexg->lszAltName) {
lsz = lpexg->lszAltName;
} else {
lsz = lpexg->lszName;
}
// Bypass the fake "#:\" drive letter the DM added for a remote drive
if (lsz[0] == '#' && lsz[1] == ':' && lsz[2] == '\\') {
lsz += 3;
}
LLUnlock (hexe);
LLUnlock (hexg);
return lsz;
}
// SHGetModNameFromHexe
//
// Purpose: Get the module name from the specified hexe
//
// Input: handle to the exe node
//
// Output: pointer to the exe's module name
LSZ
SHGetModNameFromHexe(
HEXE hexe
)
{
LSZ lsz;
HEXG hexg;
LPEXG lpexg;
assert(hpdsCur && hexe);
hexg = ((LPEXE) LLLock(hexe))->hexg;
lpexg = (LPEXG) LLLock(hexg);
lsz = lpexg->lszModule;
LLUnlock (hexe);
LLUnlock (hexg);
return lsz;
}
// SHGetSymFName
//
// Purpose: Get the symbol file name for the specified hexe
//
// Input: handle to the exe node
//
// Output: pointer to the exe's full path-name file
LSZ
SHGetSymFName(
HEXE hexe
)
{
LSZ lsz;
HEXG hexg;
LPEXG lpexg;
assert(hpdsCur && hexe);
hexg = ((LPEXE)LLLock(hexe))->hexg;
lpexg = (LPEXG)LLLock(hexg);
lsz = lpexg->lszDebug;
if (!lsz) {
lsz = lpexg->lszName;
}
// Bypass the fake "#:\" drive letter the DM added for a remote drive
if (lsz[0] == '#' && lsz[1] == ':' && lsz[2] == '\\') {
lsz += 3;
}
LLUnlock(hexe);
LLUnlock(hexg);
return lsz;
}
// SHGetNextExe
//
// Purpose: Get the handle to the next node in the exe list for the CURRENT
// process. If the hexe is null, then get the first one in the list.
//
// Input: handle to the "previous" node. If null, get the first one in
// the exe list.
//
// Output: Returns a handle to the next node. Returns NULL if the end of
// the list is reached (ie: hexe is last node in the list)
HEXE
SHGetNextExe (
HEXE hexe
)
{
HEXE hexeRet;
HLLI hlli;
if (!hpdsCur) // check for non-null process [rm]
return NULL;
hlli = ((LPPDS) LLLock (hpdsCur))->hlliExe;
hexeRet = LLNext (hlli, hexe);
LLUnlock (hpdsCur);
return hexeRet;
}
// SHHmodGetNext
//
// Purpose: Retrieve the next module in the list. If a hmod is specified, get
// the next in the list. If the hmod is NULL, then get the first module
// in the exe. If no hexe is specified, then get the first exe in the list.
//
// Input:
// hexe hexe containing list of hmod's. If NULL, get first in CURRENT
// process list.
// hmod module to get next in list. If NULL, get first in the list.
//
// Output: Returns an hmod of the next one in the list. NULL if the end of
// the list is reached.
HMOD
SHHmodGetNext (
HEXE hexe,
HMOD hmod
)
{
HMOD hmodRet = 0;
if (!hpdsCur) {
return hmodRet;
}
if (hmod) {
hmodRet = (HMOD)((LPMDS)hmod + 1);
if (((LPMDS)(hmodRet))->imds == 0xFFFF) {
hmodRet = NULL;
}
return(hmodRet);
}
if (hexe) {
MDS *pMds;
HEXG hexg;
LPEXG lpexg;
LPEXE lpexe;
lpexe = (LPEXE) LLLock(hexe);
// The test for 10 is arbitrary. Testing for only NULL
// may allow some cases to slip through.
if (lpexe < (LPEXE) 10) {
return(hmodRet);
}
hexg = lpexe->hexg;
LLUnlock(hexe);
if (hexg == 0) {
return(hmodRet);
}
lpexg = (LPEXG) LLLock(hexg);
if (lpexg < (LPEXG) 10) {
return(hmodRet);
}
pMds = lpexg->rgMod;
if (pMds != NULL) {
hmodRet = (HMOD) &pMds[1];
}
LLUnlock(hexg);
}
return hmodRet;
}
// SHFAddNewPds
//
// Purpose: Create a new process node and make it current!
//
// Input: Word value to identify pds indexing
//
// Output: Non-zero if successful, else zero.
//
// Notes: Creates a new node and initializes private list of exe's.
HPDS
SHFAddNewPds (
VOID
)
{
LPPDS lppds;
HPDS hpds;
if (hpds = (HIND) LLCreate (HlliPds)) {
lppds = (LPPDS) LLLock (hpds);
lppds->hlliExe = LLInit (sizeof(EXE), 0, KillExeNode, CmpExeNode);
// If the list create failed, destroy the node and return failure
if (!lppds->hlliExe) {
LLUnlock (hpds);
MMFree ((HDEP) hpds);
hpds = 0;
} else {
// Otherwise, add the pds to the list and return success
LLUnlock(hpds);
LLAdd (HlliPds, hpds);
hpdsCur = hpds;
}
}
return hpds;
}
// SHHexeAddNew
//
// Purpose: Create and initialize an exe node.
//
// Input:
// hpds Process hexe is assoceiated with
// hexg Symbol information for the exe.
//
// Output: Returns hexg of newly created node. NULL if OOM.
HEXE
SHHexeAddNew (
HPDS hpds,
HEXG hexg,
DWORD LoadAddress
)
{
HEXE hexe;
LPEXE lpexe;
LPEXG lpexg;
HLLI hlli;
if (!hpds) {
hpds = hpdsCur;
}
hlli = ((LPPDS)LLLock(hpds))->hlliExe;
// Ensure that the hexg isn't already in the list
hexe = LLFind(hlli, 0, (PVOID)hexg, 0L);
if (!hexe && (hexe = LLCreate (hlli))) {
// Not already there. Create one, init the fields, and add it to the list
lpexe = (LPEXE) LLLock (hexe);
lpexe->hexg = hexg;
lpexe->hpds = hpdsCur;
lpexe->fIsLoaded = TRUE;
lpexe->LoadAddress = LoadAddress;
lpexe->pDebugData = NULL;
LLAdd (hlli, hexe);
LLUnlock (hexe);
// Increment the ref count on the image.
lpexg = (LPEXG) LLLock(hexg);
lpexg->cRef++;
LLUnlock (hexg);
} else {
lpexe = (LPEXE) LLLock (hexe);
lpexe->LoadAddress = LoadAddress;
LLUnlock (hexe);
}
LLUnlock (hpds);
return hexe;
}
// SHAddDll
//
// Purpose: Notify the SH about an EXE/DLL for which symbolic information
// will need to be loaded later.
//
// During the startup of a debuggee application, this function
// will be called once for the EXE, and once for each DLL that
// is used by the EXE. After making these calls,
// SHAddDllsToProcess will be called to associate those DLLs
// with that EXE.
//
// See the comments at the top of this file for more on when
// this function should be called.
//
// Input:
// lsz Fully qualified path/file specification.
// fDll TRUE if this is a DLL, FALSE if it is an EXE.
//
// Output:
// Returns nonzero for success, zero for out of memory.
//
// Notes:
// This function does NOT actually load the symbolic information;
// SHLoadDll does that.
SHE
SHAddDll (
LSZ lsz,
BOOL fDll
)
{
HEXG hexg = hexgNull;
SHE sheRet;
EnterCriticalSection(&csSh);
sheRet = SHAddDllExt (lsz, fDll, TRUE, NULL, &hexg);
LeaveCriticalSection(&csSh);
return sheRet;
}
// SHAddDllExt
//
// Purpose: Notify the SH about an EXE/DLL for which symbolic information
// will need to be loaded later.
//
// During the startup of a debuggee application, this function
// will be called once for the EXE, and once for each DLL that
// is used by the EXE. After making these calls,
// SHAddDllsToProcess will be called to associate those DLLs
// with that EXE.
//
// See the comments at the top of this file for more on when
// this function should be called.
//
// Input:
// lsz Fully qualified path/file specification.
// fDll TRUE if this is a DLL, FALSE if it is an EXE.
// fMustExist TRUE if success requires that the dll must be found
// i.e. the user asked for symbol info for this dll
// and would expect a warning if it isn't found.
//
// Output:
// [Public interface]
// Returns nonzero for success, zero for out of memory.
//
// [Private SAPI interface]
// Returns HEXG of newly created node, or NULL if out of memory.
//
// Notes:
// This function does NOT actually load the symbolic information;
// SHLoadDll does that.
//
// This function is used internally, AND it is also exported
// to the outside world. When exported, the return value should
// just be considered a BOOL: zero means out of memory, nonzero
// means success.
SHE
SHAddDllExt (
LSZ lsz,
BOOL fDll,
BOOL fMustExist,
VLDCHK *vldChk,
HEXG *lphexg
)
{
HEXG hexg;
LPEXG lpexg;
_TXCHAR szPath [_MAX_CVPATH];
if (fDll) {
struct _stat statT;
_TXCHAR szDir[_MAX_DRIVE + _MAX_CVDIR];
_TXCHAR szFile[_MAX_CVPATH];
_TXCHAR szExt[_MAX_CVEXT];
_TXCHAR *lpszPath;
_tsplitpath (lsz, szDir, szDir + _MAX_DRIVE, szFile, szExt);
_tcscat(szFile, *szExt ? szExt : ".DLL");
_tcscat (szDir, szDir + _MAX_DRIVE);
_tfullpath (szPath, szDir, _MAX_CVDIR);
_tcsupr (szPath);
lpszPath = szPath;
lpszPath = _tcsdec(lpszPath, _tcschr(lpszPath, '\0'));
if (*lpszPath != '\\') {
_tcscat(szPath, "\\");
}
_tcsupr (szFile);
_tcscat (szPath, szFile);
if (!*szDir || _tstat (szPath, &statT)) {
_tsearchenv (szFile, "PATH", szPath);
}
if (*szPath == 0) {
if (fMustExist) {
*lphexg = hexgNull;
return sheFileOpen;
} else {
// Retain the full path instead of just the filname
// If we just remember the file name then during
// a restart when we get the same dll load again
// the IDE will report an error as the paths don't
// match.
_tcscpy (szPath, lsz);
}
}
} else {
_tcscpy(szPath, lsz);
}
hexg = LLFind (HlliExgExe, 0, &szPath, vldChk ? MATCH_NAMEONLY : 0L);
// If we have an image and a validity check structure, make
// sure we have the correct image...
if ((hexg != hexgNull) && vldChk) {
lpexg = (LPEXG) LLLock(hexg);
// A TimeAndDateStamp field of -1 means we don't care.
if (vldChk->TimeDateStamp == 0xffffffff) {
vldChk->TimeDateStamp = lpexg->ulTimeStamp;
}
if ((vldChk->TimeDateStamp != lpexg->ulTimeStamp) ||
(vldChk->CheckSum != lpexg->ulCheckSum)) {
LLUnlock(hexg);
hexg = hexgNull;
} else {
LLUnlock(hexg);
}
}
if ((hexg == hexgNull) &&
((hexg = LLCreate (HlliExgExe)) != hexgNull)) {
lpexg = (LPEXG)LLLock(hexg);
memset(lpexg, 0, sizeof(EXG));
lpexg->fOmfLoaded = FALSE;
if (lpexg->lszModule = (LSZ) MHAlloc (_tcslen(lsz) + 1)) {
// Skip past the fake "#:\" drive the DM might have added
if (lsz[0] == '#' && lsz[1] == ';' && lsz[2] == '\\') {
lsz += 3;
}
_tsplitpath(lsz, NULL, NULL, lpexg->lszModule, NULL);
}
if (lpexg->lszName = (LSZ) MHAlloc(_tcslen(szPath) + 1)) {
_tcscpy(lpexg->lszName, (LSZ) szPath);
}
if (!lpexg->lszName || !lpexg->lszModule || (*lpexg->lszName == '\0')) {
// If any of the allocated fields are NULL,
// then destroy the node and return failure
KillExgNode((PVOID)lpexg);
LLUnlock( hexg );
hexg = hexgNull;
} else {
// Otherwise, add the node to the list
LLAdd (HlliExgExe, hexg);
LLUnlock(hexg);
}
}
*lphexg = hexg;
return (hexg == hexgNull) ? sheOutOfMemory : sheNone;
}
// SHHmodGetNextGlobal
//
// Purpose: Retrieve the next module in the current PROCESS.
//
// Input:
// phexe Pointer to hexe. This will be updated. If NULL, then
// start at the first exe in the current process.
// hmod Handle to mds. If NULL, set *phexe to the next process.
// and get the first module in it. Otherwise get the next
// module in the list.
//
// Output: Returns a handle to the next module in the proces list. Will
// return hmodNull if the end of the list is reached.
HMOD
SHHmodGetNextGlobal (
HEXE *phexe,
HMOD hmod
)
{
assert(hpdsCur);
do {
// If either the hexe or hmod is NULL, then on to the next exe.
if (!*phexe || !hmod) {
*phexe = SHGetNextExe (*phexe);
hmod = hmodNull; // Start at the beginning of the next one
}
// If we've got an exe, get the next module
if (*phexe) {
hmod = SHHmodGetNext(*phexe, hmod);
}
} while(!hmod && *phexe);
return hmod;
}
// SHGetSymbol
//
// Searches for a symbol value in the symbol table containing the seg and off
//
// Entry conditions:
// paddrOp:
// segment and offset of the symbol to be found
// paddrLoc:
// assumes that the module variable startaddr is set to the beginning
// of the code line currently being disassembled.
// sop:
// symbol options: what kinds of symbols to match. (???)
// lpodr:
// pointer where delta to symbol will be stored as well as
// near/farness, FPO/NON-FPO, cbProlog and symbol name.
//
// Exit conditions:
// *lpdoff:
// offset from symbol to the address
// return value:
// ascii name of symbol, or NULL if no match found
LSZ
SHGetSymbol (
LPADDR paddrOp,
LPADDR paddrLoc,
SOP sop,
LPODR lpodr
)
{
CXT cxt = {0};
ADDR addrT = *paddrLoc;
ADDR addrOp = *paddrOp;
if (!ADDR_IS_FLAT(addrOp) && GetAddrSeg (addrOp) == 0) {
return NULL;
}
SYUnFixupAddr ( &addrT );
if (sop & sopStack) {
// UNDONE: This is the only caller of FindBpOrReg... Optimize
if (SHFindBpOrReg (&addrT,
GetAddrOff ( addrOp ),
(WORD) (ADDR_IS_OFF32 (*paddrOp) ? S_BPREL32 : S_BPREL16),
lpodr->lszName))
{
lpodr->dwDeltaOff = 0;
return lpodr->lszName;
} else {
return NULL;
}
} else {
SYUnFixupAddr ( &addrOp );
}
cxt.hMod = 0;
if (sop & sopData) {
SHSetCxtMod (&addrT, &cxt);
} else {
SHSetCxtMod (&addrOp, &cxt);
}
cxt.addr = addrOp;
// get the closest symbol, including locals
SHdNearestSymbol (&cxt, sop, lpodr);
if ((sop & sopExact) && lpodr->dwDeltaOff) {
return NULL;
} else {
if (lpodr->dwDeltaOff == CV_MAXOFFSET) {
return NULL;
} else {
return lpodr->lszName;
}
}
}
LSZ
SHGetModule (
LPADDR paddrOp,
LSZ rgbName
)
{
CXT cxt = {0};
ADDR addrOp = *paddrOp;
HEXE hexe;
HEXG hexg;
LPEXG lpexg;
rgbName[0] = '\0';
SYUnFixupAddr (&addrOp);
cxt.hMod = 0;
SHSetCxtMod (&addrOp, &cxt);
if (!cxt.hMod ) {
return NULL;
}
cxt.addr = addrOp;
hexe = SHHexeFromHmod(cxt.hMod);
if (hexe) {
hexg = ((LPEXE) LLLock (hexe))->hexg;
lpexg = (LPEXG) LLLock(hexg);
_tcscpy(rgbName, lpexg->lszModule);
LLUnlock (hexe);
LLUnlock (hexg);
return rgbName;
}
return NULL;
}
// SHHexeFromName - private function */
HEXE
SHHexeFromName (
LSZ lszName
)
{
BOOL fFound = FALSE;
HEXE hexe = hexeNull;
// Find the hexe associated with the libname
while(!fFound && (hexe = SHGetNextExe (hexe))) {
HEXG hexg = ((LPEXE)LLLock(hexe))->hexg;
LLUnlock (hexe);
fFound = !_tcsicmp (((LPEXG)LLLock(hexg))->lszName, lszName);
LLUnlock (hexg);
}
return fFound ? hexe : hexeNull;
}
// SHUnloadDll
//
// Purpose: Mark an EXE/DLL as no longer resident in memory. The debugger
// should call this function when it receives a notification from
// the OS indicating that the module has been unloaded from
// memory. This does not unload the symbolic information for the
// module.
//
// See the comments at the top of this file for more on when this
// function should be called.
//
// Input: hexe - The handle to the EXE/DLL which was unloaded. After
// getting a notification from the OS, the debugger can
// determine the HEXE by calling SHGethExeFromName.
VOID
SHUnloadDll (
HEXE hexe
)
{
LPEXE lpexe;
LPEXG lpexg;
HPDS hpds;
HEXG hexg;
LPPDS lppds;
assert(hexe);
lpexe = (LPEXE) LLLock (hexe);
assert(lpexe);
hexg = lpexe->hexg;
assert(hexg);
lpexg = (LPEXG) LLLock (hexg);
assert(lpexg);
assert(lpexg->cRef);
hpds = lpexe->hpds;
assert(hpds);
lppds = (LPPDS) LLLock(hpds);
assert(lppds);
// Decrement the reference count
lpexg->cRef--;
// See if it's in the deferred load queue. If so, remove it.
if (lpexg->fOmfDefered) {
UnloadDefered(hexg);
}
// If this exe has exg DEBUGDATA, free it.
if (lpexe->pDebugData) {
// Free the copy of the debug data that we got from the exg.
if (lpexe->pDebugData != &lpexg->debugData) {
// This must be a fixed up copy
MHFree(lpexe->pDebugData->lpRtf);
MHFree(lpexe->pDebugData);
}
lpexe->pDebugData = NULL;
}
// If we're the last reference to the module, delete it from the image list.
if (lpexg->cRef == 0) {
LLUnlock(hexg);
LLDelete(HlliExgExe, hexg);
} else {
LLUnlock(hexg);
}
LLUnlock (hexe);
// Remove this image from the process list.
LLDelete(lppds->hlliExe, hexe);
LLUnlock(hpds);
return;
}
// SHLoadDll
//
// Purpose: This function serves two purposes:
//
// (1) Load symbolic information for an EXE/DLL into memory,
// so its symbols are available to the user.
// (2) Indicate to the SH whether the EXE/DLL itself is loaded
// into memory.
//
// Because it serves two purposes, this function may be called
// more than once for the same EXE/DLL. See the comments at
// the top of this file for more on when this function should
// be called.
//
// Input:
// lszName The name of the EXE or DLL.
// fLoading TRUE if the EXE/DLL itself is actually loaded at this
// time, FALSE if not.
//
// Output:
// Returns an SHE error code.
SHE
SHLoadDll (
LSZ lszName,
BOOL fLoading
)
{
SHE she = sheNone;
HEXE hexe;
HEXG hexg = hexgNull;
LSZ lsz = lszName;
LSZ lsz2 = NULL;
LSZ lsz3;
INT hfile = -1;
HEXG hexgMaybe = hexgNull;
LSZ AltName = NULL;
char ch = 0;
VLDCHK vldChk= {0, 0};
LPEXG lpexg;
LSZ lszFname;
BOOL fContinueSearch = TRUE;
DWORD dllLoadAddress = 0;
BOOL fMpSystem = FALSE;
EnterCriticalSection(&csSh);
// Check for the possiblity that we have a module only name
//
// We may get two formats of names. The first is just a name,
// no other information. The other is a big long string
// for PE exes.
if (*lszName == '|') {
// name
++lszName;
lsz2 = _tcschr(lszName, '|');
lsz = lsz2;
if (lsz && *lsz == '|') {
// timestamp
vldChk.TimeDateStamp = strtoul(++lsz, &lsz, 16);
}
if (lsz && *lsz == '|') {
// checksum
vldChk.CheckSum = strtoul(++lsz, &lsz, 16);
}
if (lsz && *lsz == '|') {
// hfile
hfile = strtoul(++lsz, &lsz, 16);
// Always close the handle returned from the DM.
// We'll reopen it iff need be in OlLoadOmf.
if (hfile) {
SYClose(hfile);
hfile = -1;
}
}
if (lsz && *lsz == '|') {
// image base
dllLoadAddress = strtoul(++lsz, &lsz, 16);
}
if (lsz && *lsz == '|') {
// alternate symbol file name
lsz++;
if (*lsz) {
lsz3 = _tcschr(lsz, '|');
*lsz3 = 0;
if (AltName = (LSZ) MHAlloc(_tcslen(lsz) + 1)) {
_tcscpy(AltName, (LSZ) lsz);
}
*lsz3 = '|';
lsz = lsz3;
}
}
// isolate name
if (lsz2) {
*lsz2 = 0;
}
}
// We can't just call SHExeFromName here because there may be
// multiple images with the same name (for instance, when debugging
// more than one process or more than one machine) and we want to
// to share symbols wherever possible.
// Look and see if we already have this debug information loaded
while (fContinueSearch) {
// Find the next OMF set by name of EXE module
hexg = LLFind(HlliExgExe, hexg, lszName, MATCH_NAMEONLY);
if (hexg == hexgNull) {
fContinueSearch = FALSE; // Nothing by this name.
} else {
// If we found one, do the checksum and timestamp match?
lpexg = (LPEXG) LLLock(hexg);
// If the timestamp is set to -1, use whatever this image has.
if (vldChk.TimeDateStamp == 0xffffffff) {
vldChk.TimeDateStamp = lpexg->ulTimeStamp;
}
// UNDONE: We don't compare LoadAddresses here to ensure they match
// when the symbols are correct... Investigate if this is a bug.
if ((vldChk.TimeDateStamp != lpexg->ulTimeStamp) ||
(vldChk.CheckSum != lpexg->ulCheckSum)) {
// The debug info no longer matches -- check to see if
// there are any references to the debug info. If not then
// free it as it has been superceded.
if (lpexg->cRef == 0) {
LLUnlock(hexg);
LLDelete(HlliExgExe, hexg);
hexg = hexgNull;
}
// Keep looking for a match.
} else {
// Timestamp and checksum are valid. Has the user decided
// to change the load status of this exe?
lszFname = lpexg->lszName;
if (!SYGetDefaultShe(lszFname, &she)) {
SYGetDefaultShe(NULL, &she);
}
switch (she) {
case sheDeferSyms:
case sheNone:
if (!lpexg->fOmfMissing && !lpexg->fOmfSkipped) {
fContinueSearch = FALSE;
} else {
hexgMaybe = hexg;
}
break;
case sheSuppressSyms:
if (lpexg->fOmfMissing || lpexg->fOmfSkipped) {
fContinueSearch = FALSE;
} else {
hexgMaybe = hexg;
}
break;
default:
assert((she == sheDeferSyms) ||
(she == sheSuppressSyms) ||
(she == sheNone));
break;
}
}
if (hexg) {
LLUnlock(hexg);
}
}
}
// If we did not find an OMF module -- create one
if (hexg == hexgNull && hexgMaybe == hexgNull) {
SHAddDllExt (lszName, TRUE, FALSE, &vldChk, &hexg);
if (hexg == hexgNull) {
she = sheOutOfMemory;
}
else if (SHHexeAddNew (hpdsCur, hexg, dllLoadAddress) == NULL) {
she = sheOutOfMemory;
}
else {
if (AltName) {
lpexg = (LPEXG) LLLock(hexg);
lpexg->lszAltName = AltName;
LLUnlock(hexg);
}
she = OLLoadOmf(hexg, &vldChk, dllLoadAddress);
}
} else if (hexg != hexgNull) {
// We found the OMF lying around
// If we found a partial match as well, see if it s/b discarded.
if (hexgMaybe) {
lpexg = (LPEXG) LLLock(hexgMaybe);
LLUnlock(hexgMaybe);
if (lpexg->cRef == 0) {
LLDelete(HlliExgExe, hexgMaybe);
}
}
if (SHHexeAddNew(hpdsCur, hexg, dllLoadAddress) == NULL) {
she = sheOutOfMemory;
}
if (hfile != -1) {
SYClose(hfile);
}
} else {
// Found the right exg, but the wrong load state.
lpexg = (LPEXG) LLLock(hexgMaybe);
if (lpexg->fOmfMissing) {
// no OMF in image. We can't improve things.
LLUnlock(hexgMaybe);
she = sheNoSymbols;
} else if (lpexg->fOmfSkipped) {
// decided to load syms this time around.
LLUnlock(hexgMaybe);
she = OLLoadOmf(hexgMaybe, &vldChk, dllLoadAddress);
} else {
// have syms and want to discard them.
OLUnloadOmf(lpexg);
VoidCaches(); // Just in case.
lpexg->fOmfSkipped = TRUE;
LLUnlock( hexgMaybe );
she = sheSuppressSyms;
}
if (SHHexeAddNew(hpdsCur, hexgMaybe, dllLoadAddress) == NULL) {
she = sheOutOfMemory;
}
}
// Restore damage to the input buffer
if (lsz2 != NULL) {
*lsz2 = '|';
}
LeaveCriticalSection(&csSh);
return she;
}
// SHAddDllsToProcess
//
// Purpose: Associate all DLLs that have been loaded with the current EXE.
//
// The debugger, at init time, will call SHAddDll on one EXE
// and zero or more DLLs. Then it should call this function
// to indicate that those DLLs are associated with (used by)
// that EXE; thus, a user request for a symbol from the EXE
// will also search the symbolic information from those DLLs.
//
// Output: Returns an SHE error code. At this writing, the only legal values
// are sheNone and sheOutOfMemory.
SHE
SHAddDllsToProcess (
VOID
)
{
SHE she = sheNone;
HEXG hexg;
if (!SHHexeAddNew (hpdsCur, LLLast (HlliExgExe), NULL)) {
she = sheOutOfMemory;
} else {
if (she == sheNone) {
for (hexg = LLNext (HlliExgExe, hexgNull);
(hexg != hexgNull) && (she == sheNone);
hexg = LLNext (HlliExgExe, hexg))
{
if (!SHHexeAddNew (hpdsCur, hexg, NULL)) {
she = sheOutOfMemory;
break;
}
}
}
}
return she;
}
// BuildALM
//
// Purpose: Build an ALM (ALigned Memory) structure. To be used later on
// for demand symbol table loads.
LPALM
BuildALM (
BOOL fSeq,
WORD btAlign,
LPB lpbData,
DWORD cb,
WORD cbBlock
)
{
LPALM lpalm;
LPUFOP rgufop = NULL;
lpalm = (LPALM) AllocAlign (sizeof(ALM) + sizeof(UFOP) + sizeof(WORD));
if (lpalm == NULL) {
return NULL;
}
lpalm->fSeq = fSeq;
lpalm->btAlign = btAlign;
lpalm->cb = cb;
lpalm->cbBlock = cbBlock;
lpalm->pbData = lpbData;
return lpalm;
}
PVOID
LpvFromAlmLfo (
LPALM lpalm,
DWORD lfo
)
{
if (lfo > lpalm->cb) {
return(NULL);
} else {
return lpalm->pbData + lfo;
}
}
// GetNextSym
//
// Purpose: Return the next symbol from a file. Handle S_ALIGN records as
// they're encountered.
SYMPTR
GetNextSym (
SYMPTR psym,
LPALM lpalm
)
{
SYMPTR psymNext = (SYMPTR) (((LPB) psym) + psym->reclen + sizeof(WORD));
if ((BYTE *) psymNext > (lpalm->pbData + lpalm->cb)) {
return(NULL);
}
if (psymNext->rectyp == S_ALIGN) {
// If we hit a S_ALIGN record, skip over it.
psymNext = (SYMPTR) (((LPB) psymNext) + psymNext->reclen + sizeof(WORD));
}
return(psymNext);
}
// GetSymbols
//
// Purpose: Return a pointer to the symbols for a module.
PVOID
GetSymbols(
LPMDS lpmds
)
{
PVOID lpvRet = NULL;
if (lpmds->symbols) {
lpvRet = lpmds->symbols;
} else if (lpmds->pmod) {
// Allocate space for this module's local symbols, then load them
// from the PDB.
if (ModQuerySymbols(lpmds->pmod, 0, (CB *)&lpmds->cbSymbols) &&
(lpmds->symbols = (LPB) MHAlloc(lpmds->cbSymbols))) {
if (ModQuerySymbols(lpmds->pmod, lpmds->symbols, (CB *)&lpmds->cbSymbols)) {
lpvRet = lpmds->symbols;
} else {
MHFree(lpmds->symbols);
lpmds->symbols = NULL;
lpmds->cbSymbols = 0;
}
}
}
return lpvRet;
}
VOID
SHUnloadSymbolHandler(
BOOL fResetLists
)
{
// Put code here to execute just before
// the symbol handler is Freed from memory (FreeLibrary...)
if (HlliPds) {
LLDestroy(HlliPds);
HlliPds = NULL;
}
hpdsCur = hpdsNull;
if (HlliExgExe) {
LLDestroy(HlliExgExe);
HlliExgExe = NULL;
}
if (fResetLists) {
// If fResetLists, the symbol handler is NOT being unloaded, but
// the information in the linked lists are no longer valid so we
// will have destroyed the list info, just reinitialize the lists.
FInitLists();
} else {
// Otherwise, free up the last system resources.
// Release the symcvt ptr.
if (hLib != NULL) {
FreeLibrary(hLib);
hLib = NULL;
}
// null out the function pointer
pfConvertSymbolsForImage = NULL;
// cleanup synchronization objects
DeleteCriticalSection(&csSh);
#if 0
DeleteCriticalSection(&CsSymbolLoad);
DeleteCriticalSection(&CsSymbolProcess);
CloseHandle(hEventLoaded);
#endif
}
}
// Get the time stamp of the EXE (which has the CV info)
//
// Returns: sheFileOpen
// sheCorruptOmf
// sheNone
SHE
SHGetExeTimeStamp(
LSZ szExeName,
DWORD *lplTimeStamp
)
{
IMAGE_DOS_HEADER doshdr; // Old format MZ header
IMAGE_FILE_HEADER PEHeader;
BOOL fIsPE = FALSE;
DWORD dwMagic;
UINT hfile;
SHE sheRet;
if ((hfile = SYOpen (szExeName)) == -1) {
sheRet = sheFileOpen;
goto cleanup;
}
// Go to beginning of file and read old EXE header
if (SYReadFar (hfile, (LPB) &doshdr, sizeof(IMAGE_DOS_HEADER)) !=
sizeof (IMAGE_DOS_HEADER))
{
sheRet = sheCorruptOmf;
goto cleanup;
}
// Go to beginning of new header, read it in and verify
if (doshdr.e_magic == IMAGE_DOS_SIGNATURE) {
// DOS/Win16/Win32 image with stub. See if this is a PE image
// and read from there.
if (SYSeek (hfile, doshdr.e_lfanew, SEEK_SET) != doshdr.e_lfanew) {
sheRet = sheCorruptOmf;
goto cleanup;
}
} else if (doshdr.e_magic == IMAGE_NT_SIGNATURE) {
// Win32 Image w/o a stub or a ROM image. restart at the beginning.
if (SYSeek(hfile, 0, SEEK_SET) != 0) {
sheRet = sheCorruptOmf;
goto cleanup;
}
}
if ((SYReadFar (hfile, (LPB) &dwMagic, sizeof(dwMagic)) != sizeof(dwMagic)) ||
(dwMagic != IMAGE_NT_SIGNATURE))
{
sheRet = sheCorruptOmf;
goto cleanup;
}
// Retrieve the timestamp.
if (SYReadFar (hfile, (LPB) &PEHeader, sizeof(IMAGE_FILE_HEADER)) !=
sizeof(IMAGE_FILE_HEADER))
{
sheRet = sheCorruptOmf;
goto cleanup;
}
*lplTimeStamp = PEHeader.TimeDateStamp;
cleanup:
if (hfile != -1) {
SYClose (hfile);
}
return sheRet;
}
// SHLszGetErrorText
//
// Synopsis: lsz = SHLszGetErrorText( she )
//
// Entry: she - error number to get text for
//
// Returns: pointer to the string containing the error text
//
// Description: This routine is used by the debugger to get the
// text for an error which occured in the symbol handler.
LSZ
SHLszGetErrorText(
SHE she
)
{
static LSZ rglsz[] = {
SzSheNone,
SzSheNoSymbols,
SzSheFutureSymbols,
SzSheMustRelink,
SzSheNotPacked,
SzSheOutOfMemory,
SzSheCorruptOmf,
SzSheFileOpen,
SzSheSuppressSyms,
SzSheDeferSyms,
SzSheSymbolsConverted,
SzSheBadTimeStamp,
SzSheBadChecksum,
SzShePdbNotFound,
SzShePdbBadSig,
SzShePdbInvalidAge,
SzShePdbOldFormat
};
assert((sizeof(rglsz) / sizeof(*rglsz)) == sheMax);
if (she < sheMax) {
return rglsz[she];
}
return SzSheBadError;
}
// SHWantSymbols
//
// Purpose: Loads symbols of a defered module
//
// Input: HEXE for which to load symbols
BOOL
SHWantSymbols(
HEXE hexe
)
{
HEXG hexg;
LPEXE lpexe;
LPEXG lpexg;
BOOL fRet = TRUE;
if (!hexe) {
fRet = FALSE;
} else {
lpexe = (LPEXE)LLLock(hexe);
hexg = lpexe->hexg;
if (!hexg) {
fRet = FALSE;
} else {
lpexg = (LPEXG)LLLock (hexg);
if (lpexg->fOmfDefered) {
LoadDefered(hexg);
}
LLUnlock (hexg);
}
LLUnlock (hexe);
}
return fRet;
}
// SHSplitPath
//
// Custom split path that allows parameters to be null
VOID
SHSplitPath (
LSZ lszPath,
LSZ lszDrive,
LSZ lszDir,
LSZ lszName,
LSZ lszExt
)
{
// For 32-bit versions, there's no need for all the extra work
_tsplitpath(lszPath, lszDrive, lszDir, lszName, lszExt);
}