diff options
Diffstat (limited to 'src/kmk/kmkbuiltin/kDepObj.c')
-rw-r--r-- | src/kmk/kmkbuiltin/kDepObj.c | 1250 |
1 files changed, 1250 insertions, 0 deletions
diff --git a/src/kmk/kmkbuiltin/kDepObj.c b/src/kmk/kmkbuiltin/kDepObj.c new file mode 100644 index 0000000..280f15f --- /dev/null +++ b/src/kmk/kmkbuiltin/kDepObj.c @@ -0,0 +1,1250 @@ +/* $Id: kDepObj.c 3364 2020-06-08 19:29:42Z bird $ */ +/** @file + * kDepObj - Extract dependency information from an object file. + */ + +/* + * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * 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 <http://www.gnu.org/licenses/> + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#define MSCFAKES_NO_WINDOWS_H +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <stdarg.h> +#if !defined(_MSC_VER) +# include <unistd.h> +#else +# include <io.h> +typedef intptr_t ssize_t; +#endif +#include "k/kDefs.h" +#include "k/kTypes.h" +#include "k/kLdrFmts/pe.h" +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "kDep.h" +#include "err.h" +#include "kmkbuiltin.h" + + +/******************************************************************************* +* Defined Constants And Macros * +*******************************************************************************/ +#if 0 +# define dprintf(a) printf a +# define dump(pb, cb, offBase) depHexDump(pb,cb,offBase) +# define WITH_DPRINTF +#else +# define dprintf(a) do {} while (0) +# define dump(pb, cb, offBase) do {} while (0) +# undef WITH_DPRINTF +#endif + +/** @name OMF defines + * @{ */ +#define KDEPOMF_THEADR 0x80 +#define KDEPOMF_LHEADR 0x82 +#define KDEPOMF_COMENT 0x88 +#define KDEPOMF_CMTCLS_DEPENDENCY 0xe9 +#define KDEPOMF_CMTCLS_DBGTYPE 0xa1 +#define KDEPOMF_LINNUM 0x94 +#define KDEPOMF_LINNUM32 0x95 +/** @} */ + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +/** @name OMF Structures + * @{ */ +#pragma pack(1) +/** OMF record header. */ +typedef struct KDEPOMFHDR +{ + /** The record type. */ + KU8 bType; + /** The size of the record, excluding this header. */ + KU16 cbRec; +} KDEPOMFHDR; +typedef KDEPOMFHDR *PKDEPOMFHDR; +typedef const KDEPOMFHDR *PCKDEPOMFHDR; + +/** OMF string. */ +typedef struct KDEPOMFSTR +{ + KU8 cch; + char ach[1]; +} KDEPOMFSTR; +typedef KDEPOMFSTR *PKDEPOMFSTR; +typedef const KDEPOMFSTR *PCKDEPOMFSTR; + +/** THEADR/LHEADR. */ +typedef struct KDEPOMFTHEADR +{ + KDEPOMFHDR Hdr; + KDEPOMFSTR Name; +} KDEPOMFTHEADR; +typedef KDEPOMFTHEADR *PKDEPOMFTHEADR; +typedef const KDEPOMFTHEADR *PCKDEPOMFTHEADR; + +/** Dependency File. */ +typedef struct KDEPOMFDEPFILE +{ + KDEPOMFHDR Hdr; + KU8 fType; + KU8 bClass; + KU16 wDosTime; + KU16 wDosDate; + KDEPOMFSTR Name; +} KDEPOMFDEPFILE; +typedef KDEPOMFDEPFILE *PKDEPOMFDEPFILE; +typedef const KDEPOMFDEPFILE *PCKDEPOMFDEPFILE; + +#pragma pack() +/** @} */ + + +/** @name COFF Structures + * @{ */ +#pragma pack(1) + +typedef struct KDEPCVSYMHDR +{ + /** The record size minus the size field. */ + KU16 cb; + /** The record type. */ + KU16 uType; +} KDEPCVSYMHDR; +typedef KDEPCVSYMHDR *PKDEPCVSYMHDR; +typedef const KDEPCVSYMHDR *PCKDEPCVSYMHDR; + +/** @name Selection of KDEPCVSYMHDR::uType values. + * @{ */ +#define K_CV8_S_MSTOOL KU16_C(0x1116) +/** @} */ + +typedef struct KDEPCV8SYMHDR +{ + /** The record type. */ + KU32 uType; + /** The record size minus the size field. */ + KU32 cb; +} KDEPCV8SYMHDR; +typedef KDEPCV8SYMHDR *PKDEPCV8SYMHDR; +typedef const KDEPCV8SYMHDR *PCKDEPCV8SYMHDR; + +/** @name Known KDEPCV8SYMHDR::uType Values. + * @{ */ +#define K_CV8_SYMBOL_INFO KU32_C(0x000000f1) +#define K_CV8_LINE_NUMBERS KU32_C(0x000000f2) +#define K_CV8_STRING_TABLE KU32_C(0x000000f3) +#define K_CV8_SOURCE_FILES KU32_C(0x000000f4) +#define K_CV8_COMDAT_XXXXX KU32_C(0x000000f5) /**< no idea about the format... */ +/** @} */ + +#pragma pack() +/** @} */ + +/** + * Globals. + */ +typedef struct KDEPOBJGLOBALS +{ + /** The command execution context. */ + PKMKBUILTINCTX pCtx; + /** Core instance. */ + DEPGLOBALS Core; + /** The file. */ + const char *pszFile; +} KDEPOBJGLOBALS; +/** Pointer to kDepObj globals. */ +typedef KDEPOBJGLOBALS *PKDEPOBJGLOBALS; + + + +/** + * Prints an error message. + * + * @returns rc. + * @param pThis kObjDep instance data. + * @param rc The return code, for making one line return statements. + * @param pszFormat The message format string. + * @param ... Format arguments. + * @todo Promote this to kDep.c. + */ +static int kDepErr(PKDEPOBJGLOBALS pThis, int rc, const char *pszFormat, ...) +{ + char szMsg[2048]; + va_list va; + va_start(va, pszFormat); + vsnprintf(szMsg, sizeof(szMsg) - 1, pszFormat, va); + va_end(va); + szMsg[sizeof(szMsg) - 1] = '\0'; + + if (pThis->pszFile) + warnx(pThis->pCtx, "%s: error: %s", pThis->pszFile, szMsg); + else + errx(pThis->pCtx, rc, "%s", szMsg); + return rc; +} + + +/** + * Gets an index from the data. + * + * @returns The index, KU16_MAX on buffer underflow. + * @param puData The current data stream position (in/out). + * @param pcbLeft Number of bytes left (in/out). + */ +static KU16 kDepObjOMFGetIndex(KPCUINT *puData, KU16 *pcbLeft) +{ + KU16 u16; + + if (*pcbLeft >= 1 && *pcbLeft != KU16_MAX) + { + *pcbLeft -= 1; + u16 = *puData->pb++; + if (u16 & KU16_C(0x80)) + { + if (*pcbLeft >= 1) + { + *pcbLeft -= 1; + u16 = ((u16 & KU16_C(0x7f)) << 8) | *puData->pb++; + } + else + u16 = KU16_MAX; + } + } + else + u16 = KU16_MAX; + return u16; +} + + +/** + * Parses the OMF file. + * + * @returns 0 on success, 1 on failure, 2 if no dependencies was found. + * @param pThis The kDepObj instance data. + * @param pbFile The start of the file. + * @param cbFile The file size. + */ +int kDepObjOMFParse(PKDEPOBJGLOBALS pThis, const KU8 *pbFile, KSIZE cbFile) +{ + PCKDEPOMFHDR pHdr = (PCKDEPOMFHDR)pbFile; + KSIZE cbLeft = cbFile; + char uDbgType = 0; /* H or C */ + KU8 uDbgVer = KU8_MAX; + KU32 iSrc = 0; + KU32 iMaybeSrc = 0; + KU8 uLinNumType = KU8_MAX; + KU16 cLinNums = 0; + KU32 cLinFiles = 0; + KU32 iLinFile = 0; + + /* + * Iterate thru the records until we hit the end or an invalid one. + */ + while ( cbLeft >= sizeof(*pHdr) + && cbLeft >= pHdr->cbRec + sizeof(*pHdr)) + { + KPCUINT uData; + uData.pv = pHdr + 1; + + /* process selected record types. */ + dprintf(("%#07" KUPTR_PRI ": %#04x %#05x\n", (const KU8*)pHdr - pbFile, pHdr->bType, pHdr->cbRec)); + switch (pHdr->bType) + { + /* + * The T/L Header contains the source name. When emitting CodeView 4 + * and earlier (like masm and watcom does), all includes used by the + * line number tables have their own THEADR record. + */ + case KDEPOMF_THEADR: + case KDEPOMF_LHEADR: + { + PCKDEPOMFTHEADR pTHeadr = (PCKDEPOMFTHEADR)pHdr; + if (1 + pTHeadr->Name.cch + 1 != pHdr->cbRec) + return kDepErr(pThis, 1, "%#07x - Bad %cHEADR record, length mismatch.", + (const KU8*)pHdr - pbFile, pHdr->bType == KDEPOMF_THEADR ? 'T' : 'L'); + if ( ( pTHeadr->Name.cch > 2 + && pTHeadr->Name.ach[pTHeadr->Name.cch - 2] == '.' + && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'o' + || pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'O')) + || ( pTHeadr->Name.cch > 4 + && pTHeadr->Name.ach[pTHeadr->Name.cch - 4] == '.' + && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 3] == 'o' + || pTHeadr->Name.ach[pTHeadr->Name.cch - 3] == 'O') + && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 2] == 'b' + || pTHeadr->Name.ach[pTHeadr->Name.cch - 2] == 'B') + && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'j' + || pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'J')) + ) + dprintf(("%cHEADR: %.*s [ignored]\n", pHdr->bType == KDEPOMF_THEADR ? 'T' : 'L', pTHeadr->Name.cch, pTHeadr->Name.ach)); + else + { + dprintf(("%cHEADR: %.*s\n", pHdr->bType == KDEPOMF_THEADR ? 'T' : 'L', pTHeadr->Name.cch, pTHeadr->Name.ach)); + depAdd(&pThis->Core, pTHeadr->Name.ach, pTHeadr->Name.cch); + iMaybeSrc++; + } + uLinNumType = KU8_MAX; + break; + } + + case KDEPOMF_COMENT: + { + KU8 uClass; + + if (pHdr->cbRec < 2 + 1) + return kDepErr(pThis, 1, "%#07x - Bad COMMENT record, too small.", (const KU8*)pHdr - pbFile); + if (uData.pb[0] & 0x3f) + return kDepErr(pThis, 1, "%#07x - Bad COMMENT record, reserved flags set.", (const KU8*)pHdr - pbFile); + uClass = uData.pb[1]; + uData.pb += 2; + switch (uClass) + { + /* + * Borland dependency file comment (famously used by wmake and Watcom). + */ + case KDEPOMF_CMTCLS_DEPENDENCY: + { + PCKDEPOMFDEPFILE pDep = (PCKDEPOMFDEPFILE)pHdr; + if (K_OFFSETOF(KDEPOMFDEPFILE, Name.ach[pDep->Name.cch]) + 1 != pHdr->cbRec + sizeof(*pHdr)) + { + /* Empty record indicates the end of the dependency files, + no need to go on. */ + if (pHdr->cbRec == 2 + 1) + return 0; + return kDepErr(pThis, 1, "%#07lx - Bad DEPENDENCY FILE record, length mismatch. (%u/%u)", + (long)((const KU8 *)pHdr - pbFile), + K_OFFSETOF(KDEPOMFDEPFILE, Name.ach[pDep->Name.cch]) + 1, + (unsigned)(pHdr->cbRec + sizeof(*pHdr))); + } + depAdd(&pThis->Core, pDep->Name.ach, pDep->Name.cch); + iSrc++; + break; + } + + /* + * Pick up the debug type so we can parse the LINNUM records. + */ + case KDEPOMF_CMTCLS_DBGTYPE: + if (pHdr->cbRec < 2 + 3 + 1) + break; /* ignore, Borland used this for something else apparently. */ + if ( !(uData.pb[1] == 'C' && uData.pb[2] == 'V') + && !(uData.pb[1] == 'H' && uData.pb[2] == 'L')) + { + dprintf(("Unknown debug type: %c%c (%u)\n", uData.pb[1], uData.pb[2], uData.pb[0])); + break; + } + uDbgType = uData.pb[1]; + uDbgVer = uData.pb[0]; + dprintf(("Debug Type %s ver %u\n", uDbgType == 'H' ? "HLL" : "CodeView", uDbgVer)); + break; + + } + break; /* COMENT */ + } + + /* + * LINNUM + THEADR == sigar. + */ + case KDEPOMF_LINNUM: + if (uDbgType == 'C') + iMaybeSrc |= KU32_C(0x80000000); + dprintf(("LINNUM:\n")); + break; + + /* + * The HLL v4 and v6 file names table will include all files when present, which + * is perfect for generating dependencies. + */ + case KDEPOMF_LINNUM32: + if ( uDbgType == 'H' + && uDbgVer >= 3 + && uDbgVer <= 6) + { + /* skip two indexes (group & segment) */ + KU16 cbRecLeft = pHdr->cbRec - 1; + KU16 uGrp = kDepObjOMFGetIndex(&uData, &cbRecLeft); + KU16 uSeg = kDepObjOMFGetIndex(&uData, &cbRecLeft); + if (uSeg == KU16_MAX) + return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record", (long)((const KU8 *)pHdr - pbFile)); + K_NOREF(uGrp); + + if (uLinNumType == KU8_MAX) + { +#ifdef WITH_DPRINTF + static const char * const s_apsz[5] = + { + "source file", "listing file", "source & listing file", "file names table", "path table" + }; +#endif + KU16 uLine; + KU8 uReserved; + KU16 uSeg2; + KU32 cbLinNames; + + if (cbRecLeft < 2+1+1+2+2+4) + return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, too short", (long)((const KU8 *)pHdr - pbFile)); + cbRecLeft -= 2+1+1+2+2+4; + uLine = *uData.pu16++; + uLinNumType = *uData.pu8++; + uReserved = *uData.pu8++; K_NOREF(uReserved); + cLinNums = *uData.pu16++; K_NOREF(cLinNums); + uSeg2 = *uData.pu16++; K_NOREF(uSeg2); + cbLinNames = *uData.pu32++; K_NOREF(cbLinNames); + + dprintf(("LINNUM32: uGrp=%#x uSeg=%#x uSeg2=%#x uLine=%#x (MBZ) uReserved=%#x\n", + uGrp, uSeg, uSeg2, uLine, uReserved)); + dprintf(("LINNUM32: cLinNums=%#x (%u) cbLinNames=%#x (%u) uLinNumType=%#x (%s)\n", + cLinNums, cLinNums, cbLinNames, cbLinNames, uLinNumType, + uLinNumType < K_ELEMENTS(s_apsz) ? s_apsz[uLinNumType] : "??")); + + if (uLine != 0) + return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, line %#x (MBZ)", (long)((const KU8 *)pHdr - pbFile), uLine); + cLinFiles = iLinFile = KU32_MAX; + if ( uLinNumType == 3 /* file names table */ + || uLinNumType == 4 /* path table */) + cLinNums = 0; /* no line numbers */ + else if (uLinNumType > 4) + return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, type %#x unknown", (long)((const KU8 *)pHdr - pbFile), uLinNumType); + } + else + dprintf(("LINNUM32: uGrp=%#x uSeg=%#x\n", uGrp, uSeg)); + + + /* Skip file numbers (we parse them to follow the stream correctly). */ + if (uLinNumType != 3 && uLinNumType != 4) + { + static const KU16 s_acbTypes[3] = { 2+2+4, 4+4+4, 2+2+4+4+4 }; + KU16 cbEntry = s_acbTypes[uLinNumType]; + + while (cLinNums && cbRecLeft) + { + if (cbRecLeft < cbEntry) + return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, incomplete line entry", (long)((const KU8 *)pHdr - pbFile)); + + switch (uLinNumType) + { + case 0: /* source file */ + dprintf((" Line %6" KU16_PRI " of file %2" KU16_PRI " at %#010" KX32_PRI "\n", + uData.pu16[0], uData.pu16[1], uData.pu32[1])); + break; + case 1: /* listing file */ + dprintf((" Line %6" KU32_PRI ", statement %6" KU32_PRI " at %#010" KX32_PRI "\n", + uData.pu32[0], uData.pu32[1], uData.pu32[2])); + break; + case 2: /* source & listing file */ + dprintf((" Line %6" KU16_PRI " of file %2" KU16_PRI ", listning line %6" KU32_PRI ", statement %6" KU32_PRI " at %#010" KX32_PRI "\n", + uData.pu16[0], uData.pu16[1], uData.pu32[1], uData.pu32[2], uData.pu32[3])); + break; + } + uData.pb += cbEntry; + cbRecLeft -= cbEntry; + cLinNums--; + } + + /* If at end of the announced line number entiries, we may find a file names table + here (who is actually emitting this?). */ + if (!cLinNums) + { + uLinNumType = cbRecLeft > 0 ? 3 : KU8_MAX; + dprintf(("End-of-line-numbers; uLinNumType=%u cbRecLeft=%#x\n", uLinNumType, cbRecLeft)); + } + } + + if (uLinNumType == 3 || uLinNumType == 4) + { + /* Read the file/path table header (first time only). */ + if (cLinFiles == KU32_MAX && iLinFile == KU32_MAX) + { + KU32 iFirstCol; + KU32 cCols; + + if (cbRecLeft < 4+4+4) + return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, incomplete file/path table header", (long)((const KU8 *)pHdr - pbFile)); + cbRecLeft -= 4+4+4; + + iFirstCol = *uData.pu32++; K_NOREF(iFirstCol); + cCols = *uData.pu32++; K_NOREF(cCols); + cLinFiles = *uData.pu32++; + dprintf(("%s table header: cLinFiles=%#" KX32_PRI " (%" KU32_PRI ") iFirstCol=%" KU32_PRI " cCols=%" KU32_PRI"\n", + uLinNumType == 3 ? "file names" : "path", cLinFiles, cLinFiles, iFirstCol, cCols)); + if (cLinFiles == KU32_MAX) + return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, too many file/path table entries.", (long)((const KU8 *)pHdr - pbFile)); + iLinFile = 0; + } + + /* Parse the file names / path table. */ + while (iLinFile < cLinFiles && cbRecLeft) + { + KU16 cbName = *uData.pb++; + if (cbRecLeft < 1 + cbName) + return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, file/path table entry too long.", (long)((const KU8 *)pHdr - pbFile)); + iLinFile++; + dprintf(("#%" KU32_PRI": %.*s\n", iLinFile, cbName, uData.pch)); + if (uLinNumType == 3) + { + depAdd(&pThis->Core, uData.pch, cbName); + iSrc++; + } + cbRecLeft -= 1 + cbName; + uData.pb += cbName; + } + + /* The end? */ + if (iLinFile == cLinFiles) + { + uLinNumType = KU8_MAX; + dprintf(("End-of-file/path-table; cbRecLeft=%#x\n", cbRecLeft)); + } + } + } + else + dprintf(("LINNUM32: Unknown or unsupported format\n")); + break; + + } + + /* advance */ + cbLeft -= pHdr->cbRec + sizeof(*pHdr); + pHdr = (PCKDEPOMFHDR)((const KU8 *)(pHdr + 1) + pHdr->cbRec); + } + + if (cbLeft) + return kDepErr(pThis, 1, "%#07x - Unexpected EOF. cbLeft=%#x", (const KU8*)pHdr - pbFile, cbLeft); + + if (iSrc == 0 && iMaybeSrc <= 1) + { + dprintf(("kDepObjOMFParse: No cylindrical smoking thing: iSrc=0 iMaybeSrc=%#" KX32_PRI"\n", iMaybeSrc)); + return 2; + } + dprintf(("kDepObjOMFParse: iSrc=%" KU32_PRI " iMaybeSrc=%#" KX32_PRI "\n", iSrc, iMaybeSrc)); + return 0; +} + + +/** + * Checks if this file is an OMF file or not. + * + * @returns K_TRUE if it's OMF, K_FALSE otherwise. + * + * @param pb The start of the file. + * @param cb The file size. + */ +KBOOL kDepObjOMFTest(const KU8 *pbFile, KSIZE cbFile) +{ + PCKDEPOMFTHEADR pHdr = (PCKDEPOMFTHEADR)pbFile; + + if (cbFile <= sizeof(*pHdr)) + return K_FALSE; + if ( pHdr->Hdr.bType != KDEPOMF_THEADR + && pHdr->Hdr.bType != KDEPOMF_LHEADR) + return K_FALSE; + if (pHdr->Hdr.cbRec + sizeof(pHdr->Hdr) >= cbFile) + return K_FALSE; + if (pHdr->Hdr.cbRec != 1 + pHdr->Name.cch + 1) + return K_FALSE; + + return K_TRUE; +} + + +/** + * Parses a CodeView 8 symbol section. + * + * @returns 0 on success, 1 on failure, 2 if no dependencies was found. + * @param pThis The kDepObj instance data. + * @param pbSyms Pointer to the start of the symbol section. + * @param cbSyms Size of the symbol section. + */ +int kDepObjCOFFParseCV8SymbolSection(PKDEPOBJGLOBALS pThis, const KU8 *pbSyms, KU32 cbSyms) +{ + char const * pchStrTab = NULL; + KU32 cbStrTab = 0; + KPCUINT uSrcFiles = {0}; + KU32 cbSrcFiles = 0; + KU32 off = 4; + KU32 iSrc = 0; + + if (cbSyms < 16) + return 1; + + /* + * The parsing loop. + */ + while (off < cbSyms) + { + PCKDEPCV8SYMHDR pHdr = (PCKDEPCV8SYMHDR)(pbSyms + off); + KPCUINT uData; + KU32 cbData; + uData.pv = pHdr + 1; + + if (off + sizeof(*pHdr) >= cbSyms) + { + kDepErr(pThis, 1, "CV symbol table entry at %08" KX32_PRI " is too long; cbSyms=%#" KX32_PRI "", + off, cbSyms); + return 1; /* FIXME */ + } + + cbData = pHdr->cb; + if (off + cbData + sizeof(*pHdr) > cbSyms) + { + kDepErr(pThis, 1, "CV symbol table entry at %08" KX32_PRI " is too long; cbData=%#" KX32_PRI " cbSyms=%#" KX32_PRI, + off, cbData, cbSyms); + return 1; /* FIXME */ + } + + /* If the size is 0, assume it covers the rest of the section. VC++ 2003 has + been observed doing thing. */ + if (!cbData) + cbData = cbSyms - off; + + switch (pHdr->uType) + { + case K_CV8_SYMBOL_INFO: + dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Symbol Info\n", off, cbData)); + /*dump(uData.pb, cbData, 0);*/ + break; + + case K_CV8_LINE_NUMBERS: + dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Line numbers\n", off, cbData)); + /*dump(uData.pb, cbData, 0);*/ + break; + + case K_CV8_STRING_TABLE: + dprintf(("%06" KX32_PRI " %06" KX32_PRI ": String table\n", off, cbData)); + if (pchStrTab) + warnx(pThis->pCtx, "%s: warning: Found yet another string table!", pThis->pszFile); + pchStrTab = uData.pch; + cbStrTab = cbData; + /*dump(uData.pb, cbData, 0);*/ + break; + + case K_CV8_SOURCE_FILES: + dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Source files\n", off, cbData)); + if (uSrcFiles.pb) + warnx(pThis->pCtx, "%s: warning: Found yet another source files table!", pThis->pszFile); + uSrcFiles = uData; + cbSrcFiles = cbData; + /*dump(uData.pb, cbData, 0);*/ + break; + + case K_CV8_COMDAT_XXXXX: + dprintf(("%06" KX32_PRI " %06" KX32_PRI ": 0xf5 Unknown COMDAT stuff\n", off, cbData)); + /*dump(uData.pb, cbData, 0);*/ + break; + + default: + dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Unknown type %#" KX32_PRI "\n", + off, cbData, pHdr->uType)); + dump(uData.pb, cbData, 0); + break; + } + + /* next */ + cbData = (cbData + 3) & ~KU32_C(3); + off += cbData + sizeof(*pHdr); + } + + /* + * Did we find any source files and strings? + */ + if (!pchStrTab || !uSrcFiles.pv) + { + dprintf(("kDepObjCOFFParseCV8SymbolSection: No cylindrical smoking thing: pchStrTab=%p uSrcFiles.pv=%p\n", pchStrTab, uSrcFiles.pv)); + return 2; + } + + /* + * Iterate the source file table. + */ + off = 0; + while (off < cbSrcFiles) + { + KU32 offFile; + const char *pszFile; + KSIZE cchFile; + KU16 u16Type; + KPCUINT uSrc; + KU32 cbSrc; + + /* + * Validate and parse the entry (variable length record are fun). + */ + if (off + 8 > cbSrcFiles) + return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " is too long; cbSrcFiles=%#" KX32_PRI, + off, cbSrcFiles); + uSrc.pb = uSrcFiles.pb + off; + u16Type = uSrc.pu16[2]; + switch (u16Type) + { + case 0x0110: cbSrc = 6 + 16 + 2; break; /* MD5 */ + case 0x0214: cbSrc = 6 + 20 + 2; break; /* SHA1 */ /** @todo check this */ + case 0x0320: cbSrc = 6 + 32 + 2; break; /* SHA-256 */ + default: cbSrc = 6 + 0 + 2; break; + } + if (off + cbSrc > cbSrcFiles) + return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " is too long; cbSrc=%#" KX32_PRI " cbSrcFiles=%#" KX32_PRI, + off, cbSrc, cbSrcFiles); + + offFile = *uSrc.pu32; + if (offFile > cbStrTab) + return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " is out side the string table; offFile=%#" KX32_PRI " cbStrTab=%#" KX32_PRI, + off, offFile, cbStrTab); + pszFile = pchStrTab + offFile; + cchFile = strlen(pszFile); + if (cchFile == 0) + return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " has an empty file name; offFile=%#x" KX32_PRI, + off, offFile); + + /* + * Display the result and add it to the dependency database. + */ + depAdd(&pThis->Core, pszFile, cchFile); +#ifdef WITH_DPRINTF + dprintf(("#%03" KU32_PRI ": ", iSrc)); + { + KU32 off = 6; + for (;off < cbSrc - 2; off++) + dprintf(("%02" KX8_PRI, uSrc.pb[off])); + if (cbSrc == 6) + dprintf(("type=%#06" KX16_PRI, u16Type)); + dprintf((" '%s'\n", pszFile)); + } +#endif + + + /* next */ + iSrc++; + off += cbSrc; + } + + if (iSrc == 0) + { + dprintf(("kDepObjCOFFParseCV8SymbolSection: No cylindrical smoking thing: iSrc=0\n")); + return 2; + } + dprintf(("kDepObjCOFFParseCV8SymbolSection: iSrc=%" KU32_PRI "\n", iSrc)); + return 0; +} + + +/** + * Parses the OMF file. + * + * @returns 0 on success, 1 on failure, 2 if no dependencies was found. + * @param pThis The kDepObj instance data. + * @param pbFile The start of the file. + * @param cbFile The file size. + */ +int kDepObjCOFFParse(PKDEPOBJGLOBALS pThis, const KU8 *pbFile, KSIZE cbFile) +{ + IMAGE_FILE_HEADER const *pFileHdr = (IMAGE_FILE_HEADER const *)pbFile; + ANON_OBJECT_HEADER_BIGOBJ const *pBigObjHdr = (ANON_OBJECT_HEADER_BIGOBJ const *)pbFile; + IMAGE_SECTION_HEADER const *paSHdrs; + KU32 cSHdrs; + unsigned iSHdr; + KPCUINT u; + KBOOL fDebugS = K_FALSE; + int rcRet = 2; + int rc; + + if ( pBigObjHdr->Sig1 == 0 + && pBigObjHdr->Sig2 == KU16_MAX) + { + paSHdrs = (IMAGE_SECTION_HEADER const *)(pBigObjHdr + 1); + cSHdrs = pBigObjHdr->NumberOfSections; + } + else + { + paSHdrs = (IMAGE_SECTION_HEADER const *)((KU8 const *)(pFileHdr + 1) + pFileHdr->SizeOfOptionalHeader); + cSHdrs = pFileHdr->NumberOfSections; + } + + + dprintf(("COFF file!\n")); + + for (iSHdr = 0; iSHdr < cSHdrs; iSHdr++) + { + if ( !memcmp(paSHdrs[iSHdr].Name, ".debug$S", sizeof(".debug$S") - 1) + && paSHdrs[iSHdr].SizeOfRawData > 4) + { + u.pb = pbFile + paSHdrs[iSHdr].PointerToRawData; + dprintf(("CV symbol table: version=%x\n", *u.pu32)); + if (*u.pu32 == 0x000000004) + rc = kDepObjCOFFParseCV8SymbolSection(pThis, u.pb, paSHdrs[iSHdr].SizeOfRawData); + else + rc = 2; + dprintf(("rc=%d\n", rc)); + if (rcRet == 2) + rcRet = rc; + if (rcRet != 2) + return rc; + fDebugS = K_TRUE; + } + dprintf(("#%d: %.8s\n", iSHdr, paSHdrs[iSHdr].Name)); + } + + /* If we found no dependencies but did find a .debug$S section, check if + this is a case where the compile didn't emit any because there is no + code in this compilation unit. */ + if (rcRet == 2) + { + if (fDebugS) + { + for (iSHdr = 0; iSHdr < cSHdrs; iSHdr++) + if (!memcmp(paSHdrs[iSHdr].Name, ".text", sizeof(".text") - 1)) + return kDepErr(pThis, 2, "%s: no dependencies (has text).", pThis->pszFile); + warnx(pThis->pCtx, "%s: no dependencies, but also no text, so probably (mostly) harmless.", pThis->pszFile); + return 0; + } + kDepErr(pThis, 2, "%s: no dependencies.", pThis->pszFile); + } + + return rcRet; +} + + +/** + * Checks if this file is a COFF file or not. + * + * @returns K_TRUE if it's COFF, K_FALSE otherwise. + * + * @param pThis The kDepObj instance data. + * @param pb The start of the file. + * @param cb The file size. + */ +KBOOL kDepObjCOFFTest(PKDEPOBJGLOBALS pThis, const KU8 *pbFile, KSIZE cbFile) +{ + IMAGE_FILE_HEADER const *pFileHdr = (IMAGE_FILE_HEADER const *)pbFile; + ANON_OBJECT_HEADER_BIGOBJ const *pBigObjHdr = (ANON_OBJECT_HEADER_BIGOBJ const *)pbFile; + IMAGE_SECTION_HEADER const *paSHdrs; + KU32 cSHdrs; + KU32 iSHdr; + KSIZE cbHdrs; + + if (cbFile <= sizeof(*pFileHdr)) + return K_FALSE; + + /* + * Deal with -bigobj output first. + */ + if ( pBigObjHdr->Sig1 == 0 + && pBigObjHdr->Sig2 == KU16_MAX) + { + static const KU8 s_abClsId[16] = { ANON_OBJECT_HEADER_BIGOBJ_CLS_ID_BYTES }; + + paSHdrs = (IMAGE_SECTION_HEADER const *)(pBigObjHdr + 1); + cSHdrs = pBigObjHdr->NumberOfSections; + cbHdrs = sizeof(IMAGE_SECTION_HEADER) * cSHdrs; + + if (cbFile <= sizeof(*pBigObjHdr)) + return K_FALSE; + + if (pBigObjHdr->Version != 2) + return K_FALSE; + if (memcmp(&pBigObjHdr->ClassID[0], s_abClsId, sizeof(pBigObjHdr->ClassID)) != 0) + return K_FALSE; + + if ( pBigObjHdr->Machine != IMAGE_FILE_MACHINE_I386 + && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_AMD64 + && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_ARM + && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_ARMNT + && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_ARM64 + && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_EBC) + { + kDepErr(pThis, 1, "bigobj Machine not supported: %#x", pBigObjHdr->Machine); + return K_FALSE; + } + if (pBigObjHdr->Flags != 0) + { + kDepErr(pThis, 1, "bigobj Flags field is non-zero: %#x", pBigObjHdr->Flags); + return K_FALSE; + } + if (pBigObjHdr->SizeOfData != 0) + { + kDepErr(pThis, 1, "bigobj SizeOfData field is non-zero: %#x", pBigObjHdr->SizeOfData); + return K_FALSE; + } + + if ( pBigObjHdr->PointerToSymbolTable != 0 + && ( pBigObjHdr->PointerToSymbolTable < cbHdrs + || pBigObjHdr->PointerToSymbolTable > cbFile)) + return K_FALSE; + if ( pBigObjHdr->PointerToSymbolTable == 0 + && pBigObjHdr->NumberOfSymbols != 0) + return K_FALSE; + } + /* + * Look for normal COFF object. + */ + else + { + paSHdrs = (IMAGE_SECTION_HEADER const *)((KU8 const *)(pFileHdr + 1) + pFileHdr->SizeOfOptionalHeader); + cSHdrs = pFileHdr->NumberOfSections; + cbHdrs = (const KU8 *)&paSHdrs[cSHdrs] - (const KU8 *)pbFile; + + if ( pFileHdr->Machine != IMAGE_FILE_MACHINE_I386 + && pFileHdr->Machine != IMAGE_FILE_MACHINE_AMD64 + && pFileHdr->Machine != IMAGE_FILE_MACHINE_ARM + && pFileHdr->Machine != IMAGE_FILE_MACHINE_ARMNT + && pFileHdr->Machine != IMAGE_FILE_MACHINE_ARM64 + && pFileHdr->Machine != IMAGE_FILE_MACHINE_EBC) + return K_FALSE; + + if (pFileHdr->SizeOfOptionalHeader != 0) + return K_FALSE; /* COFF files doesn't have an optional header */ + + if ( pFileHdr->PointerToSymbolTable != 0 + && ( pFileHdr->PointerToSymbolTable < cbHdrs + || pFileHdr->PointerToSymbolTable > cbFile)) + return K_FALSE; + if ( pFileHdr->PointerToSymbolTable == 0 + && pFileHdr->NumberOfSymbols != 0) + return K_FALSE; + if ( pFileHdr->Characteristics + & ( IMAGE_FILE_DLL + | IMAGE_FILE_SYSTEM + | IMAGE_FILE_UP_SYSTEM_ONLY + | IMAGE_FILE_NET_RUN_FROM_SWAP + | IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP + | IMAGE_FILE_EXECUTABLE_IMAGE + | IMAGE_FILE_RELOCS_STRIPPED)) + return K_FALSE; + } + if ( cSHdrs <= 1 + || cSHdrs > cbFile) + return K_FALSE; + if (cbHdrs >= cbFile) + return K_FALSE; + + /* + * Check the section headers. + */ + for (iSHdr = 0; iSHdr < cSHdrs; iSHdr++) + { + if ( paSHdrs[iSHdr].PointerToRawData != 0 + && ( paSHdrs[iSHdr].PointerToRawData < cbHdrs + || paSHdrs[iSHdr].PointerToRawData >= cbFile + || paSHdrs[iSHdr].PointerToRawData + paSHdrs[iSHdr].SizeOfRawData > cbFile)) + return K_FALSE; + if ( paSHdrs[iSHdr].PointerToRelocations != 0 + && ( paSHdrs[iSHdr].PointerToRelocations < cbHdrs + || paSHdrs[iSHdr].PointerToRelocations >= cbFile + || paSHdrs[iSHdr].PointerToRelocations + paSHdrs[iSHdr].NumberOfRelocations * 10 > cbFile)) /* IMAGE_RELOCATION */ + return K_FALSE; + if ( paSHdrs[iSHdr].PointerToLinenumbers != 0 + && ( paSHdrs[iSHdr].PointerToLinenumbers < cbHdrs + || paSHdrs[iSHdr].PointerToLinenumbers >= cbFile + || paSHdrs[iSHdr].PointerToLinenumbers + paSHdrs[iSHdr].NumberOfLinenumbers * 6 > cbFile)) /* IMAGE_LINENUMBER */ + return K_FALSE; + } + + return K_TRUE; +} + + +/** + * Read the file into memory and parse it. + */ +static int kDepObjProcessFile(PKDEPOBJGLOBALS 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; + + /* + * See if it's an OMF file, then process it. + */ + if (kDepObjOMFTest(pbFile, cbFile)) + rc = kDepObjOMFParse(pThis, pbFile, cbFile); + else if (kDepObjCOFFTest(pThis, pbFile, cbFile)) + rc = kDepObjCOFFParse(pThis, pbFile, cbFile); + else + rc = kDepErr(pThis, 1, "Doesn't recognize the header of the OMF/COFF file."); + + depFreeFileMemory(pbFile, pvOpaque); + return rc; +} + + +static void kDebObjUsage(PKMKBUILTINCTX pCtx, int fIsErr) +{ + kmk_builtin_ctx_printf(pCtx, fIsErr, + "usage: %s -o <output> -t <target> [-fqs] [-e <ignore-ext>] <OMF or COFF file>\n" + " or: %s --help\n" + " or: %s --version\n", + pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName); +} + + +int kmk_builtin_kDepObj(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx) +{ + int i; + KDEPOBJGLOBALS This; + + /* Arguments. */ + FILE *pOutput = NULL; + const char *pszOutput = NULL; + FILE *pInput = NULL; + const char *pszTarget = NULL; + int fStubs = 0; + int fFixCase = 0; + const char *pszIgnoreExt = NULL; + /* Argument parsing. */ + int fInput = 0; /* set when we've found input argument. */ + int fQuiet = 0; + + /* Init instance data. */ + This.pCtx = pCtx; + This.pszFile = NULL; + + /* + * Parse arguments. + */ + if (argc <= 1) + { + kDebObjUsage(pCtx, 0); + return 1; + } + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + const char *pszValue; + const char *psz = &argv[i][1]; + char chOpt; + chOpt = *psz++; + if (chOpt == '-') + { + /* Convert long to short option. */ + if (!strcmp(psz, "quiet")) + chOpt = 'q'; + else if (!strcmp(psz, "help")) + chOpt = '?'; + else if (!strcmp(psz, "version")) + chOpt = 'V'; + else + { + errx(pCtx, 2, "Invalid argument '%s'.", argv[i]); + kDebObjUsage(pCtx, 1); + return 2; + } + psz = ""; + } + + /* + * Requires value? + */ + switch (chOpt) + { + case 'o': + case 't': + case 'e': + if (*psz) + pszValue = psz; + else if (++i < argc) + pszValue = argv[i]; + else + return errx(pCtx, 2, "The '-%c' option takes a value.", chOpt); + break; + + default: + pszValue = NULL; + break; + } + + + switch (chOpt) + { + /* + * Output file. + */ + case 'o': + { + if (pOutput) + return errx(pCtx, 2, "only one output file!"); + pszOutput = pszValue; + 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 = pszValue; + break; + } + + /* + * Fix case. + */ + case 'f': + { + fFixCase = 1; + break; + } + + /* + * Quiet. + */ + case 'q': + { + fQuiet = 1; + break; + } + + /* + * Generate stubs. + */ + case 's': + { + fStubs = 1; + break; + } + + /* + * Extension to ignore. + */ + case 'e': + { + if (pszIgnoreExt) + return errx(pCtx, 2, "The '-e' option can only be used once!"); + pszIgnoreExt = pszValue; + break; + } + + /* + * The mandatory version & help. + */ + case '?': + kDebObjUsage(pCtx, 0); + return 0; + case 'V': + case 'v': + return kbuild_version(argv[0]); + + /* + * Invalid argument. + */ + default: + errx(pCtx, 2, "Invalid argument '%s'.", argv[i]); + kDebObjUsage(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]); + This.pszFile = 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 = kDepObjProcessFile(&This, pInput); + fclose(pInput); + + /* + * Write the dependecy file. + */ + if (!i) + { + depOptimize(&This.Core, fFixCase, fQuiet, pszIgnoreExt); + 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)) + warn(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 = { "kDepObj", NULL }; + return kmk_builtin_kDepObj(argc, argv, envp, &Ctx); +} +#endif + |