Skip to content

Commit

Permalink
VisualC++2012 が更新された dll の pdb を読んでくれない問題に対処
Browse files Browse the repository at this point in the history
  • Loading branch information
i-saint committed Sep 1, 2013
1 parent 5edfcaa commit de042ee
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 83 deletions.
209 changes: 175 additions & 34 deletions dpBinary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,48 +599,189 @@ inline void dpEnumerateDLLExports(HMODULE module, const F &f)
}
}

struct CV_INFO_PDB70
{
DWORD CvSignature;
GUID Signature;
DWORD Age;
BYTE PdbFileName[1];
};

// fill_gap: .dll ファイルをそのままメモリに移した場合はこれを true にする必要があります。
// LoadLibrary() で正しくロードしたものは section の再配置が行われ、元ファイルとはデータの配置にズレが生じます。
// fill_gap==true の場合このズレを補正します。
CV_INFO_PDB70* dpGetPDBInfoFromModule(void *pModule, bool fill_gap)
{
if(!pModule) { return nullptr; }

PBYTE pData = (PUCHAR)pModule;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pData;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(pData + pDosHeader->e_lfanew);
if(pDosHeader->e_magic==IMAGE_DOS_SIGNATURE && pNtHeaders->Signature==IMAGE_NT_SIGNATURE) {
ULONG DebugRVA = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
if(DebugRVA==0) { return nullptr; }

PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);
for(size_t i=0; i<pNtHeaders->FileHeader.NumberOfSections; ++i) {
PIMAGE_SECTION_HEADER s = pSectionHeader+i;
if(DebugRVA >= s->VirtualAddress && DebugRVA < s->VirtualAddress+s->SizeOfRawData) {
pSectionHeader = s;
break;
}
}
if(fill_gap) {
DWORD gap = pSectionHeader->VirtualAddress - pSectionHeader->PointerToRawData;
pData -= gap;
}

PIMAGE_DEBUG_DIRECTORY pDebug;
pDebug = (PIMAGE_DEBUG_DIRECTORY)(pData + DebugRVA);
if(DebugRVA!=0 && DebugRVA < pNtHeaders->OptionalHeader.SizeOfImage && pDebug->Type==IMAGE_DEBUG_TYPE_CODEVIEW) {
CV_INFO_PDB70 *pCVI = (CV_INFO_PDB70*)(pData + pDebug->AddressOfRawData);
if(pCVI->CvSignature=='SDSR') {
return pCVI;
}
}
}
return nullptr;
}

struct PDBStream70
{
DWORD impv;
DWORD sig;
DWORD age;
GUID sig70;
};

