mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
fix(relinker): bound embedded data tables and preserve trailing gap code
This commit is contained in:
1 parent
2f3e41252d
commit
dfe24c2e3a
5 files changed
+318
-24
No files matched your search
@@ -69,18 +69,179 @@ 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;
|
||||
bool IsInvalid64BitSingleByteOpcode(std::uint8_t opcode) {
|
||||
switch (opcode) {
|
||||
case 0x06:
|
||||
case 0x07:
|
||||
case 0x0e:
|
||||
case 0x16:
|
||||
case 0x17:
|
||||
case 0x1e:
|
||||
case 0x1f:
|
||||
case 0x27:
|
||||
case 0x2f:
|
||||
case 0x37:
|
||||
case 0x3f:
|
||||
case 0x60:
|
||||
case 0x61:
|
||||
case 0x82:
|
||||
case 0x9a:
|
||||
case 0xce:
|
||||
case 0xd4:
|
||||
case 0xd5:
|
||||
case 0xd6:
|
||||
case 0xea:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsForbiddenSyscallInstruction(std::span<const std::uint8_t> code, std::size_t pos, const Codegen::DecodedInstructionInfo& info) {
|
||||
const auto opPos = pos + info.OpcodeOffset;
|
||||
if (opPos + 1 >= pos + info.Length || opPos + 1 >= code.size()) return false;
|
||||
const auto b0 = code[opPos];
|
||||
const auto b1 = code[opPos + 1];
|
||||
return (b0 == 0x0f && (b1 == 0x05 || b1 == 0x34 || b1 == 0x07)) || (b0 == 0xcd && b1 == 0x80);
|
||||
}
|
||||
|
||||
bool IsValid64BitInstruction(std::span<const std::uint8_t> code, std::size_t pos, const Codegen::DecodedInstructionInfo& info, VirtualAddress baseVAddr, VirtualAddress segmentVAddr, VirtualAddress segmentEnd) {
|
||||
if (info.Length == 0 || info.Length > code.size() - pos) return false;
|
||||
if (!info.IsTwoByteOpcode && IsInvalid64BitSingleByteOpcode(info.Opcode)) return false;
|
||||
if (info.HasBranchTarget && !info.HasRipRelativeDisp) {
|
||||
const auto target = static_cast<std::int64_t>(baseVAddr + pos + info.Length) + info.BranchDisp;
|
||||
if (target < static_cast<std::int64_t>(segmentVAddr) || target >= static_cast<std::int64_t>(segmentEnd)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EndsControlFlow(const Codegen::DecodedInstructionInfo& info, bool isSyscall) {
|
||||
return info.FlowKind == Codegen::ControlFlowKind::Return
|
||||
|| info.FlowKind == Codegen::ControlFlowKind::UnconditionalJump
|
||||
|| info.FlowKind == Codegen::ControlFlowKind::IndirectJump
|
||||
|| info.FlowKind == Codegen::ControlFlowKind::Trap || isSyscall;
|
||||
}
|
||||
|
||||
class GapAnalysis {
|
||||
enum class Trailing { Invalid, Padding, Terminator, NonTerminator };
|
||||
|
||||
public:
|
||||
GapAnalysis(std::span<const std::uint8_t> code, VirtualAddress baseVAddr, VirtualAddress segmentVAddr, VirtualAddress segmentEnd, const Codegen::X64InstructionDecoder& decoder)
|
||||
: code(code), padding(code.size() + 1), trailing(code.size() + 1), syscalls(code.size() + 1) {
|
||||
padding.back() = true;
|
||||
trailing.back() = Trailing::Padding;
|
||||
for (std::size_t end = code.size(); end != 0;) {
|
||||
const auto pos = --end;
|
||||
const bool bytePadding = code[pos] == 0x00 || code[pos] == 0xcc || code[pos] == 0x90;
|
||||
if (bytePadding && padding[pos + 1]) {
|
||||
padding[pos] = true;
|
||||
trailing[pos] = Trailing::Padding;
|
||||
continue;
|
||||
}
|
||||
Codegen::DecodedInstructionInfo info;
|
||||
try {
|
||||
info = decoder.DecodeInstruction(code.data() + pos, code.size() - pos);
|
||||
} catch (const Codegen::CodegenException&) {
|
||||
continue;
|
||||
}
|
||||
if (info.Length == 0 || info.Length > code.size() - pos) continue;
|
||||
const bool nop = IsNop(code, pos, info.Length);
|
||||
if (nop && padding[pos + info.Length]) {
|
||||
padding[pos] = true;
|
||||
trailing[pos] = Trailing::Padding;
|
||||
continue;
|
||||
}
|
||||
if (!IsValid64BitInstruction(code, pos, info, baseVAddr, segmentVAddr, segmentEnd)) continue;
|
||||
const auto next = pos + info.Length;
|
||||
const bool isSyscall = IsForbiddenSyscallInstruction(code, pos, info);
|
||||
syscalls[pos] = isSyscall || syscalls[next];
|
||||
if (!padding[next]) {
|
||||
trailing[pos] = trailing[next];
|
||||
continue;
|
||||
}
|
||||
trailing[pos] = nop || code[pos] == 0xcc || code[pos] == 0x00
|
||||
? Trailing::Padding
|
||||
: (EndsControlFlow(info, isSyscall) ? Trailing::Terminator : Trailing::NonTerminator);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
bool OnlyPadding(std::size_t from) const { return padding[from]; }
|
||||
|
||||
bool DecodesAsTrailingCodeBlock(std::size_t from, bool* outHasSyscall = nullptr) const {
|
||||
if (outHasSyscall != nullptr) *outHasSyscall = false;
|
||||
if (from >= code.size() || code[from] == 0x00 || code[from] == 0xcc || code[from] == 0x90) return false;
|
||||
if (outHasSyscall != nullptr) *outHasSyscall = syscalls[from];
|
||||
return trailing[from] == Trailing::Terminator;
|
||||
}
|
||||
|
||||
private:
|
||||
std::span<const std::uint8_t> code;
|
||||
std::vector<bool> padding;
|
||||
std::vector<Trailing> trailing;
|
||||
std::vector<bool> syscalls;
|
||||
};
|
||||
|
||||
std::size_t FindEmbeddedDataTableEnd(std::span<const std::uint8_t> code, VirtualAddress tableVAddr, VirtualAddress segmentVAddr, VirtualAddress segmentEnd, const Codegen::X64InstructionDecoder& decoder) {
|
||||
const GapAnalysis analysis(code, tableVAddr, segmentVAddr, segmentEnd, decoder);
|
||||
std::optional<std::size_t> firstInvalidPos;
|
||||
for (std::size_t pos = 0; pos < code.size();) {
|
||||
if (analysis.OnlyPadding(pos)) break;
|
||||
Codegen::DecodedInstructionInfo info;
|
||||
try {
|
||||
info = decoder.DecodeInstruction(code.data() + pos, code.size() - pos);
|
||||
} catch (const Codegen::CodegenException&) {
|
||||
firstInvalidPos = pos;
|
||||
break;
|
||||
}
|
||||
if (!IsValid64BitInstruction(code, pos, info, tableVAddr, segmentVAddr, segmentEnd)) {
|
||||
firstInvalidPos = pos;
|
||||
break;
|
||||
}
|
||||
if (IsForbiddenSyscallInstruction(code, pos, info) || info.FlowKind != Codegen::ControlFlowKind::Sequential) return 0;
|
||||
pos += info.Length;
|
||||
}
|
||||
if (!firstInvalidPos) return 0;
|
||||
const bool duplicatedEightByteRow = code.size() >= 16 && std::memcmp(code.data(), code.data() + 8, 8) == 0;
|
||||
if (*firstInvalidPos != 0 && (!duplicatedEightByteRow || *firstInvalidPos > 4)) return 0;
|
||||
|
||||
std::optional<std::size_t> firstDecodeFailure;
|
||||
for (std::size_t pos = 0; pos < code.size();) {
|
||||
if (analysis.OnlyPadding(pos)) break;
|
||||
try {
|
||||
const auto info = decoder.DecodeInstruction(code.data() + pos, code.size() - pos);
|
||||
if (info.Length == 0 || info.Length > code.size() - pos) {
|
||||
firstDecodeFailure = pos;
|
||||
break;
|
||||
}
|
||||
if (IsForbiddenSyscallInstruction(code, pos, info)) return 0;
|
||||
pos += info.Length;
|
||||
} catch (const Codegen::CodegenException&) {
|
||||
firstDecodeFailure = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!firstDecodeFailure) return 0;
|
||||
|
||||
for (std::size_t pos = *firstDecodeFailure + 1; pos < code.size();) {
|
||||
if (code[pos] != 0xcc && code[pos] != 0x90 && code[pos] != 0x00) {
|
||||
++pos;
|
||||
continue;
|
||||
}
|
||||
const auto padStart = pos;
|
||||
bool hasZeroPad = false;
|
||||
while (pos < code.size() && (code[pos] == 0xcc || code[pos] == 0x90 || code[pos] == 0x00)) {
|
||||
if (code[pos] == 0x00) hasZeroPad = true;
|
||||
++pos;
|
||||
}
|
||||
if (pos < code.size() && analysis.DecodesAsTrailingCodeBlock(pos))
|
||||
return hasZeroPad ? pos : padStart;
|
||||
}
|
||||
for (std::size_t pos = *firstDecodeFailure + 1; pos < code.size(); ++pos) {
|
||||
bool hasSyscall = false;
|
||||
if (analysis.DecodesAsTrailingCodeBlock(pos, &hasSyscall) && hasSyscall)
|
||||
return pos;
|
||||
}
|
||||
return code.size();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -237,8 +398,9 @@ std::vector<RelativeJumpTable> FindRelativeJumpTables(std::span<const std::uint8
|
||||
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});
|
||||
const auto tableSize = FindEmbeddedDataTableEnd(tableCode, tableVAddr, segmentVAddr, segmentEnd, decoder);
|
||||
if (tableSize == 0) continue;
|
||||
tables.push_back({begin, tableVAddr, tableVAddr + tableSize});
|
||||
}
|
||||
return tables;
|
||||
}
|
||||
|
||||
@@ -420,6 +420,85 @@ void CheckEmbeddedDataTables() {
|
||||
};
|
||||
const auto tablesTransitive = Relinker::FindRelativeJumpTables(withTransitiveBranch, TextAddress, functionsTransitive);
|
||||
Require(tablesTransitive.empty(), "Transitive branch target was incorrectly recognized as a data table");
|
||||
|
||||
Bytes withTrailingCode(64, 0x90);
|
||||
withTrailingCode[0] = 0x48;
|
||||
withTrailingCode[1] = 0x8d;
|
||||
withTrailingCode[2] = 0x05;
|
||||
SetDisplacement(withTrailingCode, 3, 7, TextAddress + 16);
|
||||
withTrailingCode[7] = 0xc3;
|
||||
for (std::size_t i = 8; i < 16; ++i) withTrailingCode[i] = 0xcc;
|
||||
withTrailingCode[16] = 0x0f;
|
||||
withTrailingCode[17] = 0x78;
|
||||
withTrailingCode[18] = 0x11;
|
||||
withTrailingCode[19] = 0x22;
|
||||
withTrailingCode[20] = 0x33;
|
||||
withTrailingCode[21] = 0x44;
|
||||
for (std::size_t i = 22; i < 32; ++i) withTrailingCode[i] = 0xcc;
|
||||
withTrailingCode[32] = 0xb8;
|
||||
withTrailingCode[33] = 0x3b;
|
||||
withTrailingCode[34] = 0x00;
|
||||
withTrailingCode[35] = 0x00;
|
||||
withTrailingCode[36] = 0x00;
|
||||
withTrailingCode[37] = 0x0f;
|
||||
withTrailingCode[38] = 0x05;
|
||||
withTrailingCode[39] = 0xc3;
|
||||
for (std::size_t i = 40; i < withTrailingCode.size(); ++i) withTrailingCode[i] = 0x90;
|
||||
const std::vector<std::pair<VirtualAddress, VirtualAddress>> functionsTrailing{
|
||||
{TextAddress, TextAddress + 8},
|
||||
{TextAddress + withTrailingCode.size(), TextAddress + withTrailingCode.size() + 8}
|
||||
};
|
||||
const auto tablesTrailing = Relinker::FindRelativeJumpTables(withTrailingCode, TextAddress, functionsTrailing);
|
||||
Require(tablesTrailing.size() == 1, "Expected one bounded data table before trailing code");
|
||||
Require(tablesTrailing[0].Begin == TextAddress + 16 && tablesTrailing[0].End <= TextAddress + 32, "Mask overran into trailing code");
|
||||
|
||||
Bytes withCallbackSyscall(48, 0x90);
|
||||
withCallbackSyscall[0] = 0x48;
|
||||
withCallbackSyscall[1] = 0x8d;
|
||||
withCallbackSyscall[2] = 0x05;
|
||||
SetDisplacement(withCallbackSyscall, 3, 7, TextAddress + 16);
|
||||
withCallbackSyscall[7] = 0xc3;
|
||||
for (std::size_t i = 8; i < 16; ++i) withCallbackSyscall[i] = 0xcc;
|
||||
withCallbackSyscall[16] = 0xb8;
|
||||
withCallbackSyscall[17] = 0x3b;
|
||||
withCallbackSyscall[18] = 0x00;
|
||||
withCallbackSyscall[19] = 0x00;
|
||||
withCallbackSyscall[20] = 0x00;
|
||||
withCallbackSyscall[21] = 0x0f;
|
||||
withCallbackSyscall[22] = 0x05;
|
||||
withCallbackSyscall[23] = 0x0f;
|
||||
withCallbackSyscall[24] = 0x78;
|
||||
withCallbackSyscall[25] = 0x11;
|
||||
withCallbackSyscall[26] = 0x22;
|
||||
withCallbackSyscall[27] = 0x33;
|
||||
withCallbackSyscall[28] = 0x44;
|
||||
withCallbackSyscall[29] = 0xc3;
|
||||
const std::vector<std::pair<VirtualAddress, VirtualAddress>> functionsCallback{
|
||||
{TextAddress, TextAddress + 8},
|
||||
{TextAddress + withCallbackSyscall.size(), TextAddress + withCallbackSyscall.size() + 8}
|
||||
};
|
||||
const auto tablesCallback = Relinker::FindRelativeJumpTables(withCallbackSyscall, TextAddress, functionsCallback);
|
||||
Require(tablesCallback.empty(), "Callback with syscall and undecodable tail was incorrectly recognized as a data table");
|
||||
|
||||
Bytes whirlpoolText(4224, 0x90);
|
||||
whirlpoolText[0] = 0x48;
|
||||
whirlpoolText[1] = 0x8d;
|
||||
whirlpoolText[2] = 0x2d;
|
||||
SetDisplacement(whirlpoolText, 3, 7, TextAddress + 32);
|
||||
whirlpoolText[7] = 0xc3;
|
||||
for (std::size_t i = 8; i < 32; ++i) whirlpoolText[i] = 0xcc;
|
||||
static constexpr std::uint8_t whirlpoolPrefix[]{
|
||||
0x18, 0x18, 0x60, 0x18, 0xc0, 0x78, 0x30, 0xd8, 0x18, 0x18, 0x60, 0x18, 0xc0, 0x78, 0x30, 0xd8,
|
||||
0x0f, 0x78, 0x33, 0x1e
|
||||
};
|
||||
std::memcpy(whirlpoolText.data() + 32, whirlpoolPrefix, sizeof(whirlpoolPrefix));
|
||||
const std::vector<std::pair<VirtualAddress, VirtualAddress>> functionsWhirlpool{
|
||||
{TextAddress, TextAddress + 8},
|
||||
{TextAddress + whirlpoolText.size(), TextAddress + whirlpoolText.size() + 8}
|
||||
};
|
||||
const auto tablesWhirlpool = Relinker::FindRelativeJumpTables(whirlpoolText, TextAddress, functionsWhirlpool);
|
||||
Require(tablesWhirlpool.size() == 1, "Expected Whirlpool-style table to be recognized");
|
||||
Require(tablesWhirlpool[0].Begin == TextAddress + 32, "Whirlpool table start was incorrect");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ 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)
|
||||
last_func = LATER_FUNCTION if gap_embedded_data else FUNCTION
|
||||
last_func = LATER_FUNCTION if has_gap_embedded 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))
|
||||
@@ -103,12 +103,21 @@ 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:
|
||||
if gap_embedded_data or gap_data_then_syscall or gap_callback_syscall_undecodable:
|
||||
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)
|
||||
if gap_data_then_syscall:
|
||||
image[table:table + 6] = b"\x0f\x78\x11\x22\x33\x44"
|
||||
image[table + 6:table + 16] = b"\xcc" * 10
|
||||
image[table + 16:table + 24] = b"\xb8\x3b\x00\x00\x00\x0f\x05\xc3"
|
||||
image[table + 24:LATER_FUNCTION] = b"\x90" * (LATER_FUNCTION - (table + 24))
|
||||
elif gap_callback_syscall_undecodable:
|
||||
image[table:table + 14] = b"\xb8\x3b\x00\x00\x00\x0f\x05\x0f\x78\x11\x22\x33\x44\xc3"
|
||||
image[table + 14:LATER_FUNCTION] = b"\x90" * (LATER_FUNCTION - (table + 14))
|
||||
else:
|
||||
image[table:LATER_FUNCTION] = b"\x0f\x78\x33\x1e" * ((LATER_FUNCTION - table) // 4)
|
||||
image[data:data + 0x30] = b"\x90" * 0x30
|
||||
|
||||
cie = EH_FRAME
|
||||
@@ -229,6 +238,12 @@ def main():
|
||||
"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, "sysv-gap-embedded-data-" + target,
|
||||
fixture(sce_block=False, gap_embedded_data=True), target=target)
|
||||
run(relinker, work, "gap-data-then-syscall-" + target,
|
||||
fixture(sce_block=False, gap_data_then_syscall=True), "Forbidden syscall instruction", target=target)
|
||||
run(relinker, work, "gap-callback-syscall-" + target,
|
||||
fixture(sce_block=False, gap_callback_syscall_undecodable=True), "Forbidden syscall instruction", 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")
|
||||
|
||||
@@ -100,15 +100,30 @@ def unwind_table(text, base, function, size, header, frame):
|
||||
frame - (header + 4), 1, function - header, fde - header)
|
||||
|
||||
|
||||
def sce_module():
|
||||
def unwind_table_multiple(text, base, functions, header, frame):
|
||||
cie_body = b"\x00\x00\x00\x00\x01zR\x00\x01\x78\x10\x01\x1b\x00\x00\x00"
|
||||
struct.pack_into("<I", text, frame - base, len(cie_body))
|
||||
text[frame - base + 4:frame - base + 4 + len(cie_body)] = cie_body
|
||||
fde = frame + 4 + len(cie_body)
|
||||
struct.pack_into("<BBBBiI", text, header - base, 1, 0x1B, 0x03, 0x3B,
|
||||
frame - (header + 4), len(functions))
|
||||
for index, (func_addr, func_size) in enumerate(functions):
|
||||
struct.pack_into("<IIiiB3x", text, fde - base, 16, fde + 4 - frame, func_addr - (fde + 8), func_size, 0)
|
||||
struct.pack_into("<ii", text, header - base + 12 + index * 8, func_addr - header, fde - header)
|
||||
fde += 20
|
||||
struct.pack_into("<I", text, fde - base, 0)
|
||||
|
||||
|
||||
def sce_module(gap_embedded_data=False, gap_data_then_syscall=False, gap_callback_syscall_undecodable=False):
|
||||
image = bytearray(0xC00)
|
||||
tags = [(0x61000035, 0), (0x61000037, 1), (0x61000039, 0x20), (0x6100003B, 24), (0x6100003F, 24), (0, 0)]
|
||||
has_gap = gap_embedded_data or gap_data_then_syscall or gap_callback_syscall_undecodable
|
||||
headers = [
|
||||
(PT_LOAD, 5, 0x400, 0x1000, 0x100, 0x100, 0x100),
|
||||
(PT_LOAD, 6, 0x600, 0x2000, 0x400, 0x400, 0x100),
|
||||
(PT_DYNAMIC, 6, 0x600, 0x2000, len(tags) * 16, len(tags) * 16, 8),
|
||||
(PT_SCE_DYNLIBDATA, 4, 0xA00, 0, 0x200, 0, 0x10),
|
||||
(PT_GNU_EH_FRAME, 4, 0x480, 0x1080, 20, 20, 4),
|
||||
(PT_GNU_EH_FRAME, 4, 0x480, 0x1080, 48 if has_gap else 20, 48 if has_gap else 20, 4),
|
||||
]
|
||||
image[:16] = b"\x7fELF\x02\x01\x01" + bytes(9)
|
||||
struct.pack_into("<HHIQQQIHHHHHH", image, 16, 3, 62, 1, 0x1000, 64, 0, 0, 64, 56, len(headers), 64, 0, 0)
|
||||
@@ -117,21 +132,41 @@ def sce_module():
|
||||
for index, tag in enumerate(tags):
|
||||
struct.pack_into("<qQ", image, 0x600 + index * 16, *tag)
|
||||
text = memoryview(image)[0x400:0x500]
|
||||
text[0:FUNCTION_SIZE] = b"\xc3" + b"\x90" * (FUNCTION_SIZE - 1)
|
||||
text[0x20:0x22] = b"\x0f\x05"
|
||||
unwind_table(text, 0x1000, 0x1000, FUNCTION_SIZE, 0x1080, 0x10A0)
|
||||
if has_gap:
|
||||
text[0:8] = b"\x48\x8d\x05\x19\x00\x00\x00\xc3"
|
||||
table = 0x20
|
||||
if gap_data_then_syscall:
|
||||
text[table:table + 6] = b"\x0f\x78\x11\x22\x33\x44"
|
||||
text[table + 6:table + 16] = b"\xcc" * 10
|
||||
text[table + 16:table + 24] = b"\xb8\x3b\x00\x00\x00\x0f\x05\xc3"
|
||||
text[table + 24:0x40] = b"\x90" * 8
|
||||
elif gap_callback_syscall_undecodable:
|
||||
text[table:table + 14] = b"\xb8\x3b\x00\x00\x00\x0f\x05\x0f\x78\x11\x22\x33\x44\xc3"
|
||||
text[table + 14:0x40] = b"\x90" * 18
|
||||
else:
|
||||
text[table:0x40] = b"\x0f\x78\x33\x1e" * 8
|
||||
text[0x40:0x48] = b"\xc3" + b"\x90" * 7
|
||||
unwind_table_multiple(text, 0x1000, [(0x1000, 8), (0x1040, 8)], 0x1080, 0x10A0)
|
||||
else:
|
||||
text[0:FUNCTION_SIZE] = b"\xc3" + b"\x90" * (FUNCTION_SIZE - 1)
|
||||
text[0x20:0x22] = b"\x0f\x05"
|
||||
unwind_table(text, 0x1000, 0x1000, FUNCTION_SIZE, 0x1080, 0x10A0)
|
||||
return image
|
||||
|
||||
|
||||
def run_module(relinker, work, name, module, flags):
|
||||
def run_module(relinker, work, name, module, flags, expected_error=None):
|
||||
case = work / name
|
||||
(case / "sce_module").mkdir(parents=True)
|
||||
(case / "input.elf").write_bytes(importing_executable([]))
|
||||
(case / "sce_module" / "libExample.prx").write_bytes(module)
|
||||
result = subprocess.run([str(relinker), *flags, str(case / "input.elf"), str(case / "output.exe")],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert result.returncode == 0 and (case / "app0" / "sce_module" / "libExample.prx.guest.prx").exists(), (
|
||||
name, result.stdout, result.stderr)
|
||||
if expected_error is None:
|
||||
assert result.returncode == 0 and (case / "app0" / "sce_module" / "libExample.prx.guest.prx").exists(), (
|
||||
name, result.stdout, result.stderr)
|
||||
else:
|
||||
assert result.returncode == 2 and expected_error in result.stderr, (
|
||||
name, result.returncode, result.stdout, result.stderr)
|
||||
|
||||
|
||||
def run(relinker, work, name, image, flags, expected_error=None):
|
||||
@@ -154,6 +189,9 @@ def main():
|
||||
for system, flags in (("linux", []), ("windows", ["--windows"]), ("macos", ["--macos"])):
|
||||
run(relinker, work, "sce-loader-path-without-interp-header-" + system, fixture(rodata_address=False, interp_header=False), flags)
|
||||
run_module(relinker, work, "sce-module-data-after-code-" + system, sce_module(), flags)
|
||||
run_module(relinker, work, "sce-module-gap-embedded-data-" + system, sce_module(gap_embedded_data=True), flags)
|
||||
run_module(relinker, work, "sce-module-gap-data-then-syscall-" + system, sce_module(gap_data_then_syscall=True), flags, "Forbidden syscall instruction")
|
||||
run_module(relinker, work, "sce-module-gap-callback-syscall-" + system, sce_module(gap_callback_syscall_undecodable=True), flags, "Forbidden syscall instruction")
|
||||
print("SCE module code extent tests passed")
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +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.
|
||||
- [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 a RIP-relative `LEA` that hold undecodable bytes, and trailing relative jump tables, are masked with `0x90` in the scan buffer before syscall scanning and kept out of the code roots of [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp), so embedded data such as `0F 78` in `ubiservices.prx` (The Rogue Prince of Persia, PPSA28155, #7547) is no longer refused as an illegal opcode. The masked range of a table starts at the candidate `LEA` target and stops at the first byte whose linear decoding reaches a jump, return or trap, so gap bytes that are code keep being scanned, such as a callback reached through `lea` or a routine that contains a syscall, and so does a gap that a direct branch targets. The bytes from the end of a function to the candidate table must be padding only, and a gap whose bytes decode as valid instructions is left alone. The per-byte padding, trailing-code and syscall properties of a gap are computed once in a backward pass, so the analysis stays linear in the size of the gap. The bytes written to the output are unchanged apart from 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