1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 /*-------------------------------------------------------------------------
4 Portions of this source have been derived from the 'bindexplib' tool
5 provided by the CERN ROOT Data Analysis Framework project (root.cern.ch).
6 Permission has been granted by Pere Mato <pere.mato@cern.ch> to distribute
7 this derived work under the CMake license.
8 -------------------------------------------------------------------------*/
11 *----------------------------------------------------------------------
12 * Program: dumpexts.exe
13 * Author: Gordon Chaffee
15 * History: The real functionality of this file was written by
16 * Matt Pietrek in 1993 in his pedump utility. I've
17 * modified it to dump the externals in a bunch of object
18 * files to create a .def file.
20 * Notes: Visual C++ puts an underscore before each exported symbol.
21 * This file removes them. I don't know if this is a problem
22 * this other compilers. If _MSC_VER is defined,
23 * the underscore is removed. If not, it isn't. To get a
24 * full dump of an object file, use the -f option. This can
25 * help determine the something that may be different with a
26 * compiler other than Visual C++.
27 * ======================================
28 * Corrections (Axel 2006-04-04):
29 * Conversion to C++. Mostly.
31 * Extension (Axel 2006-03-15)
32 * As soon as an object file contains an /EXPORT directive (which
33 * is generated by the compiler when a symbol is declared as
34 * __declspec(dllexport) no to-be-exported symbols are printed,
35 * as the linker will see these directives, and if those directives
36 * are present we only export selectively (i.e. we trust the
39 * ======================================
40 * ======================================
41 * Corrections (Valery Fine 23/02/98):
43 * The "(vector) deleting destructor" MUST not be exported
44 * To recognize it the following test are introduced:
45 * "@@UAEPAXI@Z" scalar deleting dtor
46 * "@@QAEPAXI@Z" vector deleting dtor
47 * "AEPAXI@Z" vector deleting dtor with thunk adjustor
48 * ======================================
49 * Corrections (Valery Fine 12/02/97):
51 * It created a wrong EXPORTS for the global pointers and constants.
52 * The Section Header has been involved to discover the missing information
53 * Now the pointers are correctly supplied with "DATA" descriptor
54 * the constants with no extra descriptor.
56 * Corrections (Valery Fine 16/09/96):
58 * It didn't work for C++ code with global variables and class definitions
59 * The DumpExternalObject function has been introduced to generate .DEF
62 * Author: Valery Fine 16/09/96 (E-mail: fine@vxcern.cern.ch)
63 *----------------------------------------------------------------------
65 #include "bindexplib.h"
67 #include <cstddef> // IWYU pragma: keep
74 # include "cmsys/Encoding.hxx"
77 #include "cmsys/FStream.hxx"
79 #include "cmSystemTools.h"
82 # ifndef IMAGE_FILE_MACHINE_ARM
83 # define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
86 # ifndef IMAGE_FILE_MACHINE_THUMB
87 # define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM Thumb/Thumb-2 Little-Endian
90 # ifndef IMAGE_FILE_MACHINE_ARMNT
91 # define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian
94 # ifndef IMAGE_FILE_MACHINE_ARM64
95 # define IMAGE_FILE_MACHINE_ARM64 0xaa64 // ARM64 Little-Endian
98 # ifndef IMAGE_FILE_MACHINE_ARM64EC
99 # define IMAGE_FILE_MACHINE_ARM64EC 0xa641 // ARM64EC Little-Endian
102 typedef struct cmANON_OBJECT_HEADER_BIGOBJ
104 /* same as ANON_OBJECT_HEADER_V2 */
105 WORD Sig1; // Must be IMAGE_FILE_MACHINE_UNKNOWN
106 WORD Sig2; // Must be 0xffff
107 WORD Version; // >= 2 (implies the Flags field is present)
108 WORD Machine; // Actual machine - IMAGE_FILE_MACHINE_xxx
110 CLSID ClassID; // {D1BAA1C7-BAEE-4ba9-AF20-FAF66AA4DCB8}
111 DWORD SizeOfData; // Size of data that follows the header
112 DWORD Flags; // 0x1 -> contains metadata
113 DWORD MetaDataSize; // Size of CLR metadata
114 DWORD MetaDataOffset; // Offset of CLR metadata
116 /* bigobj specifics */
117 DWORD NumberOfSections; // extended from WORD
118 DWORD PointerToSymbolTable;
119 DWORD NumberOfSymbols;
120 } cmANON_OBJECT_HEADER_BIGOBJ;
122 typedef struct _cmIMAGE_SYMBOL_EX
129 DWORD Short; // if 0, use LongName
130 DWORD Long; // offset into string table
132 DWORD LongName[2]; // PBYTE [2]
138 BYTE NumberOfAuxSymbols;
140 typedef cmIMAGE_SYMBOL_EX UNALIGNED* cmPIMAGE_SYMBOL_EX;
149 PIMAGE_SECTION_HEADER GetSectionHeaderOffset(
150 PIMAGE_FILE_HEADER pImageFileHeader)
152 return (PIMAGE_SECTION_HEADER)((DWORD_PTR)pImageFileHeader +
153 IMAGE_SIZEOF_FILE_HEADER +
154 pImageFileHeader->SizeOfOptionalHeader);
157 PIMAGE_SECTION_HEADER GetSectionHeaderOffset(
158 cmANON_OBJECT_HEADER_BIGOBJ* pImageFileHeader)
160 return (PIMAGE_SECTION_HEADER)((DWORD_PTR)pImageFileHeader +
161 sizeof(cmANON_OBJECT_HEADER_BIGOBJ));
165 + * Utility func, strstr with size
167 const char* StrNStr(const char* start, const char* find, size_t& size)
172 if (!start || !find || !size) {
178 while ((hint = (const char*)memchr(start, find[0], size - len + 1))) {
179 size -= (hint - start);
180 if (!strncmp(hint, find, len))
190 // cmANON_OBJECT_HEADER_BIGOBJ or IMAGE_FILE_HEADER
191 class ObjectHeaderType,
192 // cmPIMAGE_SYMBOL_EX or PIMAGE_SYMBOL
193 class SymbolTableType>
198 *----------------------------------------------------------------------
201 * Initialize variables from pointer to object header.
203 *----------------------------------------------------------------------
206 DumpSymbols(ObjectHeaderType* ih, std::set<std::string>& symbols,
207 std::set<std::string>& dataSymbols,
208 Arch symbolArch = Arch::Generic)
210 , DataSymbols(dataSymbols)
212 this->ObjectImageHeader = ih;
214 (SymbolTableType*)((DWORD_PTR)this->ObjectImageHeader +
215 this->ObjectImageHeader->PointerToSymbolTable);
216 this->SectionHeaders = GetSectionHeaderOffset(this->ObjectImageHeader);
217 this->SymbolCount = this->ObjectImageHeader->NumberOfSymbols;
218 this->SymbolArch = symbolArch;
222 *----------------------------------------------------------------------
225 * Dump an object file's exported symbols.
226 *----------------------------------------------------------------------
228 void DumpObjFile() { this->DumpExternalsObjects(); }
231 *----------------------------------------------------------------------
232 * DumpExternalsObjects --
234 * Dumps a COFF symbol table from an OBJ.
235 *----------------------------------------------------------------------
237 void DumpExternalsObjects()
244 * The string table apparently starts right after the symbol table
246 stringTable = (PSTR) & this->SymbolTable[this->SymbolCount];
247 SymbolTableType* pSymbolTable = this->SymbolTable;
248 for (i = 0; i < this->SymbolCount; i++) {
249 if (pSymbolTable->SectionNumber > 0 &&
250 (pSymbolTable->Type == 0x20 || pSymbolTable->Type == 0x0)) {
251 if (pSymbolTable->StorageClass == IMAGE_SYM_CLASS_EXTERNAL) {
253 * The name of the Function entry points
255 if (pSymbolTable->N.Name.Short != 0) {
257 symbol.insert(0, (const char*)pSymbolTable->N.ShortName, 8);
259 symbol = stringTable + pSymbolTable->N.Name.Long;
262 // clear out any leading spaces
263 while (isspace(symbol[0]))
265 // if it starts with _ and has an @ then it is a __cdecl
266 // so remove the @ stuff for the export
267 if (symbol[0] == '_') {
268 std::string::size_type posAt = symbol.find('@');
269 if (posAt != std::string::npos) {
273 // For i386 builds we need to remove _
274 if (this->SymbolArch == Arch::I386 && symbol[0] == '_') {
278 // Check whether it is "Scalar deleting destructor" and "Vector
279 // deleting destructor"
280 // if scalarPrefix and vectorPrefix are not found then print
282 const char* scalarPrefix = "??_G";
283 const char* vectorPrefix = "??_E";
284 // The original code had a check for
285 // symbol.find("real@") == std::string::npos)
286 // but this disallows member functions with the name "real".
287 if (symbol.compare(0, 4, scalarPrefix) &&
288 symbol.compare(0, 4, vectorPrefix)) {
289 SectChar = this->SectionHeaders[pSymbolTable->SectionNumber - 1]
291 // skip symbols containing a dot or are from managed code
292 if (symbol.find('.') == std::string::npos &&
293 !SymbolIsFromManagedCode(symbol)) {
294 // skip arm64ec thunk symbols
295 if (this->SymbolArch != Arch::ARM64EC ||
296 (symbol.find("$ientry_thunk") == std::string::npos &&
297 symbol.find("$entry_thunk") == std::string::npos &&
298 symbol.find("$iexit_thunk") == std::string::npos &&
299 symbol.find("$exit_thunk") == std::string::npos)) {
300 if (!pSymbolTable->Type && (SectChar & IMAGE_SCN_MEM_WRITE)) {
301 // Read only (i.e. constants) must be excluded
302 this->DataSymbols.insert(symbol);
304 if (pSymbolTable->Type || !(SectChar & IMAGE_SCN_MEM_READ) ||
305 (SectChar & IMAGE_SCN_MEM_EXECUTE)) {
306 this->Symbols.insert(symbol);
316 * Take into account any aux symbols
318 i += pSymbolTable->NumberOfAuxSymbols;
319 pSymbolTable += pSymbolTable->NumberOfAuxSymbols;
325 bool SymbolIsFromManagedCode(std::string const& symbol)
327 return symbol == "__t2m" || symbol == "__m2mep" || symbol == "__mep" ||
328 symbol.find("$$F") != std::string::npos ||
329 symbol.find("$$J") != std::string::npos;
332 std::set<std::string>& Symbols;
333 std::set<std::string>& DataSymbols;
334 DWORD_PTR SymbolCount;
335 PIMAGE_SECTION_HEADER SectionHeaders;
336 ObjectHeaderType* ObjectImageHeader;
337 SymbolTableType* SymbolTable;
342 static bool DumpFileWithLlvmNm(std::string const& nmPath, const char* filename,
343 std::set<std::string>& symbols,
344 std::set<std::string>& dataSymbols)
347 // break up command line into a vector
348 std::vector<std::string> command;
349 command.push_back(nmPath);
350 command.emplace_back("--no-weak");
351 command.emplace_back("--defined-only");
352 command.emplace_back("--format=posix");
353 command.emplace_back(filename);
357 cmSystemTools::RunSingleCommand(command, &output, &output, &exit_code,
358 nullptr, cmSystemTools::OUTPUT_NONE);
360 if (exit_code != 0) {
361 fprintf(stderr, "llvm-nm returned an error: %s\n", output.c_str());
365 std::istringstream ss(output);
367 while (std::getline(ss, line)) {
368 if (line.empty()) { // last line
371 size_t sym_end = line.find(' ');
372 if (sym_end == std::string::npos) {
373 fprintf(stderr, "Couldn't parse llvm-nm output line: %s\n",
377 if (line.size() < sym_end + 1) {
378 fprintf(stderr, "Couldn't parse llvm-nm output line: %s\n",
382 const char sym_type = line[sym_end + 1];
383 line.resize(sym_end);
386 dataSymbols.insert(line);
389 symbols.insert(line);
397 static bool DumpFile(std::string const& nmPath, const char* filename,
398 std::set<std::string>& symbols,
399 std::set<std::string>& dataSymbols)
402 return DumpFileWithLlvmNm(nmPath, filename, symbols, dataSymbols);
408 hFile = CreateFileW(cmsys::Encoding::ToWide(filename).c_str(), GENERIC_READ,
409 FILE_SHARE_READ, NULL, OPEN_EXISTING,
410 FILE_ATTRIBUTE_NORMAL, 0);
412 if (hFile == INVALID_HANDLE_VALUE) {
413 fprintf(stderr, "Couldn't open file '%s' with CreateFile()\n", filename);
417 hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
418 if (hFileMapping == 0) {
420 fprintf(stderr, "Couldn't open file mapping with CreateFileMapping()\n");
424 lpFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
425 if (lpFileBase == 0) {
426 CloseHandle(hFileMapping);
428 fprintf(stderr, "Couldn't map view of file with MapViewOfFile()\n");
432 const PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)lpFileBase;
433 if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) {
434 fprintf(stderr, "File is an executable. I don't dump those.\n");
437 const PIMAGE_FILE_HEADER imageHeader = (PIMAGE_FILE_HEADER)lpFileBase;
438 /* Does it look like a COFF OBJ file??? */
439 if (((imageHeader->Machine == IMAGE_FILE_MACHINE_I386) ||
440 (imageHeader->Machine == IMAGE_FILE_MACHINE_AMD64) ||
441 (imageHeader->Machine == IMAGE_FILE_MACHINE_ARM) ||
442 (imageHeader->Machine == IMAGE_FILE_MACHINE_ARMNT) ||
443 (imageHeader->Machine == IMAGE_FILE_MACHINE_ARM64) ||
444 (imageHeader->Machine == IMAGE_FILE_MACHINE_ARM64EC)) &&
445 (imageHeader->Characteristics == 0)) {
447 * The tests above are checking for IMAGE_FILE_HEADER.Machine
448 * if it contains supported machine formats (currently ARM and x86)
449 * and IMAGE_FILE_HEADER.Characteristics == 0 indicating that
450 * this is not linked COFF OBJ file;
452 DumpSymbols<IMAGE_FILE_HEADER, IMAGE_SYMBOL> symbolDumper(
453 (PIMAGE_FILE_HEADER)lpFileBase, symbols, dataSymbols,
454 (imageHeader->Machine == IMAGE_FILE_MACHINE_I386
456 : (imageHeader->Machine == IMAGE_FILE_MACHINE_ARM64EC
459 symbolDumper.DumpObjFile();
461 // check for /bigobj and llvm LTO format
462 cmANON_OBJECT_HEADER_BIGOBJ* h =
463 (cmANON_OBJECT_HEADER_BIGOBJ*)lpFileBase;
464 if (h->Sig1 == 0x0 && h->Sig2 == 0xffff) {
466 DumpSymbols<cmANON_OBJECT_HEADER_BIGOBJ, cmIMAGE_SYMBOL_EX>
468 (cmANON_OBJECT_HEADER_BIGOBJ*)lpFileBase, symbols, dataSymbols,
469 (h->Machine == IMAGE_FILE_MACHINE_I386
471 : (h->Machine == IMAGE_FILE_MACHINE_ARM64EC ? Arch::ARM64EC
473 symbolDumper.DumpObjFile();
475 // BCexCODE - llvm bitcode
476 (h->Sig1 == 0x4342 && h->Sig2 == 0xDEC0) ||
477 // 0x0B17C0DE - llvm bitcode BC wrapper
478 (h->Sig1 == 0x0B17 && h->Sig2 == 0xC0DE)) {
480 return DumpFileWithLlvmNm(nmPath, filename, symbols, dataSymbols);
483 printf("unrecognized file format in '%s, %u'\n", filename,
484 imageHeader->Machine);
489 UnmapViewOfFile(lpFileBase);
490 CloseHandle(hFileMapping);
496 bool bindexplib::AddObjectFile(const char* filename)
498 return DumpFile(this->NmPath, filename, this->Symbols, this->DataSymbols);
501 bool bindexplib::AddDefinitionFile(const char* filename)
503 cmsys::ifstream infile(filename);
505 fprintf(stderr, "Couldn't open definition file '%s'\n", filename);
509 while (std::getline(infile, str)) {
510 // skip the LIBRARY and EXPORTS lines (if any)
511 if ((str.compare(0, 7, "LIBRARY") == 0) ||
512 (str.compare(0, 7, "EXPORTS") == 0)) {
515 // remove leading tabs & spaces
516 str.erase(0, str.find_first_not_of(" \t"));
517 std::size_t found = str.find(" \t DATA");
518 if (found != std::string::npos) {
519 str.erase(found, std::string::npos);
520 this->DataSymbols.insert(str);
522 this->Symbols.insert(str);
529 void bindexplib::WriteFile(FILE* file)
531 fprintf(file, "EXPORTS \n");
532 for (std::string const& ds : this->DataSymbols) {
533 fprintf(file, "\t%s \t DATA\n", ds.c_str());
535 for (std::string const& s : this->Symbols) {
536 fprintf(file, "\t%s\n", s.c_str());
540 void bindexplib::SetNmPath(std::string const& nm)