400 lines
11 KiB
C++
400 lines
11 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "UbaObjectFile.h"
|
|
#include "UbaBinaryReaderWriter.h"
|
|
#include "UbaCompressedFileHeader.h"
|
|
#include "UbaFileAccessor.h"
|
|
#include "UbaObjectFileCoff.h"
|
|
#include "UbaObjectFileElf.h"
|
|
#include "UbaObjectFileImportLib.h"
|
|
#include "UbaObjectFileLLVMIR.h"
|
|
#include <oodle2.h>
|
|
|
|
namespace uba
|
|
{
|
|
u8 SymbolFileVersion = 2;
|
|
|
|
ObjectFile* ObjectFile::OpenAndParse(Logger& logger, ObjectFileParseMode parseMode, const tchar* filename)
|
|
{
|
|
auto file = new FileAccessor(logger, filename);
|
|
auto fileGuard = MakeGuard([&]() { delete file; });
|
|
|
|
if (!file->OpenMemoryRead())
|
|
return nullptr;
|
|
|
|
ObjectFile* objectFile = Parse(logger, parseMode, file->GetData(), file->GetSize(), filename);
|
|
if (!objectFile)
|
|
return nullptr;
|
|
|
|
fileGuard.Cancel();
|
|
objectFile->m_file = file;
|
|
return objectFile;
|
|
}
|
|
|
|
ObjectFile* ObjectFile::Parse(Logger& logger, ObjectFileParseMode parseMode, u8* data, u64 dataSize, const tchar* hint)
|
|
{
|
|
ObjectFile* objectFile = nullptr;
|
|
|
|
bool ownsData = false;
|
|
if (dataSize >= sizeof(CompressedFileHeader) && ((CompressedFileHeader*)data)->IsValid())
|
|
{
|
|
u64 decompressedSize = *(u64*)(data + sizeof(CompressedFileHeader));
|
|
u8* readPos = data + sizeof(CompressedFileHeader) + 8;
|
|
|
|
u8* decompressedData = (u8*)malloc(decompressedSize);
|
|
u8* writePos = decompressedData;
|
|
|
|
OO_SINTa decoredMemSize = OodleLZDecoder_MemorySizeNeeded(OodleLZ_Compressor_Kraken);
|
|
void* decoderMem = malloc(decoredMemSize);
|
|
auto mg = MakeGuard([decoderMem]() { free(decoderMem); });
|
|
|
|
u64 left = decompressedSize;
|
|
while (left)
|
|
{
|
|
u32 compressedBlockSize = *(u32*)readPos;
|
|
readPos += 4;
|
|
u32 decompressedBlockSize = *(u32*)readPos;
|
|
readPos += 4;
|
|
|
|
OO_SINTa decompLen = OodleLZ_Decompress(readPos, (OO_SINTa)compressedBlockSize, writePos, (OO_SINTa)decompressedBlockSize,
|
|
OodleLZ_FuzzSafe_Yes, OodleLZ_CheckCRC_No, OodleLZ_Verbosity_None, NULL, 0, NULL, NULL, decoderMem, decoredMemSize);
|
|
if (decompLen != decompressedBlockSize)
|
|
{
|
|
logger.Error(TC("Failed to decompress file %s (Compressed size %llu)"), hint, dataSize);
|
|
return nullptr;
|
|
}
|
|
|
|
readPos += compressedBlockSize;
|
|
writePos += decompressedBlockSize;
|
|
left -= decompressedBlockSize;
|
|
}
|
|
|
|
data = decompressedData;
|
|
dataSize = decompressedSize;
|
|
ownsData = true;
|
|
}
|
|
|
|
if (IsElfFile(data, dataSize))
|
|
objectFile = new ObjectFileElf();
|
|
else if (IsLLVMIRFile(data, dataSize))
|
|
objectFile = new ObjectFileLLVMIR();
|
|
else if (IsCoffFile(data, dataSize))
|
|
objectFile = new ObjectFileCoff();
|
|
else if (IsImportLib(data, dataSize))
|
|
objectFile = new ObjectFileImportLib();
|
|
else
|
|
{
|
|
if (ownsData)
|
|
free(data);
|
|
logger.Error(TC("Unknown object file format (Size %llu). Maybe msvc FE IL? (%s)"), dataSize, hint);
|
|
return nullptr;
|
|
}
|
|
|
|
objectFile->m_data = data;
|
|
objectFile->m_dataSize = dataSize;
|
|
objectFile->m_ownsData = ownsData;
|
|
|
|
if (objectFile->Parse(logger, parseMode, hint))
|
|
return objectFile;
|
|
|
|
if (ownsData)
|
|
free(data);
|
|
delete objectFile;
|
|
return nullptr;
|
|
}
|
|
|
|
bool ObjectFile::CopyMemoryAndClose()
|
|
{
|
|
u8* data = (u8*)malloc(m_dataSize);
|
|
UBA_ASSERT(false);
|
|
if (!data)
|
|
return false;
|
|
memcpy(data, m_data, m_dataSize);
|
|
if (m_ownsData)
|
|
free(m_data);
|
|
m_data = data;
|
|
m_ownsData = true;
|
|
delete m_file;
|
|
m_file = nullptr;
|
|
return true;
|
|
}
|
|
|
|
bool ObjectFile::WriteImportsAndExports(Logger& logger, MemoryBlock& memoryBlock, bool verbose)
|
|
{
|
|
auto write = [&](const void* data, u64 dataSize) { memcpy(memoryBlock.Allocate(dataSize, 1, TC("ObjectFile::WriteImportsAndExports")), data, dataSize); };
|
|
if (!WriteImportsAndExports(logger, write, verbose))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool ObjectFile::WriteImportsAndExports(Logger& logger, const tchar* exportsFilename, bool verbose)
|
|
{
|
|
FileAccessor exportsFile(logger, exportsFilename);
|
|
if (!exportsFile.CreateWrite())
|
|
return false;
|
|
|
|
char buffer[256*1024];
|
|
u64 bufferPos = 0;
|
|
auto flush = [&]() { exportsFile.Write(buffer, bufferPos); bufferPos = 0; };
|
|
auto write = [&](const void* data, u64 dataSize) { if (bufferPos + dataSize > sizeof(buffer)) flush(); memcpy(buffer + bufferPos, data, dataSize); bufferPos += dataSize; };
|
|
|
|
if (!WriteImportsAndExports(logger, write, verbose))
|
|
return false;
|
|
|
|
flush();
|
|
|
|
return exportsFile.Close();
|
|
}
|
|
|
|
template<typename WriteFunc>
|
|
bool ObjectFile::WriteImportsAndExports(Logger& logger, const WriteFunc& write, bool verbose)
|
|
{
|
|
write(&SymbolFileVersion, 1);
|
|
write(&m_type, 1);
|
|
write(&verbose, 1);
|
|
|
|
// Write all imports
|
|
for (auto& symbol : m_imports)
|
|
{
|
|
write(symbol.c_str(), symbol.size());
|
|
write("\n", 1);
|
|
}
|
|
write("\n", 1);
|
|
|
|
if (verbose)
|
|
{
|
|
// Write all exports
|
|
for (auto& kv : m_exports)
|
|
{
|
|
write(kv.second.symbol.c_str(), kv.second.symbol.size());
|
|
if (kv.second.isData)
|
|
write(",DATA", 5);
|
|
write("\n", 1);
|
|
}
|
|
write("\n", 1);
|
|
}
|
|
else
|
|
{
|
|
u32 count = u32(m_exports.size());
|
|
write(&count, sizeof(count));
|
|
for (auto& kv : m_exports)
|
|
{
|
|
write(&kv.first, sizeof(StringKey));
|
|
write(&kv.second.isData, 1);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const char* ObjectFile::GetLibName()
|
|
{
|
|
UBA_ASSERT(false);
|
|
return "";
|
|
}
|
|
|
|
ObjectFile::~ObjectFile()
|
|
{
|
|
if (m_ownsData)
|
|
free(m_data);
|
|
delete m_file;
|
|
}
|
|
|
|
void ObjectFile::RemoveExportedSymbol(const char* symbol)
|
|
{
|
|
m_exports.erase(ToStringKeyRaw(symbol, strlen(symbol)));
|
|
}
|
|
|
|
const tchar* ObjectFile::GetFileName() const
|
|
{
|
|
return m_file->GetFileName();
|
|
}
|
|
|
|
const UnorderedSymbols& ObjectFile::GetImports() const
|
|
{
|
|
return m_imports;
|
|
}
|
|
|
|
const UnorderedExports& ObjectFile::GetExports() const
|
|
{
|
|
return m_exports;
|
|
}
|
|
|
|
const UnorderedSymbols& ObjectFile::GetPotentialDuplicates() const
|
|
{
|
|
return m_potentialDuplicates;
|
|
}
|
|
|
|
bool ObjectFile::CreateExtraFile(Logger& logger, const StringView& extraObjFilename, const StringView& moduleName, const StringView& platform, const AllExternalImports& allExternalImports, const UnorderedSymbols& allInternalImports, const AllExports& allExports, const ExtraExports& extraExports, bool includeExportsInFile)
|
|
{
|
|
ObjectFileCoff objectFileCoff;
|
|
ObjectFileElf objectFileElf;
|
|
|
|
MemoryBlock memoryBlock(16*1024*1024);
|
|
|
|
bool res;
|
|
if (extraObjFilename.EndsWith(TCV(".obj")))
|
|
res = ObjectFileCoff::CreateExtraFile(logger, platform, memoryBlock, allExternalImports, allInternalImports, allExports, includeExportsInFile);
|
|
else if (extraObjFilename.EndsWith(TCV(".dynlist")))
|
|
res = CreateVersionScript(logger, memoryBlock, allExternalImports, allInternalImports, allExports, extraExports, includeExportsInFile, true);
|
|
else if (extraObjFilename.EndsWith(TCV(".ldscript")))
|
|
res = CreateVersionScript(logger, memoryBlock, allExternalImports, allInternalImports, allExports, extraExports, includeExportsInFile, false);
|
|
else if (extraObjFilename.EndsWith(TCV(".emd")))
|
|
res = CreateEmdFile(logger, memoryBlock, moduleName, allExternalImports, allInternalImports, allExports, includeExportsInFile);
|
|
else
|
|
res = ObjectFileElf::CreateExtraFile(logger, platform, memoryBlock, allExternalImports, allInternalImports, allExports, includeExportsInFile);
|
|
|
|
if (!res)
|
|
return false;
|
|
|
|
FileAccessor extraFile(logger, extraObjFilename.data);
|
|
if (!extraFile.CreateWrite())
|
|
return false;
|
|
|
|
if (!extraFile.Write(memoryBlock.memory, memoryBlock.writtenSize))
|
|
return false;
|
|
|
|
return extraFile.Close();
|
|
}
|
|
|
|
bool SymbolFile::ParseFile(Logger& logger, const tchar* filename)
|
|
{
|
|
FileAccessor symFile(logger, filename);
|
|
if (!symFile.OpenMemoryRead())
|
|
return false;
|
|
if (symFile.GetSize() == 0)
|
|
return logger.Error(TC("%s - Import/export file corrupt (size 0)"), filename);
|
|
|
|
auto readPos = (const char*)symFile.GetData();
|
|
|
|
u8 version = *(u8*)readPos++;
|
|
if (SymbolFileVersion != version)
|
|
return logger.Error(TC("%s - Import/export file version mismatch (application version %u, file version %u)"), filename, SymbolFileVersion, version);
|
|
|
|
type = *(const ObjectFileType*)readPos++;
|
|
bool verbose = *(bool*)readPos++;
|
|
|
|
while (*readPos != '\n')
|
|
{
|
|
auto start = readPos;
|
|
while (*readPos != '\n')
|
|
++readPos;
|
|
imports.insert(std::string(start, readPos - start));
|
|
++readPos;
|
|
}
|
|
++readPos;
|
|
|
|
if (verbose)
|
|
{
|
|
while (*readPos != '\n')
|
|
{
|
|
auto start = readPos;(void)start;
|
|
const char* comma = nullptr;
|
|
while (*readPos != '\n')
|
|
{
|
|
if (*readPos == ',')
|
|
comma = readPos;
|
|
++readPos;
|
|
}
|
|
|
|
auto end = readPos;
|
|
|
|
ExportInfo info;
|
|
if (comma)
|
|
{
|
|
end = comma;
|
|
info.isData = true;
|
|
}
|
|
//info.symbol.assign(start, end - start);
|
|
exports.emplace(ToStringKeyRaw(start, end - start), std::move(info));
|
|
++readPos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
u32 count = *(u32*)readPos;
|
|
readPos += sizeof(u32);
|
|
while (count--)
|
|
{
|
|
StringKey key = *(StringKey*)readPos;
|
|
readPos += sizeof(StringKey);
|
|
u8 isData = *readPos != 0;
|
|
++readPos;
|
|
ExportInfo info;
|
|
info.isData = isData;
|
|
exports.emplace(key, info);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ObjectFile::CreateVersionScript(Logger& logger, MemoryBlock& memoryBlock, const AllExternalImports& allExternalImports, const UnorderedSymbols& allInternalImports, const AllExports& allExports, const ExtraExports& extraExports, bool includeExportsInFile, bool isDynList)
|
|
{
|
|
auto WriteString = [&](const char* str, u64 strLen) { memcpy(memoryBlock.Allocate(strLen, 1, TC("")), str, strLen); };
|
|
|
|
bool isFirst = true;
|
|
auto WriteSymbol = [&](const char* str, u64 strLen)
|
|
{
|
|
if (isFirst)
|
|
WriteString("global:\n", 8);
|
|
WriteString(str, strLen);
|
|
WriteString(";\n", 2);
|
|
isFirst = false;
|
|
};
|
|
|
|
//WriteString("VERSION ", 8);
|
|
WriteString("{\n", 2);
|
|
|
|
for (auto& imp : allExternalImports)
|
|
{
|
|
StringKey impKey = ToStringKeyRaw(imp.c_str(), imp.size());
|
|
auto findIt = allExports.find(impKey);
|
|
if (findIt != allExports.end())
|
|
WriteSymbol(imp.c_str(), imp.size());
|
|
}
|
|
|
|
for (auto& symbol : extraExports)
|
|
WriteSymbol(symbol.c_str(), symbol.size());
|
|
|
|
if (!isDynList)
|
|
WriteString("local: *;\n", 10);
|
|
else if (isFirst)
|
|
WriteSymbol("ThisIsAnUnrealEngineModule", 26); // Workaround for tool not liking empty lists
|
|
|
|
WriteString("};", 2);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ObjectFile::CreateEmdFile(Logger& logger, MemoryBlock& memoryBlock, const StringView& moduleName, const AllExternalImports& allExternalImports, const UnorderedSymbols& allInternalImports, const AllExports& allExports, bool includeExportsInFile)
|
|
{
|
|
auto WriteString = [&](const char* str, u64 strLen) { memcpy(memoryBlock.Allocate(strLen, 1, TC("")), str, strLen); };
|
|
|
|
char moduleName2[256];
|
|
u32 moduleNameLen = StringBuffer<>(moduleName.data).Parse(moduleName2, 256) - 1;
|
|
|
|
WriteString("Library: ", 9);
|
|
WriteString(moduleName2, moduleNameLen);
|
|
WriteString(" { export: {\n", 13);
|
|
|
|
bool symbolAdded = false;
|
|
|
|
for (auto& imp : allExternalImports)
|
|
{
|
|
StringKey impKey = ToStringKeyRaw(imp.c_str(), imp.size());
|
|
auto findIt = allExports.find(impKey);
|
|
if (findIt == allExports.end())
|
|
continue;
|
|
WriteString(imp.c_str(), imp.size());
|
|
WriteString("\n", 1);
|
|
symbolAdded = true;
|
|
}
|
|
|
|
if (!symbolAdded)
|
|
WriteString("ThisIsAnUnrealEngineModule\n", 27); // Workaround for tool not liking empty lists
|
|
|
|
WriteString("}}", 2);
|
|
|
|
return true;
|
|
}
|
|
}
|