diff --git a/core/adapters/corelliumadapter.cpp b/core/adapters/corelliumadapter.cpp index db410daf..27ba8e2e 100644 --- a/core/adapters/corelliumadapter.cpp +++ b/core/adapters/corelliumadapter.cpp @@ -49,6 +49,8 @@ using namespace BinaryNinja; using namespace std; using namespace BinaryNinjaDebugger; +static bool IsBigEndianArchitecture(const std::string& arch); + CorelliumAdapter::CorelliumAdapter(BinaryView* data, bool redirectGDBServer): DebugAdapter(data) { m_isTargetRunning = false; @@ -93,6 +95,7 @@ bool CorelliumAdapter::LoadRegisterInfo() std::string architecture{}; std::string os_abi{}; + std::string endian{}; size_t lastRegIndex = -1; for (auto node = doc.first_child().child("architecture"); node; node = node.next_sibling()) { @@ -100,10 +103,11 @@ bool CorelliumAdapter::LoadRegisterInfo() if ( node.name() == "architecture"s ) architecture = node.child_value(); - if ( node.name() == "osabi"s ) + else if ( node.name() == "osabi"s ) os_abi = node.child_value(); - - if ( node.name() == "feature"s ) + else if ( node.name() == "endian"s ) + endian = node.child_value(); + else if ( node.name() == "feature"s ) { for (auto reg_child = node.child("reg"); reg_child; reg_child = reg_child.next_sibling()) { @@ -134,6 +138,9 @@ bool CorelliumAdapter::LoadRegisterInfo() if (architecture.empty()) throw std::runtime_error("failed to find architecture"); + // Store the original architecture for endianness detection before stripping the prefix + std::string fullArchitecture = architecture; + if (architecture.find(':') != std::string::npos) { architecture.erase(0, architecture.find(':') + 1); @@ -143,6 +150,12 @@ bool CorelliumAdapter::LoadRegisterInfo() } m_remoteArch = architecture; + // Determine endianness: prefer explicit element, fall back to architecture-based detection + if (!endian.empty()) + m_isBigEndian = (endian == "big"); + else + m_isBigEndian = IsBigEndianArchitecture(fullArchitecture); + std::unordered_map id_name{}; std::unordered_map id_width{}; @@ -438,6 +451,22 @@ bool CorelliumAdapter::BreakpointExists(uint64_t address) const DebugBreakpoint(address)) != this->m_debugBreakpoints.end(); } +static bool IsBigEndianArchitecture(const std::string& arch) { + // PowerPC architectures (powerpc, ppc, common from "powerpc:common") + if (arch.find("powerpc") != std::string::npos || arch.find("ppc") != std::string::npos || arch == "common") + return true; + // SPARC + if (arch.find("sparc") != std::string::npos) + return true; + // Motorola 68k + if (arch.find("m68k") != std::string::npos || arch.find("68k") != std::string::npos) + return true; + // IBM S/390 + if (arch.find("s390") != std::string::npos) + return true; + return false; +} + static intx::uint512 parseLittleEndianHexToUint512(const std::string& hex) { if (hex.size() % 2 != 0) return {}; @@ -456,6 +485,27 @@ static intx::uint512 parseLittleEndianHexToUint512(const std::string& hex) { return intx::le::load(buffer); } +static intx::uint512 parseBigEndianHexToUint512(const std::string& hex) { + if (hex.size() % 2 != 0) + return {}; + + uint8_t buffer[64] = {}; // Zero-initialized + + size_t byteCount = hex.size() / 2; + size_t limit = std::min(byteCount, size_t(64)); + + // For big-endian: the hex string has MSB first. intx::be::load expects MSB at buffer[0] + // for a full 512-bit value, so we must right-justify the bytes in the buffer. + size_t offset = 64 - limit; + for (size_t i = 0; i < limit; ++i) + { + std::string byteStr = hex.substr(i * 2, 2); + buffer[offset + i] = static_cast(strtoul(byteStr.c_str(), nullptr, 16)); + } + + return intx::be::load(buffer); +} + std::unordered_map CorelliumAdapter::ReadAllRegisters() { if (m_regCache.has_value()) @@ -485,7 +535,8 @@ std::unordered_map CorelliumAdapter::ReadAllRegister const auto number_of_chars = 2 * ( register_info.m_bitSize / 8 ); const auto value_string = register_info_reply_string.substr(0, number_of_chars); if (!value_string.empty()) { - intx::uint512 value = parseLittleEndianHexToUint512(value_string); + intx::uint512 value = m_isBigEndian ? parseBigEndianHexToUint512(value_string) + : parseLittleEndianHexToUint512(value_string); all_regs[register_name] = DebugRegister(register_name, value, register_info.m_bitSize, register_info.m_regNum); } register_info_reply_string.erase(0, number_of_chars); @@ -521,6 +572,23 @@ static std::string uint512ToLittleEndianHex(const intx::uint512& value, size_t w return result; } +static std::string uint512ToBigEndianHex(const intx::uint512& value, size_t width) { + // Truncate to 64 bytes (512 bits max) + if (width > 64) + width = 64; + + uint8_t buffer[64] = {}; + intx::be::store(buffer, value); // Store as big-endian + + // For big-endian, we need to output starting from the correct offset to get 'width' bytes + size_t offset = 64 - width; + std::string result; + for (size_t i = 0; i < width; ++i) + result += fmt::format("{:02X}", buffer[offset + i]); + + return result; +} + bool CorelliumAdapter::WriteRegister(const std::string& reg, intx::uint512 value) { if (m_isTargetRunning) @@ -529,7 +597,8 @@ bool CorelliumAdapter::WriteRegister(const std::string& reg, intx::uint512 value if (!this->m_registerInfo.contains(reg)) return false; - const auto newRegString = uint512ToLittleEndianHex(value, this->m_registerInfo[reg].m_bitSize / 8); + const auto newRegString = m_isBigEndian ? uint512ToBigEndianHex(value, this->m_registerInfo[reg].m_bitSize / 8) + : uint512ToLittleEndianHex(value, this->m_registerInfo[reg].m_bitSize / 8); const auto reply = this->m_rspConnector->TransmitAndReceive(RspData("P{:02X}={}", this->m_registerInfo[reg].m_regNum, newRegString)); if (reply.m_data[0]) diff --git a/core/adapters/corelliumadapter.h b/core/adapters/corelliumadapter.h index 1574e2a1..49b6342d 100644 --- a/core/adapters/corelliumadapter.h +++ b/core/adapters/corelliumadapter.h @@ -76,6 +76,10 @@ namespace BinaryNinjaDebugger // support the case -- so we do not really lose a lot anyways. std::string m_remoteArch; + // Whether the target uses big-endian byte order. Determined from target XML element + // or inferred from architecture name. + bool m_isBigEndian = false; + void InvalidateCache(); virtual DebugStopReason SignalToStopReason(std::unordered_map& map); diff --git a/core/adapters/esrevenadapter.cpp b/core/adapters/esrevenadapter.cpp index 922a8ec5..835733be 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -51,6 +51,8 @@ using namespace BinaryNinja; using namespace std; using namespace BinaryNinjaDebugger; +static bool IsBigEndianArchitecture(const std::string& arch); + EsrevenAdapter::EsrevenAdapter(BinaryView* data, bool redirectGDBServer): DebugAdapter(data) { m_isTargetRunning = false; @@ -95,6 +97,7 @@ bool EsrevenAdapter::LoadRegisterInfo() std::string architecture{}; std::string os_abi{}; + std::string endian{}; size_t lastRegIndex = -1; auto processFeatures = [&](const pugi::xml_node& node) { @@ -134,6 +137,8 @@ bool EsrevenAdapter::LoadRegisterInfo() architecture = node.child_value(); else if ( node.name() == "osabi"s ) os_abi = node.child_value(); + else if ( node.name() == "endian"s ) + endian = node.child_value(); else if ( node.name() == "feature"s ) processFeatures(node); else if (node.name() == "xi:include"s ) @@ -157,6 +162,9 @@ bool EsrevenAdapter::LoadRegisterInfo() if (architecture.empty()) throw std::runtime_error("failed to find architecture"); + // Store the original architecture for endianness detection before stripping the prefix + std::string fullArchitecture = architecture; + if (architecture.find(':') != std::string::npos) { architecture.erase(0, architecture.find(':') + 1); @@ -166,6 +174,12 @@ bool EsrevenAdapter::LoadRegisterInfo() } m_remoteArch = architecture; + // Determine endianness: prefer explicit element, fall back to architecture-based detection + if (!endian.empty()) + m_isBigEndian = (endian == "big"); + else + m_isBigEndian = IsBigEndianArchitecture(fullArchitecture); + std::unordered_map id_name{}; std::unordered_map id_width{}; @@ -473,6 +487,22 @@ bool EsrevenAdapter::BreakpointExists(uint64_t address) const DebugBreakpoint(address)) != this->m_debugBreakpoints.end(); } +static bool IsBigEndianArchitecture(const std::string& arch) { + // PowerPC architectures (powerpc, ppc, common from "powerpc:common") + if (arch.find("powerpc") != std::string::npos || arch.find("ppc") != std::string::npos || arch == "common") + return true; + // SPARC + if (arch.find("sparc") != std::string::npos) + return true; + // Motorola 68k + if (arch.find("m68k") != std::string::npos || arch.find("68k") != std::string::npos) + return true; + // IBM S/390 + if (arch.find("s390") != std::string::npos) + return true; + return false; +} + static intx::uint512 parseLittleEndianHexToUint512(const std::string& hex) { if (hex.size() % 2 != 0) return {}; @@ -491,6 +521,27 @@ static intx::uint512 parseLittleEndianHexToUint512(const std::string& hex) { return intx::le::load(buffer); } +static intx::uint512 parseBigEndianHexToUint512(const std::string& hex) { + if (hex.size() % 2 != 0) + return {}; + + uint8_t buffer[64] = {}; // Zero-initialized + + size_t byteCount = hex.size() / 2; + size_t limit = std::min(byteCount, size_t(64)); + + // For big-endian: the hex string has MSB first. intx::be::load expects MSB at buffer[0] + // for a full 512-bit value, so we must right-justify the bytes in the buffer. + size_t offset = 64 - limit; + for (size_t i = 0; i < limit; ++i) + { + std::string byteStr = hex.substr(i * 2, 2); + buffer[offset + i] = static_cast(strtoul(byteStr.c_str(), nullptr, 16)); + } + + return intx::be::load(buffer); +} + std::unordered_map EsrevenAdapter::ReadAllRegisters() { if (m_isTargetRunning || !m_rspConnector) @@ -523,7 +574,8 @@ std::unordered_map EsrevenAdapter::ReadAllRegisters( const auto number_of_chars = 2 * ( register_info.m_bitSize / 8 ); const auto value_string = register_info_reply_string.substr(0, number_of_chars); if (!value_string.empty()) { - intx::uint512 value = parseLittleEndianHexToUint512(value_string); + intx::uint512 value = m_isBigEndian ? parseBigEndianHexToUint512(value_string) + : parseLittleEndianHexToUint512(value_string); all_regs[register_name] = DebugRegister(register_name, value, register_info.m_bitSize, register_info.m_regNum); } register_info_reply_string.erase(0, number_of_chars); @@ -559,6 +611,24 @@ static std::string uint512ToLittleEndianHex(const intx::uint512& value, size_t w return result; } +static std::string uint512ToBigEndianHex(const intx::uint512& value, size_t width) { + // Truncate to 64 bytes (512 bits max) + if (width > 64) + width = 64; + + uint8_t buffer[64] = {}; + intx::be::store(buffer, value); // Store as big-endian + + std::string result; + // For big-endian, we need to output from the position where the value starts + // The value is stored right-aligned in the 64-byte buffer + size_t offset = 64 - width; + for (size_t i = 0; i < width; ++i) + result += fmt::format("{:02X}", buffer[offset + i]); + + return result; +} + bool EsrevenAdapter::WriteRegister(const std::string& reg, intx::uint512 value) { if (m_isTargetRunning || !m_rspConnector) @@ -567,7 +637,8 @@ bool EsrevenAdapter::WriteRegister(const std::string& reg, intx::uint512 value) if (!this->m_registerInfo.contains(reg)) return false; - const auto newRegString = uint512ToLittleEndianHex(value, this->m_registerInfo[reg].m_bitSize / 8); + const auto newRegString = m_isBigEndian ? uint512ToBigEndianHex(value, this->m_registerInfo[reg].m_bitSize / 8) + : uint512ToLittleEndianHex(value, this->m_registerInfo[reg].m_bitSize / 8); const auto reply = this->m_rspConnector->TransmitAndReceive(RspData("P{:02X}={}", this->m_registerInfo[reg].m_regNum, newRegString)); diff --git a/core/adapters/esrevenadapter.h b/core/adapters/esrevenadapter.h index b702bfe5..d36ed29f 100644 --- a/core/adapters/esrevenadapter.h +++ b/core/adapters/esrevenadapter.h @@ -79,6 +79,10 @@ namespace BinaryNinjaDebugger // support the case -- so we do not really lose a lot anyways. std::string m_remoteArch; + // Whether the target uses big-endian byte order. Determined from target XML element + // or inferred from architecture name. + bool m_isBigEndian = false; + bool m_canReverseContinue = false; bool m_canReverseStep = false; diff --git a/core/adapters/gdbadapter.cpp b/core/adapters/gdbadapter.cpp index fd59106f..056aef68 100644 --- a/core/adapters/gdbadapter.cpp +++ b/core/adapters/gdbadapter.cpp @@ -51,6 +51,8 @@ using namespace BinaryNinja; using namespace std; using namespace BinaryNinjaDebugger; +static bool IsBigEndianArchitecture(const std::string& arch); + GdbAdapter::GdbAdapter(BinaryView* data, bool redirectGDBServer): DebugAdapter(data) { m_isTargetRunning = false; @@ -95,6 +97,7 @@ bool GdbAdapter::LoadRegisterInfo() std::string architecture{}; std::string os_abi{}; + std::string endian{}; size_t lastRegIndex = -1; auto processFeatures = [&](const pugi::xml_node& node) { @@ -134,6 +137,8 @@ bool GdbAdapter::LoadRegisterInfo() architecture = node.child_value(); else if ( node.name() == "osabi"s ) os_abi = node.child_value(); + else if ( node.name() == "endian"s ) + endian = node.child_value(); else if ( node.name() == "feature"s ) processFeatures(node); else if (node.name() == "xi:include"s ) @@ -157,6 +162,9 @@ bool GdbAdapter::LoadRegisterInfo() if (architecture.empty()) throw std::runtime_error("failed to find architecture"); + // Store the original architecture for endianness detection before stripping the prefix + std::string fullArchitecture = architecture; + if (architecture.find(':') != std::string::npos) { architecture.erase(0, architecture.find(':') + 1); @@ -166,6 +174,12 @@ bool GdbAdapter::LoadRegisterInfo() } m_remoteArch = architecture; + // Determine endianness: prefer explicit element, fall back to architecture-based detection + if (!endian.empty()) + m_isBigEndian = (endian == "big"); + else + m_isBigEndian = IsBigEndianArchitecture(fullArchitecture); + std::unordered_map id_name{}; std::unordered_map id_width{}; @@ -471,6 +485,22 @@ bool GdbAdapter::BreakpointExists(uint64_t address) const DebugBreakpoint(address)) != this->m_debugBreakpoints.end(); } +static bool IsBigEndianArchitecture(const std::string& arch) { + // PowerPC architectures (powerpc, ppc, common from "powerpc:common") + if (arch.find("powerpc") != std::string::npos || arch.find("ppc") != std::string::npos || arch == "common") + return true; + // SPARC + if (arch.find("sparc") != std::string::npos) + return true; + // Motorola 68k + if (arch.find("m68k") != std::string::npos || arch.find("68k") != std::string::npos) + return true; + // IBM S/390 + if (arch.find("s390") != std::string::npos) + return true; + return false; +} + static intx::uint512 parseLittleEndianHexToUint512(const std::string& hex) { if (hex.size() % 2 != 0) return {}; @@ -489,6 +519,27 @@ static intx::uint512 parseLittleEndianHexToUint512(const std::string& hex) { return intx::le::load(buffer); } +static intx::uint512 parseBigEndianHexToUint512(const std::string& hex) { + if (hex.size() % 2 != 0) + return {}; + + uint8_t buffer[64] = {}; // Zero-initialized + + size_t byteCount = hex.size() / 2; + size_t limit = std::min(byteCount, size_t(64)); + + // For big-endian: the hex string has MSB first. intx::be::load expects MSB at buffer[0] + // for a full 512-bit value, so we must right-justify the bytes in the buffer. + size_t offset = 64 - limit; + for (size_t i = 0; i < limit; ++i) + { + std::string byteStr = hex.substr(i * 2, 2); + buffer[offset + i] = static_cast(strtoul(byteStr.c_str(), nullptr, 16)); + } + + return intx::be::load(buffer); +} + std::unordered_map GdbAdapter::ReadAllRegisters() { if (m_isTargetRunning || !m_rspConnector) @@ -521,7 +572,8 @@ std::unordered_map GdbAdapter::ReadAllRegisters() const auto number_of_chars = 2 * ( register_info.m_bitSize / 8 ); const auto value_string = register_info_reply_string.substr(0, number_of_chars); if (!value_string.empty()) { - intx::uint512 value = parseLittleEndianHexToUint512(value_string); + intx::uint512 value = m_isBigEndian ? parseBigEndianHexToUint512(value_string) + : parseLittleEndianHexToUint512(value_string); all_regs[register_name] = DebugRegister(register_name, value, register_info.m_bitSize, register_info.m_regNum); } register_info_reply_string.erase(0, number_of_chars); @@ -557,6 +609,24 @@ static std::string uint512ToLittleEndianHex(const intx::uint512& value, size_t w return result; } +static std::string uint512ToBigEndianHex(const intx::uint512& value, size_t width) { + // Truncate to 64 bytes (512 bits max) + if (width > 64) + width = 64; + + uint8_t buffer[64] = {}; + intx::be::store(buffer, value); // Store as big-endian + + // For big-endian, we need to output starting from the correct offset to get 'width' bytes + // The be::store puts MSB at buffer[0], so we need to skip leading zeros + size_t offset = 64 - width; + std::string result; + for (size_t i = 0; i < width; ++i) + result += fmt::format("{:02X}", buffer[offset + i]); + + return result; +} + bool GdbAdapter::WriteRegister(const std::string& reg, intx::uint512 value) { if (m_isTargetRunning || !m_rspConnector) @@ -565,7 +635,8 @@ bool GdbAdapter::WriteRegister(const std::string& reg, intx::uint512 value) if (!this->m_registerInfo.contains(reg)) return false; - const auto newRegString = uint512ToLittleEndianHex(value, this->m_registerInfo[reg].m_bitSize / 8); + const auto newRegString = m_isBigEndian ? uint512ToBigEndianHex(value, this->m_registerInfo[reg].m_bitSize / 8) + : uint512ToLittleEndianHex(value, this->m_registerInfo[reg].m_bitSize / 8); const auto reply = this->m_rspConnector->TransmitAndReceive(RspData("P{:02X}={}", this->m_registerInfo[reg].m_regNum, newRegString)); if (reply.m_data[0]) diff --git a/core/adapters/gdbadapter.h b/core/adapters/gdbadapter.h index dd62b1b5..5d9896b1 100644 --- a/core/adapters/gdbadapter.h +++ b/core/adapters/gdbadapter.h @@ -79,6 +79,10 @@ namespace BinaryNinjaDebugger // support the case -- so we do not really lose a lot anyways. std::string m_remoteArch; + // Whether the target uses big-endian byte order. Determined from target XML element + // or inferred from architecture name. + bool m_isBigEndian = false; + bool m_canReverseContinue = false; bool m_canReverseStep = false;