// pdb ファイルから Age & GUID 情報を抽出します
PDBStream70* dpGetPDBSignature(void *mapped_pdb_file)
{
// thanks to https://code.google.com/p/pdbparser/

#define ALIGN_UP(x, align) ((x+align-1) & ~(align-1))
#define STREAM_SPAN_PAGES(size) (ALIGN_UP(size,pHeader->dwPageSize)/pHeader->dwPageSize)
#define PAGE(x) (pImageBase + pHeader->dwPageSize*(x))
#define PDB_STREAM_PDB 1

struct MSF_Header
{
char szMagic[32]; // 0x00 Signature
DWORD dwPageSize; // 0x20 Number of bytes in the pages (i.e. 0x400)
DWORD dwFpmPage; // 0x24 FPM (free page map) page (i.e. 0x2)
DWORD dwPageCount; // 0x28 Page count (i.e. 0x1973)
DWORD dwRootSize; // 0x2c Size of stream directory (in bytes; i.e. 0x6540)
DWORD dwReserved; // 0x30 Always zero.
DWORD dwRootPointers[0x49];// 0x34 Array of pointers to root pointers stream.
};

BYTE *pImageBase = (BYTE*)mapped_pdb_file;
MSF_Header *pHeader = (MSF_Header*)pImageBase;

DWORD RootPages = STREAM_SPAN_PAGES(pHeader->dwRootSize);
DWORD RootPointersPages = STREAM_SPAN_PAGES(RootPages*sizeof(DWORD));

std::string RootPointersRaw;
RootPointersRaw.resize(RootPointersPages * pHeader->dwPageSize);
for(DWORD i=0; i<RootPointersPages; i++) {
PVOID Page = PAGE(pHeader->dwRootPointers[i]);
SIZE_T Offset = pHeader->dwPageSize * i;
memcpy(&RootPointersRaw[0]+Offset, Page, pHeader->dwPageSize);
}
DWORD *RootPointers = (DWORD*)&RootPointersRaw[0];

std::string StreamInfoRaw;
StreamInfoRaw.resize(RootPages * pHeader->dwPageSize);
for(DWORD i=0; i<RootPages; i++) {
PVOID Page = PAGE(RootPointers[i]);
SIZE_T Offset = pHeader->dwPageSize * i;
memcpy(&StreamInfoRaw[0]+Offset, Page, pHeader->dwPageSize);
}
DWORD StreamCount = *(DWORD*)&StreamInfoRaw[0];
DWORD *dwStreamSizes = (DWORD*)&StreamInfoRaw[4];

{
DWORD *StreamPointers = &dwStreamSizes[StreamCount];
DWORD page = 0;
for(DWORD i=0; i<PDB_STREAM_PDB; i++) {
DWORD nPages = STREAM_SPAN_PAGES(dwStreamSizes[i]);
page += nPages;
}
DWORD *pdwStreamPointers = &StreamPointers[page];

PVOID Page = PAGE(pdwStreamPointers[0]);
return (PDBStream70*)Page;
}

#undef PDB_STREAM_PDB
#undef PAGE
#undef STREAM_SPAN_PAGES
#undef ALIGN_UP
}

