From cf15979b40af75e18b60d807d41a4e1338a3f1bc Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 26 May 2026 15:05:27 -0400 Subject: [PATCH 01/10] Makefile: quote prefix Allows installation into paths with spaces Signed-off-by: John Parent --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 38a71c8..975b8ad 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ cl.exe : $(SRCS) link $(LFLAGS) $** $(API_LIBS) /out:cl.exe install : cl.exe - @if not exist "$(PREFIX)" mkdir "$(PREFIX)"" + @if not exist "$(PREFIX)" mkdir "$(PREFIX)" @if not exist "$(PREFIX)\cl.exe" move cl.exe "$(PREFIX)" @if not exist "$(PREFIX)\link.exe" mklink "$(PREFIX)\link.exe" "$(PREFIX)\cl.exe" @if not exist "$(PREFIX)\ifx.exe" mklink "$(PREFIX)\ifx.exe" "$(PREFIX)\cl.exe" From 91b89fde7f79684d111cc81d40d2b5e00f04a2d6 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 1 Jun 2026 11:16:04 -0400 Subject: [PATCH 02/10] tools: allow help and argless invocation to invoke underlying tool help Signed-off-by: John Parent --- src/ld.cxx | 5 +++++ src/linker_invocation.cxx | 11 ++++++++++- src/linker_invocation.h | 2 ++ src/main.cxx | 9 +++++---- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/ld.cxx b/src/ld.cxx index c3fbc76..b6472ac 100644 --- a/src/ld.cxx +++ b/src/ld.cxx @@ -47,6 +47,11 @@ DWORD LdInvocation::InvokeToolchain() { } catch (const FileIOError& e) { return ExitConditions::FILE_IO_FAILURE; } + // If there are no arguments (or the argument is just "/?") + // just print help and return + if(this->inputs.empty() || link_run.isHelp()) { + return ToolChainInvocation::InvokeToolchain(); + } try { link_run.makeRsp(); diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx index e7b7b4f..07fe2e7 100644 --- a/src/linker_invocation.cxx +++ b/src/linker_invocation.cxx @@ -78,6 +78,9 @@ void LinkerInvocation::ProcessTokens(const std::string &normal_token, const std: this->piped_args_.end()) { this->piped_args_.at(normal_token).emplace_back(token); } + else if (normal_token == "?") { + this->is_help_ = true; + } } @@ -113,7 +116,9 @@ void LinkerInvocation::Parse() { // /NAME // if no /NAME // first input file (post rc expansion) - + if (this->is_help_ || this->input_files_.empty()) { + return; + } this->processDefFile(); std::string const ext = this->is_exe_ ? ".exe" : ".dll"; if (this->output_.empty()) { @@ -318,3 +323,7 @@ std::string LinkerInvocation::get_mangled_out() const { bool LinkerInvocation::IsExeLink() const { return this->is_exe_ || endswith(this->get_out(), ".exe"); } + +bool LinkerInvocation::isHelp() const { + return this->is_help_; +} diff --git a/src/linker_invocation.h b/src/linker_invocation.h index 2a41639..b0f6dba 100644 --- a/src/linker_invocation.h +++ b/src/linker_invocation.h @@ -24,6 +24,7 @@ class LinkerInvocation { StrList get_input_files() const; std::string get_lib_link_args() const; bool makeRsp(); + bool isHelp() const; private: void ProcessTokens(const std::string &normal_token, const std::string& token); @@ -40,6 +41,7 @@ class LinkerInvocation { StrList input_files_; StrList tokens_; bool is_exe_; + bool is_help_; std::map piped_args_ = { {"export", {}}, {"include", {}}, {"libpath", {}}, {"ltcg", {}}, {"machine", {}}, {"nodefaultlib", {}}, diff --git a/src/main.cxx b/src/main.cxx index 7fa7708..c29ab21 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -19,11 +19,12 @@ #include int main(int argc, const char* argv[]) { - - if (CheckAndPrintHelp(argv, argc)) { + const bool is_relocate = IsRelocate(argv[0]); + const bool is_report = IsReport(argv[0]); + if ((is_relocate || is_report) && CheckAndPrintHelp(argv, argc)) { return 0; } - if (IsRelocate(argv[0])) { + if (is_relocate) { std::map patch_args = ParseRelocate(argv + 1, argc - 1); if (patch_args.empty()) { @@ -90,7 +91,7 @@ int main(int argc, const char* argv[]) { std::cerr << "Library rename failed\n"; return ExitConditions::RENAME_FAILURE; } - } else if (IsReport(argv[0])) { + } else if (is_report) { std::map report_args = ParseReport(argc - 1, argv + 1); if (report_args.empty()) { From 8a84f27a6c90452816fff7fa78e96eeb0a923768 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 1 Jun 2026 11:24:52 -0400 Subject: [PATCH 03/10] preserve --help option Signed-off-by: John Parent --- src/commandline.cxx | 6 +++--- src/commandline.h | 2 +- src/main.cxx | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/commandline.cxx b/src/commandline.cxx index df2bff1..9d977e0 100644 --- a/src/commandline.cxx +++ b/src/commandline.cxx @@ -178,11 +178,11 @@ std::map ParseReport(int argc, const char** args) { return opts; } -bool CheckAndPrintHelp(const char** arg, int argc) { - if (argc < 2) { +bool CheckAndPrintHelp(const char** arg, bool no_args, bool is_report, bool is_relocate) { + if (no_args && (is_relocate || is_report)) { return print_help(); } - if (strcmp(arg[1], "--help") == 0 || strcmp(arg[1], "-h") == 0) { + if (!no_args && (strcmp(arg[1], "--help") == 0 || strcmp(arg[1], "-h") == 0)) { return print_help(); } return false; diff --git a/src/commandline.h b/src/commandline.h index 28c93e2..d0e1cd5 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -19,4 +19,4 @@ std::map ParseRelocate(const char** args, int argc); std::map ParseReport(int argc, const char** args); // Writes CLI help message to stdout -bool CheckAndPrintHelp(const char** arg, int argc); \ No newline at end of file +bool CheckAndPrintHelp(const char** arg, bool no_args, bool is_report, bool is_relocate); \ No newline at end of file diff --git a/src/main.cxx b/src/main.cxx index c29ab21..9b704be 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -21,7 +21,8 @@ int main(int argc, const char* argv[]) { const bool is_relocate = IsRelocate(argv[0]); const bool is_report = IsReport(argv[0]); - if ((is_relocate || is_report) && CheckAndPrintHelp(argv, argc)) { + const bool no_args = argc < 2; + if (CheckAndPrintHelp(argv, no_args, is_report, is_relocate)) { return 0; } if (is_relocate) { From 6aa8fe9bbf47d80559e496169114343abca95180 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 1 Jun 2026 20:02:14 -0400 Subject: [PATCH 04/10] Improve handling of mixed "\" "/" paths Signed-off-by: John Parent --- src/utils.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.cxx b/src/utils.cxx index 3d8ef6b..582d174 100644 --- a/src/utils.cxx +++ b/src/utils.cxx @@ -428,7 +428,7 @@ std::string stem(const std::string& file) { } std::string basename(const std::string& file) { - size_t const last_path = file.find_last_of('\\') + 1; + size_t const last_path = file.find_last_of("/\\") + 1; if (last_path == std::string::npos) { return file; } From 1612064bbbd89d8adb8d8398b5a0a3e80b6a2451 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 1 Jun 2026 20:16:08 -0400 Subject: [PATCH 05/10] wip Signed-off-by: John Parent --- src/coff_parser.cxx | 11 +++++++ src/coff_parser.h | 1 + src/winrpath.cxx | 71 ++++++--------------------------------------- src/winrpath.h | 3 -- 4 files changed, 21 insertions(+), 65 deletions(-) diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx index 4cee1f0..f171e7b 100644 --- a/src/coff_parser.cxx +++ b/src/coff_parser.cxx @@ -579,6 +579,17 @@ void CoffParser::ReportLongName(const char* data) { std::cout << "DLL: " << data << "\n"; } +std::vector CoffParser::GetExportNames() const { + std::vector names; + for (const auto& mem : this->coff_.members) { + if (mem.member->is_short && mem.member->short_member->short_name && + mem.member->short_member->short_name[0] != '\0') { + names.emplace_back(mem.member->short_member->short_name); + } + } + return names; +} + std::string CoffParser::GetLongName() const { // TODO(johnwparent): I think we can access the // 2nd index of the members vec to get the long diff --git a/src/coff_parser.h b/src/coff_parser.h index e485af0..dbb5b81 100644 --- a/src/coff_parser.h +++ b/src/coff_parser.h @@ -43,6 +43,7 @@ class CoffParser { bool NormalizeName(std::string& name); void Report(); int Verify(); + std::vector GetExportNames() const; std::string GetLongName() const; std::string GetShortName() const; std::string GetName() const; diff --git a/src/winrpath.cxx b/src/winrpath.cxx index 7953501..98768c2 100644 --- a/src/winrpath.cxx +++ b/src/winrpath.cxx @@ -17,14 +17,12 @@ #include "utils.h" #include -#include #include #include #include #include #include #include -#include /* * Checks a DLL name for path characters @@ -238,82 +236,31 @@ LibRename::LibRename(std::string p_exe, std::string coff, bool full, coff(std::move(coff)) { this->pe = MakePathAbsolute(this->pe); std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; this->def_file = coff_path + ".def"; - this->def_executor = - ExecuteCommand("dumpbin.exe", {this->ComputeDefLine()}); this->lib_executor = ExecuteCommand("lib.exe", {this->ComputeRenameLink()}); } /** - * Creates the line to be provided to dumpbin.exe to produce the exports of a given - * dll in the case where we do not have access to the original link line - * - * Produces something like `/EXPORTS ` - */ -std::string LibRename::ComputeDefLine() { - return "/NOLOGO /EXPORTS \"" + this->coff + "\""; -} - -/** - * Drives the process of running dumpbin.exe on a PE file to determine its exports - * and produce a `.def` file - * - * Returns the return code of the Def file computation operation + * Produces a `.def` file describing the exports of the import library by + * parsing the COFF archive directly, without invoking dumpbin.exe. */ bool LibRename::ComputeDefFile() { - this->def_executor.Execute(this->tmp_def_file); - DWORD const def_res = this->def_executor.Join(); - if (def_res) { - return false; - } - // Need to process the produced def file because it's wrong - // Open input file - std::ifstream input_file(this->tmp_def_file); - if (!input_file.is_open()) { - std::cerr << "Error: Could not open input file " << tmp_def_file - << '\n'; + CoffReaderWriter coff_reader(this->coff); + CoffParser coff_parser(&coff_reader); + if (!coff_parser.Parse()) { + std::cerr << "Error: Could not parse import library " << this->coff << "\n"; return false; } - - // Open output file std::ofstream output_file(this->def_file); if (!output_file.is_open()) { - std::cerr << "Error: Could not open output file " << this->def_file - << '\n'; + std::cerr << "Error: Could not open output file " << this->def_file << "\n"; return false; } - - // Write the standard .def file header - // You might want to get the DLL name dynamically from the input filename or dumpbin output output_file << "EXPORTS\n"; - - std::string line; - // Read until the output column titles - while (std::getline(input_file, line)) { - std::smatch search_res = regexSearch(line, R"(ordinal\s+name)"); - if (!search_res.empty()) break; - std::string const res = search_res.str(); - if (!res.empty()) { - break; - } - } - while (std::getline(input_file, line)) { - if (line.empty()) { - continue; - } - if (line.find("Summary") != - std::string:: - npos) { // Skip header in export block if still present - break; - } - output_file << " " - << regexMatch(line, R"(^.*?(\S+)(?:\s+\(.*\))?\s*$)").str(1) - << '\n'; + for (const auto& name : coff_parser.GetExportNames()) { + output_file << " " << name << "\n"; } - input_file.close(); output_file.close(); - std::remove(this->tmp_def_file.c_str()); return true; } diff --git a/src/winrpath.h b/src/winrpath.h index 16aeff6..19abd19 100644 --- a/src/winrpath.h +++ b/src/winrpath.h @@ -38,19 +38,16 @@ class LibRename { bool ExecutePERename(); bool ComputeDefFile(); std::string ComputeRenameLink(); - std::string ComputeDefLine(); private: static bool FindDllAndRename(HANDLE& pe_in); static bool SpackCheckForDll(const std::string& dll_path) ; static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; ExecuteCommand lib_executor; std::string pe; std::string coff; std::string new_lib; std::string def_file; - std::string tmp_def_file; bool full; bool replace; }; From 6b3152c309ea6cee8e4d3e558197b58750ca141d Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 1 Jun 2026 21:44:21 -0400 Subject: [PATCH 06/10] default init "is_help" Signed-off-by: John Parent --- src/linker_invocation.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linker_invocation.h b/src/linker_invocation.h index b0f6dba..7429351 100644 --- a/src/linker_invocation.h +++ b/src/linker_invocation.h @@ -40,8 +40,8 @@ class LinkerInvocation { StrList command_files_; StrList input_files_; StrList tokens_; - bool is_exe_; - bool is_help_; + bool is_exe_ = true; + bool is_help_ = false; std::map piped_args_ = { {"export", {}}, {"include", {}}, {"libpath", {}}, {"ltcg", {}}, {"machine", {}}, {"nodefaultlib", {}}, From 1fa40545bf9d012948ad36124da73325ff906afa Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 1 Jun 2026 21:44:29 -0400 Subject: [PATCH 07/10] replace dumpbin usage Signed-off-by: John Parent --- src/coff_parser.cxx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx index f171e7b..5d69b38 100644 --- a/src/coff_parser.cxx +++ b/src/coff_parser.cxx @@ -582,9 +582,12 @@ void CoffParser::ReportLongName(const char* data) { std::vector CoffParser::GetExportNames() const { std::vector names; for (const auto& mem : this->coff_.members) { - if (mem.member->is_short && mem.member->short_member->short_name && - mem.member->short_member->short_name[0] != '\0') { - names.emplace_back(mem.member->short_member->short_name); + if (!mem.member->is_short) continue; + const auto* short_mem = mem.member->short_member; + if (short_mem->im_h->NameType == IMPORT_OBJECT_ORDINAL) { + names.push_back("@" + std::to_string(short_mem->im_h->Ordinal) + " NONAME"); + } else if (short_mem->short_name && short_mem->short_name[0] != '\0') { + names.emplace_back(short_mem->short_name); } } return names; From 7b41382bcb5f8782344a319d95eaf29d2758a904 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 2 Jun 2026 15:03:12 -0400 Subject: [PATCH 08/10] revert dumpbin removal Signed-off-by: John Parent --- src/coff_parser.cxx | 14 ----------- src/coff_parser.h | 1 - src/winrpath.cxx | 60 ++++++++++++++++++++++++++++++++++++++------- src/winrpath.h | 3 +++ 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx index 5d69b38..4cee1f0 100644 --- a/src/coff_parser.cxx +++ b/src/coff_parser.cxx @@ -579,20 +579,6 @@ void CoffParser::ReportLongName(const char* data) { std::cout << "DLL: " << data << "\n"; } -std::vector CoffParser::GetExportNames() const { - std::vector names; - for (const auto& mem : this->coff_.members) { - if (!mem.member->is_short) continue; - const auto* short_mem = mem.member->short_member; - if (short_mem->im_h->NameType == IMPORT_OBJECT_ORDINAL) { - names.push_back("@" + std::to_string(short_mem->im_h->Ordinal) + " NONAME"); - } else if (short_mem->short_name && short_mem->short_name[0] != '\0') { - names.emplace_back(short_mem->short_name); - } - } - return names; -} - std::string CoffParser::GetLongName() const { // TODO(johnwparent): I think we can access the // 2nd index of the members vec to get the long diff --git a/src/coff_parser.h b/src/coff_parser.h index dbb5b81..e485af0 100644 --- a/src/coff_parser.h +++ b/src/coff_parser.h @@ -43,7 +43,6 @@ class CoffParser { bool NormalizeName(std::string& name); void Report(); int Verify(); - std::vector GetExportNames() const; std::string GetLongName() const; std::string GetShortName() const; std::string GetName() const; diff --git a/src/winrpath.cxx b/src/winrpath.cxx index 98768c2..c8e577a 100644 --- a/src/winrpath.cxx +++ b/src/winrpath.cxx @@ -17,12 +17,14 @@ #include "utils.h" #include +#include #include #include #include #include #include #include +#include /* * Checks a DLL name for path characters @@ -236,31 +238,71 @@ LibRename::LibRename(std::string p_exe, std::string coff, bool full, coff(std::move(coff)) { this->pe = MakePathAbsolute(this->pe); std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; this->def_file = coff_path + ".def"; + this->def_executor = + ExecuteCommand("dumpbin.exe", {this->ComputeDefLine()}); this->lib_executor = ExecuteCommand("lib.exe", {this->ComputeRenameLink()}); } /** - * Produces a `.def` file describing the exports of the import library by - * parsing the COFF archive directly, without invoking dumpbin.exe. + * Creates the line to be provided to dumpbin.exe to produce the exports of a given + * dll in the case where we do not have access to the original link line + * + * Produces something like `/EXPORTS ` + */ +std::string LibRename::ComputeDefLine() { + return "/NOLOGO /EXPORTS \"" + this->coff + "\""; +} + +/** + * Drives the process of running dumpbin.exe on a PE file to determine its exports + * and produce a `.def` file + * + * Returns the return code of the Def file computation operation */ bool LibRename::ComputeDefFile() { - CoffReaderWriter coff_reader(this->coff); - CoffParser coff_parser(&coff_reader); - if (!coff_parser.Parse()) { - std::cerr << "Error: Could not parse import library " << this->coff << "\n"; + this->def_executor.Execute(this->tmp_def_file); + DWORD const def_res = this->def_executor.Join(); + if (def_res) { + return false; + } + std::ifstream input_file(this->tmp_def_file); + if (!input_file.is_open()) { + std::cerr << "Error: Could not open input file " << tmp_def_file + << '\n'; return false; } std::ofstream output_file(this->def_file); if (!output_file.is_open()) { - std::cerr << "Error: Could not open output file " << this->def_file << "\n"; + std::cerr << "Error: Could not open output file " << this->def_file + << '\n'; return false; } output_file << "EXPORTS\n"; - for (const auto& name : coff_parser.GetExportNames()) { - output_file << " " << name << "\n"; + std::string line; + while (std::getline(input_file, line)) { + std::smatch search_res = regexSearch(line, R"(ordinal\s+name)"); + if (!search_res.empty()) break; + std::string const res = search_res.str(); + if (!res.empty()) { + break; + } + } + while (std::getline(input_file, line)) { + if (line.empty()) { + continue; + } + if (line.find("Summary") != std::string::npos) { + break; + } + output_file << " " + << regexMatch(line, R"(^.*?(\S+)(?:\s+\(.*\))?\s*$)").str(1) + << '\n'; } + input_file.close(); output_file.close(); + std::remove(this->tmp_def_file.c_str()); return true; } diff --git a/src/winrpath.h b/src/winrpath.h index 19abd19..16aeff6 100644 --- a/src/winrpath.h +++ b/src/winrpath.h @@ -38,16 +38,19 @@ class LibRename { bool ExecutePERename(); bool ComputeDefFile(); std::string ComputeRenameLink(); + std::string ComputeDefLine(); private: static bool FindDllAndRename(HANDLE& pe_in); static bool SpackCheckForDll(const std::string& dll_path) ; static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; ExecuteCommand lib_executor; std::string pe; std::string coff; std::string new_lib; std::string def_file; + std::string tmp_def_file; bool full; bool replace; }; From 9d3b269b9822fbf184e7ba0193a14d9b1d6e9fcc Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 2 Jun 2026 21:29:51 -0400 Subject: [PATCH 09/10] Allow for hint rva fields in dumpbin output Signed-off-by: John Parent --- src/winrpath.cxx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/winrpath.cxx b/src/winrpath.cxx index c8e577a..8128265 100644 --- a/src/winrpath.cxx +++ b/src/winrpath.cxx @@ -282,12 +282,7 @@ bool LibRename::ComputeDefFile() { output_file << "EXPORTS\n"; std::string line; while (std::getline(input_file, line)) { - std::smatch search_res = regexSearch(line, R"(ordinal\s+name)"); - if (!search_res.empty()) break; - std::string const res = search_res.str(); - if (!res.empty()) { - break; - } + if (!regexSearch(line, R"(ordinal\s+(?:hint\s+RVA\s+)?name)").empty()) break; } while (std::getline(input_file, line)) { if (line.empty()) { From 51358dd5c37a77b9a5816b6f9c8e3e4f6e07fb78 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 3 Jun 2026 10:58:17 -0400 Subject: [PATCH 10/10] Yet another missing "access" Signed-off-by: John Parent --- src/coff_reader_writer.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx index 6e34736..abddb60 100644 --- a/src/coff_reader_writer.cxx +++ b/src/coff_reader_writer.cxx @@ -30,7 +30,8 @@ bool CoffReaderWriter::Open() { return false; } try { - ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); + ScopedFileAccess obtain_write(coff_file, GENERIC_ALL); + obtain_write.Access(); this->pe_stream_.open(this->file_, std::ios::in | std::ios::out | std::ios::binary); return this->pe_stream_.is_open();