resolve cyclic dependency with zstd
[platform/upstream/cmake.git] / Source / bindexplib.cxx
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 -------------------------------------------------------------------------*/
9
10 /*
11  *----------------------------------------------------------------------
12  * Program:  dumpexts.exe
13  * Author:   Gordon Chaffee
14  *
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.
19  *
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.
30  *
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
37  *    programmer).
38  *
39  *   ======================================
40  *   ======================================
41  * Corrections (Valery Fine 23/02/98):
42  *
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):
50  *
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.
55  *
56  * Corrections (Valery Fine 16/09/96):
57  *
58  *     It didn't work for C++ code with global variables and class definitions
59  *     The DumpExternalObject function has been introduced to generate .DEF
60  *file
61  *
62  * Author:   Valery Fine 16/09/96  (E-mail: fine@vxcern.cern.ch)
63  *----------------------------------------------------------------------
64  */
65 #include "bindexplib.h"
66
67 #include <cstddef> // IWYU pragma: keep
68 #include <sstream>
69 #include <vector>
70
71 #ifdef _WIN32
72 #  include <windows.h>
73
74 #  include "cmsys/Encoding.hxx"
75 #endif
76
77 #include "cmsys/FStream.hxx"
78
79 #include "cmSystemTools.h"
80
81 #ifdef _WIN32
82 #  ifndef IMAGE_FILE_MACHINE_ARM
83 #    define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
84 #  endif
85
86 #  ifndef IMAGE_FILE_MACHINE_THUMB
87 #    define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM Thumb/Thumb-2 Little-Endian
88 #  endif
89
90 #  ifndef IMAGE_FILE_MACHINE_ARMNT
91 #    define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian
92 #  endif
93
94 #  ifndef IMAGE_FILE_MACHINE_ARM64
95 #    define IMAGE_FILE_MACHINE_ARM64 0xaa64 // ARM64 Little-Endian
96 #  endif
97
98 #  ifndef IMAGE_FILE_MACHINE_ARM64EC
99 #    define IMAGE_FILE_MACHINE_ARM64EC 0xa641 // ARM64EC Little-Endian
100 #  endif
101
102 typedef struct cmANON_OBJECT_HEADER_BIGOBJ
103 {
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
109   DWORD TimeDateStamp;
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
115
116   /* bigobj specifics */
117   DWORD NumberOfSections; // extended from WORD
118   DWORD PointerToSymbolTable;
119   DWORD NumberOfSymbols;
120 } cmANON_OBJECT_HEADER_BIGOBJ;
121
122 typedef struct _cmIMAGE_SYMBOL_EX
123 {
124   union
125   {
126     BYTE ShortName[8];
127     struct
128     {
129       DWORD Short; // if 0, use LongName
130       DWORD Long;  // offset into string table
131     } Name;
132     DWORD LongName[2]; // PBYTE  [2]
133   } N;
134   DWORD Value;
135   LONG SectionNumber;
136   WORD Type;
137   BYTE StorageClass;
138   BYTE NumberOfAuxSymbols;
139 } cmIMAGE_SYMBOL_EX;
140 typedef cmIMAGE_SYMBOL_EX UNALIGNED* cmPIMAGE_SYMBOL_EX;
141
142 enum class Arch
143 {
144   Generic,
145   I386,
146   ARM64EC,
147 };
148
149 PIMAGE_SECTION_HEADER GetSectionHeaderOffset(
150   PIMAGE_FILE_HEADER pImageFileHeader)
151 {
152   return (PIMAGE_SECTION_HEADER)((DWORD_PTR)pImageFileHeader +
153                                  IMAGE_SIZEOF_FILE_HEADER +
154                                  pImageFileHeader->SizeOfOptionalHeader);
155 }
156
157 PIMAGE_SECTION_HEADER GetSectionHeaderOffset(
158   cmANON_OBJECT_HEADER_BIGOBJ* pImageFileHeader)
159 {
160   return (PIMAGE_SECTION_HEADER)((DWORD_PTR)pImageFileHeader +
161                                  sizeof(cmANON_OBJECT_HEADER_BIGOBJ));
162 }
163
164 /*
165 + * Utility func, strstr with size
166 + */
167 const char* StrNStr(const char* start, const char* find, size_t& size)
168 {
169   size_t len;
170   const char* hint;
171
172   if (!start || !find || !size) {
173     size = 0;
174     return 0;
175   }
176   len = strlen(find);
177
178   while ((hint = (const char*)memchr(start, find[0], size - len + 1))) {
179     size -= (hint - start);
180     if (!strncmp(hint, find, len))
181       return hint;
182     start = hint + 1;
183   }
184
185   size = 0;
186   return 0;
187 }
188
189 template <
190   // cmANON_OBJECT_HEADER_BIGOBJ or IMAGE_FILE_HEADER
191   class ObjectHeaderType,
192   // cmPIMAGE_SYMBOL_EX or PIMAGE_SYMBOL
193   class SymbolTableType>
194 class DumpSymbols
195 {
196 public:
197   /*
198    *----------------------------------------------------------------------
199    * Constructor --
200    *
201    *     Initialize variables from pointer to object header.
202    *
203    *----------------------------------------------------------------------
204    */
205
206   DumpSymbols(ObjectHeaderType* ih, std::set<std::string>& symbols,
207               std::set<std::string>& dataSymbols,
208               Arch symbolArch = Arch::Generic)
209     : Symbols(symbols)
210     , DataSymbols(dataSymbols)
211   {
212     this->ObjectImageHeader = ih;
213     this->SymbolTable =
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;
219   }
220
221   /*
222    *----------------------------------------------------------------------
223    * DumpObjFile --
224    *
225    *      Dump an object file's exported symbols.
226    *----------------------------------------------------------------------
227    */
228   void DumpObjFile() { this->DumpExternalsObjects(); }
229
230   /*
231    *----------------------------------------------------------------------
232    * DumpExternalsObjects --
233    *
234    *      Dumps a COFF symbol table from an OBJ.
235    *----------------------------------------------------------------------
236    */
237   void DumpExternalsObjects()
238   {
239     unsigned i;
240     PSTR stringTable;
241     std::string symbol;
242     DWORD SectChar;
243     /*
244      * The string table apparently starts right after the symbol table
245      */
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) {
252           /*
253            *    The name of the Function entry points
254            */
255           if (pSymbolTable->N.Name.Short != 0) {
256             symbol.clear();
257             symbol.insert(0, (const char*)pSymbolTable->N.ShortName, 8);
258           } else {
259             symbol = stringTable + pSymbolTable->N.Name.Long;
260           }
261
262           // clear out any leading spaces
263           while (isspace(symbol[0]))
264             symbol.erase(0, 1);
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) {
270               symbol.erase(posAt);
271             }
272           }
273           // For i386 builds we need to remove _
274           if (this->SymbolArch == Arch::I386 && symbol[0] == '_') {
275             symbol.erase(0, 1);
276           }
277
278           // Check whether it is "Scalar deleting destructor" and "Vector
279           // deleting destructor"
280           // if scalarPrefix and vectorPrefix are not found then print
281           // the symbol
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]
290                          .Characteristics;
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);
303                 } else {
304                   if (pSymbolTable->Type || !(SectChar & IMAGE_SCN_MEM_READ) ||
305                       (SectChar & IMAGE_SCN_MEM_EXECUTE)) {
306                     this->Symbols.insert(symbol);
307                   }
308                 }
309               }
310             }
311           }
312         }
313       }
314
315       /*
316        * Take into account any aux symbols
317        */
318       i += pSymbolTable->NumberOfAuxSymbols;
319       pSymbolTable += pSymbolTable->NumberOfAuxSymbols;
320       pSymbolTable++;
321     }
322   }
323
324 private:
325   bool SymbolIsFromManagedCode(std::string const& symbol)
326   {
327     return symbol == "__t2m" || symbol == "__m2mep" || symbol == "__mep" ||
328       symbol.find("$$F") != std::string::npos ||
329       symbol.find("$$J") != std::string::npos;
330   }
331
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;
338   Arch SymbolArch;
339 };
340 #endif
341
342 static bool DumpFileWithLlvmNm(std::string const& nmPath, const char* filename,
343                                std::set<std::string>& symbols,
344                                std::set<std::string>& dataSymbols)
345 {
346   std::string output;
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);
354
355   // run the command
356   int exit_code = 0;
357   cmSystemTools::RunSingleCommand(command, &output, &output, &exit_code,
358                                   nullptr, cmSystemTools::OUTPUT_NONE);
359
360   if (exit_code != 0) {
361     fprintf(stderr, "llvm-nm returned an error: %s\n", output.c_str());
362     return false;
363   }
364
365   std::istringstream ss(output);
366   std::string line;
367   while (std::getline(ss, line)) {
368     if (line.empty()) { // last line
369       continue;
370     }
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",
374               line.c_str());
375       return false;
376     }
377     if (line.size() < sym_end + 1) {
378       fprintf(stderr, "Couldn't parse llvm-nm output line: %s\n",
379               line.c_str());
380       return false;
381     }
382     const char sym_type = line[sym_end + 1];
383     line.resize(sym_end);
384     switch (sym_type) {
385       case 'D':
386         dataSymbols.insert(line);
387         break;
388       case 'T':
389         symbols.insert(line);
390         break;
391     }
392   }
393
394   return true;
395 }
396
397 static bool DumpFile(std::string const& nmPath, const char* filename,
398                      std::set<std::string>& symbols,
399                      std::set<std::string>& dataSymbols)
400 {
401 #ifndef _WIN32
402   return DumpFileWithLlvmNm(nmPath, filename, symbols, dataSymbols);
403 #else
404   HANDLE hFile;
405   HANDLE hFileMapping;
406   LPVOID lpFileBase;
407
408   hFile = CreateFileW(cmsys::Encoding::ToWide(filename).c_str(), GENERIC_READ,
409                       FILE_SHARE_READ, NULL, OPEN_EXISTING,
410                       FILE_ATTRIBUTE_NORMAL, 0);
411
412   if (hFile == INVALID_HANDLE_VALUE) {
413     fprintf(stderr, "Couldn't open file '%s' with CreateFile()\n", filename);
414     return false;
415   }
416
417   hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
418   if (hFileMapping == 0) {
419     CloseHandle(hFile);
420     fprintf(stderr, "Couldn't open file mapping with CreateFileMapping()\n");
421     return false;
422   }
423
424   lpFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
425   if (lpFileBase == 0) {
426     CloseHandle(hFileMapping);
427     CloseHandle(hFile);
428     fprintf(stderr, "Couldn't map view of file with MapViewOfFile()\n");
429     return false;
430   }
431
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");
435     return false;
436   } else {
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)) {
446       /*
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;
451        */
452       DumpSymbols<IMAGE_FILE_HEADER, IMAGE_SYMBOL> symbolDumper(
453         (PIMAGE_FILE_HEADER)lpFileBase, symbols, dataSymbols,
454         (imageHeader->Machine == IMAGE_FILE_MACHINE_I386
455            ? Arch::I386
456            : (imageHeader->Machine == IMAGE_FILE_MACHINE_ARM64EC
457                 ? Arch::ARM64EC
458                 : Arch::Generic)));
459       symbolDumper.DumpObjFile();
460     } else {
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) {
465         // bigobj
466         DumpSymbols<cmANON_OBJECT_HEADER_BIGOBJ, cmIMAGE_SYMBOL_EX>
467           symbolDumper(
468             (cmANON_OBJECT_HEADER_BIGOBJ*)lpFileBase, symbols, dataSymbols,
469             (h->Machine == IMAGE_FILE_MACHINE_I386
470                ? Arch::I386
471                : (h->Machine == IMAGE_FILE_MACHINE_ARM64EC ? Arch::ARM64EC
472                                                            : Arch::Generic)));
473         symbolDumper.DumpObjFile();
474       } else if (
475         // BCexCODE - llvm bitcode
476         (h->Sig1 == 0x4342 && h->Sig2 == 0xDEC0) ||
477         // 0x0B17C0DE - llvm bitcode BC wrapper
478         (h->Sig1 == 0x0B17 && h->Sig2 == 0xC0DE)) {
479
480         return DumpFileWithLlvmNm(nmPath, filename, symbols, dataSymbols);
481
482       } else {
483         printf("unrecognized file format in '%s, %u'\n", filename,
484                imageHeader->Machine);
485         return false;
486       }
487     }
488   }
489   UnmapViewOfFile(lpFileBase);
490   CloseHandle(hFileMapping);
491   CloseHandle(hFile);
492   return true;
493 #endif
494 }
495
496 bool bindexplib::AddObjectFile(const char* filename)
497 {
498   return DumpFile(this->NmPath, filename, this->Symbols, this->DataSymbols);
499 }
500
501 bool bindexplib::AddDefinitionFile(const char* filename)
502 {
503   cmsys::ifstream infile(filename);
504   if (!infile) {
505     fprintf(stderr, "Couldn't open definition file '%s'\n", filename);
506     return false;
507   }
508   std::string str;
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)) {
513       continue;
514     }
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);
521     } else {
522       this->Symbols.insert(str);
523     }
524   }
525   infile.close();
526   return true;
527 }
528
529 void bindexplib::WriteFile(FILE* file)
530 {
531   fprintf(file, "EXPORTS \n");
532   for (std::string const& ds : this->DataSymbols) {
533     fprintf(file, "\t%s \t DATA\n", ds.c_str());
534   }
535   for (std::string const& s : this->Symbols) {
536     fprintf(file, "\t%s\n", s.c_str());
537   }
538 }
539
540 void bindexplib::SetNmPath(std::string const& nm)
541 {
542   this->NmPath = nm;
543 }