From 8814cb4cb224d3bbff5e3ee7cece1cf5a86dadb9 Mon Sep 17 00:00:00 2001 From: Ivan Movchan Date: Thu, 17 Jul 2025 15:25:07 +0300 Subject: [PATCH 1/8] Refuse loading executable files without executable code HyperCPU emulator loads the executable file, calculates the size of executable code in it (aka "binary size"), and then allocates the memory for reading and executing the code. If the executable code is missing for some reason, emulator will fail with std::bad_alloc exception. This commit fixes this bug and adds a little check if the binary size is more than zero. Signed-off-by: Ivan Movchan --- src/Emulator/Main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Emulator/Main.cpp b/src/Emulator/Main.cpp index a70fc7ac..4d51db57 100644 --- a/src/Emulator/Main.cpp +++ b/src/Emulator/Main.cpp @@ -140,6 +140,11 @@ int main(int argc, char** argv) { spdlog::error("Invalid type field!"); return 1; } + + if (binarysize <= 0) { + spdlog::error("No executable code found!"); + return 1; + } std::unique_ptr buf(new char[binarysize]); file.read(buf.get(), binarysize); From f225eb9d56582ca706781fd905cb0f1b814e0c37 Mon Sep 17 00:00:00 2001 From: Ivan Movchan Date: Thu, 17 Jul 2025 15:25:49 +0300 Subject: [PATCH 2/8] Fix invalid CPU halt without HALT command HyperCPU emulator runs the executable code until HALT command is sent. If the end of the code is reached, and there is no HALT command, emulator will continue reading and executing the code and finally abort with error like "Interrupt was triggered, but failed to execute handler" with XIP exceeding the size of the executable code (binary size). This commit implements automatic CPU halt in case XIP exceeds the binary size. Signed-off-by: Ivan Movchan --- src/Emulator/Core/CPU/CPU.cpp | 6 ++++++ src/Emulator/Core/CPU/CPU.hpp | 1 + 2 files changed, 7 insertions(+) diff --git a/src/Emulator/Core/CPU/CPU.cpp b/src/Emulator/Core/CPU/CPU.cpp index 31d0f01e..ebd3b454 100644 --- a/src/Emulator/Core/CPU/CPU.cpp +++ b/src/Emulator/Core/CPU/CPU.cpp @@ -10,6 +10,7 @@ HyperCPU::CPU::CPU(std::uint16_t core_count, std::uint64_t mem_size, char* binar : mem_controller(dynamic_cast(new MemoryControllerST(mem_size, this))), core_count(core_count), total_mem(mem_size), + binary_size(binary_size), halted(false), ivt_initialized(false), io_ctl(std::make_unique()) { @@ -251,6 +252,11 @@ void HyperCPU::CPU::Run() { pending_interrupt.reset(); continue; } + + if ((*xip) >= binary_size) { + halted = true; + break; + } buffer = m_decoder->FetchAndDecode(); diff --git a/src/Emulator/Core/CPU/CPU.hpp b/src/Emulator/Core/CPU/CPU.hpp index 8899dfd8..36da7013 100644 --- a/src/Emulator/Core/CPU/CPU.hpp +++ b/src/Emulator/Core/CPU/CPU.hpp @@ -29,6 +29,7 @@ namespace HyperCPU { // Data std::uint16_t core_count; std::uint64_t total_mem; + std::uint64_t binary_size; bool halted; // General space for registers From 5d80fa995b4eff904b529c46506103075daf28c0 Mon Sep 17 00:00:00 2001 From: Ivan Movchan Date: Thu, 17 Jul 2025 17:35:04 +0300 Subject: [PATCH 3/8] Move XIP exception handling to interrupt handler The previous solution in HyperCPU::CPU::Run() method could lead to performance degradation. Signed-off-by: Ivan Movchan --- src/Emulator/Core/CPU/CPU.cpp | 5 ----- src/Emulator/Core/CPU/Interrupts/InterruptHandler.cpp | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Emulator/Core/CPU/CPU.cpp b/src/Emulator/Core/CPU/CPU.cpp index ebd3b454..472c15af 100644 --- a/src/Emulator/Core/CPU/CPU.cpp +++ b/src/Emulator/Core/CPU/CPU.cpp @@ -253,11 +253,6 @@ void HyperCPU::CPU::Run() { continue; } - if ((*xip) >= binary_size) { - halted = true; - break; - } - buffer = m_decoder->FetchAndDecode(); switch (buffer.m_opcode) { diff --git a/src/Emulator/Core/CPU/Interrupts/InterruptHandler.cpp b/src/Emulator/Core/CPU/Interrupts/InterruptHandler.cpp index 3c3ed6f5..09eeedb2 100644 --- a/src/Emulator/Core/CPU/Interrupts/InterruptHandler.cpp +++ b/src/Emulator/Core/CPU/Interrupts/InterruptHandler.cpp @@ -6,6 +6,11 @@ #include "PCH/CStd.hpp" void HyperCPU::CPU::TriggerInterrupt(HyperCPU::cpu_exceptions exception) { + if (*xip >= binary_size) { + spdlog::error("XIP exceeded the binary size, aborting!"); + std::abort(); + } + if (!ivt_initialized || pending_interrupt.has_value()) { spdlog::error("Interrupt was triggered, but failed to execute handler! XIP: {}", *xip); std::abort(); From ffc7aae9e671704ef3c2b7f30df7c322339c41b7 Mon Sep 17 00:00:00 2001 From: Ivan Movchan Date: Thu, 17 Jul 2025 19:48:01 +0300 Subject: [PATCH 4/8] Improve binary file validation check HyperCPU emulator will refuse binary file if its executable code size is lesser than declared in the header, or if its header is broken. Signed-off-by: Ivan Movchan --- src/Emulator/Main.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Emulator/Main.cpp b/src/Emulator/Main.cpp index 4d51db57..5e7f9e74 100644 --- a/src/Emulator/Main.cpp +++ b/src/Emulator/Main.cpp @@ -141,11 +141,16 @@ int main(int argc, char** argv) { return 1; } - if (binarysize <= 0) { - spdlog::error("No executable code found!"); + if (std::filesystem::file_size(source) < sizeof(HyperCPU::GenericHeader)) { + spdlog::error("Invalid binary header! (excepted {} bytes, got {})", sizeof(HyperCPU::GenericHeader), std::filesystem::file_size(source)); return 1; } - + + if (std::filesystem::file_size(source) != (sizeof(HyperCPU::GenericHeader) + header.code_size)) { + spdlog::error("Invalid binary code! (expected {} bytes, got {})", header.code_size, binarysize); + return 1; + } + std::unique_ptr buf(new char[binarysize]); file.read(buf.get(), binarysize); From 644940c2e51128942fde4f1233478941b95a35fe Mon Sep 17 00:00:00 2001 From: Ivan Movchan Date: Thu, 17 Jul 2025 19:50:05 +0300 Subject: [PATCH 5/8] Fix CPU_TEST.INSTR_INTR_R_b64 test regression Signed-off-by: Ivan Movchan --- tests/fixtures.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures.hpp b/tests/fixtures.hpp index 12c80ce3..d7f4a7f2 100644 --- a/tests/fixtures.hpp +++ b/tests/fixtures.hpp @@ -92,7 +92,7 @@ class CPU_TEST : public ::testing::Test { HyperCPU::CPU cpu; CPU_TEST() - : cpu(1, 4096) { + : cpu(1, 4096, nullptr, 4096) { } }; From 7f9e8149de16f5dc00f94dddef41471dc0e50b62 Mon Sep 17 00:00:00 2001 From: Ivan Movchan Date: Thu, 17 Jul 2025 20:12:54 +0300 Subject: [PATCH 6/8] Fix EXCEPTION_TEST.CPU_EXCEPTION_IO test Signed-off-by: Ivan Movchan --- tests/fixtures.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures.hpp b/tests/fixtures.hpp index d7f4a7f2..aa8257b2 100644 --- a/tests/fixtures.hpp +++ b/tests/fixtures.hpp @@ -131,7 +131,7 @@ class EXCEPTION_TEST : public ::testing::Test { HyperCPU::CPU cpu; EXCEPTION_TEST() - : cpu(1, 4096) { + : cpu(1, 4096, nullptr, 4096) { } virtual void SetUp() { From c3afdf434bb1aed4c906295ff41212413b5b18de Mon Sep 17 00:00:00 2001 From: Ivan Movchan Date: Sat, 26 Jul 2025 17:59:11 +0300 Subject: [PATCH 7/8] Improved binary file verification Most of the code responsible for binary file verification is now available in VerifyBinaryFile method. The emulator now refuses to run the binary file with a size less than default binary header size. Signed-off-by: Ivan Movchan --- src/Emulator/Main.cpp | 77 +++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/src/Emulator/Main.cpp b/src/Emulator/Main.cpp index 5e7f9e74..69fb612f 100644 --- a/src/Emulator/Main.cpp +++ b/src/Emulator/Main.cpp @@ -75,6 +75,45 @@ std::uint64_t ParseMemoryString(const std::string& str) { return result * multiplier; } +int VerifyBinaryFile(std::string source, HyperCPU::GenericHeader header) { + if (header.magic != HyperCPU::magic) { + spdlog::error("Invalid magic!"); + return 1; + } + + switch (header.version) { + case HyperCPU::Version::PreRelease: + case HyperCPU::Version::Release1_0: + break; + default: + spdlog::error("Invalid release field!"); + return 1; + } + + switch (header.type) { + case HyperCPU::FileType::Binary: + break; + case HyperCPU::FileType::Object: + spdlog::error("Executing object files is not supported, please link it first!"); + return 1; + default: + spdlog::error("Invalid type field!"); + return 1; + } + + if (std::filesystem::file_size(source) < sizeof(HyperCPU::GenericHeader)) { + spdlog::error("Invalid binary header! (excepted {} bytes, got {})", sizeof(HyperCPU::GenericHeader), std::filesystem::file_size(source)); + return 1; + } + + if (std::filesystem::file_size(source) != (sizeof(HyperCPU::GenericHeader) + header.code_size)) { + spdlog::error("Invalid binary code! (expected {} bytes, got {})", header.code_size, (std::filesystem::file_size(source) - sizeof(HyperCPU::GenericHeader))); + return 1; + } + + return 0; +} + int main(int argc, char** argv) { #ifdef HCPU_ENABLE_LIBUNWIND global_bt_controller = BacktraceController(argv[0]); @@ -111,43 +150,17 @@ int main(int argc, char** argv) { } std::ifstream file(source); - std::int64_t binarysize = std::filesystem::file_size(source) - sizeof(HyperCPU::GenericHeader); - HyperCPU::GenericHeader header = ParseHeader(file); - - // Validate header contents - if (header.magic != HyperCPU::magic) { - spdlog::error("Invalid magic!"); + if (std::filesystem::file_size(source) < sizeof(HyperCPU::GenericHeader)) { + spdlog::error("The binary file is too small! (No binary header?)"); return 1; } - switch (header.version) { - case HyperCPU::Version::PreRelease: - case HyperCPU::Version::Release1_0: - break; - default: - spdlog::error("Invalid release field!"); - return 1; - } + std::int64_t binarysize = std::filesystem::file_size(source) - sizeof(HyperCPU::GenericHeader); - switch (header.type) { - case HyperCPU::FileType::Binary: - break; - case HyperCPU::FileType::Object: - spdlog::error("Executing object files is not supported, please link it first!"); - return 1; - default: - spdlog::error("Invalid type field!"); - return 1; - } - - if (std::filesystem::file_size(source) < sizeof(HyperCPU::GenericHeader)) { - spdlog::error("Invalid binary header! (excepted {} bytes, got {})", sizeof(HyperCPU::GenericHeader), std::filesystem::file_size(source)); - return 1; - } - - if (std::filesystem::file_size(source) != (sizeof(HyperCPU::GenericHeader) + header.code_size)) { - spdlog::error("Invalid binary code! (expected {} bytes, got {})", header.code_size, binarysize); + HyperCPU::GenericHeader header = ParseHeader(file); + + if (!VerifyBinaryFile(source, header)) { return 1; } From a6937500219a1cdec2ec3fb90cdc2b45971c36c0 Mon Sep 17 00:00:00 2001 From: Ivan Movchan Date: Sun, 3 Aug 2025 22:27:55 +0300 Subject: [PATCH 8/8] Improve binary file verification and fix a little bug I wrongly typed !VerifyBinaryFile instead of VerifyBinaryFile. As a result, emulator could continue working in case VerifyBinaryFile returned 1 (error) or shutdown if 0 (no errors). Signed-off-by: Ivan Movchan --- src/Emulator/Main.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Emulator/Main.cpp b/src/Emulator/Main.cpp index 69fb612f..80634cef 100644 --- a/src/Emulator/Main.cpp +++ b/src/Emulator/Main.cpp @@ -75,8 +75,13 @@ std::uint64_t ParseMemoryString(const std::string& str) { return result * multiplier; } -int VerifyBinaryFile(std::string source, HyperCPU::GenericHeader header) { - if (header.magic != HyperCPU::magic) { +int VerifyBinaryFile(std::int64_t filesize, HyperCPU::GenericHeader header) { + if (filesize < sizeof(HyperCPU::GenericHeader)) { + spdlog::error("Invalid binary header! (excepted {} bytes, got {})", sizeof(HyperCPU::GenericHeader), filesize); + return 1; + } + + if (header.magic != HyperCPU::magic) { spdlog::error("Invalid magic!"); return 1; } @@ -101,16 +106,11 @@ int VerifyBinaryFile(std::string source, HyperCPU::GenericHeader header) { return 1; } - if (std::filesystem::file_size(source) < sizeof(HyperCPU::GenericHeader)) { - spdlog::error("Invalid binary header! (excepted {} bytes, got {})", sizeof(HyperCPU::GenericHeader), std::filesystem::file_size(source)); + if (filesize != (sizeof(HyperCPU::GenericHeader) + header.code_size)) { + spdlog::error("Invalid binary code! (expected {} bytes, got {})", header.code_size, (filesize - sizeof(HyperCPU::GenericHeader))); return 1; } - if (std::filesystem::file_size(source) != (sizeof(HyperCPU::GenericHeader) + header.code_size)) { - spdlog::error("Invalid binary code! (expected {} bytes, got {})", header.code_size, (std::filesystem::file_size(source) - sizeof(HyperCPU::GenericHeader))); - return 1; - } - return 0; } @@ -155,12 +155,13 @@ int main(int argc, char** argv) { spdlog::error("The binary file is too small! (No binary header?)"); return 1; } - - std::int64_t binarysize = std::filesystem::file_size(source) - sizeof(HyperCPU::GenericHeader); + + std::int64_t filesize = std::filesystem::file_size(source); + std::int64_t binarysize = filesize - sizeof(HyperCPU::GenericHeader); HyperCPU::GenericHeader header = ParseHeader(file); - if (!VerifyBinaryFile(source, header)) { + if (VerifyBinaryFile(filesize, header)) { return 1; }