/* $Id: kDepIDB.c 3315 2020-03-31 01:12:19Z bird $ */ /** @file * kDepIDB - Extract dependency information from a MS Visual C++ .idb file. */ /* * Copyright (c) 2007-2010 knut st. osmundsen * * This file is part of kBuild. * * kBuild is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * kBuild is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with kBuild. If not, see * */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_ALLOCA_H # include #endif #if !defined(_MSC_VER) # include #else # include #endif #include "k/kDefs.h" #include "k/kTypes.h" #include "kDep.h" #include "err.h" #include "kmkbuiltin.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /*#define DEBUG*/ #ifdef DEBUG # define dprintf(a) printf a # define dump(pb, cb, offBase) depHexDump(pb,cb,offBase) #else # define dprintf(a) do {} while (0) # define dump(pb, cb, offBase) do {} while (0) #endif /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ typedef struct KDEPIDBGLOBALS { PKMKBUILTINCTX pCtx; DEPGLOBALS Core; } KDEPIDBGLOBALS; typedef KDEPIDBGLOBALS *PKDEPIDBGLOBALS; /** * Scans a stream (chunk of data really) for dependencies. * * @returns 0 on success. * @returns !0 on failure. * @param pThis The kDepIDB instance. * @param pbStream The stream bits. * @param cbStream The size of the stream. * @param pszPrefix The dependency prefix. * @param cchPrefix The size of the prefix. */ static int ScanStream(PKDEPIDBGLOBALS pThis, KU8 *pbStream, size_t cbStream, const char *pszPrefix, size_t cchPrefix) { const KU8 *pbCur = pbStream; size_t cbLeft = cbStream; register char chFirst = *pszPrefix; while (cbLeft > cchPrefix + 2) { if ( *pbCur != chFirst || memcmp(pbCur, pszPrefix, cchPrefix)) { pbCur++; cbLeft--; } else { size_t cchDep; pbCur += cchPrefix; cchDep = strlen((const char *)pbCur); depAdd(&pThis->Core, (const char *) pbCur, cchDep); dprintf(("%05x: '%s'\n", pbCur - pbStream, pbCur)); pbCur += cchDep; cbLeft -= cchDep + cchPrefix; } } return 0; } /*///////////////////////////////////////////////////////////////////////////// // // // P D B 7 . 0 // // /////////////////////////////////////////////////////////////////////////////*/ /** A PDB 7.0 Page number. */ typedef KU32 PDB70PAGE; /** Pointer to a PDB 7.0 Page number. */ typedef PDB70PAGE *PPDB70PAGE; /** * A PDB 7.0 stream. */ typedef struct PDB70STREAM { /** The size of the stream. */ KU32 cbStream; } PDB70STREAM, *PPDB70STREAM; /** The PDB 7.00 signature. */ #define PDB_SIGNATURE_700 "Microsoft C/C++ MSF 7.00\r\n\x1A" "DS\0\0" /** * The PDB 7.0 header. */ typedef struct PDB70HDR { /** The signature string. */ KU8 szSignature[sizeof(PDB_SIGNATURE_700)]; /** The page size. */ KU32 cbPage; /** The start page. */ PDB70PAGE iStartPage; /** The number of pages in the file. */ PDB70PAGE cPages; /** The root stream directory. */ KU32 cbRoot; /** Unknown function, always 0. */ KU32 u32Reserved; /** The page index of the root page table. */ PDB70PAGE iRootPages; } PDB70HDR, *PPDB70HDR; /** * The PDB 7.0 root directory. */ typedef struct PDB70ROOT { /** The number of streams */ KU32 cStreams; /** Array of streams. */ PDB70STREAM aStreams[1]; /* KU32 aiPages[] */ } PDB70ROOT, *PPDB70ROOT; /** * The PDB 7.0 name stream (#1) header. */ typedef struct PDB70NAMES { /** The structure version. */ KU32 Version; /** Timestamp. */ KU32 TimeStamp; /** Unknown. */ KU32 Unknown1; /** GUID. */ KU32 u32Guid[4]; /** The size of the following name table. */ KU32 cbNames; /** The name table. */ char szzNames[1]; } PDB70NAMES, *PPDB70NAMES; /** The version / magic of the names structure. */ #define PDB70NAMES_VERSION 20000404 static int Pdb70ValidateHeader(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr, size_t cbFile) { if (pHdr->cbPage * pHdr->cPages != cbFile) return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - cbPage * cPages != cbFile."); if (pHdr->iStartPage >= pHdr->cPages && pHdr->iStartPage <= 0) return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - iStartPage=%u cPages=%u.", pHdr->iStartPage, pHdr->cPages); if (pHdr->iRootPages >= pHdr->cPages && pHdr->iRootPages <= 0) return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - iRootPages=%u cPage=%u.", pHdr->iStartPage, pHdr->cPages); return 0; } #ifdef DEBUG static size_t Pdb70Align(PPDB70HDR pHdr, size_t cb) { if (cb == ~(KU32)0 || !cb) return 0; return ((cb + pHdr->cbPage - 1) / pHdr->cbPage) * pHdr->cbPage; } #endif /* DEBUG */ static size_t Pdb70Pages(PPDB70HDR pHdr, size_t cb) { if (cb == ~(KU32)0 || !cb) return 0; return (cb + pHdr->cbPage - 1) / pHdr->cbPage; } static void *Pdb70AllocAndRead(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr, size_t cb, PPDB70PAGE paiPageMap) { const size_t cbPage = pHdr->cbPage; size_t cPages = Pdb70Pages(pHdr, cb); KU8 *pbBuf = malloc(cPages * cbPage + 1); if (pbBuf) { size_t iPage = 0; while (iPage < cPages) { size_t off = paiPageMap[iPage]; if (off < pHdr->cPages) { off *= cbPage; memcpy(pbBuf + iPage * cbPage, (KU8 *)pHdr + off, cbPage); dump(pbBuf + iPage * cbPage, iPage + 1 < cPages ? cbPage : cb % cbPage, off); } else { warnx(pThis->pCtx, "warning: Invalid page index %u (max %u)!\n", (unsigned)off, pHdr->cPages); memset(pbBuf + iPage * cbPage, 0, cbPage); } iPage++; } pbBuf[cPages * cbPage] = '\0'; } else { errx(pThis->pCtx, 1, "failed to allocate %lu bytes", (unsigned long)(cPages * cbPage + 1)); return NULL; } return pbBuf; } static PPDB70ROOT Pdb70AllocAndReadRoot(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr) { /* * The tricky bit here is to find the right length. Really? * (Todo: Check if we can just use the stream #0 size..) */ PPDB70PAGE piPageMap = (KU32 *)((KU8 *)pHdr + pHdr->iRootPages * pHdr->cbPage); PPDB70ROOT pRoot = Pdb70AllocAndRead(pThis, pHdr, pHdr->cbRoot, piPageMap); if (pRoot) { #if 1 /* This stuff is probably unnecessary: */ /* size = stream header + array of stream. */ size_t cb = K_OFFSETOF(PDB70ROOT, aStreams[pRoot->cStreams]); free(pRoot); pRoot = Pdb70AllocAndRead(pThis, pHdr, cb, piPageMap); if (pRoot) { /* size += page tables. */ unsigned iStream = pRoot->cStreams; while (iStream-- > 0) if (pRoot->aStreams[iStream].cbStream != ~(KU32)0) cb += Pdb70Pages(pHdr, pRoot->aStreams[iStream].cbStream) * sizeof(PDB70PAGE); free(pRoot); pRoot = Pdb70AllocAndRead(pThis, pHdr, cb, piPageMap); if (pRoot) { /* validate? */ return pRoot; } } #else /* validate? */ return pRoot; #endif } return NULL; } static void *Pdb70AllocAndReadStream(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr, PPDB70ROOT pRoot, unsigned iStream, size_t *pcbStream) { const size_t cbStream = pRoot->aStreams[iStream].cbStream; PPDB70PAGE paiPageMap; if ( iStream >= pRoot->cStreams || cbStream == ~(KU32)0) { errx(pThis->pCtx, 1, "Invalid stream %d", iStream); return NULL; } paiPageMap = (PPDB70PAGE)&pRoot->aStreams[pRoot->cStreams]; while (iStream-- > 0) if (pRoot->aStreams[iStream].cbStream != ~(KU32)0) paiPageMap += Pdb70Pages(pHdr, pRoot->aStreams[iStream].cbStream); if (pcbStream) *pcbStream = cbStream; return Pdb70AllocAndRead(pThis, pHdr, cbStream, paiPageMap); } static int Pdb70Process(PKDEPIDBGLOBALS pThis, KU8 *pbFile, size_t cbFile) { PPDB70HDR pHdr = (PPDB70HDR)pbFile; PPDB70ROOT pRoot; PPDB70NAMES pNames; size_t cbStream = 0; unsigned fDone = 0; unsigned iStream; int rc = 0; dprintf(("pdb70\n")); /* * Validate the header and read the root stream. */ if (Pdb70ValidateHeader(pThis, pHdr, cbFile)) return 1; pRoot = Pdb70AllocAndReadRoot(pThis, pHdr); if (!pRoot) return 1; /* * The names we want are usually all found in the 'Names' stream, that is #1. */ dprintf(("Reading the names stream....\n")); pNames = Pdb70AllocAndReadStream(pThis, pHdr, pRoot, 1, &cbStream); if (pNames) { dprintf(("Names: Version=%u cbNames=%u (%#x)\n", pNames->Version, pNames->cbNames, pNames->cbNames)); if ( pNames->Version == PDB70NAMES_VERSION && pNames->cbNames > 32 && pNames->cbNames + K_OFFSETOF(PDB70NAMES, szzNames) <= pRoot->aStreams[1].cbStream) { /* * Iterate the names and add the /mr/inversedeps/ ones to the dependency list. */ const char *psz = &pNames->szzNames[0]; size_t cb = pNames->cbNames; size_t off = 0; dprintf(("0x0000 #0: %6d bytes [root / toc]\n", pRoot->aStreams[0].cbStream)); for (iStream = 1; cb > 0; iStream++) { int fAdded = 0; size_t cch = strlen(psz); if ( cch >= sizeof("/mr/inversedeps/") && !memcmp(psz, "/mr/inversedeps/", sizeof("/mr/inversedeps/") - 1)) { depAdd(&pThis->Core, psz + sizeof("/mr/inversedeps/") - 1, cch - (sizeof("/mr/inversedeps/") - 1)); fAdded = 1; } dprintf(("%#06x #%d: %6d bytes %s%s\n", off, iStream, iStream < pRoot->cStreams ? pRoot->aStreams[iStream].cbStream : -1, psz, fAdded ? " [dep]" : "")); (void)fAdded; /* next */ if (cch >= cb) { dprintf(("warning! cch=%d cb=%d\n", cch, cb)); cch = cb - 1; } cb -= cch + 1; psz += cch + 1; off += cch + 1; } rc = 0; fDone = 1; } else dprintf(("Unknown version or bad size: Version=%u cbNames=%d cbStream=%d\n", pNames->Version, pNames->cbNames, cbStream)); free(pNames); } if (!fDone) { /* * Iterate the streams in the root and scan their content for * dependencies. */ rc = 0; for (iStream = 0; iStream < pRoot->cStreams && !rc; iStream++) { KU8 *pbStream; if ( pRoot->aStreams[iStream].cbStream == ~(KU32)0 || !pRoot->aStreams[iStream].cbStream) continue; dprintf(("Stream #%d: %#x bytes (%#x aligned)\n", iStream, pRoot->aStreams[iStream].cbStream, Pdb70Align(pHdr, pRoot->aStreams[iStream].cbStream))); pbStream = (KU8 *)Pdb70AllocAndReadStream(pThis, pHdr, pRoot, iStream, &cbStream); if (pbStream) { rc = ScanStream(pThis, pbStream, cbStream, "/mr/inversedeps/", sizeof("/mr/inversedeps/") - 1); free(pbStream); } else rc = 1; } } free(pRoot); return rc; } /*///////////////////////////////////////////////////////////////////////////// // // // P D B 2 . 0 // // /////////////////////////////////////////////////////////////////////////////*/ /** A PDB 2.0 Page number. */ typedef KU16 PDB20PAGE; /** Pointer to a PDB 2.0 Page number. */ typedef PDB20PAGE *PPDB20PAGE; /** * A PDB 2.0 stream. */ typedef struct PDB20STREAM { /** The size of the stream. */ KU32 cbStream; /** Some unknown value. */ KU32 u32Unknown; } PDB20STREAM, *PPDB20STREAM; /** The PDB 2.00 signature. */ #define PDB_SIGNATURE_200 "Microsoft C/C++ program database 2.00\r\n\x1A" "JG\0" /** * The PDB 2.0 header. */ typedef struct PDB20HDR { /** The signature string. */ KU8 szSignature[sizeof(PDB_SIGNATURE_200)]; /** The page size. */ KU32 cbPage; /** The start page - whatever that is... */ PDB20PAGE iStartPage; /** The number of pages in the file. */ PDB20PAGE cPages; /** The root stream directory. */ PDB20STREAM RootStream; /** The root page table. */ PDB20PAGE aiRootPageMap[1]; } PDB20HDR, *PPDB20HDR; /** * The PDB 2.0 root directory. */ typedef struct PDB20ROOT { /** The number of streams */ KU16 cStreams; /** Reserved or high part of cStreams. */ KU16 u16Reserved; /** Array of streams. */ PDB20STREAM aStreams[1]; } PDB20ROOT, *PPDB20ROOT; static int Pdb20ValidateHeader(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr, size_t cbFile) { if (pHdr->cbPage * pHdr->cPages != cbFile) return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - cbPage * cPages != cbFile."); if (pHdr->iStartPage >= pHdr->cPages && pHdr->iStartPage <= 0) return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - cbPage * cPages != cbFile."); return 0; } static size_t Pdb20Pages(PPDB20HDR pHdr, size_t cb) { if (cb == ~(KU32)0 || !cb) return 0; return (cb + pHdr->cbPage - 1) / pHdr->cbPage; } static void *Pdb20AllocAndRead(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr, size_t cb, PPDB20PAGE paiPageMap) { size_t cPages = Pdb20Pages(pHdr, cb); KU8 *pbBuf = malloc(cPages * pHdr->cbPage + 1); if (pbBuf) { size_t iPage = 0; while (iPage < cPages) { size_t off = paiPageMap[iPage]; off *= pHdr->cbPage; memcpy(pbBuf + iPage * pHdr->cbPage, (KU8 *)pHdr + off, pHdr->cbPage); iPage++; } pbBuf[cPages * pHdr->cbPage] = '\0'; } else errx(pThis->pCtx, 1, "failed to allocate %lu bytes", (unsigned long)(cPages * pHdr->cbPage + 1)); return pbBuf; } static PPDB20ROOT Pdb20AllocAndReadRoot(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr) { /* * The tricky bit here is to find the right length. * (Todo: Check if we can just use the stream size..) */ PPDB20ROOT pRoot = Pdb20AllocAndRead(pThis, pHdr, sizeof(*pRoot), &pHdr->aiRootPageMap[0]); if (pRoot) { /* size = stream header + array of stream. */ size_t cb = K_OFFSETOF(PDB20ROOT, aStreams[pRoot->cStreams]); free(pRoot); pRoot = Pdb20AllocAndRead(pThis, pHdr, cb, &pHdr->aiRootPageMap[0]); if (pRoot) { /* size += page tables. */ unsigned iStream = pRoot->cStreams; while (iStream-- > 0) if (pRoot->aStreams[iStream].cbStream != ~(KU32)0) cb += Pdb20Pages(pHdr, pRoot->aStreams[iStream].cbStream) * sizeof(PDB20PAGE); free(pRoot); pRoot = Pdb20AllocAndRead(pThis, pHdr, cb, &pHdr->aiRootPageMap[0]); if (pRoot) { /* validate? */ return pRoot; } } } return NULL; } static void *Pdb20AllocAndReadStream(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr, PPDB20ROOT pRoot, unsigned iStream, size_t *pcbStream) { size_t cbStream = pRoot->aStreams[iStream].cbStream; PPDB20PAGE paiPageMap; if ( iStream >= pRoot->cStreams || cbStream == ~(KU32)0) { errx(pThis->pCtx, 1, "Invalid stream %d", iStream); return NULL; } paiPageMap = (PPDB20PAGE)&pRoot->aStreams[pRoot->cStreams]; while (iStream-- > 0) if (pRoot->aStreams[iStream].cbStream != ~(KU32)0) paiPageMap += Pdb20Pages(pHdr, pRoot->aStreams[iStream].cbStream); if (pcbStream) *pcbStream = cbStream; return Pdb20AllocAndRead(pThis, pHdr, cbStream, paiPageMap); } static int Pdb20Process(PKDEPIDBGLOBALS pThis, KU8 *pbFile, size_t cbFile) { PPDB20HDR pHdr = (PPDB20HDR)pbFile; PPDB20ROOT pRoot; unsigned iStream; int rc = 0; /* * Validate the header and read the root stream. */ if (Pdb20ValidateHeader(pThis, pHdr, cbFile)) return 1; pRoot = Pdb20AllocAndReadRoot(pThis, pHdr); if (!pRoot) return 1; /* * Iterate the streams in the root and scan their content for * dependencies. */ rc = 0; for (iStream = 0; iStream < pRoot->cStreams && !rc; iStream++) { KU8 *pbStream; if (pRoot->aStreams[iStream].cbStream == ~(KU32)0) continue; pbStream = (KU8 *)Pdb20AllocAndReadStream(pThis, pHdr, pRoot, iStream, NULL); if (pbStream) { rc = ScanStream(pThis, pbStream, pRoot->aStreams[iStream].cbStream, "/ipm/header/", sizeof("/ipm/header/") - 1); free(pbStream); } else rc = 1; } free(pRoot); return rc; } /** * Make an attempt at parsing a Visual C++ IDB file. */ static int ProcessIDB(PKDEPIDBGLOBALS pThis, FILE *pInput) { size_t cbFile; KU8 *pbFile; void *pvOpaque; int rc = 0; /* * Read the file into memory. */ pbFile = (KU8 *)depReadFileIntoMemory(pInput, &cbFile, &pvOpaque); if (!pbFile) return 1; /* * Figure out which parser to use. */ if (!memcmp(pbFile, PDB_SIGNATURE_700, sizeof(PDB_SIGNATURE_700))) rc = Pdb70Process(pThis, pbFile, cbFile); else if (!memcmp(pbFile, PDB_SIGNATURE_200, sizeof(PDB_SIGNATURE_200))) rc = Pdb20Process(pThis, pbFile, cbFile); else rc = errx(pThis->pCtx, 1, "Doesn't recognize the header of the Visual C++ IDB file."); depFreeFileMemory(pbFile, pvOpaque); return rc; } static void kDepIDBUsage(PKMKBUILTINCTX pCtx, int fIsErr) { kmk_builtin_ctx_printf(pCtx, fIsErr, "usage: %s -o -t [-fqs] \n" " or: %s --help\n" " or: %s --version\n", pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName); } int kmk_builtin_kDepIDB(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx) { int i; KDEPIDBGLOBALS This; /* Arguments. */ FILE *pOutput = NULL; const char *pszOutput = NULL; FILE *pInput = NULL; const char *pszTarget = NULL; int fStubs = 0; int fFixCase = 0; /* Argument parsing. */ int fInput = 0; /* set when we've found input argument. */ int fQuiet = 0; /* Init the instance data. */ This.pCtx = pCtx; /* * Parse arguments. */ if (argc <= 1) { kDepIDBUsage(pCtx, 0); return 1; } for (i = 1; i < argc; i++) { if (argv[i][0] == '-') { const char *psz = &argv[i][1]; if (*psz == '-') { if (!strcmp(psz, "-quiet")) psz = "q"; else if (!strcmp(psz, "-help")) psz = "?"; else if (!strcmp(psz, "-version")) psz = "V"; } switch (*psz) { /* * Output file. */ case 'o': { pszOutput = &argv[i][2]; if (pOutput) return errx(pCtx, 2, "only one output file!"); if (!*pszOutput) { if (++i >= argc) return errx(pCtx, 2, "The '-o' argument is missing the filename."); pszOutput = argv[i]; } if (pszOutput[0] == '-' && !pszOutput[1]) pOutput = stdout; else pOutput = fopen(pszOutput, "w" KMK_FOPEN_NO_INHERIT_MODE); if (!pOutput) return err(pCtx, 1, "Failed to create output file '%s'", pszOutput); break; } /* * Target name. */ case 't': { if (pszTarget) return errx(pCtx, 2, "only one target!"); pszTarget = &argv[i][2]; if (!*pszTarget) { if (++i >= argc) return errx(pCtx, 2, "The '-t' argument is missing the target name."); pszTarget = argv[i]; } break; } /* * Fix case. */ case 'f': { fFixCase = 1; break; } /* * Quiet. */ case 'q': { fQuiet = 1; break; } /* * Generate stubs. */ case 's': { fStubs = 1; break; } /* * The mandatory version & help. */ case '?': kDepIDBUsage(pCtx, 0); return 0; case 'V': case 'v': return kbuild_version(pCtx->pszProgName); /* * Invalid argument. */ default: errx(pCtx, 2, "Invalid argument '%s.'", argv[i]); kDepIDBUsage(pCtx, 1); return 2; } } else { pInput = fopen(argv[i], "rb" KMK_FOPEN_NO_INHERIT_MODE); if (!pInput) return err(pCtx, 1, "Failed to open input file '%s'", argv[i]); fInput = 1; } /* * End of the line? */ if (fInput) { if (++i < argc) return errx(pCtx, 2, "No arguments shall follow the input spec."); break; } } /* * Got all we require? */ if (!pInput) return errx(pCtx, 2, "No input!"); if (!pOutput) return errx(pCtx, 2, "No output!"); if (!pszTarget) return errx(pCtx, 2, "No target!"); /* * Do the parsing. */ depInit(&This.Core); i = ProcessIDB(&This, pInput); fclose(pInput); /* * Write the dependecy file. */ if (!i) { depOptimize(&This.Core, fFixCase, fQuiet, NULL /*pszIgnoredExt*/); depPrintTargetWithDeps(&This.Core, pOutput, pszTarget, 1 /*fEscapeTarget*/); if (fStubs) depPrintStubs(&This.Core, pOutput); } /* * Close the output, delete output on failure. */ if (!i && ferror(pOutput)) i = errx(pCtx, 1, "Error writing to '%s'.", pszOutput); fclose(pOutput); if (i) { if (unlink(pszOutput)) warnx(pCtx, "warning: failed to remove output file '%s' on failure.", pszOutput); } depCleanup(&This.Core); return i; } #ifdef KMK_BUILTIN_STANDALONE int main(int argc, char **argv, char **envp) { KMKBUILTINCTX Ctx = { "kmk_kDepIDB", NULL }; return kmk_builtin_kDepIDB(argc, argv, envp, &Ctx); } #endif