Files
UnrealEngine/Engine/Source/Programs/UnrealBuildAccelerator/ObjTool/Private/UbaObjTool.cpp
2025-05-18 13:04:45 +08:00

373 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UbaObjectFile.h"
#include "UbaDirectoryIterator.h"
#include "UbaFileAccessor.h"
#include "UbaImportLibWriter.h"
#include "UbaPathUtils.h"
#include "UbaVersion.h"
#include "UbaWorkManager.h"
namespace uba
{
const tchar* Version = GetVersionString();
u32 DefaultProcessorCount = []() { return GetLogicalProcessorCount(); }();
int PrintHelp(const tchar* message)
{
LoggerWithWriter logger(g_consoleLogWriter, TC(""));
if (*message)
{
logger.Info(TC(""));
logger.Error(TC("%s"), message);
}
const tchar* dbgStr = TC("");
#if UBA_DEBUG
dbgStr = TC(" (DEBUG)");
#endif
logger.Info(TC(""));
logger.Info(TC("-------------------------------------------"));
logger.Info(TC(" UbaObjTool v%s%s"), Version, dbgStr);
logger.Info(TC("-------------------------------------------"));
logger.Info(TC(""));
logger.Info(TC(" UbaObjTool.exe [options...] <objfile/libfile>"));
logger.Info(TC(""));
logger.Info(TC(" Options:"));
logger.Info(TC(" -printsymbols Print the symbols found in obj file"));
logger.Info(TC(" -stripexports Will strip exports and write them out in a .exp file"));
logger.Info(TC(" -writeimplib=<file> Will create a import library from symbols collected from obj/lib files"));
logger.Info(TC(""));
logger.Info(TC(" --- OR ---"));
logger.Info(TC(""));
logger.Info(TC(" UbaObjTool.exe @<rspfile>"));
logger.Info(TC(""));
logger.Info(TC(" Response file options:"));
logger.Info(TC(" /S:<objfile> Obj file to export from. Multiple allowed"));
logger.Info(TC(" /D:<objfile> Obj file depending on obj files to strip. Multiple allowed"));
logger.Info(TC(" /O:<objfile> Obj file to output containing exports and loopbacks"));
logger.Info(TC(" /T:<platform> Target platform"));
logger.Info(TC(" /M:<module> Name of module. Needed in emd files"));
logger.Info(TC(" /E:<symbol> Additional symbol to be exported. To solve combination of dynlist files"));
logger.Info(TC(" /COMPRESS Write '/O' file compressed"));
logger.Info(TC(""));
return -1;
}
int WrappedMain(int argc, tchar* argv[])
{
using namespace uba;
TString objFile;
bool printSymbols = false;
bool writeImpLib = false;
bool allowLibInputs = false;
bool isImpLibRsp = false;
Vector<TString> objFilesToExport;
Vector<TString> objFilesDependencies;
TString extraObjFile;
Vector<TString> objFilesForImpLib;
ExtraExports extraExports;
std::string impLibName;
TString impLibFile;
TString platform;
TString moduleName;
auto parseArg = [&](const tchar* arg, bool isRsp)
{
StringBuffer<> name;
StringBuffer<> value;
if (const tchar* equals = TStrchr(arg,'='))
{
name.Append(arg, equals - arg);
value.Append(equals+1);
}
else
{
const tchar* colon = TStrchr(arg,':');
if (colon && colon[1] != '\\' && colon[1] != '/')
{
name.Append(arg, colon - arg);
const tchar* valueStart = colon+1;
if (*valueStart == '\"')
++valueStart;
value.Append(valueStart);
if (value.data[value.count-1] == '\"')
value.Resize(value.count-1);
}
else
{
name.Append(arg);
}
}
if (isImpLibRsp)
{
if (name.Equals(TCV("/NOLOGO")))
{
}
else if (name.Equals(TCV("/errorReport")))
{
}
else if (name.Equals(TCV("/MACHINE")))
{
//if (!value.Equals(TCV("x64")))
//{
// logger.Error(TC("only x64 supported"));
// return 0;
//}
}
else if (name.Equals(TCV("/SUBSYSTEM")))
{
}
else if (name.Equals(TCV("/DEF")))
{
writeImpLib = true;
}
else if (name.Equals(TCV("/NAME")))
{
char buffer[256];
value.Parse(buffer, sizeof(buffer));
impLibName = buffer;
}
else if (name.Equals(TCV("/OUT")))
{
impLibFile = value.data;
}
else if (name.Equals(TCV("/IGNORE")))
{
}
else if (name.Equals(TCV("/NODEFAULTLIB")))
{
}
else if (name.Equals(TCV("/LTCG")))
{
}
else if (name.StartsWith(TC("/OPT:")))
{
}
else
{
objFilesForImpLib.push_back(name.data);
}
}
else if (name.StartsWith(TC("/D")))
{
objFilesDependencies.push_back(value.data);
}
else if (name.StartsWith(TC("/S")))
{
objFilesToExport.push_back(value.data);
}
else if (name.StartsWith(TC("/O")))
{
extraObjFile = value.data;
}
else if (name.StartsWith(TC("/T")))
{
platform = value.data;
}
else if (name.StartsWith(TC("/M")))
{
moduleName = value.data;
}
else if (name.StartsWith(TC("/E")))
{
char buffer[512];
if (!value.Parse(buffer, sizeof(buffer)))
return PrintHelp(TC("Bad symbol name"));
extraExports.push_back(buffer);
}
else if (name.Equals(TCV("-printsymbols")))
{
printSymbols = true;
}
else if (name.Equals(TCV("-writeimplib")))
{
impLibFile = value.data;
writeImpLib = true;
allowLibInputs = true;
}
else if (name.Equals(TCV("/LIB")))
{
isImpLibRsp = true;
writeImpLib = true;
}
else if (name.Equals(TCV("-?")))
{
return PrintHelp(TC(""));
}
else if (objFile.empty() && name[0] != '-' && name[0] != '/')
{
objFile = name.data;
return 0;
}
else
{
StringBuffer<> msg;
msg.Appendf(TC("Unknown argument '%s'"), name.data);
return PrintHelp(msg.data);
}
return 0;
};
for (int i=1; i!=argc; ++i)
{
const tchar* arg = argv[i];
if (*arg == '@')
{
++arg;
StringBuffer<> temp;
if (*arg == '\"')
{
temp.Append(arg + 1);
temp.Resize(temp.count - 1);
arg = temp.data;
}
int res = 0;
LoggerWithWriter logger(g_consoleLogWriter, TC(""));
if (!ReadLines(logger, arg, [&](const TString& line)
{
res = parseArg(line.c_str(), true);
return res == 0;
}))
return -1;
if (res != 0)
return res;
continue;
}
int res = parseArg(arg, false);
if (res != 0)
return res;
}
FilteredLogWriter logWriter(g_consoleLogWriter, LogEntryType_Info);
LoggerWithWriter logger(logWriter, TC("UbaObjTool"));
if (!objFilesToExport.empty())
{
CriticalSection cs;
Atomic<bool> success = true;
AllExternalImports& allExternalImports = *new AllExternalImports(16*1024); // Imports needed from the outside of the stripped obj files. Let it leak to speedup shutdown
u32 workerCount = DefaultProcessorCount-1;
WorkManagerImpl workManager(workerCount, TC("UbaWrk/StrpObj"));
workManager.ParallelFor(workerCount, objFilesDependencies, [&](const WorkContext&, auto& it)
{
const TString& exiFilename = *it;
SymbolFile symbolFile;
if (!symbolFile.ParseFile(logger, exiFilename.c_str()))
{
success = false;
return;
}
SCOPED_CRITICAL_SECTION(cs, _);
allExternalImports.insert(symbolFile.imports.begin(), symbolFile.imports.end());
}, TCV("ObjFilesDeps"));
if (!success)
return -1;
AllInternalImports& allInternalImports = *new AllInternalImports(512*1024); // Imports that the obj files has. Note these could be existing in the obj files and then we might need to create loopbacks
AllExports& allExports = *new AllExports(12*1024*1024); // Exports from all the obj files.
Map<TString, ObjectFile*> objectFiles;
auto g = MakeGuard([&]() { for (auto& kv : objectFiles) delete kv.second; });
workManager.ParallelFor(workerCount, objFilesToExport, [&](const WorkContext&, auto& it)
{
const TString& exiFilename = *it;
SymbolFile symbolFile;
if (!symbolFile.ParseFile(logger, exiFilename.c_str()))
{
success = false;
return;
}
SCOPED_CRITICAL_SECTION(cs, _);
allInternalImports.insert(symbolFile.imports.begin(), symbolFile.imports.end());
allExports.insert(symbolFile.exports.begin(), symbolFile.exports.end());
}, TCV("ObjFilesToStrip"));
if (!success)
return -1;
if (!extraObjFile.empty())
if (!ObjectFile::CreateExtraFile(logger, extraObjFile, moduleName, platform, allExternalImports, allInternalImports, allExports, extraExports, true))
return -1;
//logger.Info(TC("Reduced export count from %llu to %llu"), totalExportCount.load(), totalKeptExportCount.size());
}
else if (writeImpLib)
{
StringBuffer<> currentDir;
GetCurrentDirectoryW(currentDir);
currentDir.EnsureEndsWithSlash();
ImportLibWriter writer;
if (objFilesForImpLib.empty() && !objFile.empty())
objFilesForImpLib.push_back(objFile);
Atomic<bool> success = true;
Vector<ObjectFile*> objFiles;
objFiles.resize(objFilesForImpLib.size());
u32 workerCount = DefaultProcessorCount - 1;
WorkManagerImpl workManager(workerCount, TC("UbaWrk/Load"));
workManager.ParallelFor(workerCount, objFilesForImpLib, [&](const WorkContext&, auto& it)
{
auto& o = *it;
StringBuffer<> fixedPath;
FixPath(o.c_str(), currentDir.data, currentDir.count, fixedPath);
if (fixedPath.EndsWith(TCV(".res")) || (fixedPath.EndsWith(TCV(".lib")) && !allowLibInputs))
return;
if (ObjectFile* objectFile = ObjectFile::OpenAndParse(logger, ObjectFileParseMode_Exports, fixedPath.data))
{
//objectFile->RemoveExportedSymbol("DllMain"); // This is used to patch xinput.lib
objFiles[it - objFilesForImpLib.begin()] = objectFile;
}
else
success = false;
}, TCV("OpenAndParse"));
if (!success)
return -1;
if (impLibName.empty() && objFiles.size() == 1)
impLibName = objFiles[0]->GetLibName();
writer.Write(logger, objFiles, impLibName.c_str(), impLibFile.c_str());
}
else
{
if (objFile.empty())
return PrintHelp(TC("No obj, lib or rsp file provided"));
ObjectFile* objectFile = ObjectFile::OpenAndParse(logger, ObjectFileParseMode_All, objFile.c_str());
if (!objectFile)
return -1;
auto g = MakeGuard([&](){ delete objectFile; });
if (printSymbols)
{
for (auto& symbol : objectFile->GetImports())
logger.Info(TC("I %S"), symbol.c_str());
for (auto& kv : objectFile->GetExports())
logger.Info(TC("E %S%S"), kv.second.symbol.c_str(), kv.second.isData ? ",DATA" : "");
}
}
return 0;
}
}
#if PLATFORM_WINDOWS
int wmain(int argc, wchar_t* argv[])
{
return uba::WrappedMain(argc, argv);
}
#else
int main(int argc, char* argv[])
{
return uba::WrappedMain(argc, argv);
}
#endif