/* $Id: kLibTweaker.c 2791 2015-09-15 22:57:44Z bird $ */ /** @file * kLibTweaker - Import library tweaker for windows. */ /* * Copyright (c) 2007-2015 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 * *********************************************************************************************************************************/ #if 0 # define ELECTRIC_HEAP # include "../kmk/electric.h" # include "../kmk/electric.c" #endif #include #include #include #include #include #include #include #include "k/kLdrFmts/pe.h" /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Microsoft import library archive header. * * This has the same size as a COFF header, which is probably not a coincidence. */ typedef struct COFFIMPLIBHDR { KU16 uSig1; /* 0 */ KU16 uSig2; /* 0xffff */ KU16 uVersion; /* 0 */ KU16 uMachine; /* IMAGE_FILE_MACHINE_I386, ... */ KU32 uTimeDateStamp; KU32 cbData; KU16 uOrdinalOrHint; KU16 uFlags; } COFFIMPLIBHDR; /** * COFF symbol. * * This one has an odd size and will cause misaligned accesses on platforms * which cares about such. */ #pragma pack(1) typedef struct COFFSYMBOL { union { char e_name[8]; struct { KU32 e_zeros; /**< Zero to distinguish it from ascii name. */ KU32 e_offset; /**< String table offset. */ } e; } e; KU32 e_value; KI16 e_scnum; KU16 e_type; KU8 e_sclass; KU8 e_numaux; } COFFSYMBOL; #pragma pack() /** * Archive file header. */ typedef struct ARCHFILEHDR { char achName[16]; char achModtime[12]; char achOwnerId[6]; char achGroupId[6]; char achMode[8]; char achSize[10]; char achMagic[2]; } ARCHFILEHDR; /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** Whether verbose output is enabled. */ static unsigned g_cVerbosityLevel = 0; /** What to prefix the errors with. */ static char g_szErrorPrefix[128]; void FatalMsg(const char *pszFormat, ...) { va_list va; if (g_szErrorPrefix[0]) fprintf(stderr, "%s - fatal error: ", g_szErrorPrefix); else fprintf(stderr, "fatal error: "); va_start(va, pszFormat); vfprintf(stderr, pszFormat, va); va_end(va); } void FatalDie(const char *pszFormat, ...) { va_list va; if (g_szErrorPrefix[0]) fprintf(stderr, "%s - fatal error: ", g_szErrorPrefix); else fprintf(stderr, "fatal error: "); va_start(va, pszFormat); vfprintf(stderr, pszFormat, va); va_end(va); exit(1); } static int ErrorMsg(const char *pszFormat, ...) { va_list va; if (g_szErrorPrefix[0]) fprintf(stderr, "%s - error: ", g_szErrorPrefix); else fprintf(stderr, "error: "); va_start(va, pszFormat); vfprintf(stderr, pszFormat, va); va_end(va); return 1; } static void InfoMsg(unsigned uLevel, const char *pszFormat, ...) { if (uLevel <= g_cVerbosityLevel) { va_list va; if (g_szErrorPrefix[0]) fprintf(stderr, "%s - info: ", g_szErrorPrefix); else fprintf(stderr, "info: "); va_start(va, pszFormat); vfprintf(stderr, pszFormat, va); va_end(va); } } static void SetErrorPrefix(const char *pszPrefix, ...) { int cch; va_list va; va_start(va, pszPrefix); #if defined(_MSC_VER) || defined(__sun__) cch = vsprintf(g_szErrorPrefix, pszPrefix, va); if (cch >= sizeof(g_szErrorPrefix)) FatalDie("Buffer overflow setting error prefix!\n"); #else vsnprintf(g_szErrorPrefix, sizeof(g_szErrorPrefix), pszPrefix, va); #endif va_end(va); (void)cch; } #ifndef ELECTRIC_HEAP void *xmalloc(size_t cb) { void *pv = malloc(cb); if (!pv) FatalDie("out of memory (%d)\n", (int)cb); return pv; } void *xrealloc(void *pvOld, size_t cb) { void *pv = realloc(pvOld, cb); if (!pv) FatalDie("out of memory (%d)\n", (int)cb); return pv; } char *xstrdup(const char *pszIn) { char *psz; if (pszIn) { psz = strdup(pszIn); if (!psz) FatalDie("out of memory (%d)\n", (int)strlen(pszIn)); } else psz = NULL; return psz; } #endif void *xmallocz(size_t cb) { void *pv = xmalloc(cb); memset(pv, 0, cb); return pv; } /** * Adds the arguments found in the pszCmdLine string to argument vector. * * The parsing of the pszCmdLine string isn't very sophisticated, no * escaping or quotes. * * @param pcArgs Pointer to the argument counter. * @param ppapszArgs Pointer to the argument vector pointer. * @param pszCmdLine The command line to parse and append. * @param pszWedgeArg Argument to put infront of anything found in pszCmdLine. */ static void AppendArgs(int *pcArgs, char ***ppapszArgs, const char *pszCmdLine, const char *pszWedgeArg) { int i; int cExtraArgs; const char *psz; char **papszArgs; /* * Count the new arguments. */ cExtraArgs = 0; psz = pszCmdLine; while (*psz) { while (isspace(*psz)) psz++; if (!psz) break; cExtraArgs++; while (!isspace(*psz) && *psz) psz++; } if (!cExtraArgs) return; /* * Allocate a new vector that can hold the arguments. * (Reallocating might not work since the argv might not be allocated * from the heap but off the stack or somewhere... ) */ i = *pcArgs; *pcArgs = i + cExtraArgs + !!pszWedgeArg; papszArgs = xmalloc((*pcArgs + 1) * sizeof(char *)); *ppapszArgs = memcpy(papszArgs, *ppapszArgs, i * sizeof(char *)); if (pszWedgeArg) papszArgs[i++] = xstrdup(pszWedgeArg); psz = pszCmdLine; while (*psz) { size_t cch; const char *pszEnd; while (isspace(*psz)) psz++; if (!psz) break; pszEnd = psz; while (!isspace(*pszEnd) && *pszEnd) pszEnd++; cch = pszEnd - psz; papszArgs[i] = xmalloc(cch + 1); memcpy(papszArgs[i], psz, cch); papszArgs[i][cch] = '\0'; i++; psz = pszEnd; } papszArgs[i] = NULL; } static fpos_t kLibTweakerAsciiToSize(const char *pch, size_t cch) { fpos_t cb = 0; /* strip leading spaces. */ while (cch > 0 && (*pch == ' ' || *pch == '\t')) cch--, pch++; /* Convert decimal to binary. */ while (cch-- > 0) { char ch = *pch++; if (ch >= '0' && ch <= '9') { cb *= 10; cb += ch - '0'; } else break; } return cb; } static int kLibMyReadAt(FILE *pFile, void *pv, size_t cb, fpos_t off, int fEofOk) { if (fsetpos(pFile, &off) == 0) { size_t cbActual = fread(pv, 1, cb, pFile); if (cbActual == cb) return 0; if (!fEofOk || !feof(pFile)) ErrorMsg("fread returned %#lx, expected %#lx!\n", (unsigned long)cbActual, (unsigned long)cb); } else ErrorMsg("seek error!\n"); return 1; } static int kLibMyWriteAt(FILE *pFile, const void *pv, size_t cb, fpos_t off) { if (fsetpos(pFile, &off) == 0) { size_t cbActual = fwrite(pv, 1, cb, pFile); if (cbActual == cb) return 0; ErrorMsg("fwrite returned %#lx, expected %#lx!\n", (unsigned long)cbActual, (unsigned long)cb); } else ErrorMsg("seek error!\n"); return 1; } static int kLibFillNullThunkData(FILE *pFile, fpos_t cbFile, fpos_t offFileBytes) { size_t off; IMAGE_FILE_HEADER CoffHdr; IMAGE_SECTION_HEADER SecHdr; unsigned iSecHdr; fpos_t offCur; unsigned cbMachineWord; KU32 cbStrTab; COFFSYMBOL *paSymbols; int rcRet = 0; /* * Read the COFF file header and filter out unlikly files based on * section and symbol counts. */ if (cbFile <= sizeof(IMAGE_FILE_HEADER) + sizeof(IMAGE_SECTION_HEADER) * 2 + 4) return 0; if (kLibMyReadAt(pFile, &CoffHdr, sizeof(CoffHdr), offFileBytes, 0) != 0) return 1; if ( CoffHdr.Machine != IMAGE_FILE_MACHINE_I386 && CoffHdr.Machine != IMAGE_FILE_MACHINE_AMD64) return 0; cbMachineWord = CoffHdr.Machine == IMAGE_FILE_MACHINE_I386 ? 4 : 8; if ( CoffHdr.NumberOfSections == 0 || CoffHdr.NumberOfSymbols == 0) return 0; off = sizeof(IMAGE_FILE_HEADER) + CoffHdr.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); if ((fpos_t)off >= cbFile) return 0; if ( CoffHdr.PointerToSymbolTable >= (KU64)cbFile || CoffHdr.PointerToSymbolTable < off) return 0; /* * Search for the .idata$5 section which the thunk data is usually found in. */ offCur = offFileBytes + sizeof(CoffHdr); for (iSecHdr = 0; iSecHdr < CoffHdr.NumberOfSections; iSecHdr++) { if (kLibMyReadAt(pFile, &SecHdr, sizeof(SecHdr), offCur, 0) != 0) return 1; InfoMsg(2, "#2: %.8s VirtualSize=%#lx\n", SecHdr.Name, SecHdr.SizeOfRawData); if ( !memcmp(SecHdr.Name, ".idata$5", 8) && SecHdr.SizeOfRawData == cbMachineWord) break; offCur += sizeof(SecHdr); } if (iSecHdr == CoffHdr.NumberOfSections) return 0; /* * Read in the symbo and string tables. */ off = CoffHdr.PointerToSymbolTable + CoffHdr.NumberOfSymbols * sizeof(COFFSYMBOL); if (kLibMyReadAt(pFile, &cbStrTab, sizeof(cbStrTab), offFileBytes + off, 0) != 0) return 1; InfoMsg(2, "#2: Found COFF file header, cbStrTab=%#x; off=%#lx NumberOfSymbols=%#x PointerToSymbolTable=%#x\n", cbStrTab, (long)off, CoffHdr.NumberOfSymbols, CoffHdr.PointerToSymbolTable); if ( cbStrTab <= 4U || cbStrTab >= 16*1024*1024U /* 16MB */ || (fpos_t)off + cbStrTab > cbFile) return 0; paSymbols = xmalloc(CoffHdr.NumberOfSymbols * sizeof(COFFSYMBOL) + cbStrTab + 1); if (kLibMyReadAt(pFile, paSymbols, CoffHdr.NumberOfSymbols * sizeof(COFFSYMBOL) + cbStrTab, offFileBytes + CoffHdr.PointerToSymbolTable, 0) == 0) { char *pchStrTab = (char *)&paSymbols[CoffHdr.NumberOfSymbols]; unsigned iSym; pchStrTab[cbStrTab] = '\0'; pchStrTab[0] = '\0'; pchStrTab[1] = '\0'; pchStrTab[2] = '\0'; pchStrTab[3] = '\0'; for (iSym = 0; iSym < CoffHdr.NumberOfSymbols; iSym++) { static char const s_szSuffix[] = "NULL_THUNK_DATA"; const char *pchName; size_t cchName; if (paSymbols[iSym].e.e.e_zeros != 0) { pchName = &paSymbols[iSym].e.e_name[0]; cchName = (char *)memchr(pchName, '\0', sizeof(paSymbols[iSym].e.e_name)) - pchName; if (cchName > sizeof(paSymbols[iSym].e.e_name)) cchName = sizeof(paSymbols[iSym].e.e_name); } else if ( paSymbols[iSym].e.e.e_offset == 0 || paSymbols[iSym].e.e.e_offset >= cbStrTab) continue; else { pchName = &pchStrTab[paSymbols[iSym].e.e.e_offset]; cchName = strlen(pchName); } if ( *pchName == 0x7f && cchName >= sizeof(s_szSuffix) && memcmp(&pchName[cchName - sizeof(s_szSuffix) + 1], s_szSuffix, sizeof(s_szSuffix) - 1) == 0) { if (pchName[cchName] == '\0') InfoMsg(1, "#2: Found '%s': value=%#lx\n", pchName, paSymbols[iSym].e_value); else InfoMsg(1, "#2: Found '%.8s': value=%#lx\n", pchName, paSymbols[iSym].e_value); if ( paSymbols[iSym].e_scnum > 0 && paSymbols[iSym].e_scnum <= CoffHdr.NumberOfSections) { if (paSymbols[iSym].e_scnum != iSecHdr + 1) InfoMsg(0, "#2: '%s' in section %u, expected %u\n", pchName, paSymbols[iSym].e_scnum, iSecHdr); else if (paSymbols[iSym].e_value != 0) InfoMsg(0, "#2: '%s' in value %#xu, expected 0x0\n", pchName, paSymbols[iSym].e_value); else if ( SecHdr.PointerToRawData < sizeof(CoffHdr) + CoffHdr.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) || (fpos_t)SecHdr.PointerToRawData + cbMachineWord > cbFile) InfoMsg(0, "#2: Unexpected PointerToRawData value: %#x\n", SecHdr.PointerToRawData); else { union { KU8 ab[8]; KU32 u32; KU64 u64; } uBuf; uBuf.u64 = 0; off = offFileBytes + SecHdr.PointerToRawData; if (kLibMyReadAt(pFile, &uBuf, cbMachineWord, off, 0) == 0) { static const KU8 s_abGarbage[8] = { 0xaa, 0x99, 0x88, 0xbb, 0xbb, 0xaa, 0x88, 0x99 }; static const KU8 s_abZero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; if (memcmp(&uBuf, s_abZero,cbMachineWord) == 0) { rcRet = kLibMyWriteAt(pFile, s_abGarbage, cbMachineWord, off); if (!rcRet) InfoMsg(1, "#2: Updated '%s'\n", pchName); } else if (memcmp(&uBuf, s_abGarbage, cbMachineWord) == 0) { InfoMsg(1, "#2: Already modified '%s'\n", pchName); rcRet = 0; } else rcRet = ErrorMsg(0, "#2: Unexpected '%s' data: %02x %02x %02x %02x %02x %02x %02x %02x\n", pchName, uBuf.ab[0], uBuf.ab[1], uBuf.ab[2], uBuf.ab[3], uBuf.ab[4], uBuf.ab[5], uBuf.ab[6], uBuf.ab[7]); } break; } } } } } else rcRet = 1; free(paSymbols); return rcRet; } /** * Clears timestamps to avoid rebuilding stuff just because the internal * timestamps changed in an import library. */ static int kLibClearTimestamps(FILE *pFile, fpos_t offFileHdr, ARCHFILEHDR *pFileHdr, fpos_t cbFile, fpos_t offFileBytes) { union { IMAGE_FILE_HEADER CoffHdr; COFFIMPLIBHDR ImpLibHdr; } u; if (sizeof(u.CoffHdr) != sizeof(u.ImpLibHdr)) FatalDie("Oops!"); /* * Clear the timestamp in the library file header. */ memset(pFileHdr->achModtime, '0', sizeof(pFileHdr->achModtime)); if (kLibMyWriteAt(pFile, pFileHdr, sizeof(*pFileHdr), offFileHdr) != 0) return 1; /* * Clear the timestamp in the COFF header, if we find one. */ if (cbFile <= sizeof(IMAGE_FILE_HEADER)) return 0; if (kLibMyReadAt(pFile, &u.CoffHdr, sizeof(u.CoffHdr), offFileBytes, 0) != 0) return 1; if ( ( u.CoffHdr.Machine == IMAGE_FILE_MACHINE_I386 || u.CoffHdr.Machine == IMAGE_FILE_MACHINE_AMD64) && sizeof(IMAGE_FILE_HEADER) + u.CoffHdr.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) <= (KU64)cbFile && u.CoffHdr.PointerToSymbolTable <= (KU64)cbFile) { InfoMsg(1, "Found COFF file header\n"); if (u.CoffHdr.TimeDateStamp != 0) { u.CoffHdr.TimeDateStamp = 0; return kLibMyWriteAt(pFile, &u.CoffHdr, sizeof(u.CoffHdr), offFileBytes); } } else if ( u.ImpLibHdr.uSig1 == 0 && u.ImpLibHdr.uSig2 == 0xffff && u.ImpLibHdr.uVersion == 0 && ( u.ImpLibHdr.uMachine == IMAGE_FILE_MACHINE_I386 || u.ImpLibHdr.uMachine == IMAGE_FILE_MACHINE_AMD64) && u.ImpLibHdr.cbData <= cbFile) { InfoMsg(1, "Found COFF import library header\n"); if (u.ImpLibHdr.uTimeDateStamp) { u.ImpLibHdr.uTimeDateStamp = 0; return kLibMyWriteAt(pFile, &u.ImpLibHdr, sizeof(u.ImpLibHdr), offFileBytes); } } else InfoMsg(1, "CoffHdr.Machine=%#x ImpLibHdr.Machine=%#x\n", u.CoffHdr.Machine, u.ImpLibHdr.uMachine); return 0; } static int kLibTweakerDoIt(const char *pszLib, int fClearTimestamps, int fFillNullThunkData) { int rcRet = 0; FILE *pFile = fopen(pszLib, "r+b"); if (pFile) { /* * Read the header. */ static char s_szMagic[] = "!\n"; union { char ab[1024]; IMAGE_FILE_HEADER CoffHdr; } uBuf; if ( fread(uBuf.ab, 1, sizeof(s_szMagic) - 1, pFile) == sizeof(s_szMagic) - 1 && memcmp(uBuf.ab, s_szMagic, sizeof(s_szMagic) - 1) == 0) { fpos_t offFileHdr = sizeof(s_szMagic) - 1; while (!feof(pFile)) { ARCHFILEHDR FileHdr; if (kLibMyReadAt(pFile, &FileHdr, sizeof(FileHdr), offFileHdr, 1) != 0) { if (feof(pFile)) break; rcRet = ErrorMsg("failed reading the file header (offset %ld)\n", (long)offFileHdr); break; } if ( FileHdr.achMagic[0] == 0x60 && FileHdr.achMagic[1] == 0x0a) { fpos_t const offFileBytes = offFileHdr + sizeof(FileHdr); /* * Convert the size from decimal to binary as we need it to skip to * the next file header. */ fpos_t const cb = kLibTweakerAsciiToSize(FileHdr.achSize, sizeof(FileHdr.achSize)); InfoMsg(1, "Found header at %#lx: cbFile=%#lx, bytes at %#lx\n", (unsigned long)offFileHdr, (unsigned long)cb, (unsigned long)offFileBytes); /* * Make the requested changes. */ if (fClearTimestamps) rcRet |= kLibClearTimestamps(pFile, offFileHdr, &FileHdr, cb, offFileBytes); if (fFillNullThunkData) rcRet |= kLibFillNullThunkData(pFile, cb, offFileBytes); /* * Skip to the next header. */ offFileHdr = offFileBytes + ((cb + 1) & ~(fpos_t)1); } else rcRet = ErrorMsg("invalid file header magic (offset %ld)\n", (long)offFileHdr); } } else rcRet = ErrorMsg("Didn't find '!\\n' magic in '%s' (or read error)\n", pszLib); if (fclose(pFile) != 0) rcRet = ErrorMsg("Error closing '%s'\n"); } else rcRet = ErrorMsg("Failed to open '%s' for read+write\n", pszLib); return rcRet; } /** * Prints a syntax error and returns the appropriate exit code * * @returns approriate exit code. * @param pszFormat The syntax error message. * @param ... Message args. */ static int SyntaxError(const char *pszFormat, ...) { va_list va; fprintf(stderr, "kObjCache: syntax error: "); va_start(va, pszFormat); vfprintf(stderr, pszFormat, va); va_end(va); return 1; } /** * Prints the usage. * @returns 0. */ static int usage(FILE *pOut) { fprintf(pOut, "syntax: kLibTweaker [-v|--verbose] [--clear-timestamps] \n" "\n"); return 0; } int main(int argc, char **argv) { char *psz; int i; int fClearTimestamps = 0; int fFillNullThunkData = 0; const char *pszLib = NULL; SetErrorPrefix("kLibTweaker"); /* * Arguments passed in the environmnet? */ psz = getenv("KLIBTWEAKER_OPTS"); if (psz) AppendArgs(&argc, &argv, psz, NULL); /** @todo Add the capability to produce import/stub libraries from ELF shared * objects that we can use while linking and break up linking dependencies * (i.e. not relink everything just because something in VBoxRT change that * didn't make any difference to the symbols it exports). */ /* * Parse the arguments. */ if (argc <= 1) return usage(stderr) + 1; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--clear-timestamps")) fClearTimestamps = 1; else if (!strcmp(argv[i], "--fill-null_thunk_data")) fFillNullThunkData = 1; /* Standard stuff: */ else if (!strcmp(argv[i], "--help")) return usage(stderr); else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) g_cVerbosityLevel++; else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet")) g_cVerbosityLevel = 0; else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?") || !strcmp(argv[i], "/h") || !strcmp(argv[i], "/?") || !strcmp(argv[i], "/help")) return usage(stdout); else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version")) { printf("kLibTweaker - kBuild version %d.%d.%d ($Revision: 2791 $)\n" "Copyright (c) 2007-2015 knut st. osmundsen\n", KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH); return 0; } else if (!strcmp(argv[i], "--")) { i++; if (i == argc) return SyntaxError("No library given!\n"); if (i + 1 != argc || pszLib) return SyntaxError("Only one library can be tweaked at a time!\n"); pszLib = argv[i]; break; } else if (argv[i][0] == '-') return SyntaxError("Doesn't grok '%s'!\n", argv[i]); else if (!pszLib) pszLib = argv[i]; else return SyntaxError("Only one library can be tweaked at a time!\n"); } if (!pszLib) return SyntaxError("No library given!\n"); return kLibTweakerDoIt(pszLib, fClearTimestamps, fFillNullThunkData); }