fix(relinker): hand the guest the real argv on Linux

The Linux entry stub built a copy of the entry parameter block holding
argc and a pointer to the host argv array, so the guest read that pointer
as argv[0] and uninitialised stack as argv[1..]. The process stack at
entry already has the guest's layout (argc as a qword, then the argv
pointers and a null), so pass rsp itself.

Test: linux_entry_argv relinks a fixture whose entry checks argc and
argv and runs the output on Linux x86-64.
This commit is contained in:
Dean Galvin 2026-10-03 13:40:06 +13:00
parent 7d40a57d18
commit d8b8aafc65
4 changed files with 53 additions and 11 deletions

View File

@ -180,6 +180,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_function_coverage.py $<TARGET_FILE:relinker>)
add_test(NAME linux_load_alignment COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_load_alignment.py $<TARGET_FILE:relinker>)
add_test(NAME linux_entry_argv COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_entry_argv.py $<TARGET_FILE:relinker>)
add_test(NAME windows_gui COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_gui.py $<TARGET_FILE:relinker>)
add_test(NAME guest_intel_trampolines COMMAND ${Python3_EXECUTABLE}

View File

@ -115,12 +115,7 @@ inline constexpr std::uint32_t kDynSymInfoFirstGlobal = 1;
inline constexpr std::uint64_t kDynSymAlign = 8;
inline constexpr std::uint32_t kDynamicLinkToStrTab = 2;
inline constexpr std::uint8_t kStubOpPopRax = 0x58;
inline constexpr std::uint8_t kStubOpMovRbxRsp[] = {0x48, 0x89, 0xe3};
inline constexpr std::uint8_t kStubOpSubRsp0x30[] = {0x48, 0x83, 0xec, 0x30};
inline constexpr std::uint8_t kStubOpAndRsp0xf0[] = {0x48, 0x83, 0xe4, 0xf0};
inline constexpr std::uint8_t kStubOpMovDwordPtrRsp[] = {0x89, 0x04, 0x24};
inline constexpr std::uint8_t kStubOpMovQwordPtrRsp8Rbx[] = {0x48, 0x89, 0x5c, 0x24, 0x08};
inline constexpr std::uint8_t kStubOpMovRdiRsp[] = {0x48, 0x89, 0xe7};
inline constexpr std::uint8_t kStubOpXorRsiRsi[] = {0x48, 0x31, 0xf6};
inline constexpr std::uint8_t kStubOpCallRel32 = 0xe8;

View File

@ -15,13 +15,8 @@ std::vector<std::uint8_t> EntryStubBuilder::BuildEntryStub(
const std::uint64_t realEntryVaddr
) const {
std::vector<std::uint8_t> s;
s.push_back(kStubOpPopRax);
_appendBytes(s, kStubOpMovRbxRsp, sizeof(kStubOpMovRbxRsp));
_appendBytes(s, kStubOpSubRsp0x30, sizeof(kStubOpSubRsp0x30));
_appendBytes(s, kStubOpAndRsp0xf0, sizeof(kStubOpAndRsp0xf0));
_appendBytes(s, kStubOpMovDwordPtrRsp, sizeof(kStubOpMovDwordPtrRsp));
_appendBytes(s, kStubOpMovQwordPtrRsp8Rbx, sizeof(kStubOpMovQwordPtrRsp8Rbx));
_appendBytes(s, kStubOpMovRdiRsp, sizeof(kStubOpMovRdiRsp));
_appendBytes(s, kStubOpAndRsp0xf0, sizeof(kStubOpAndRsp0xf0));
_appendBytes(s, kStubOpXorRsiRsi, sizeof(kStubOpXorRsiRsi));
const std::uint64_t callInsnVaddr = stubVaddr + s.size();
const std::uint64_t callNextVaddr = callInsnVaddr + kStubCallInstructionSize;

View File

@ -0,0 +1,50 @@
"""Check that a Linux relink hands the guest entry the process argc and inline argv array."""
from pathlib import Path
import platform
import signal
import struct
import subprocess
import sys
import tempfile
from test_linux_load_alignment import fixture
ENTRY = 0x10
ARGV_CHECK = bytes.fromhex(
"833f02" "751e"
"48837f0800" "7417"
"488b4710"
"80385a" "750e"
"80780100" "7508"
"48837f1800" "7501"
"cc"
"0f0b")
def argv_fixture():
image = fixture()
struct.pack_into("<Q", image, 24, ENTRY)
image[0x4000 + ENTRY:0x4000 + ENTRY + len(ARGV_CHECK)] = ARGV_CHECK
return image
def main():
relinker = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="anyps5-argv-") as directory:
source = Path(directory) / "input.elf"
output = Path(directory) / "output.elf"
source.write_bytes(argv_fixture())
result = subprocess.run([str(relinker), "--skip-sce-module", str(source), str(output)],
capture_output=True, text=True, timeout=20)
assert result.returncode == 0, (result.stdout, result.stderr)
if sys.platform.startswith("linux") and platform.machine() in ("x86_64", "AMD64"):
output.chmod(0o755)
for arguments, expected in ((["Z"], -signal.SIGTRAP), (["Z", "extra"], -signal.SIGILL)):
executed = subprocess.run([str(output), *arguments], capture_output=True, timeout=20)
assert executed.returncode == expected, (arguments, executed.returncode)
print("Linux entry argv test passed")
if __name__ == "__main__":
main()