mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
fix(relinker): mask embedded data tables in inter-FDE gaps
In PS5 Prospero PRX binaries, compilers place embedded constant data tables (such as the OpenSSL Whirlpool lookup table in ubiservices.prx) in inter-FDE gaps after function code, referenced via RIP-relative LEA instructions. - In RelativeJumpTables, detect constant data tables in inter-FDE gaps that begin with padding, contain no direct branch targets, and contain undecodable non-code bytes, distinguishing them from valid unannotated code routines and multi-hop gap branches. - In GuestModuleBuilder and RelinkerPipeline, extract unwind functions from PT_GNU_EH_FRAME as well as PT_SCE_DYNLIBDATA, and mask embedded data tables with 0x90 in scan buffers before SyscallScanner. - In CodeInstructionCollector, populate table boundaries whenever unwind functions are present to prevent addAddressTakenRoot from adding data table addresses to CFG roots on Windows and macOS. - Update relinker technical debt documentation and add regression tests. Fixes #7547 Unblocks: PPSA28155 #7547
This commit is contained in:
1 parent
1f68b1f776
commit
2f3e41252d
7 files changed
+209
-11
No files matched your search
@@ -346,7 +346,7 @@ std::set<Domain::VirtualAddress> CodeInstructionCollector::Collect(const std::ve
|
||||
addFunction(function.Begin, function.End - function.Begin, false);
|
||||
for (const auto target : function.ExtraTargets) addRoot(target);
|
||||
}
|
||||
if (sceBase) {
|
||||
if (sceBase || !functions.empty()) {
|
||||
const std::vector<std::pair<std::uint64_t, std::uint64_t>> ranges(functions.begin(), functions.end());
|
||||
for (const auto& header : codeHeaders) {
|
||||
if (header.Type != 1 || (header.Flags & 1) == 0) continue;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <codegen/CodegenException.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
namespace Relinker {
|
||||
|
||||
@@ -67,6 +69,20 @@ std::size_t TablesEnd(std::span<const std::uint8_t> code, std::size_t firstTable
|
||||
return table;
|
||||
}
|
||||
|
||||
bool HasUndecodableBytes(std::span<const std::uint8_t> code, const Codegen::X64InstructionDecoder& decoder) {
|
||||
for (std::size_t position = 0; position < code.size();) {
|
||||
if (OnlyPadding(code, position, code.size(), decoder)) return false;
|
||||
try {
|
||||
const auto info = decoder.DecodeInstruction(code.data() + position, code.size() - position);
|
||||
if (info.Length == 0 || info.Length > code.size() - position) return true;
|
||||
position += info.Length;
|
||||
} catch (const Codegen::CodegenException&) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::size_t FindTrailingRelativeJumpTable(std::span<const std::uint8_t> code, VirtualAddress codeAddress, std::size_t* tableEnd, std::size_t unwindEnd) {
|
||||
@@ -121,6 +137,62 @@ std::size_t FindTrailingRelativeJumpTable(std::span<const std::uint8_t> code, Vi
|
||||
std::vector<RelativeJumpTable> FindRelativeJumpTables(std::span<const std::uint8_t> segment, VirtualAddress segmentVAddr, const std::vector<std::pair<VirtualAddress, VirtualAddress>>& functions) {
|
||||
std::vector<RelativeJumpTable> tables;
|
||||
const auto segmentEnd = segmentVAddr + segment.size();
|
||||
const Codegen::X64InstructionDecoder decoder;
|
||||
std::set<VirtualAddress> branchTargets;
|
||||
for (const auto& [funcBegin, funcEnd] : functions) {
|
||||
if (funcBegin < segmentVAddr || funcBegin >= segmentEnd || funcEnd <= funcBegin) continue;
|
||||
const auto inspectFuncEnd = std::min(funcEnd, segmentEnd);
|
||||
const auto funcCode = segment.subspan(funcBegin - segmentVAddr, inspectFuncEnd - funcBegin);
|
||||
for (std::size_t pos = 0; pos < funcCode.size();) {
|
||||
Codegen::DecodedInstructionInfo info;
|
||||
try {
|
||||
info = decoder.DecodeInstruction(funcCode.data() + pos, funcCode.size() - pos);
|
||||
} catch (const Codegen::CodegenException&) {
|
||||
break;
|
||||
}
|
||||
if (info.Length == 0 || info.Length > funcCode.size() - pos) break;
|
||||
if (info.HasBranchTarget && !info.HasRipRelativeDisp) {
|
||||
const auto target = static_cast<std::int64_t>(funcBegin + pos + info.Length) + info.BranchDisp;
|
||||
if (target >= static_cast<std::int64_t>(segmentVAddr) && target < static_cast<std::int64_t>(segmentEnd))
|
||||
branchTargets.insert(static_cast<VirtualAddress>(target));
|
||||
}
|
||||
pos += info.Length;
|
||||
}
|
||||
}
|
||||
std::vector<VirtualAddress> pendingTargets(branchTargets.begin(), branchTargets.end());
|
||||
std::set<VirtualAddress> exploredGaps;
|
||||
while (!pendingTargets.empty()) {
|
||||
const auto current = pendingTargets.back();
|
||||
pendingTargets.pop_back();
|
||||
if (current < segmentVAddr || current >= segmentEnd) continue;
|
||||
bool inKnownFunction = false;
|
||||
for (const auto& [funcBegin, funcEnd] : functions) {
|
||||
if (current >= funcBegin && current < funcEnd) {
|
||||
inKnownFunction = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inKnownFunction || !exploredGaps.insert(current).second) continue;
|
||||
auto pos = static_cast<std::size_t>(current - segmentVAddr);
|
||||
while (pos < segment.size()) {
|
||||
Codegen::DecodedInstructionInfo info;
|
||||
try {
|
||||
info = decoder.DecodeInstruction(segment.data() + pos, segment.size() - pos);
|
||||
} catch (const Codegen::CodegenException&) {
|
||||
break;
|
||||
}
|
||||
if (info.Length == 0 || info.Length > segment.size() - pos) break;
|
||||
if (info.HasBranchTarget && !info.HasRipRelativeDisp) {
|
||||
const auto target = static_cast<std::int64_t>(segmentVAddr + pos + info.Length) + info.BranchDisp;
|
||||
if (target >= static_cast<std::int64_t>(segmentVAddr) && target < static_cast<std::int64_t>(segmentEnd)) {
|
||||
const auto targetVAddr = static_cast<VirtualAddress>(target);
|
||||
if (branchTargets.insert(targetVAddr).second) pendingTargets.push_back(targetVAddr);
|
||||
}
|
||||
}
|
||||
if (info.FlowKind == Codegen::ControlFlowKind::Return || info.FlowKind == Codegen::ControlFlowKind::IndirectJump || info.FlowKind == Codegen::ControlFlowKind::Trap) break;
|
||||
pos += info.Length;
|
||||
}
|
||||
}
|
||||
for (std::size_t index = 0; index < functions.size(); ++index) {
|
||||
const auto [begin, end] = functions[index];
|
||||
if (begin < segmentVAddr || begin >= segmentEnd || end <= begin) continue;
|
||||
@@ -129,8 +201,44 @@ std::vector<RelativeJumpTable> FindRelativeJumpTables(std::span<const std::uint8
|
||||
inspectEnd = std::min(functions[index + 1].first, segmentEnd);
|
||||
const auto code = segment.subspan(begin - segmentVAddr, inspectEnd - begin);
|
||||
std::size_t tableEnd = code.size();
|
||||
if (const auto tail = FindTrailingRelativeJumpTable(code, begin, &tableEnd, std::min(end, segmentEnd) - begin); tail != 0 && tail < code.size())
|
||||
if (const auto tail = FindTrailingRelativeJumpTable(code, begin, &tableEnd, std::min(end, segmentEnd) - begin); tail != 0 && tail < code.size()) {
|
||||
tables.push_back({begin, begin + tail, begin + tableEnd});
|
||||
continue;
|
||||
}
|
||||
if (inspectEnd <= end) continue;
|
||||
const auto funcCode = segment.subspan(begin - segmentVAddr, end - begin);
|
||||
std::optional<VirtualAddress> candidateTable;
|
||||
bool sawIndirectJump = false;
|
||||
for (std::size_t pos = 0; pos < funcCode.size();) {
|
||||
Codegen::DecodedInstructionInfo info;
|
||||
try {
|
||||
info = decoder.DecodeInstruction(funcCode.data() + pos, funcCode.size() - pos);
|
||||
} catch (const Codegen::CodegenException&) {
|
||||
break;
|
||||
}
|
||||
if (info.Length == 0 || info.Length > funcCode.size() - pos) break;
|
||||
if (info.FlowKind == Codegen::ControlFlowKind::IndirectJump) sawIndirectJump = true;
|
||||
const auto next = pos + info.Length;
|
||||
if (info.HasRipRelativeDisp && !info.IsTwoByteOpcode && info.OpcodeOffset < info.Length && info.Length >= 4 && funcCode[pos + info.OpcodeOffset] == 0x8d) {
|
||||
std::int32_t displacement = 0;
|
||||
std::memcpy(&displacement, funcCode.data() + next - 4, sizeof(displacement));
|
||||
const auto target = static_cast<std::int64_t>(begin + next) + displacement;
|
||||
if (target >= static_cast<std::int64_t>(end) && target < static_cast<std::int64_t>(inspectEnd)) {
|
||||
const auto targetVAddr = static_cast<VirtualAddress>(target);
|
||||
candidateTable = candidateTable ? std::min(*candidateTable, targetVAddr) : targetVAddr;
|
||||
}
|
||||
}
|
||||
pos = next;
|
||||
}
|
||||
if (sawIndirectJump || !candidateTable) continue;
|
||||
const auto tableVAddr = *candidateTable;
|
||||
const auto paddingCode = segment.subspan(end - segmentVAddr, tableVAddr - end);
|
||||
if (!OnlyPadding(paddingCode, 0, paddingCode.size(), decoder)) continue;
|
||||
const auto it = branchTargets.lower_bound(tableVAddr);
|
||||
if (it != branchTargets.end() && *it < inspectEnd) continue;
|
||||
const auto tableCode = segment.subspan(tableVAddr - segmentVAddr, inspectEnd - tableVAddr);
|
||||
if (!HasUndecodableBytes(tableCode, decoder)) continue;
|
||||
tables.push_back({begin, tableVAddr, inspectEnd});
|
||||
}
|
||||
return tables;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <relinker/analysis/RelativeJumpTables.hpp>
|
||||
#include <relinker/analysis/SceCodeExtent.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/EhFrameReader.hpp>
|
||||
#include <elfpatcher/general/GuestModuleWriter.hpp>
|
||||
@@ -395,14 +396,26 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
||||
}
|
||||
if (!image.PatchedRanges.empty()) GamePatch::CheckOverlaps(Io::Utf8Path(image.SourcePath), image.PatchedRanges, {.RelaData = image.Dynamic.RelaData, .RelaPltData = image.Dynamic.RelaPltData});
|
||||
const bool sceImage = std::any_of(image.Headers.begin(), image.Headers.end(), [](const Domain::ProgramHeader& header) { return header.Type == 0x61000000; });
|
||||
const auto unwindFunctions = sceImage ? UnusedNidFilter::ReadExceptionFunctions(image.Bytes, image.Headers, {}, {}) : std::vector<UnusedNidFilter::StrictCodeRegion>{};
|
||||
const bool hasEhFrame = sceImage || std::any_of(image.Headers.begin(), image.Headers.end(), [](const Domain::ProgramHeader& header) { return header.Type == 0x6474e550; });
|
||||
const auto unwindFunctions = hasEhFrame ? UnusedNidFilter::ReadExceptionFunctions(image.Bytes, image.Headers, {}, {}) : std::vector<UnusedNidFilter::StrictCodeRegion>{};
|
||||
std::vector<std::pair<VirtualAddress, VirtualAddress>> unwindRanges;
|
||||
for (const auto& function : unwindFunctions) unwindRanges.emplace_back(function.Begin, function.End);
|
||||
std::sort(unwindRanges.begin(), unwindRanges.end());
|
||||
for (const auto& header : codeHeaders) {
|
||||
std::vector<std::uint8_t> code(image.Bytes.begin() + header.Offset, image.Bytes.begin() + header.Offset + header.FileSize);
|
||||
if (sceImage)
|
||||
if (const auto end = FindSceCodeEnd(code, header.MappedAddress, unwindFunctions, image.Got, image.Dynamic.RelaPltData.size()))
|
||||
code.resize(*end - header.MappedAddress);
|
||||
const auto unwindRanges = sceImage ? SceUnwindRanges(code, header.MappedAddress, unwindFunctions) : std::vector<CodeRange>{};
|
||||
for (const auto offset : syscallScanner.ScanCodeSectionForSyscalls(code, header.MappedAddress, code.size(), Io::Utf8Path(image.SourcePath), unwindRanges)) {
|
||||
auto scanCode = code;
|
||||
for (const auto& table : FindRelativeJumpTables(code, header.MappedAddress, unwindRanges)) {
|
||||
if (table.Begin >= header.MappedAddress && table.End >= table.Begin) {
|
||||
const auto maskEnd = std::min<std::size_t>(table.End - header.MappedAddress, scanCode.size());
|
||||
const auto maskBegin = std::min<std::size_t>(table.Begin - header.MappedAddress, maskEnd);
|
||||
std::fill(scanCode.begin() + static_cast<std::ptrdiff_t>(maskBegin), scanCode.begin() + static_cast<std::ptrdiff_t>(maskEnd), std::uint8_t{0x90});
|
||||
}
|
||||
}
|
||||
const auto scanUnwindRanges = hasEhFrame ? SceUnwindRanges(code, header.MappedAddress, unwindFunctions) : std::vector<CodeRange>{};
|
||||
for (const auto offset : syscallScanner.ScanCodeSectionForSyscalls(scanCode, header.MappedAddress, scanCode.size(), Io::Utf8Path(image.SourcePath), scanUnwindRanges)) {
|
||||
if (offset > code.size() || code.size() - offset < 2u)
|
||||
throw Domain::RelinkerException("Syscall trap patch exceeds guest executable segment", header.MappedAddress + offset);
|
||||
if (!image.PatchedRanges.empty()) {
|
||||
|
||||
@@ -393,15 +393,18 @@ RelinkResult RelinkerPipeline::Relink() {
|
||||
|
||||
std::vector<std::pair<VirtualAddress, VirtualAddress>> unwindRanges;
|
||||
std::vector<std::vector<CodeRange>> segmentUnwindRanges(executableSegments.size());
|
||||
if (std::any_of(programHeaders.begin(), programHeaders.end(), [](const ProgramHeader& ph) { return ph.Type == PT_SCE_DYNLIBDATA; })) {
|
||||
const bool sceImage = std::any_of(programHeaders.begin(), programHeaders.end(), [](const ProgramHeader& ph) { return ph.Type == PT_SCE_DYNLIBDATA; });
|
||||
const bool hasEhFrame = sceImage || std::any_of(programHeaders.begin(), programHeaders.end(), [](const ProgramHeader& ph) { return ph.Type == PT_GNU_EH_FRAME; });
|
||||
if (hasEhFrame) {
|
||||
const auto functions = UnusedNidFilter::ReadExceptionFunctions(raw, programHeaders, {}, {});
|
||||
for (const auto& function : functions) unwindRanges.emplace_back(function.Begin, function.End);
|
||||
std::sort(unwindRanges.begin(), unwindRanges.end());
|
||||
for (std::size_t index = 0; index < executableSegments.size(); ++index) {
|
||||
auto& [segment, segmentVAddr, segmentFileOffset] = executableSegments[index];
|
||||
static_cast<void>(segmentFileOffset);
|
||||
if (const auto codeEnd = FindSceCodeEnd(segment, segmentVAddr, functions, gotVAddr, dynJmpRelSize))
|
||||
segment.resize(*codeEnd - segmentVAddr);
|
||||
if (sceImage)
|
||||
if (const auto codeEnd = FindSceCodeEnd(segment, segmentVAddr, functions, gotVAddr, dynJmpRelSize))
|
||||
segment.resize(*codeEnd - segmentVAddr);
|
||||
segmentUnwindRanges[index] = SceUnwindRanges(segment, segmentVAddr, functions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,6 +364,64 @@ void CheckRelativeJumpTables() {
|
||||
Require(Relinker::FindRelativeJumpTables(cutShort, TextAddress, {{TextAddress, TextAddress + cutShort.size()}}).empty(), "Table followed by more of its function was accepted");
|
||||
}
|
||||
|
||||
void CheckEmbeddedDataTables() {
|
||||
constexpr std::size_t tableOffset = 24;
|
||||
Bytes text(48, 0x90);
|
||||
text[0] = 0x48;
|
||||
text[1] = 0x8d;
|
||||
text[2] = 0x05;
|
||||
SetDisplacement(text, 3, 7, TextAddress + tableOffset);
|
||||
text[7] = 0xc3;
|
||||
for (std::size_t i = 8; i < tableOffset; ++i) text[i] = 0xcc;
|
||||
text[tableOffset] = 0x0f;
|
||||
text[tableOffset + 1] = 0x78;
|
||||
text[tableOffset + 2] = 0x33;
|
||||
text[tableOffset + 3] = 0x1e;
|
||||
|
||||
const std::vector<std::pair<VirtualAddress, VirtualAddress>> functions{
|
||||
{TextAddress, TextAddress + 8},
|
||||
{TextAddress + text.size(), TextAddress + text.size() + 8}
|
||||
};
|
||||
const auto tables = Relinker::FindRelativeJumpTables(text, TextAddress, functions);
|
||||
Require(tables.size() == 1, "Expected one embedded data table");
|
||||
Require(tables[0].Function == TextAddress && tables[0].Begin == TextAddress + tableOffset && tables[0].End == TextAddress + text.size(), "Embedded data table range was incorrect");
|
||||
|
||||
Bytes withBranch = text;
|
||||
withBranch[7] = 0xe8;
|
||||
SetDisplacement(withBranch, 8, 12, TextAddress + tableOffset);
|
||||
withBranch[12] = 0xc3;
|
||||
const std::vector<std::pair<VirtualAddress, VirtualAddress>> functionsWithBranch{
|
||||
{TextAddress, TextAddress + 13},
|
||||
{TextAddress + withBranch.size(), TextAddress + withBranch.size() + 8}
|
||||
};
|
||||
const auto tablesWithBranch = Relinker::FindRelativeJumpTables(withBranch, TextAddress, functionsWithBranch);
|
||||
Require(tablesWithBranch.empty(), "Branch target was incorrectly recognized as a data table");
|
||||
|
||||
Bytes withValidCode = text;
|
||||
withValidCode[tableOffset] = 0x48;
|
||||
withValidCode[tableOffset + 1] = 0x89;
|
||||
withValidCode[tableOffset + 2] = 0xc0;
|
||||
withValidCode[tableOffset + 3] = 0xc3;
|
||||
for (std::size_t i = tableOffset + 4; i < withValidCode.size(); ++i) withValidCode[i] = 0xcc;
|
||||
const auto tablesWithValidCode = Relinker::FindRelativeJumpTables(withValidCode, TextAddress, functions);
|
||||
Require(tablesWithValidCode.empty(), "Valid unannotated code in gap was incorrectly recognized as a data table");
|
||||
|
||||
Bytes withTransitiveBranch = text;
|
||||
withTransitiveBranch[7] = 0xe8;
|
||||
SetDisplacement(withTransitiveBranch, 8, 12, TextAddress + 16);
|
||||
withTransitiveBranch[12] = 0xc3;
|
||||
for (std::size_t i = 13; i < 16; ++i) withTransitiveBranch[i] = 0xcc;
|
||||
withTransitiveBranch[16] = 0xe9;
|
||||
SetDisplacement(withTransitiveBranch, 17, 21, TextAddress + tableOffset);
|
||||
for (std::size_t i = 21; i < tableOffset; ++i) withTransitiveBranch[i] = 0xcc;
|
||||
const std::vector<std::pair<VirtualAddress, VirtualAddress>> functionsTransitive{
|
||||
{TextAddress, TextAddress + 13},
|
||||
{TextAddress + withTransitiveBranch.size(), TextAddress + withTransitiveBranch.size() + 8}
|
||||
};
|
||||
const auto tablesTransitive = Relinker::FindRelativeJumpTables(withTransitiveBranch, TextAddress, functionsTransitive);
|
||||
Require(tablesTransitive.empty(), "Transitive branch target was incorrectly recognized as a data table");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -382,6 +440,7 @@ int main() {
|
||||
CheckAddressOverflow();
|
||||
CheckHighVirtualAddress();
|
||||
CheckRelativeJumpTables();
|
||||
CheckEmbeddedDataTables();
|
||||
std::cout << "Control-flow graph tests passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -26,17 +26,21 @@ UNSUPPORTED_TLS = bytes.fromhex("64 2d 65 6c 66 2e c3")
|
||||
INVALID = bytes.fromhex("41 41 41 41 41 41 66 66 66 66 2e 0f 1f 84 00 00 00 00 00")
|
||||
TLS_LOAD = bytes.fromhex("64 48 8b 04 25 00 00 00 00 c3")
|
||||
FS_SUBTRACT = bytes.fromhex("64 2d 65 6c 66 2e")
|
||||
LATER_FUNCTION = 0x300
|
||||
|
||||
|
||||
def fixture(sce_block=True, plt_push_offset=8, gap=0, interpreter_pointer=False, data_pointer=False, gap_entry=False,
|
||||
plt_at=None, early_function=False, function_tls=False, pointer_target=None, gap_bytes=None, functions=None,
|
||||
tls=False):
|
||||
plt = FUNCTION + FUNCTION_SIZE + gap if plt_at is None else plt_at
|
||||
tls=False, gap_embedded_data=False, gap_data_then_syscall=False, gap_callback_syscall_undecodable=False):
|
||||
has_gap_embedded = gap_embedded_data or gap_data_then_syscall or gap_callback_syscall_undecodable
|
||||
plt = (LATER_FUNCTION + FUNCTION_SIZE + gap if has_gap_embedded else FUNCTION + FUNCTION_SIZE + gap) if plt_at is None else plt_at
|
||||
entry = FUNCTION + FUNCTION_SIZE if gap_entry else FUNCTION
|
||||
data = plt + 0x20 if plt > FUNCTION else FUNCTION + FUNCTION_SIZE
|
||||
if functions is None:
|
||||
functions = [(EARLY_FUNCTION, FUNCTION_SIZE)] if early_function else []
|
||||
functions.append((FUNCTION, FUNCTION_SIZE))
|
||||
if has_gap_embedded:
|
||||
functions.append((LATER_FUNCTION, FUNCTION_SIZE))
|
||||
interpreter = FUNCTION - len(INTERPRETER)
|
||||
image = bytearray(0x1000)
|
||||
image[:16] = b"\x7fELF\x02\x01\x01" + bytes(9)
|
||||
@@ -73,7 +77,8 @@ def fixture(sce_block=True, plt_push_offset=8, gap=0, interpreter_pointer=False,
|
||||
|
||||
for begin, size in functions:
|
||||
image[begin:begin + size] = b"\xc3" + b"\x90" * (size - 1)
|
||||
image[FUNCTION + FUNCTION_SIZE:plt] = b"\x90" * gap
|
||||
last_func = LATER_FUNCTION if gap_embedded_data else FUNCTION
|
||||
image[last_func + FUNCTION_SIZE:plt] = b"\x90" * gap
|
||||
image[plt:plt + 2] = b"\xff\x35"
|
||||
struct.pack_into("<i", image, plt + 2, GOT + plt_push_offset - (plt + 6))
|
||||
image[plt + 6:plt + 8] = b"\xff\x25"
|
||||
@@ -98,6 +103,13 @@ def fixture(sce_block=True, plt_push_offset=8, gap=0, interpreter_pointer=False,
|
||||
image[entry:entry + len(UNSUPPORTED_TLS)] = UNSUPPORTED_TLS
|
||||
if function_tls:
|
||||
image[FUNCTION:FUNCTION + len(UNSUPPORTED_TLS)] = UNSUPPORTED_TLS
|
||||
if gap_embedded_data:
|
||||
table = FUNCTION + FUNCTION_SIZE + 0x10
|
||||
image[FUNCTION:FUNCTION + 3] = b"\x48\x8d\x05"
|
||||
struct.pack_into("<iB", image, FUNCTION + 3, table - (FUNCTION + 7), 0xC3)
|
||||
image[FUNCTION + FUNCTION_SIZE:table] = b"\xcc" * 0x10
|
||||
image[table:LATER_FUNCTION] = b"\x0f\x78\x33\x1e" * ((LATER_FUNCTION - table) // 4)
|
||||
image[data:data + 0x30] = b"\x90" * 0x30
|
||||
|
||||
cie = EH_FRAME
|
||||
cie_body = b"\x00\x00\x00\x00\x01zR\x00\x01\x78\x10\x01\x1b\x00\x00\x00"
|
||||
@@ -215,6 +227,8 @@ def main():
|
||||
fixture(plt_at=PLT_BEFORE_CODE, early_function=True), target=target)
|
||||
run(relinker, work, "sce-code-after-plt-" + target, fixture(plt_at=PLT_BEFORE_CODE, function_tls=True),
|
||||
"Unsupported " + ("Windows" if target == "windows" else "macOS") + " guest TLS instruction", target)
|
||||
run(relinker, work, "sce-gap-embedded-data-" + target,
|
||||
fixture(sce_block=True, gap_embedded_data=True), target=target)
|
||||
run(relinker, work, "sce-tls-rewrite-over-lea-target", entry_overlap_fixture(),
|
||||
"TLS rewrite would overwrite an executable entry point (offset 0x304)")
|
||||
print("SCE code extent tests passed")
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
- In the same executables and modules, the bytes between unwind functions are 16-byte slots of function tails and padding, and a tail can begin inside an instruction. The [instruction scanner](../../../core/relinker/codegen/src/InstructionScanner.cpp) of the syscall and call-site scans therefore resumes at the next byte when bytes outside every unwind function do not decode, and ends a decoding outside them at the start of the next unwind function. Code without an unwind entry that does not decode from its first byte is skipped by these scans instead of failing the relink; inside an unwind function a decode failure still fails it.
|
||||
- Read-only data can also sit between the unwind functions of such images. There, a `syscall`, `sysenter` or `int 0x80` pattern fails the [syscall scan](../../../core/relinker/relinker/src/analysis/SyscallScanner.cpp) only when a constant syscall number is set before it; the others, and every `sysret`, are skipped and counted in one message. The number is looked for in the decoded instructions and, whatever alignment the decoding took, in the bytes before the pattern: a `mov eax, imm32`, `mov rax, imm32`, `mov rax, imm64` or `xor eax, eax`, optionally followed by `mov al, imm8` or `mov ax, imm16`, then by any number of register moves or zeroings that leave `rax` alone. A real syscall without an unwind entry whose number is set otherwise is not refused and runs on the host: from memory or another register (`mov eax, ecx`), by `push`/`pop` or arithmetic (`or eax, imm32`), or with any other instruction between the load and the pattern.
|
||||
- In the [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp) of the Windows and macOS outputs for such images, a relocation or `lea` target outside every unwind function is followed as code only when its linear decoding, up to the first jump, return or trap, the next unwind function or 256 instructions, holds an `fs:[disp32]` access, or when it is 16-byte aligned, starts less than 256 bytes after an unwind function and ends in a jump, return or trap. Every other such target is taken as data and counted in one message, and the Windows TLS rewrite fails instead of overwriting one. A TLS access of another form, such as `mov rax, fs:[rax]`, or a `gs` instruction in code reached only through such a target is therefore not rewritten or trapped. Other fs and gs instructions do not tell code from data there: string bytes decode as these prefixes, and in two measured titles about 2,500 of about 5,500 such targets decode to one before their first jump.
|
||||
- [Embedded data tables and relative jump tables in inter-FDE gaps](../../../core/relinker/relinker/src/analysis/RelativeJumpTables.cpp) - in modules with unwind records (`PT_GNU_EH_FRAME` or `PT_SCE_DYNLIBDATA`), constant data tables referenced by RIP-relative `LEA` containing undecodable bytes and trailing jump tables in inter-FDE gaps are masked with `0x90` in the scan buffer before syscall scanning and excluded from code roots and control-flow analysis in [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp), avoiding illegal opcode rejections on embedded data such as `0F 78` in `ubiservices.prx` (The Rogue Prince of Persia, PPSA28155, #7547). Gaps with direct branch targets (`CALL`, `JMP`, `Jcc`) or valid instructions are preserved as code and undergo strict scanning. Underlying bytes in `image.Bytes` remain unmodified except for syscall traps.
|
||||
- In executables and guest modules with a `PT_SCE_DYNLIBDATA` segment, the [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp) of the Windows and macOS outputs follows code only from the end of `PT_INTERP`, or of the `/libexec/ld-elf.so.1` string that begins the segment when no `PT_INTERP` covers it, as in OpenOrbis modules, to the end of the PLT after the last function of the unwind table, or to the end of that function when the PLT comes before the code, as in the `libSceFios2.prx` and `libc.prx` that CUSA02636 bundles (PLT at 0xc8); the pipeline's scans of executables above stop at this same end, also when the PLT comes before the code. A guest module with PLT relocations and no PLT before or after its unwind functions, or with a PLT that overlaps one, fails as the executable does. Code after that end is not analyzed, such as the `DT_FINI` routine without an unwind entry that follows the last unwind function in both modules and that the Windows and macOS outputs call when the program exits: a TLS access in it is not rewritten, the ELF entry point, `DT_INIT`, `DT_FINI`, a branch or a pointer into it is ignored, and a function symbol or init/fini array entry pointing into it fails the conversion.
|
||||
- Under Rosetta (M5, macOS 27.0.1), a VMASKMOVPS, VMASKMOVPD, VPMASKMOVD or VPMASKMOVQ store or load that faults on an element other than an active first one ends the process (`unexpectedly need to EmulateForward on a synchronous exception`) instead of raising SIGSEGV or SIGBUS, so a masked store into a page the macOS write watch keeps read-only ends the title. `--to-rosetta` moves each masked store to a [stub](../../../core/relinker/codegen/src/x86/MaskedStoreLowering.cpp) that first writes to the first and last byte of every active element with `LOCK OR` of 0, which faults normally and changes nothing; each store then costs up to 16 locked byte writes. A page made read-only again by another thread between those writes and the store still ends the process. A stack-relative displacement that exceeds signed disp32 after the spill frame throws. Masked loads are not converted.
|
||||
- A relocation against a dynamic symbol entry that is all zero, like the null symbol, takes the value 0 plus its addend, as the gABI defines for `STN_UNDEF` ([pipeline](../../../core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp)). The Linux output imports such an entry as an unnamed weak symbol, which the host loader resolves to 0; the Windows and macOS outputs write the value into the slot ([relocation builder](../../../core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp)). What the console's loader does with such an entry is not measured. OliSe Player (PPSA01153) has seven of them, and its code calls through those slots only when they are not 0 ([#4437](https://github.com/boykopovar/AnyPS5/issues/4437)).
|
||||
|
||||
Reference in new issue
Block a user