bool dpDllFile::loadFile(const char *path)
{
dpTime mtime = dpGetMTime(path);
if(m_module && m_path==path && mtime<=m_mtime) { return true; }

// ロード中の dll と関連する pdb はロックがかかってしまい、リビルドに失敗するようになるため、
// 一時ファイルにコピーする処理をここで行う。
// dll に含まれる .pdb のパスを書き換えてコピーしないといけない。
// ロード中の dll と関連する pdb はロックがかかってしまい、以降のビルドが失敗するようになるため、その対策を行う。
// .dll と .pdb を一時ファイルにコピーしてそれをロードする。
// コピーの際、
// ・.dll に含まれる .pdb へのパスをコピー版へのパスに書き換える
// ・.dll と .pdb 両方に含まれる pdb の GUID を更新する
// (VisualC++2012 の場合、これを怠ると最初にロードした dll の pdb が以降の更新された dll でも使われ続けてしまう)

// dll をメモリに map
void *data = nullptr;
size_t datasize = 0;
if(!dpMapFile(path, data, datasize, malloc)) {
dpPrintError("file not found %s\n", path);
return false;
std::string pdb_base;
GUID uuid;
{
// dll をメモリに map
void *data = nullptr;
size_t datasize = 0;
if(!dpMapFile(path, data, datasize, malloc)) {
dpPrintError("file not found %s\n", path);
return false;
}

// 一時ファイル名を算出
char rev[8] = {0};
for(int i=0; i<0xfff; ++i) {
_snprintf(rev, _countof(rev), "%x", i);
m_path = path;
m_actual_file.clear();
std::string ext;
dpSeparateFileExt(path, &m_actual_file, &ext);
m_actual_file+=rev;
m_actual_file+=".";
m_actual_file+=ext;
if(!dpFileExists(m_actual_file.c_str())) { break; }
}

// pdb へのパスと GUID を更新
if(CV_INFO_PDB70 *cv=dpGetPDBInfoFromModule(data, true)) {
char *pdb = (char*)cv->PdbFileName;
pdb_base = pdb;
strncpy(pdb+pdb_base.size()-3, rev, 3);
m_pdb_path = pdb;
cv->Signature.Data1 += ::clock();
uuid = cv->Signature;
}
// dll を一時ファイルにコピー
dpWriteFile(m_actual_file.c_str(), data, datasize);
free(data);
}

// 一時ファイル名を算出
char rev[8] = {0};
for(int i=0; i<0xfff; ++i) {
_snprintf(rev, _countof(rev), "%x", i);
m_path = path;
m_actual_file.clear();
std::string ext;
dpSeparateFileExt(path, &m_actual_file, &ext);
m_actual_file+=rev;
m_actual_file+=".";
m_actual_file+=ext;
if(!dpFileExists(m_actual_file.c_str())) { break; }
}

// pdb のパスを抽出。あればパスを書き換え、書き換え後のパスに元ファイルをコピー
if(char *pdb=dpGetPDBPathFromModule(data, true)) {
std::string before;
before = pdb;
strncpy(pdb+before.size()-3, rev, 3);
m_pdb_path = pdb;
dpCopyFile(before.c_str(), m_pdb_path.c_str());
}
// dll を一時ファイルにコピー
dpWriteFile(m_actual_file.c_str(), data, datasize);
free(data);
{
// pdb を GUID を更新してコピー
void *data = nullptr;
size_t datasize = 0;
if(dpMapFile(pdb_base.c_str(), data, datasize, malloc)) {
if(PDBStream70 *sig = dpGetPDBSignature(data)) {
sig->sig70 = uuid;
}
dpWriteFile(m_pdb_path.c_str(), data, datasize);
free(data);
}
}

HMODULE module = ::LoadLibraryA(m_actual_file.c_str());
if(loadMemory(path, module, 0, mtime)) {
Expand Down
47 changes: 0 additions & 47 deletions dpFoundation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,53 +156,6 @@ dpTime dpGetSystemTime()
return ret.qword;
}

// fill_gap: .dll ファイルをそのままメモリに移した場合はこれを true にする必要があります。
// LoadLibrary() で正しくロードしたものは section の再配置が行われ、元ファイルとはデータの配置にズレが生じます。
// fill_gap==true の場合このズレを補正します。
char* dpGetPDBPathFromModule(void *pModule, bool fill_gap)
{
if(!pModule) { return nullptr; }

struct CV_INFO_PDB70
{
DWORD CvSignature;
GUID Signature;
DWORD Age;
BYTE PdbFileName[1];
};

PBYTE pData = (PUCHAR)pModule;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pData;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(pData + pDosHeader->e_lfanew);
if(pDosHeader->e_magic==IMAGE_DOS_SIGNATURE && pNtHeaders->Signature==IMAGE_NT_SIGNATURE) {
ULONG DebugRVA = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
if(DebugRVA==0) { return nullptr; }

PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);
for(size_t i=0; i<pNtHeaders->FileHeader.NumberOfSections; ++i) {
PIMAGE_SECTION_HEADER s = pSectionHeader+i;
if(DebugRVA >= s->VirtualAddress && DebugRVA < s->VirtualAddress+s->SizeOfRawData) {
pSectionHeader = s;
break;
}
}
if(fill_gap) {
DWORD gap = pSectionHeader->VirtualAddress - pSectionHeader->PointerToRawData;
pData -= gap;
}

PIMAGE_DEBUG_DIRECTORY pDebug;
pDebug = (PIMAGE_DEBUG_DIRECTORY)(pData + DebugRVA);
if(DebugRVA!=0 && DebugRVA < pNtHeaders->OptionalHeader.SizeOfImage && pDebug->Type==IMAGE_DEBUG_TYPE_CODEVIEW) {
CV_INFO_PDB70 *pCVI = (CV_INFO_PDB70*)(pData + pDebug->AddressOfRawData);
if(pCVI->CvSignature=='SDSR') {
return (char*)pCVI->PdbFileName;
}
}
}
return nullptr;
}

bool dpCopyFile(const char *srcpath, const char *dstpath)
{
return ::CopyFileA(srcpath, dstpath, FALSE)==TRUE;
Expand Down
2 changes: 0 additions & 2 deletions dpInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ void* dpAllocateModule(size_t size);
void dpDeallocate(void *location, size_t size);
dpTime dpGetMTime(const char *path);
dpTime dpGetSystemTime();

char* dpGetPDBPathFromModule(void *pModule, bool fill_gap=false);
bool dpCopyFile(const char *srcpath, const char *dstpath);

template<class F> void dpGlob(const char *path, const F &f);
Expand Down

0 comments on commit de042ee

Please sign in to comment.