From a887fda85e5fb4441cb19dca271e97439c042e43 Mon Sep 17 00:00:00 2001 From: Chuck Atkins Date: Wed, 5 Aug 2026 10:57:23 -0400 Subject: [PATCH] WINDOWS_EXPORT_ALL_SYMBOLS: Improve performance with Clang toolsets - Separate definition files from object files while reading the generated input list. - Add batch bindexplib interfaces. - Preserve native Windows COFF parsing for ordinary objects. - Collect objects requiring nm and pass them to one nm process through a response file. On non-Windows hosts, pass every object to nm. - Request --print-file-name so single- and multi-object output use the same format. - Recognize text, data, and MSVC vftable symbols; ignore known non-exported types and warn about unknown types. - Capture nm stderr separately so diagnostics cannot corrupt stdout symbol records. Closes: #27995 --- Source/bindexplib.cxx | 247 ++++++++++++++++++++++++++++++++++-------- Source/bindexplib.h | 13 ++- Source/cmcmd.cxx | 34 +++--- 3 files changed, 227 insertions(+), 67 deletions(-) diff --git a/Source/bindexplib.cxx b/Source/bindexplib.cxx index 4535e27943..4d5e86a63f 100644 --- a/Source/bindexplib.cxx +++ b/Source/bindexplib.cxx @@ -64,7 +64,9 @@ */ #include "bindexplib.h" +#include #include // IWYU pragma: keep +#include #include #include @@ -77,6 +79,9 @@ #include "cmsys/FStream.hxx" +#include "cmGeneratedFileStream.h" +#include "cmOutputConverter.h" +#include "cmStringAlgorithms.h" #include "cmSystemTools.h" #ifdef _WIN32 @@ -356,27 +361,57 @@ private: }; #endif -static bool DumpFileWithLlvmNm(std::string const& nmPath, char const* filename, - std::set& symbols, - std::set& dataSymbols) +static bool CreateResponseFile(std::vector const& arguments, + std::string const& responseFile) { + cmGeneratedFileStream responseStream; + responseStream.Open(responseFile, true, true); + if (!responseStream) { + std::cerr << "Could not open response file '" << responseFile << "'.\n"; + return false; + } + + int responseFlags = cmOutputConverter::Shell_Flag_IsResponse; +#if !defined(_WIN32) || defined(__CYGWIN__) + responseFlags |= cmOutputConverter::Shell_Flag_IsUnix; +#endif + for (auto const& argument : arguments) { + responseStream << cmOutputConverter::EscapeForShell(argument, + responseFlags) + << '\n'; + } + if (!responseStream.Close()) { + std::cerr << "Could not write response file '" << responseFile << "'.\n"; + return false; + } + + return true; +} + +static bool DumpFileWithNm(std::string const& nmPath, + std::string const& filename, + std::set& symbols, + std::set& dataSymbols) +{ + std::string const nmName = cmSystemTools::GetFilenameName(nmPath); std::string output; + std::string error; // break up command line into a vector std::vector command; command.push_back(nmPath); command.emplace_back("--no-weak"); command.emplace_back("--defined-only"); command.emplace_back("--format=posix"); - command.emplace_back(filename); + command.emplace_back("--print-file-name"); + command.push_back(filename); // run the command int exit_code = 0; - bool const commandResult = - cmSystemTools::RunSingleCommand(command, &output, &output, &exit_code, - nullptr, cmSystemTools::OUTPUT_NONE); + bool const commandResult = cmSystemTools::RunSingleCommand( + command, &output, &error, &exit_code, nullptr, cmSystemTools::OUTPUT_NONE); if (!commandResult || exit_code != 0) { - fprintf(stderr, "llvm-nm returned an error: %s\n", output.c_str()); + std::cerr << nmName << " returned an error: " << output << error << '\n'; return false; } @@ -386,39 +421,107 @@ static bool DumpFileWithLlvmNm(std::string const& nmPath, char const* filename, if (line.empty()) { // last line continue; } - size_t sym_end = line.find(' '); - if (sym_end == std::string::npos) { - fprintf(stderr, "Couldn't parse llvm-nm output line: %s\n", - line.c_str()); + size_t const filename_end = line.find(": "); + if (filename_end == std::string::npos) { + std::cerr << "Couldn't parse " << nmName << " output line: " << line + << '\n'; return false; } - if (line.size() < sym_end) { - fprintf(stderr, "Couldn't parse llvm-nm output line: %s\n", - line.c_str()); + size_t const sym_start = filename_end + 2; + size_t const sym_end = line.find(' ', sym_start); + if (sym_end == std::string::npos) { + std::cerr << "Couldn't parse " << nmName << " output line: " << line + << '\n'; + return false; + } + if (line.size() <= sym_end + 1) { + std::cerr << "Couldn't parse " << nmName << " output line: " << line + << '\n'; return false; } char const sym_type = line[sym_end + 1]; - line.resize(sym_end); + std::string const symbol = line.substr(sym_start, sym_end - sym_start); switch (sym_type) { + case 'B': + case 'C': case 'D': - dataSymbols.insert(line); - break; + dataSymbols.insert(symbol); + continue; case 'T': - symbols.insert(line); - break; + symbols.insert(symbol); + continue; + case 'R': + if (cmHasLiteralPrefix(symbol, "??_7")) { + dataSymbols.insert(symbol); + } + continue; + case 'A': + case 'I': + case 'N': + case 'S': + case 'U': + case 'V': + case 'W': + case 'a': + case 'b': + case 'c': + case 'd': + case 'i': + case 'n': + case 'r': + case 's': + case 't': + case 'v': + case 'w': + continue; + default: + std::cerr << "Ignoring symbol '" << symbol + << "' with unrecognized type '" << sym_type + << "' in object '" << line.substr(0, filename_end) << "'.\n"; } } return true; } -static bool DumpFile(std::string const& nmPath, char const* filename, - std::set& symbols, - std::set& dataSymbols) +static bool DumpFileWithNm(std::string const& nmPath, + std::vector const& objectFiles, + std::string const& responseFile, + std::set& symbols, + std::set& dataSymbols) +{ + if (!CreateResponseFile(objectFiles, responseFile)) { + return false; + } + std::string const rspFile = '@' + responseFile; + bool result = DumpFileWithNm(nmPath, rspFile, symbols, dataSymbols); + cmSystemTools::RemoveFile(responseFile); + return result; +} + +enum class DumpFileResult +{ + Success, + Failed, + LLVMBitcodeDetected, + NotImplemented, +}; + +static DumpFileResult DumpFile(std::string const& filename, + std::set& symbols, + std::set& dataSymbols) { #ifndef _WIN32 - return DumpFileWithLlvmNm(nmPath, filename, symbols, dataSymbols); + static_cast(filename); + static_cast(symbols); + static_cast(dataSymbols); + return DumpFileResult::NotImplemented; #else + if (filename.empty()) { + fprintf(stderr, "No object file was specified.\n"); + return DumpFileResult::Failed; + } + HANDLE hFile; HANDLE hFileMapping; LPVOID lpFileBase; @@ -428,8 +531,9 @@ static bool DumpFile(std::string const& nmPath, char const* filename, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) { - fprintf(stderr, "Couldn't open file '%s' with CreateFile()\n", filename); - return false; + fprintf(stderr, "Couldn't open file '%s' with CreateFile()\n", + filename.c_str()); + return DumpFileResult::Failed; } hFileMapping = @@ -437,7 +541,7 @@ static bool DumpFile(std::string const& nmPath, char const* filename, if (hFileMapping == 0) { CloseHandle(hFile); fprintf(stderr, "Couldn't open file mapping with CreateFileMapping()\n"); - return false; + return DumpFileResult::Failed; } lpFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0); @@ -445,13 +549,14 @@ static bool DumpFile(std::string const& nmPath, char const* filename, CloseHandle(hFileMapping); CloseHandle(hFile); fprintf(stderr, "Couldn't map view of file with MapViewOfFile()\n"); - return false; + return DumpFileResult::Failed; } + DumpFileResult result = DumpFileResult::Success; const PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)lpFileBase; if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) { fprintf(stderr, "File is an executable. I don't dump those.\n"); - return false; + result = DumpFileResult::Failed; } else { const PIMAGE_FILE_HEADER imageHeader = (PIMAGE_FILE_HEADER)lpFileBase; /* Does it look like a COFF OBJ file??? */ @@ -495,33 +600,72 @@ static bool DumpFile(std::string const& nmPath, char const* filename, (h->Sig1 == 0x4342 && h->Sig2 == 0xDEC0) || // 0x0B17C0DE - llvm bitcode BC wrapper (h->Sig1 == 0x0B17 && h->Sig2 == 0xC0DE)) { - - return DumpFileWithLlvmNm(nmPath, filename, symbols, dataSymbols); - + result = DumpFileResult::LLVMBitcodeDetected; } else { - printf("unrecognized file format in '%s, %u'\n", filename, + printf("unrecognized file format in '%s, %u'\n", filename.c_str(), imageHeader->Machine); - return false; + result = DumpFileResult::Failed; } } } UnmapViewOfFile(lpFileBase); CloseHandle(hFileMapping); CloseHandle(hFile); - return true; + return result; #endif } -bool bindexplib::AddObjectFile(char const* filename) +bool bindexplib::AddObjectFile(std::string const& objectFile) { - return DumpFile(this->NmPath, filename, this->Symbols, this->DataSymbols); + auto const result = DumpFile(objectFile, this->Symbols, this->DataSymbols); + if (result == DumpFileResult::Success) { + return true; + } + if (result == DumpFileResult::Failed) { + return false; + } + if (this->NmPath.empty()) { + std::cerr << "No nm tool was specified for object file '" << objectFile + << "'.\n"; + return false; + } + return DumpFileWithNm(this->NmPath, objectFile, this->Symbols, + this->DataSymbols); } -bool bindexplib::AddDefinitionFile(char const* filename) +bool bindexplib::AddObjectFile(std::vector const& objectFiles, + std::string const& responseFile) { - cmsys::ifstream infile(filename); + std::vector objectFilesTryNm; + for (auto const& objectFile : objectFiles) { + auto const result = DumpFile(objectFile, this->Symbols, this->DataSymbols); + if (result == DumpFileResult::Success) { + continue; + } + if (result == DumpFileResult::Failed) { + return false; + } + objectFilesTryNm.push_back(objectFile); + } + + if (objectFilesTryNm.empty()) { + return true; + } + if (this->NmPath.empty()) { + std::cerr << "No nm tool was specified for object file '" + << objectFilesTryNm.front() << "'.\n"; + return false; + } + + return DumpFileWithNm(this->NmPath, objectFilesTryNm, responseFile, + this->Symbols, this->DataSymbols); +} + +bool bindexplib::AddDefinitionFile(std::string const& definitionFile) +{ + cmsys::ifstream infile(definitionFile.c_str()); if (!infile) { - fprintf(stderr, "Couldn't open definition file '%s'\n", filename); + std::cerr << "Couldn't open definition file '" << definitionFile << "'\n"; return false; } std::string str; @@ -545,15 +689,26 @@ bool bindexplib::AddDefinitionFile(char const* filename) return true; } -void bindexplib::WriteFile(FILE* file) +bool bindexplib::AddDefinitionFile( + std::vector const& definitionFiles) { - fprintf(file, "EXPORTS \n"); - for (std::string const& ds : this->DataSymbols) { - fprintf(file, "\t%s \t DATA\n", ds.c_str()); + return std::all_of(definitionFiles.cbegin(), definitionFiles.cend(), + [this](std::string const& definitionFile) { + return this->AddDefinitionFile(definitionFile); + }); +} + +bool bindexplib::WriteFile(std::ostream& output) +{ + output << "EXPORTS \n"; + for (auto const& ds : this->DataSymbols) { + output << '\t' << ds << " \t DATA\n"; } - for (std::string const& s : this->Symbols) { - fprintf(file, "\t%s\n", s.c_str()); + for (auto const& s : this->Symbols) { + output << '\t' << s << '\n'; } + output.flush(); + return output.good(); } void bindexplib::SetNmPath(std::string const& nm) diff --git a/Source/bindexplib.h b/Source/bindexplib.h index f98ba0199f..b42f28f977 100644 --- a/Source/bindexplib.h +++ b/Source/bindexplib.h @@ -4,17 +4,20 @@ #include "cmConfigure.h" // IWYU pragma: keep -#include +#include #include #include +#include class bindexplib { public: - bindexplib() { NmPath = "nm"; } - bool AddDefinitionFile(char const* filename); - bool AddObjectFile(char const* filename); - void WriteFile(FILE* file); + bool AddDefinitionFile(std::string const& definitionFile); + bool AddDefinitionFile(std::vector const& definitionFiles); + bool AddObjectFile(std::string const& objectFile); + bool AddObjectFile(std::vector const& objectFiles, + std::string const& responseFile = "exports.def.objs.rsp"); + bool WriteFile(std::ostream& output); void SetNmPath(std::string const& nm); diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index 01be8a1b2c..517767acc7 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -1201,13 +1201,20 @@ int cmcmd::ExecuteCMakeCommand(std::vector const& args, std::cerr << "could not open object list file: " << args[3] << '\n'; return 1; } - std::vector files; + std::vector defFiles; + std::vector objFiles; { std::string file; cmFileTime outTime; bool outValid = outTime.Load(args[2]); while (cmSystemTools::GetLineFromStream(fin, file)) { - files.push_back(file); + std::string const& ext = + cmSystemTools::GetFilenameLastExtension(file); + if (cmSystemTools::LowerCase(ext) == ".def") { + defFiles.push_back(file); + } else { + objFiles.push_back(file); + } if (outValid) { cmFileTime inTime; outValid = inTime.Load(file) && inTime.Older(outTime); @@ -1219,7 +1226,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector const& args, return 0; } } - FILE* fout = cmsys::SystemTools::Fopen(args[2], "w+"); + fin.close(); + cmsys::ofstream fout(args[2].c_str()); if (!fout) { std::cerr << "could not open output .def file: " << args[2] << '\n'; return 1; @@ -1233,20 +1241,14 @@ int cmcmd::ExecuteCMakeCommand(std::vector const& args, std::cerr << "unknown argument: " << a << '\n'; } } - for (std::string const& file : files) { - std::string const& ext = cmSystemTools::GetFilenameLastExtension(file); - if (cmSystemTools::LowerCase(ext) == ".def") { - if (!deffile.AddDefinitionFile(file.c_str())) { - return 1; - } - } else { - if (!deffile.AddObjectFile(file.c_str())) { - return 1; - } - } + if (!deffile.AddDefinitionFile(defFiles) || + !deffile.AddObjectFile(objFiles, cmStrCat(args[3], ".rsp"))) { + return 1; + } + if (!deffile.WriteFile(fout)) { + std::cerr << "could not write output .def file: " << args[2] << '\n'; + return 1; } - deffile.WriteFile(fout); - fclose(fout); return 0; } #endif