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
This commit is contained in:
Chuck Atkins
2026-08-05 10:58:20 -04:00
parent 6d6e58a15e
commit a887fda85e
3 changed files with 227 additions and 67 deletions
+201 -46
View File
@@ -64,7 +64,9 @@
*/
#include "bindexplib.h"
#include <algorithm>
#include <cstddef> // IWYU pragma: keep
#include <iostream>
#include <sstream>
#include <vector>
@@ -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<std::string>& symbols,
std::set<std::string>& dataSymbols)
static bool CreateResponseFile(std::vector<std::string> 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<std::string>& symbols,
std::set<std::string>& dataSymbols)
{
std::string const nmName = cmSystemTools::GetFilenameName(nmPath);
std::string output;
std::string error;
// break up command line into a vector
std::vector<std::string> 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<std::string>& symbols,
std::set<std::string>& dataSymbols)
static bool DumpFileWithNm(std::string const& nmPath,
std::vector<std::string> const& objectFiles,
std::string const& responseFile,
std::set<std::string>& symbols,
std::set<std::string>& 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<std::string>& symbols,
std::set<std::string>& dataSymbols)
{
#ifndef _WIN32
return DumpFileWithLlvmNm(nmPath, filename, symbols, dataSymbols);
static_cast<void>(filename);
static_cast<void>(symbols);
static_cast<void>(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<std::string> const& objectFiles,
std::string const& responseFile)
{
cmsys::ifstream infile(filename);
std::vector<std::string> 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<std::string> 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)
+8 -5
View File
@@ -4,17 +4,20 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include <cstdio>
#include <iosfwd>
#include <set>
#include <string>
#include <vector>
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<std::string> const& definitionFiles);
bool AddObjectFile(std::string const& objectFile);
bool AddObjectFile(std::vector<std::string> const& objectFiles,
std::string const& responseFile = "exports.def.objs.rsp");
bool WriteFile(std::ostream& output);
void SetNmPath(std::string const& nm);
+18 -16
View File
@@ -1201,13 +1201,20 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
std::cerr << "could not open object list file: " << args[3] << '\n';
return 1;
}
std::vector<std::string> files;
std::vector<std::string> defFiles;
std::vector<std::string> 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<std::string> 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<std::string> 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