mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-16 12:09:07 +00:00
CALL/RET translation
This commit is contained in:
parent
9e8a328235
commit
851131e888
@ -132,12 +132,18 @@ CodeBlock* ExecutionEngine::TranslateBasicBlock(VAddr start_address, size_t max_
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track branch target before translation
|
// Track branch/call target before translation
|
||||||
if (instruction.mnemonic == ZYDIS_MNEMONIC_JMP &&
|
if (instruction.mnemonic == ZYDIS_MNEMONIC_JMP &&
|
||||||
operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
|
operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
|
||||||
s64 offset = static_cast<s64>(operands[0].imm.value.s);
|
s64 offset = static_cast<s64>(operands[0].imm.value.s);
|
||||||
branch_target = current_address + instruction.length + offset;
|
branch_target = current_address + instruction.length + offset;
|
||||||
branch_patch_location = code_generator->getCurr();
|
branch_patch_location = code_generator->getCurr();
|
||||||
|
} else if (instruction.mnemonic == ZYDIS_MNEMONIC_CALL &&
|
||||||
|
operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
|
||||||
|
// Track CALL target for potential linking (though CALL typically goes to HLE)
|
||||||
|
s64 offset = static_cast<s64>(operands[0].imm.value.s);
|
||||||
|
branch_target = current_address + instruction.length + offset;
|
||||||
|
branch_patch_location = code_generator->getCurr();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool translated = translator->TranslateInstruction(instruction, operands, current_address);
|
bool translated = translator->TranslateInstruction(instruction, operands, current_address);
|
||||||
|
|||||||
@ -559,13 +559,75 @@ bool X86_64Translator::TranslatePop(const ZydisDecodedInstruction& instruction,
|
|||||||
|
|
||||||
bool X86_64Translator::TranslateCall(const ZydisDecodedInstruction& instruction,
|
bool X86_64Translator::TranslateCall(const ZydisDecodedInstruction& instruction,
|
||||||
const ZydisDecodedOperand* operands, VAddr address) {
|
const ZydisDecodedOperand* operands, VAddr address) {
|
||||||
LOG_WARNING(Core, "CALL instruction translation needs execution engine integration");
|
const auto& target = operands[0];
|
||||||
return false;
|
VAddr target_address = 0;
|
||||||
|
VAddr return_address = address + instruction.length;
|
||||||
|
|
||||||
|
// Calculate target address based on operand type
|
||||||
|
if (target.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
|
||||||
|
// Direct relative call: CALL rel32
|
||||||
|
// Target = current_address + instruction.length + offset
|
||||||
|
s64 offset = static_cast<s64>(target.imm.value.s);
|
||||||
|
target_address = address + instruction.length + offset;
|
||||||
|
} else if (target.type == ZYDIS_OPERAND_TYPE_MEMORY) {
|
||||||
|
// Indirect call: CALL [mem]
|
||||||
|
// Load address from memory into scratch register
|
||||||
|
LoadMemoryOperand(RegisterMapper::SCRATCH_REG, target, 8);
|
||||||
|
// Push return address
|
||||||
|
int sp_reg = RegisterMapper::STACK_POINTER;
|
||||||
|
codegen.sub_imm(sp_reg, sp_reg, 8); // Decrement stack by 8 bytes
|
||||||
|
codegen.mov_imm(RegisterMapper::SCRATCH_REG2, return_address);
|
||||||
|
codegen.str(RegisterMapper::SCRATCH_REG2, sp_reg, 0); // Store return address
|
||||||
|
// Call via register
|
||||||
|
codegen.blr(RegisterMapper::SCRATCH_REG);
|
||||||
|
return true;
|
||||||
|
} else if (target.type == ZYDIS_OPERAND_TYPE_REGISTER) {
|
||||||
|
// Indirect call: CALL reg
|
||||||
|
int reg = GetArm64Register(target);
|
||||||
|
if (reg == -1) {
|
||||||
|
LOG_ERROR(Core, "Invalid register for CALL");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Push return address
|
||||||
|
int sp_reg = RegisterMapper::STACK_POINTER;
|
||||||
|
codegen.sub_imm(sp_reg, sp_reg, 8); // Decrement stack by 8 bytes
|
||||||
|
codegen.mov_imm(RegisterMapper::SCRATCH_REG, return_address);
|
||||||
|
codegen.str(RegisterMapper::SCRATCH_REG, sp_reg, 0); // Store return address
|
||||||
|
// Call via register
|
||||||
|
codegen.blr(reg);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Core, "Unsupported CALL operand type");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For direct calls, push return address and branch to target
|
||||||
|
// Push return address onto stack
|
||||||
|
int sp_reg = RegisterMapper::STACK_POINTER;
|
||||||
|
codegen.sub_imm(sp_reg, sp_reg, 8); // Decrement stack by 8 bytes (x86_64 stack grows down)
|
||||||
|
codegen.mov_imm(RegisterMapper::SCRATCH_REG, return_address);
|
||||||
|
codegen.str(RegisterMapper::SCRATCH_REG, sp_reg, 0); // Store return address at [SP]
|
||||||
|
|
||||||
|
// Branch to target (will be linked later if target block is available)
|
||||||
|
void* placeholder_target = reinterpret_cast<void*>(target_address);
|
||||||
|
codegen.bl(placeholder_target); // Use bl (branch with link) for calls
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool X86_64Translator::TranslateRet(const ZydisDecodedInstruction& instruction,
|
bool X86_64Translator::TranslateRet(const ZydisDecodedInstruction& instruction,
|
||||||
const ZydisDecodedOperand* operands) {
|
const ZydisDecodedOperand* operands) {
|
||||||
codegen.ret();
|
// x86_64 RET pops return address from stack and jumps to it
|
||||||
|
int sp_reg = RegisterMapper::STACK_POINTER;
|
||||||
|
int scratch_reg = RegisterMapper::SCRATCH_REG;
|
||||||
|
|
||||||
|
// Load return address from stack
|
||||||
|
codegen.ldr(scratch_reg, sp_reg, 0); // Load return address from [SP]
|
||||||
|
codegen.add_imm(sp_reg, sp_reg, 8); // Increment stack by 8 bytes (pop)
|
||||||
|
|
||||||
|
// Jump to return address
|
||||||
|
codegen.br(scratch_reg);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ add_executable(jit_tests
|
|||||||
test_block_manager.cpp
|
test_block_manager.cpp
|
||||||
test_execution_engine.cpp
|
test_execution_engine.cpp
|
||||||
test_block_linking.cpp
|
test_block_linking.cpp
|
||||||
|
test_call_ret.cpp
|
||||||
main.cpp
|
main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
151
tests/test_call_ret.cpp
Normal file
151
tests/test_call_ret.cpp
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/decoder.h"
|
||||||
|
#include "core/jit/arm64_codegen.h"
|
||||||
|
#include "core/jit/register_mapping.h"
|
||||||
|
#include "core/jit/x86_64_translator.h"
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#if defined(__APPLE__) && defined(ARCH_ARM64)
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace Core::Jit;
|
||||||
|
|
||||||
|
class CallRetTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
// Allocate executable memory for test code
|
||||||
|
#if defined(__APPLE__) && defined(ARCH_ARM64)
|
||||||
|
test_code_buffer = mmap(nullptr, 64 * 1024, PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
ASSERT_NE(test_code_buffer, MAP_FAILED)
|
||||||
|
<< "Failed to allocate executable memory for test";
|
||||||
|
pthread_jit_write_protect_np(0);
|
||||||
|
#else
|
||||||
|
test_code_buffer =
|
||||||
|
mmap(nullptr, 64 * 1024, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
ASSERT_NE(test_code_buffer, MAP_FAILED)
|
||||||
|
<< "Failed to allocate executable memory for test";
|
||||||
|
#endif
|
||||||
|
codegen = std::make_unique<Arm64CodeGenerator>(64 * 1024, test_code_buffer);
|
||||||
|
register_mapper = std::make_unique<RegisterMapper>();
|
||||||
|
translator = std::make_unique<X86_64Translator>(*codegen, *register_mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
translator.reset();
|
||||||
|
register_mapper.reset();
|
||||||
|
codegen.reset();
|
||||||
|
if (test_code_buffer != MAP_FAILED) {
|
||||||
|
munmap(test_code_buffer, 64 * 1024);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void *test_code_buffer = MAP_FAILED;
|
||||||
|
std::unique_ptr<Arm64CodeGenerator> codegen;
|
||||||
|
std::unique_ptr<RegisterMapper> register_mapper;
|
||||||
|
std::unique_ptr<X86_64Translator> translator;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test that RET translation generates ARM64 code
|
||||||
|
TEST_F(CallRetTest, TranslateRet) {
|
||||||
|
// x86_64 RET instruction: C3
|
||||||
|
u8 x86_ret[] = {0xC3};
|
||||||
|
|
||||||
|
ZydisDecodedInstruction instruction;
|
||||||
|
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||||
|
|
||||||
|
ZyanStatus status = Common::Decoder::Instance()->decodeInstruction(
|
||||||
|
instruction, operands, x86_ret, sizeof(x86_ret));
|
||||||
|
|
||||||
|
if (!ZYAN_SUCCESS(status)) {
|
||||||
|
GTEST_SKIP()
|
||||||
|
<< "Failed to decode RET instruction - Zydis may not be available";
|
||||||
|
}
|
||||||
|
|
||||||
|
// RET translation should succeed
|
||||||
|
bool result = translator->TranslateRet(instruction, operands);
|
||||||
|
EXPECT_TRUE(result) << "RET translation should succeed";
|
||||||
|
EXPECT_GT(codegen->getSize(), 0) << "RET should generate ARM64 code";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that CALL translation generates ARM64 code
|
||||||
|
TEST_F(CallRetTest, TranslateDirectCall) {
|
||||||
|
// x86_64 CALL instruction: E8 <offset> (near relative call, 32-bit offset)
|
||||||
|
// E8 00 10 00 00 = CALL +0x1000
|
||||||
|
u8 x86_call[] = {0xE8, 0x00, 0x10, 0x00, 0x00};
|
||||||
|
|
||||||
|
ZydisDecodedInstruction instruction;
|
||||||
|
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||||
|
|
||||||
|
ZyanStatus status = Common::Decoder::Instance()->decodeInstruction(
|
||||||
|
instruction, operands, x86_call, sizeof(x86_call));
|
||||||
|
|
||||||
|
if (!ZYAN_SUCCESS(status)) {
|
||||||
|
GTEST_SKIP()
|
||||||
|
<< "Failed to decode CALL instruction - Zydis may not be available";
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALL translation should succeed
|
||||||
|
bool result = translator->TranslateCall(instruction, operands, 0x400000);
|
||||||
|
EXPECT_TRUE(result) << "CALL translation should succeed";
|
||||||
|
EXPECT_GT(codegen->getSize(), 0) << "CALL should generate ARM64 code";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that CALL pushes return address to stack
|
||||||
|
TEST_F(CallRetTest, CallPushesReturnAddress) {
|
||||||
|
// Simulate a CALL instruction
|
||||||
|
// We need to verify that the stack pointer is decremented and return address
|
||||||
|
// is stored This is a simplified test - full implementation will need
|
||||||
|
// execution engine integration
|
||||||
|
|
||||||
|
// For now, just verify CALL generates code
|
||||||
|
u8 x86_call[] = {0xE8, 0x00, 0x10, 0x00, 0x00};
|
||||||
|
|
||||||
|
ZydisDecodedInstruction instruction;
|
||||||
|
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||||
|
|
||||||
|
ZyanStatus status = Common::Decoder::Instance()->decodeInstruction(
|
||||||
|
instruction, operands, x86_call, sizeof(x86_call));
|
||||||
|
|
||||||
|
if (!ZYAN_SUCCESS(status)) {
|
||||||
|
GTEST_SKIP() << "Failed to decode CALL instruction";
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size_before = codegen->getSize();
|
||||||
|
bool result = translator->TranslateCall(instruction, operands, 0x400000);
|
||||||
|
size_t size_after = codegen->getSize();
|
||||||
|
|
||||||
|
EXPECT_TRUE(result);
|
||||||
|
EXPECT_GT(size_after, size_before) << "CALL should generate code";
|
||||||
|
// CALL should generate more code than a simple branch (needs stack
|
||||||
|
// manipulation)
|
||||||
|
EXPECT_GE(size_after - size_before, 4)
|
||||||
|
<< "CALL should generate multiple instructions";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that RET pops return address from stack
|
||||||
|
TEST_F(CallRetTest, RetPopsReturnAddress) {
|
||||||
|
// RET instruction should pop return address and jump to it
|
||||||
|
u8 x86_ret[] = {0xC3};
|
||||||
|
|
||||||
|
ZydisDecodedInstruction instruction;
|
||||||
|
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||||
|
|
||||||
|
ZyanStatus status = Common::Decoder::Instance()->decodeInstruction(
|
||||||
|
instruction, operands, x86_ret, sizeof(x86_ret));
|
||||||
|
|
||||||
|
if (!ZYAN_SUCCESS(status)) {
|
||||||
|
GTEST_SKIP() << "Failed to decode RET instruction";
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size_before = codegen->getSize();
|
||||||
|
bool result = translator->TranslateRet(instruction, operands);
|
||||||
|
size_t size_after = codegen->getSize();
|
||||||
|
|
||||||
|
EXPECT_TRUE(result);
|
||||||
|
EXPECT_GT(size_after, size_before) << "RET should generate code";
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user