--- /dev/null
+// Copyright (c) 2023 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#include "debugger/interop_arm32_singlestep_helpers.h"
+#include "debugger/interop_brk_helpers.h"
+
+#include <sys/uio.h> // iovec
+#include <elf.h> // NT_PRSTATUS
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include <functional>
+#include "utils/logger.h"
+
+
+namespace netcoredbg
+{
+namespace InteropDebugging
+{
+
+struct sw_singlestep_nextpc_t
+{
+ std::uintptr_t addr = 0;
+ bool isThumb = false;
+
+ sw_singlestep_nextpc_t(std::uintptr_t addr_, bool isThumb_) :
+ addr(addr_),
+ isThumb(isThumb_)
+ {}
+};
+
+constexpr int REG_SP = 13;
+constexpr int REG_LR = 14;
+constexpr int REG_PC = 15;
+constexpr int REG_CPSR = 16;
+
+// Return trueif the processor is currently executing in Thumb mode
+static bool IsExecutingThumb(const user_regs_struct ®s)
+{
+ constexpr int REG_CPSR = 16;
+ std::uintptr_t cpsr = regs.uregs[REG_CPSR];
+
+ // FIXME for `M profiles` (Cortex-M), XPSR_T_BIT must be used instead.
+ // CPSR_T_BIT 0x20 // 5 bit
+ // XPSR_T_BIT 0x01000000 // 25 bit
+ return cpsr & 0x20;
+}
+
+template<typename T>
+bool GetDataFromMemory(pid_t pid, std::uintptr_t addr, T &result)
+{
+ errno = 0;
+ word_t wData = async_ptrace(PTRACE_PEEKDATA, pid, (void*)addr, nullptr);
+ if (errno != 0)
+ {
+ LOGE("Ptrace peekdata error: %s", strerror(errno));
+ return false;
+ }
+
+ // Note, little-endian only architectures are supported now.
+ assert(sizeof(word_t) % sizeof(T) == 0); // result with type `T` must be element of `word_t`-size array
+ result = ((T*)&wData)[0]; // just get first element of array
+ return true;
+}
+
+static inline std::uint32_t MakeSubmask(std::uint32_t x)
+{
+ return ((std::uint32_t)1 << (x + 1)) - 1;
+}
+static inline std::uint32_t GetBits(std::uint32_t val, std::uint32_t first, std::uint32_t last)
+{
+ return (val >> first) & MakeSubmask(last - first);
+}
+static inline std::uint32_t GetBit(std::uint32_t val, std::uint32_t number)
+{
+ return (val >> number) & 1;
+}
+static inline std::uint32_t GetSBits(std::uint32_t val, std::uint32_t first, std::uint32_t last)
+{
+ return (std::int32_t)GetBits(val, first, last) | ((std::int32_t)GetBit(val, last) * ~MakeSubmask(last - first));
+}
+static inline std::uintptr_t CalculateBranchDest(std::uint32_t addr, std::uint32_t instr)
+{
+ // From "4.4 Branch and Branch with Link (B, BL)":
+ // Branch instructions contain a signed 2’s complement 24 bit offset. This is shifted left
+ // two bits, sign extended to 32 bits, and added to the PC. The instruction can therefore
+ // specify a branch of +/- 32Mbytes. The branch offset must take account of the prefetch
+ // operation, which causes the PC to be 2 words (8 bytes) ahead of the current instruction.
+ return addr + 8 + (GetSBits(instr, 0, 23) << 2);
+}
+
+constexpr std::uint32_t INST_AL = 0xe; // allways
+constexpr std::uint32_t INST_NV = 0xf; // unconditional/allways
+
+constexpr std::uint32_t FLAG_N = 0x80000000;
+constexpr std::uint32_t FLAG_Z = 0x40000000;
+constexpr std::uint32_t FLAG_C = 0x20000000;
+constexpr std::uint32_t FLAG_V = 0x10000000;
+
+static bool IsConditionTrue(std::uint32_t cond, std::uint32_t regPS)
+{
+ if (cond == INST_AL || cond == INST_NV)
+ return true;
+
+ static std::vector<std::function<bool(std::uint32_t)>> conditionLogic
+ {
+ [](std::uint32_t regPS) {return ((regPS & FLAG_Z) != 0);}, // INST_EQ = 0x0
+ [](std::uint32_t regPS) {return ((regPS & FLAG_Z) == 0);}, // INST_NE = 0x1
+ [](std::uint32_t regPS) {return ((regPS & FLAG_C) != 0);}, // INST_CS = 0x2
+ [](std::uint32_t regPS) {return ((regPS & FLAG_C) == 0);}, // INST_CC = 0x3
+ [](std::uint32_t regPS) {return ((regPS & FLAG_N) != 0);}, // INST_MI = 0x4
+ [](std::uint32_t regPS) {return ((regPS & FLAG_N) == 0);}, // INST_PL = 0x5
+ [](std::uint32_t regPS) {return ((regPS & FLAG_V) != 0);}, // INST_VS = 0x6
+ [](std::uint32_t regPS) {return ((regPS & FLAG_V) == 0);}, // INST_VC = 0x7
+ [](std::uint32_t regPS) {return ((regPS & (FLAG_C | FLAG_Z)) == FLAG_C);}, // INST_HI = 0x8
+ [](std::uint32_t regPS) {return ((regPS & (FLAG_C | FLAG_Z)) != FLAG_C);}, // INST_LS = 0x9
+ [](std::uint32_t regPS) {return (((regPS & FLAG_N) == 0) == ((regPS & FLAG_V) == 0));}, // INST_GE = 0xa
+ [](std::uint32_t regPS) {return (((regPS & FLAG_N) == 0) != ((regPS & FLAG_V) == 0));}, // INST_LT = 0xb
+ [](std::uint32_t regPS) {return (((regPS & FLAG_Z) == 0) && (((regPS & FLAG_N) == 0) == ((regPS & FLAG_V) == 0)));}, // INST_GT = 0xc
+ [](std::uint32_t regPS) {return (((regPS & FLAG_Z) != 0) || (((regPS & FLAG_N) == 0) != ((regPS & FLAG_V) == 0)));} // INST_LE = 0xd
+ };
+
+ if (conditionLogic.size() < cond)
+ return conditionLogic[cond](regPS);
+
+ return true;
+}
+
+static std::uint32_t ShiftRegValue(const user_regs_struct ®s, std::uint32_t inst, bool carry, std::uint32_t regPC)
+{
+ // if 4 bit == 0
+ // 11-7 bits - shift amount, 5 bit unsigned integer
+ // if 4 bit == 1
+ // 11-8 bits - shift register
+ // 7 bit is `0`
+ //
+ // 6-5 bits - shift type:
+ // 00 = logical left
+ // 01 = logical right
+ // 10 = arithmetic right
+ // 11 = rotate right
+ // 3-0 bits - offset register
+
+ std::uint32_t shift;
+ if (GetBit(inst, 4))
+ {
+ int shiftReg = GetBits(inst, 8, 11);
+ shift = (shiftReg == REG_PC ? regPC + 8 : (std::uint32_t)(regs.uregs[shiftReg])) & 0xff;
+ }
+ else
+ {
+ shift = GetBits(inst, 7, 11);
+ }
+
+ int offsetReg = GetBits(inst, 0, 3);
+ std::uint32_t result = (offsetReg == REG_PC ? (regPC + (GetBit(inst, 4) ? 12 : 8)) : (std::uint32_t)(regs.uregs[offsetReg]));
+
+ static std::vector<std::function<void(std::uint32_t,bool,std::uint32_t&)>> shiftLogic
+ {
+ [](std::uint32_t shift, bool, std::uint32_t &result) { result = shift >= 32 ? 0 : result << shift;}, // LSL = 0
+ [](std::uint32_t shift, bool, std::uint32_t &result) { result = shift >= 32 ? 0 : result >> shift;}, // LSR = 1
+ [](std::uint32_t shift, bool, std::uint32_t &result)
+ {
+ if (shift >= 32)
+ shift = 31;
+
+ result = ((result & 0x80000000L) ? ~((~result) >> shift) : result >> shift);
+ }, // ASR = 2
+ [](std::uint32_t shift, bool carry, std::uint32_t &result)
+ {
+ shift &= 31;
+ if (shift == 0)
+ result = (result >> 1) | (carry ? 0x80000000L : 0);
+ else
+ result = (result >> shift) | (result << (32 - shift));
+ } // ROR/RRX = 3
+ };
+
+ std::uint32_t shiftType = GetBits(inst, 5, 6);
+ if (shiftLogic.size() < shiftType)
+ shiftLogic[shiftType](shift, carry, result);
+
+ return result;
+}
+
+static bool ArmUnconditional_Branches(std::uintptr_t currentPC, std::uint32_t currentInstr, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ nextPC = CalculateBranchDest(currentPC, currentInstr);
+ nextPC |= GetBit(currentInstr, 24) << 1;
+ switchToThumbCode = true;
+ return true;
+};
+
+static bool ArmUnconditional_CoprocessorOperations(std::uintptr_t, std::uint32_t currentInstr, std::uintptr_t&, bool&)
+{
+ if (GetBits(currentInstr, 12, 15) == REG_PC)
+ {
+ LOGE("Failed next PC calculation");
+ return false;
+ }
+ return true;
+};
+
+static bool ArmConditionTrue_Miscellaneous(pid_t, const user_regs_struct ®s, std::uintptr_t currentPS, std::uintptr_t currentPC,
+ std::uint32_t currentInstr, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ // Multiply and Multiply-Accumulate (MUL, MLA)
+ // 4.7.1 Operand restrictions
+ // R15 must not be used as an operand or as the destination register.
+ if (GetBits(currentInstr, 22, 27) == 0 && GetBits(currentInstr, 4, 7) == 9)
+ return true;
+
+ // Multiply Long and Multiply-Accumulate Long (MULL, MLAL)
+ // 4.8.1 Operand restrictions
+ // R15 must not be used as an operand or as a destination register.
+ if (GetBits(currentInstr, 23, 27) == 1 && GetBits(currentInstr, 4, 7) == 9)
+ return true;
+
+ // Single Data Swap (SWP)
+ // 4.12.2 Use of R15
+ // Do not use R15 as an operand (Rd, Rn or Rs) in a SWP instruction.
+ if (GetBits(currentInstr, 23, 27) == 0x2 && GetBits(currentInstr, 20, 21) == 0 && GetBits(currentInstr, 4, 11) == 9)
+ return true;
+
+ // BX register, BLX register
+ if (GetBits(currentInstr, 4, 27) == 0x12fff1 ||
+ GetBits(currentInstr, 4, 27) == 0x12fff3)
+ {
+ std::uintptr_t rn = GetBits(currentInstr, 0, 3);
+ nextPC = ((rn == REG_PC) ? (currentPC + 8) : std::uintptr_t(regs.uregs[rn]));
+ // This instruction also permits the instruction set to be exchanged. When the instruction is executed,
+ // the value of Rn[0] determines whether the instruction stream will be decoded as ARM or THUMB instructions.
+ switchToThumbCode = nextPC & 1;
+ nextPC = nextPC & ~(std::uintptr_t(1)); // Remove useless bits from address.
+ return true;
+ }
+
+ // Halfword and Signed Data Transfer (LDRH/STRH/LDRSB/LDRSH)
+ if ((GetBits(currentInstr, 25, 27) == 0 && GetBit(currentInstr, 22) == 0 && GetBits(currentInstr, 7, 11) == 1 && GetBit(currentInstr, 4) == 1) || // register offset
+ (GetBits(currentInstr, 25, 27) == 0 && GetBit(currentInstr, 22) == 1 && GetBit(currentInstr, 7) == 1 && GetBit(currentInstr, 4) == 1)) // immediate offset
+ {
+ // TODO (load from memory)
+ if (GetBits(currentInstr, 12, 15) == REG_PC && GetBit(currentInstr, 21) == 1)
+ {
+ LOGE("Load PC register from memory for LDRH/LDRSB/LDRSH not implemented.");
+ return false;
+ }
+ // 4.10.5 Use of R15
+ // Write-back should not be specified if R15 is specified as the base register (Rn).
+ if (GetBits(currentInstr, 16, 19) == REG_PC && GetBit(currentInstr, 21) == 1)
+ return false;
+
+ return true;
+ }
+
+ // Parsing of data processing / PSR transfer instructions.
+
+ // if dest register is not PC, just leave
+ if (GetBits(currentInstr, 12, 15) != REG_PC)
+ return true;
+
+ bool carry = (currentPS & FLAG_C) == FLAG_C;
+ std::uintptr_t rn = GetBits(currentInstr, 16, 19);
+ std::uintptr_t operand1 = ((rn == REG_PC) ? (currentPC + 8) : std::uintptr_t(regs.uregs[rn]));
+ std::uintptr_t operand2 = 0;
+
+ if (GetBit(currentInstr, 25))
+ {
+ std::uint32_t immval = GetBits(currentInstr, 0, 7);
+ std::uint32_t rotate = 2 * GetBits(currentInstr, 8, 11);
+ operand2 = ((immval >> rotate) | (immval << (32 - rotate)));
+ }
+ else // operand 2 is a shifted register.
+ {
+ operand2 = ShiftRegValue(regs, currentInstr, carry, currentPC);
+ }
+
+ static std::vector<std::function<std::uintptr_t(std::uintptr_t,std::uintptr_t,std::uintptr_t, bool)>> dataOperations
+ {
+ [](std::uintptr_t, std::uintptr_t operand1, std::uintptr_t operand2, bool) { return operand1 & operand2; }, // and = 0x0
+ [](std::uintptr_t, std::uintptr_t operand1, std::uintptr_t operand2, bool) { return operand1 ^ operand2; }, // eor = 0x1
+ [](std::uintptr_t, std::uintptr_t operand1, std::uintptr_t operand2, bool) { return operand1 - operand2; }, // sub = 0x2
+ [](std::uintptr_t, std::uintptr_t operand1, std::uintptr_t operand2, bool) { return operand2 - operand1; }, // rsb = 0x3
+ [](std::uintptr_t, std::uintptr_t operand1, std::uintptr_t operand2, bool) { return operand1 + operand2; }, // add = 0x4
+ [](std::uintptr_t, std::uintptr_t operand1, std::uintptr_t operand2, bool carry) { return operand1 + operand2 + (carry ? 1 : 0); }, // adc = 0x5
+ [](std::uintptr_t, std::uintptr_t operand1, std::uintptr_t operand2, bool carry) { return operand1 - operand2 + (carry ? 1 : 0); }, // sbc = 0x6
+ [](std::uintptr_t, std::uintptr_t operand1, std::uintptr_t operand2, bool carry) { return operand2 - operand1 + (carry ? 1 : 0); }, // rsc = 0x7
+ // Note, we don't set condition codes, just "do nothing" for this OpCodes.
+ [](std::uintptr_t nextPC, std::uintptr_t, std::uintptr_t, bool) { return nextPC; }, // tst = 0x8
+ [](std::uintptr_t nextPC, std::uintptr_t, std::uintptr_t, bool) { return nextPC; }, // teq = 0x9
+ [](std::uintptr_t nextPC, std::uintptr_t, std::uintptr_t, bool) { return nextPC; }, // cmp = 0xa
+ [](std::uintptr_t nextPC, std::uintptr_t, std::uintptr_t, bool) { return nextPC; }, // cmn = 0xb
+ [](std::uintptr_t, std::uintptr_t operand1, std::uintptr_t operand2, bool) { return operand1 | operand2; }, // orr = 0xc
+ [](std::uintptr_t, std::uintptr_t, std::uintptr_t operand2, bool) { return operand2; }, // mov = 0xd
+ [](std::uintptr_t, std::uintptr_t operand1, std::uintptr_t operand2, bool) { return operand1 & ~operand2; }, // bic = 0xe
+ [](std::uintptr_t, std::uintptr_t, std::uintptr_t operand2, bool) { return ~operand2; }, // mvn = 0xf
+ };
+
+ nextPC = dataOperations[GetBits(currentInstr, 21, 24)](nextPC, operand1, operand2, carry);
+ nextPC = nextPC & ~(std::uintptr_t(1)); // Remove useless bits from address. Note, we don't support M-profiles here.
+ return true;
+};
+
+static bool ArmConditionTrue_MemoryOperations(pid_t pid, const user_regs_struct ®s, std::uintptr_t currentPS, std::uintptr_t currentPC,
+ std::uint32_t currentInstr, std::uintptr_t &nextPC, bool&)
+{
+ // We care about LDR only here.
+ if (GetBits(currentInstr, 25, 27) == 0x3 && GetBit(currentInstr, 4) == 1)
+ return true;
+
+ if (GetBit(currentInstr, 20) && GetBits(currentInstr, 12, 15) == REG_PC) // LDR (in case PC is dest)
+ {
+ // 1 - transfer byte quantity
+ if (GetBit(currentInstr, 22) == 1)
+ {
+ LOGE("Failed next PC calculation");
+ return false;
+ }
+
+ std::uint32_t baseReg = GetBits(currentInstr, 16, 19);
+ std::uintptr_t baseData = ((baseReg == REG_PC) ? (currentPC + 8) : std::uintptr_t(regs.uregs[baseReg]));
+
+ if (GetBit(currentInstr, 24)) // pre-index
+ {
+ bool carry = (currentPS & FLAG_C) == FLAG_C;
+ std::uint32_t offset = (GetBit(currentInstr, 25) ?
+ ShiftRegValue(regs, currentInstr, carry, currentPC) : // shift
+ GetBits(currentInstr, 0, 11)); // 12-bit immediate
+
+ if (GetBit(currentInstr, 23))
+ baseData += offset; // up
+ else
+ baseData -= offset; // down
+ }
+
+ if (!GetDataFromMemory(pid, baseData, nextPC))
+ {
+ LOGE("Failed next PC calculation");
+ return false;
+ }
+ }
+
+ return true;
+};
+
+static bool ArmConditionTrue_MultipleMemoryOperations(pid_t pid, const user_regs_struct ®s, std::uintptr_t, std::uintptr_t,
+ std::uint32_t currentInstr, std::uintptr_t &nextPC, bool&)
+{
+ if (GetBit(currentInstr, 20) && GetBit(currentInstr, REG_PC)) // LDM (in case PC also included in register list)
+ {
+ int offset = 0;
+ if (GetBit(currentInstr, 23)) // up
+ {
+ unsigned long reglist = GetBits(currentInstr, 0, 14); // in this case reglist is "array of bits"
+ offset = __builtin_popcountl(reglist) * 4; // count offset for all registers that are set in register list
+ if (GetBit(currentInstr, 24)) // pre-index
+ offset += 4; // count offset for PC too
+ }
+ else if (GetBit(currentInstr, 24)) // down + pre-index
+ {
+ offset = -4; // count offset for PC only in this case
+ }
+
+ std::uint32_t baseReg = GetBits(currentInstr, 16, 19);
+ std::uintptr_t addr = std::uintptr_t(regs.uregs[baseReg]) + offset;
+ if (!GetDataFromMemory(pid, addr, nextPC))
+ {
+ LOGE("Failed next PC calculation");
+ return false;
+ }
+ }
+
+ return true;
+};
+
+static bool ArmConditionTrue_Branches(pid_t, const user_regs_struct&, std::uintptr_t, std::uintptr_t currentPC,
+ std::uint32_t currentInstr, std::uintptr_t &nextPC, bool&)
+{
+ nextPC = CalculateBranchDest(currentPC, currentInstr);
+ return true;
+};
+
+// Get next possible addresses for Arm instruction subset.
+static bool GetArmCodeNextPCs(pid_t pid, const user_regs_struct ®s, std::vector<sw_singlestep_nextpc_t> &swSingleStepNextPCs)
+{
+ std::uintptr_t currentPC = std::uintptr_t(regs.uregs[REG_PC]);
+ std::uintptr_t nextPC = currentPC + 4; // default PC changes
+ bool switchToThumbCode = false;
+
+ std::uint32_t currentInstr = 0;
+ if (!GetDataFromMemory<std::uint32_t>(pid, currentPC, currentInstr))
+ return false;
+
+ std::uintptr_t currentPS = regs.uregs[REG_CPSR];
+
+ constexpr unsigned unconditionalOpsShift = 0xa;
+ static std::vector<std::function<bool(std::uintptr_t, std::uint32_t, std::uintptr_t&, bool&)>> unconditionalOperations
+ {
+ // branches
+ ArmUnconditional_Branches, // 0xa - branch and change to Thumb
+ ArmUnconditional_Branches, // 0xb - branch & link and change to Thumb
+ // coprocessor operations
+ ArmUnconditional_CoprocessorOperations, // 0xc
+ ArmUnconditional_CoprocessorOperations, // 0xd
+ ArmUnconditional_CoprocessorOperations // 0xe
+ };
+
+ static std::vector<std::function<bool(pid_t, const user_regs_struct&, std::uintptr_t, std::uintptr_t, std::uint32_t, std::uintptr_t&, bool&)>> conditionTrueOperations
+ {
+ // miscellaneous instructions (multiply, swap, branch and exchange, data operations)
+ ArmConditionTrue_Miscellaneous, // 0x0
+ ArmConditionTrue_Miscellaneous, // 0x1
+ ArmConditionTrue_Miscellaneous, // 0x2
+ ArmConditionTrue_Miscellaneous, // 0x3
+ // memory operations
+ ArmConditionTrue_MemoryOperations, // 0x4
+ ArmConditionTrue_MemoryOperations, // 0x5
+ ArmConditionTrue_MemoryOperations, // 0x6
+ ArmConditionTrue_MemoryOperations, // 0x7
+ // block/multiple memory operations
+ ArmConditionTrue_MultipleMemoryOperations, // 0x8
+ ArmConditionTrue_MultipleMemoryOperations, // 0x9
+ // branches
+ ArmConditionTrue_Branches, // 0xa - branch
+ ArmConditionTrue_Branches, // 0xb - branch & link
+ // coprocessor operations (do nothing)
+ // 0xc
+ // 0xd
+ // 0xe
+ // system calls (do nothing)
+ // 0xf - TODO care about SIGRETURN/RT_SIGRETURN syscalls.
+ };
+
+ if (GetBits(currentInstr, 28, 31) == INST_NV)
+ {
+ unsigned op = GetBits(currentInstr, 24, 27);
+ if (op > unconditionalOpsShift && op < unconditionalOpsShift + unconditionalOperations.size())
+ {
+ if (!unconditionalOperations[op - unconditionalOpsShift](currentPC, currentInstr, nextPC, switchToThumbCode))
+ return false;
+ }
+
+ // Note, Linux kernel could offers some helpers/intrisics in a high page that we can't read (and write).
+ // For BL and BLX move to address of following instruction, in case tail called functions return to the address in LR.
+ if (nextPC > 0xffff0000)
+ {
+ switchToThumbCode = false;
+ if (op == 0xb) // BLX <label>
+ nextPC = currentPC + 4;
+ else
+ nextPC = std::uintptr_t(regs.uregs[REG_LR]);
+ }
+ }
+ else if (IsConditionTrue(GetBits(currentInstr, 28, 31), currentPS))
+ {
+ unsigned op = GetBits(currentInstr, 24, 27);
+ if (op < conditionTrueOperations.size())
+ {
+ if (!conditionTrueOperations[op](pid, regs, currentPS, currentPC, currentInstr, nextPC, switchToThumbCode))
+ return false;
+ }
+
+ // Note, Linux kernel could offers some helpers/intrisics in a high page that we can't read (and write).
+ // For BL and BLX move to address of following instruction, in case tail called functions return to the address in LR.
+ if (nextPC > 0xffff0000)
+ {
+ switchToThumbCode = false;
+ if (op == 0xb || // BL <label>
+ GetBits(currentInstr, 4, 27) == 0x12fff3) // BLX register
+ nextPC = currentPC + 4;
+ else
+ nextPC = std::uintptr_t(regs.uregs[REG_LR]);
+ }
+ }
+
+ swSingleStepNextPCs.emplace_back(nextPC, switchToThumbCode);
+ return true;
+}
+
+static std::uint32_t ThumbInsnructionSize(std::uint16_t inst1)
+{
+ return ((inst1 & 0xe000) == 0xe000 && (inst1 & 0x1800) != 0) ? 4 : 2;
+}
+
+static std::uint32_t ThumbAdvanceITState(std::uint32_t itstate)
+{
+ // IT[7:5] Holds the base condition for the current IT block. The base condition is the top 3 bits of the condition specified by the IT instruction.
+ // IT[4:0] The size of the IT block. This is the number of instructions that are to be conditionally executed.
+ // All we need here is decrement IT[4:0] part by 1.
+ itstate = (itstate & 0xe0) | ((itstate << 1) & 0x1f);
+
+ // In case IT[3:0] == 0, mean IT block is finished, clear the state.
+ // See https://developer.arm.com/documentation/ddi0406/b/Application-Level-Architecture/Application-Level-Programmers--Model/Execution-state-registers/ITSTATE?lang=en
+ // "Table 2.2. Effect of IT execution state bits" for more info.
+ return ((itstate & 0x0f) == 0) ? 0 : itstate;
+}
+
+static bool GetThumbConditionalBlockNextPCs(pid_t pid, std::uintptr_t currentPS, std::uintptr_t currentPC, std::uint16_t inst1, std::vector<sw_singlestep_nextpc_t> &swSingleStepNextPCs)
+{
+ // In Linux, breakpoint is illegal instruction. IT can disable illegal instruction execution.
+ // This mean, we could never reach this breakpoint. Plus, conditional instructions can change flags,
+ // that change execution route, this mean we could need set 2 breakpoints and care about this case too.
+
+ if ((inst1 & 0xff00) == 0xbf00 && (inst1 & 0x000f) != 0) // thumb 16-bit "If-Then" instructions
+ {
+ std::uint32_t ITState = inst1 & 0x00ff;
+ std::uintptr_t nextPC = currentPC + ThumbInsnructionSize(inst1);
+
+ while (ITState != 0 && !IsConditionTrue(ITState >> 4, currentPS))
+ {
+ if (!GetDataFromMemory<std::uint16_t>(pid, nextPC, inst1))
+ return false;
+
+ nextPC += ThumbInsnructionSize(inst1);
+ ITState = ThumbAdvanceITState(ITState);
+ }
+
+ swSingleStepNextPCs.emplace_back(nextPC, true);
+ return true;
+ }
+
+ // https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-core-registers/Program-Status-Registers--PSRs-
+ // IT[7:0], CPSR bits [15:10,26:25]
+ std::uint32_t ITState = ((currentPS >> 8) & 0xfc) | ((currentPS >> 25) & 0x3);
+
+ if (ITState == 0)
+ return true;
+
+ if (!IsConditionTrue(ITState >> 4, currentPS))
+ {
+ // Advance to the next executed instruction till this block ends.
+ std::uintptr_t nextPC = currentPC + ThumbInsnructionSize(inst1);
+ ITState = ThumbAdvanceITState(ITState);
+
+ while (ITState != 0 && !IsConditionTrue(ITState >> 4, currentPS))
+ {
+ if (!GetDataFromMemory<std::uint16_t>(pid, nextPC, inst1))
+ return false;
+
+ nextPC += ThumbInsnructionSize(inst1);
+ ITState = ThumbAdvanceITState(ITState);
+ }
+
+ swSingleStepNextPCs.emplace_back(nextPC, true);
+ return true;
+ }
+
+ if ((ITState & 0x0f) == 0x08) // current instruction is the last instruction of conditional block
+ return true;
+
+ // Current instruction is conditional instruction that may change flags.
+ // Note, we can't predict what the next executed instruction will be.
+ std::uintptr_t nextPC = currentPC + ThumbInsnructionSize(inst1);
+ // Set a breakpoint on the following instruction
+ swSingleStepNextPCs.emplace_back(nextPC, true);
+
+ ITState = ThumbAdvanceITState(ITState);
+ std::uint32_t negatedInitialCondition = (ITState >> 4) & 1;
+ // "skip" all instruction with the same condition or till this block ends.
+ do
+ {
+ if (!GetDataFromMemory<std::uint16_t>(pid, nextPC, inst1))
+ return false;
+
+ nextPC += ThumbInsnructionSize(inst1);
+ ITState = ThumbAdvanceITState(ITState);
+ }
+ while (ITState != 0 && ((ITState >> 4) & 1) == negatedInitialCondition);
+
+ swSingleStepNextPCs.emplace_back(nextPC, true);
+ return true;
+}
+
+static bool Thumb16_Default(pid_t, const user_regs_struct&, std::uintptr_t, std::uintptr_t, std::uint16_t, std::uintptr_t&, bool&)
+{
+ return true;
+};
+
+static bool Thumb16_BranchExchangeAndDataProcessing(pid_t, const user_regs_struct ®s, std::uintptr_t, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ if ((inst1 & 0xff00) == 0x4700) // BX REG, BLX REG
+ {
+ if (GetBits(inst1, 3, 6) == REG_PC)
+ {
+ const std::uintptr_t prefetchedPC = currentPC + 4; // PC after prefetch
+ nextPC = prefetchedPC;
+ switchToThumbCode = false;
+ }
+ else
+ {
+ std::uint32_t sourceReg = GetBits(inst1, 3, 6);
+ nextPC = std::uintptr_t(regs.uregs[sourceReg]);
+ switchToThumbCode = nextPC & 1;
+ nextPC = nextPC & ~(std::uintptr_t(1)); // Remove useless bits from address.
+ }
+ }
+ else if ((inst1 & 0xff87) == 0x4687) // MOV PC, REG
+ {
+ if (GetBits(inst1, 3, 6) == REG_PC)
+ {
+ const std::uintptr_t prefetchedPC = currentPC + 4; // PC after prefetch
+ nextPC = prefetchedPC;
+ }
+ else
+ {
+ std::uint32_t sourceReg = GetBits(inst1, 3, 6);
+ nextPC = std::uintptr_t(regs.uregs[sourceReg]);
+ nextPC = nextPC & ~(std::uintptr_t(1)); // Remove useless bits from address.
+ }
+ }
+
+ return true;
+};
+
+static bool Thumb16_Miscellaneous(pid_t pid, const user_regs_struct ®s, std::uintptr_t, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ if ((inst1 & 0xff00) == 0xbd00) // POP {reglist, PC}
+ {
+ // Count offset for all registers that are set in register list. Note, PC stored above all of the other registers.
+ int offset = __builtin_popcountl(GetBits(inst1, 0, 7)) * 4;
+ std::uintptr_t regSP = std::uintptr_t(regs.uregs[REG_SP]);
+ if (!GetDataFromMemory<std::uint32_t>(pid, regSP + offset, nextPC))
+ return false;
+ // Bit[0] of the loaded value determines whether execution continues after this branch in ARM state or in Thumb state.
+ if ((nextPC & 1) == 0)
+ switchToThumbCode = false;
+ else
+ nextPC = nextPC & ~(std::uintptr_t(1)); // Remove useless bits from address.
+ }
+ else if ((inst1 & 0xf500) == 0xb100) // CBZ or CBNZ (Compare and Branch on Zero, Compare and Branch on Non-Zero)
+ {
+ std::uintptr_t operandReg = std::uintptr_t(regs.uregs[GetBits(inst1, 0, 2)]);
+
+ if ((GetBit(inst1, 11) && operandReg != 0) ||
+ (!GetBit(inst1, 11) && operandReg == 0))
+ {
+ int imm = (GetBit(inst1, 9) << 6) + (GetBits(inst1, 3, 7) << 1);
+ const std::uintptr_t prefetchedPC = currentPC + 4; // PC after prefetch
+ nextPC = prefetchedPC + imm;
+ }
+ }
+
+ return true;
+};
+
+static bool Thumb16_ConditionalBranch(pid_t, const user_regs_struct&, std::uintptr_t currentPS, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uintptr_t &nextPC, bool&)
+{
+ if ((inst1 & 0xf000) == 0xd000) // Conditional branch
+ {
+ std::uint32_t cond = GetBits(inst1, 8, 11);
+ if (cond == INST_NV) // syscall
+ {
+ // TODO care about SIGRETURN/RT_SIGRETURN syscalls.
+ }
+ else if (IsConditionTrue(cond, currentPS))
+ {
+ const std::uintptr_t prefetchedPC = currentPC + 4; // PC after prefetch
+ nextPC = prefetchedPC + (GetSBits(inst1, 0, 7) << 1);
+ }
+ }
+
+ return true;
+};
+
+static bool Thumb16_UnconditionalBranch(pid_t, const user_regs_struct&, std::uintptr_t, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uintptr_t &nextPC, bool&)
+{
+ if ((inst1 & 0xf800) == 0xe000) // unconditional branch
+ {
+ const std::uintptr_t prefetchedPC = currentPC + 4; // PC after prefetch
+ nextPC = prefetchedPC + (GetSBits(inst1, 0, 10) << 1);
+ }
+
+ return true;
+};
+
+static bool Thumb32_BranchesMiscControl(pid_t pid, const user_regs_struct ®s, std::uintptr_t currentPS, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uint16_t inst2, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ if ((inst2 & 0x1000) != 0 || (inst2 & 0xd001) == 0xc000) // B, BL, BLX
+ {
+ int imm1 = GetSBits(inst1, 0, 10);
+ int imm2 = GetBits(inst2, 0, 10);
+ int j1 = GetBit(inst2, 13);
+ int j2 = GetBit(inst2, 11);
+
+ // I1 = NOT(J1 EOR S); I2 = NOT(J2 EOR S);
+ // imm32 = SignExtend(S:I1:I2:imm10:imm11:'0', 32);
+ std::uint32_t offset = ((imm1 << 12) + (imm2 << 1));
+ offset ^= ((!j1) << 23) | ((!j2) << 22);
+ const std::uintptr_t prefetchedPC = currentPC + 4; // PC after prefetch
+ nextPC = prefetchedPC + offset;
+
+ if (GetBit(inst2, 12) == 0) // BLX
+ {
+ switchToThumbCode = false;
+ // ARM Architecture Reference Manual Thumb-2 Supplement
+ // 4.6.18 BL, BLX (immediate)
+ // For BLX (encoding T2), the assembler calculates the required value of the offset from the
+ // Align(PC,4) value of the BLX instruction to this label, then selects an encoding that will
+ // set imm32 to that offset. A
+ nextPC = nextPC & 0xfffffffc;
+ }
+ }
+ else if (inst1 == 0xf3de && (inst2 & 0xff00) == 0x3f00) // SUBS PC, LR, #imm8
+ {
+ nextPC = std::uintptr_t(regs.uregs[REG_LR]);
+ // imm32 = ZeroExtend(imm8, 32);
+ nextPC -= inst2 & 0x00ff; // 8-bit immediate constant
+ }
+ else if ((inst2 & 0xd000) == 0x8000 && (inst1 & 0x0380) != 0x0380) // conditional branch
+ {
+ if (IsConditionTrue(GetBits(inst1, 6, 9), currentPS))
+ {
+ int sign = GetSBits(inst1, 10, 10);
+ int imm1 = GetBits(inst1, 0, 5);
+ int imm2 = GetBits(inst2, 0, 10);
+ int j1 = GetBit(inst2, 13);
+ int j2 = GetBit(inst2, 11);
+
+ // imm32 = SignExtend(S:J2:J1:imm6:imm11:'0', 32);
+ std::uint32_t offset = (sign << 20) + (j2 << 19) + (j1 << 18);
+ offset += (imm1 << 12) + (imm2 << 1);
+ const std::uintptr_t prefetchedPC = currentPC + 4; // PC after prefetch
+ nextPC = prefetchedPC + offset;
+ }
+ }
+
+ return true;
+}
+
+static bool Thumb32_LDM(pid_t pid, const user_regs_struct ®s, std::uintptr_t currentPS, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uint16_t inst2, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ auto loadPC = [&](std::int32_t offset)
+ {
+ int baseReg = GetBits(inst1, 0, 3);
+ std::uintptr_t addr = std::uintptr_t(regs.uregs[baseReg]);
+ if (!GetDataFromMemory<std::uint32_t>(pid, addr + offset, nextPC))
+ return false;
+
+ if ((nextPC & 1) == 0)
+ switchToThumbCode = false;
+ else
+ nextPC = nextPC & ~(std::uintptr_t(1)); // Remove useless bits from address.
+
+ return true;
+ };
+
+ if (GetBit(inst1, 7) && !GetBit(inst1, 8)) // LDMIA
+ {
+ std::int32_t offset = __builtin_popcountl(inst2) * 4 - 4;
+ if (!loadPC(offset))
+ return false;
+ }
+ else if (!GetBit(inst1, 7) && GetBit(inst1, 8)) // LDMDB
+ {
+ if (!loadPC(-4))
+ return false;
+ }
+
+ return true;
+}
+
+static bool Thumb32_RFE(pid_t pid, const user_regs_struct ®s, std::uintptr_t currentPS, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uint16_t inst2, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ auto loadPC = [&](std::int32_t offset)
+ {
+ int baseReg = GetBits(inst1, 0, 3);
+ std::uintptr_t addr = std::uintptr_t(regs.uregs[baseReg]);
+ if (!GetDataFromMemory<std::uint32_t>(pid, addr + offset, nextPC))
+ return false;
+
+ std::uint32_t nextCPSR = 0;
+ if (!GetDataFromMemory<std::uint32_t>(pid, addr + offset + 4, nextCPSR))
+ return false;
+
+ // FIXME for `M profiles` (Cortex-M), XPSR_T_BIT must be used instead.
+ // CPSR_T_BIT 0x20 // 5 bit
+ // XPSR_T_BIT 0x01000000 // 25 bit
+ switchToThumbCode = nextCPSR & 0x20;
+
+ return true;
+ };
+
+ if (GetBit(inst1, 7) && GetBit(inst1, 8)) // RFEIA
+ {
+ if (!loadPC(0))
+ return false;
+ }
+ else if (!GetBit(inst1, 7) && !GetBit(inst1, 8)) // RFEDB
+ {
+ if (!loadPC(-8))
+ return false;
+ }
+
+ return true;
+}
+
+static bool Thumb32_MOV(pid_t pid, const user_regs_struct ®s, std::uintptr_t currentPS, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uint16_t inst2, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ if (GetBits(inst2, 8, 11) == REG_PC) // only if <Rd> is PC
+ {
+ int srcReg = GetBits(inst2, 0, 3);
+ nextPC = std::uintptr_t(regs.uregs[srcReg]);
+ }
+
+ return true;
+}
+
+static bool Thumb32_LDR(pid_t pid, const user_regs_struct ®s, std::uintptr_t currentPS, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uint16_t inst2, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ int rn = GetBits(inst1, 0, 3);
+ std::uintptr_t base = std::uintptr_t(regs.uregs[rn]);
+
+ auto loadPC = [&]()
+ {
+ return GetDataFromMemory<std::uint32_t>(pid, base, nextPC);
+ };
+
+ if (rn == REG_PC)
+ {
+ base = (base + 4) & ~(std::uintptr_t)0x3;
+ if (GetBit(inst1, 7))
+ base += GetBits(inst2, 0, 11);
+ else
+ base -= GetBits(inst2, 0, 11);
+
+ return loadPC();
+ }
+ else if (GetBit(inst1, 7))
+ {
+ base += GetBits(inst2, 0, 11); // imm12
+ return loadPC();
+ }
+ else if (GetBit(inst2, 11))
+ {
+ if (GetBit(inst2, 10))
+ {
+ if (GetBit(inst2, 9))
+ base += GetBits(inst2, 0, 7);
+ else
+ base -= GetBits(inst2, 0, 7);
+ }
+ return loadPC();
+ }
+ else if ((inst2 & 0x0fc0) == 0x0000)
+ {
+ int shift = GetBits(inst2, 4, 5);
+ int rm = GetBits(inst2, 0, 3);
+ base += std::uintptr_t(regs.uregs[rm]) << shift;
+ return loadPC();
+ }
+
+ return true;
+}
+
+static bool Thumb32_TBB(pid_t pid, const user_regs_struct ®s, std::uintptr_t currentPS, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uint16_t inst2, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ std::uintptr_t table;
+ std::uintptr_t tableReg = GetBits(inst1, 0, 3);
+ if (tableReg == REG_PC)
+ table = currentPC + 4;
+ else
+ table = std::uintptr_t(regs.uregs[tableReg]);
+
+ std::uintptr_t offset = std::uintptr_t(regs.uregs[GetBits(inst2, 0, 3)]);
+
+ std::uint8_t tmp = 0;
+ if (!GetDataFromMemory<std::uint8_t>(pid, table + offset, tmp))
+ return false;
+
+ std::uintptr_t length = 2 * tmp;
+ const std::uintptr_t prefetchedPC = currentPC + 4; // PC after prefetch
+ nextPC = prefetchedPC + length;
+
+ return true;
+}
+
+static bool Thumb32_TBH(pid_t pid, const user_regs_struct ®s, std::uintptr_t currentPS, std::uintptr_t currentPC,
+ std::uint16_t inst1, std::uint16_t inst2, std::uintptr_t &nextPC, bool &switchToThumbCode)
+{
+ std::uintptr_t table;
+ std::uintptr_t tableReg = GetBits(inst1, 0, 3);
+ if (tableReg == REG_PC)
+ table = currentPC + 4;
+ else
+ table = std::uintptr_t(regs.uregs[tableReg]);
+
+ std::uintptr_t offset = 2 * std::uintptr_t(regs.uregs[GetBits(inst2, 0, 3)]);
+
+ std::uint16_t tmp = 0;
+ if (!GetDataFromMemory<std::uint16_t>(pid, table + offset, tmp))
+ return false;
+
+ std::uintptr_t length = 2 * tmp;
+ const std::uintptr_t prefetchedPC = currentPC + 4; // PC after prefetch
+ nextPC = prefetchedPC + length;
+
+ return true;
+}
+
+static bool FixThumbCodeNextPCs(pid_t pid, const user_regs_struct ®s, std::vector<sw_singlestep_nextpc_t> &swSingleStepNextPCs)
+{
+ // Note, Linux kernel could offers some helpers/intrisics in a high page that we can't read (and write).
+ // For BL and BLX move to address of following instruction, in case tail called functions return to the address in LR.
+
+ std::uintptr_t currentPC = std::uintptr_t(regs.uregs[REG_PC]);
+
+ for (auto &entry : swSingleStepNextPCs)
+ {
+ if (entry.addr <= 0xffff0000)
+ continue;
+
+ bool isBLorBLX = false;
+ std::uintptr_t incrPC = 0;
+ std::uint16_t inst1 = 0;
+ if (!GetDataFromMemory<std::uint16_t>(pid, currentPC, inst1))
+ return false;
+
+ if (GetBits(inst1, 8, 15) == 0x47 && GetBit(inst1, 7)) // BLX register
+ {
+ isBLorBLX = true;
+ incrPC = 2;
+ }
+ else if (ThumbInsnructionSize(inst1) == 4) // 32-bit instruction
+ {
+ std::uint16_t inst2 = 0;
+ if (!GetDataFromMemory<std::uint16_t>(pid, currentPC + 2, inst2))
+ return false;
+
+ if ((inst1 & 0xf800) == 0xf000 && GetBits(inst2, 14, 15) == 0x3) // BL <label> or BLX <label>
+ {
+ isBLorBLX = true;
+ incrPC = 4;
+ }
+ }
+
+ entry.isThumb = true;
+
+ if (isBLorBLX)
+ entry.addr = currentPC + incrPC;
+ else
+ entry.addr = std::uintptr_t(regs.uregs[REG_LR]);
+ }
+
+ return true;
+}
+
+// Get next possible addresses for Thumb instruction subset.
+static bool GetThumbCodeNextPCs(pid_t pid, const user_regs_struct ®s, std::vector<sw_singlestep_nextpc_t> &swSingleStepNextPCs)
+{
+ std::uintptr_t currentPC = std::uintptr_t(regs.uregs[REG_PC]);
+ std::uint32_t currentData32 = 0;
+ if (!GetDataFromMemory<std::uint32_t>(pid, currentPC, currentData32))
+ return false;
+
+ static std::vector<std::function<bool(pid_t, const user_regs_struct&, std::uintptr_t, std::uintptr_t, std::uint16_t, std::uintptr_t&, bool&)>> thumb16Operations
+ {
+ Thumb16_Default, // 0x0
+ Thumb16_Default, // 0x1
+ Thumb16_Default, // 0x2
+ Thumb16_Default, // 0x3
+ Thumb16_BranchExchangeAndDataProcessing, // 0x4 - Branch Exchange, Data-processing register
+ Thumb16_Default, // 0x5
+ Thumb16_Default, // 0x6
+ Thumb16_Default, // 0x7
+ Thumb16_Default, // 0x8
+ Thumb16_Default, // 0x9
+ Thumb16_Default, // 0xa
+ Thumb16_Miscellaneous, // 0xb - POP {reglist, pc}, CBZ or CBNZ (Compare and Branch on Zero, Compare and Branch on Non-Zero)
+ Thumb16_Default, // 0xc
+ Thumb16_ConditionalBranch, // 0xd - Conditional branch
+ Thumb16_UnconditionalBranch // 0xe - Unconditional branch
+ };
+
+ struct thumb32entry
+ {
+ std::uint32_t mask;
+ std::uint32_t opcode;
+ std::function<bool(pid_t, const user_regs_struct&, std::uintptr_t, std::uintptr_t, std::uint16_t, std::uint16_t, std::uintptr_t&, bool&)> func;
+ };
+ static std::vector<thumb32entry> thumb32Operations
+ {
+ // mask/opcode - instr2(16bit):instr1(16bit)
+ {0x8000f800, 0x8000f000, Thumb32_BranchesMiscControl}, // Branches, miscellaneous control instructions
+ {0x2000ffd0, 0x0000e910, Thumb32_LDM}, // LDMDB
+ {0x2000ffd0, 0x0000e890, Thumb32_LDM}, // LDMIA
+ {0xffffffd0, 0xc000e990, Thumb32_RFE}, // RFEDB
+ {0xffffffd0, 0xc000e810, Thumb32_RFE}, // RFEIA
+ {0xf0f0ffef, 0x0000ea4f, Thumb32_MOV}, // MOV{S}
+ {0xfff0fff0, 0xf000e8d0, Thumb32_TBB}, // TBB
+ {0xfff0fff0, 0xf010e8d0, Thumb32_TBH}, // TBH
+ {0xf000ff70, 0xf000f850, Thumb32_LDR}, // LDR, where Rm is PC
+ };
+
+ std::uintptr_t currentPS = std::uintptr_t(regs.uregs[REG_CPSR]);
+ if (!GetThumbConditionalBlockNextPCs(pid, currentPS, currentPC, ((std::uint16_t*)¤tData32)[0], swSingleStepNextPCs))
+ return false;
+ else if (swSingleStepNextPCs.empty())
+ {
+ std::uintptr_t nextPC = currentPC + 2; // default PC changes for thumb16
+ bool switchToThumbCode = true;
+
+ if (!IsThumbOpcode32Bits(currentData32)) // 16-bit instruction
+ {
+ unsigned op = GetBits(currentData32, 12, 15);
+ if (op < thumb16Operations.size())
+ {
+ if (!thumb16Operations[op](pid, regs, currentPS, currentPC, ((std::uint16_t*)¤tData32)[0], nextPC, switchToThumbCode))
+ return false;
+ }
+ }
+ else // 32-bit instruction
+ {
+ nextPC = currentPC + 4; // default PC changes for thumb32
+
+ for (auto &entry : thumb32Operations)
+ {
+ if ((currentData32 & entry.mask) == entry.opcode)
+ {
+ if (!entry.func(pid, regs, currentPS, currentPC, ((std::uint16_t*)¤tData32)[0], ((std::uint16_t*)¤tData32)[1], nextPC, switchToThumbCode))
+ return false;
+
+ break;
+ }
+ }
+ }
+
+ swSingleStepNextPCs.emplace_back(nextPC, switchToThumbCode);
+ }
+
+ return FixThumbCodeNextPCs(pid, regs, swSingleStepNextPCs);
+}
+
+bool ARM32_RemoveSoftwareSingleStepBreakpoints(pid_t pid, std::vector<sw_singlestep_brk_t> &swSingleStepBreakpoints)
+{
+ for (auto &entry : swSingleStepBreakpoints)
+ {
+ errno = 0;
+ word_t brkData = async_ptrace(PTRACE_PEEKDATA, pid, (void*)entry.bpAddr, nullptr);
+ if (errno != 0)
+ {
+ LOGE("Ptrace peekdata error: %s", strerror(errno));
+ return false;
+ }
+
+ entry.restoreData = RestoredOpcode(brkData, entry.restoreData); // fix restore data in case breakpoint opcode size less than word_t
+
+ if (async_ptrace(PTRACE_POKEDATA, pid, (void*)entry.bpAddr, (void*)entry.restoreData) == -1)
+ {
+ LOGE("Ptrace pokedata error: %s\n", strerror(errno));
+ return false;
+ }
+ }
+ swSingleStepBreakpoints.clear();
+
+ return true;
+};
+
+bool ARM32_DoSoftwareSingleStep(pid_t pid, std::vector<sw_singlestep_brk_t> &swSingleStepBreakpoints)
+{
+ user_regs_struct regs;
+ iovec iov;
+ iov.iov_base = ®s;
+ iov.iov_len = sizeof(user_regs_struct);
+ if (async_ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov) == -1)
+ {
+ LOGW("Ptrace getregset error: %s\n", strerror(errno));
+ return false;
+ }
+
+ std::vector<sw_singlestep_nextpc_t> swSingleStepNextPCs;
+
+ if (!IsExecutingThumb(regs))
+ {
+ // TODO care about atomic sequence of instructions beginning with LDREX{,B,H,D} and ending with STREX{,B,H,D}.
+ if (!GetArmCodeNextPCs(pid, regs, swSingleStepNextPCs))
+ return false;
+ }
+ else
+ {
+ // TODO care about atomic sequence of instructions beginning with LDREX{,B,H,D} and ending with STREX{,B,H,D}.
+ if (!GetThumbCodeNextPCs(pid, regs, swSingleStepNextPCs))
+ return false;
+ }
+
+ if (swSingleStepNextPCs.empty())
+ return false;
+
+ for (auto &entry : swSingleStepNextPCs)
+ {
+ errno = 0; // Since the value returned by a successful PTRACE_PEEK* request may be -1, the caller must clear errno before the call,
+ // and then check it afterward to determine whether or not an error occurred.
+ word_t nextPCData = async_ptrace(PTRACE_PEEKDATA, pid, (void*)entry.addr, nullptr);
+ if (errno != 0)
+ {
+ LOGE("Ptrace peekdata error: %s", strerror(errno));
+ return false;
+ }
+
+ word_t dataWithBrk = EncodeBrkOpcode(nextPCData, entry.isThumb);
+
+ if (async_ptrace(PTRACE_POKEDATA, pid, (void*)entry.addr, (void*)dataWithBrk) == -1)
+ {
+ LOGE("Ptrace pokedata error: %s", strerror(errno));
+ return false;
+ }
+
+ swSingleStepBreakpoints.emplace_back(entry.addr, nextPCData);
+ }
+
+ return true;
+};
+
+} // namespace InteropDebugging
+} // namespace netcoredbg