microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
arm_exidx_decoder.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
10#include "address_space.hpp"
11#include "register_context.hpp"
12#include <cstdint>
13
14namespace microfmt {
15
21public:
33 RELOCO_UNSAFE_BUFFER_USAGE
34 static bool execute_bytecode(address_space_ref space, uint32_t unwind_word, uintptr_t &io_sp, uintptr_t &,
35 register_context_ref reg_ctx) noexcept {
36 // Check personality routine bits (bits [31:24])
37 uint8_t personality = (unwind_word >> 24) & 0x0F;
38
39 // Personality 0 or 1 with inline data in the lower 3 bytes
40 if (personality == 0 || personality == 1) {
41 uint8_t bytes[3] = {
42 static_cast<uint8_t>((unwind_word >> 16) & 0xFF), // Byte 1
43 static_cast<uint8_t>((unwind_word >> 8) & 0xFF), // Byte 2
44 static_cast<uint8_t>(unwind_word & 0xFF) // Byte 3
45 };
46
47 return parse_and_apply_bytes(space, io_sp, reg_ctx, bytes);
48 }
49
50 return false; // Complex personality routines require external .ARM.extab
51 // reads
52 }
53
54private:
67 RELOCO_UNSAFE_BUFFER_USAGE
68 static bool parse_and_apply_bytes(address_space_ref space, uintptr_t &io_sp, register_context_ref reg_ctx,
69 const uint8_t bytes[3]) noexcept {
70 int idx = 0;
71 while (idx < 3) {
72 uint8_t opcode = bytes[idx++];
73 if (opcode == 0x00) {
74 continue; // NOP / Padding byte
75 }
76
77 // 0xB0 = FINISH opcode (terminate inline bytecode block)
78 if (opcode == 0xB0) {
79 return true;
80 }
81
82 // 00xxxxxx: vsp = vsp + (xxxxxx << 2) + 4
83 if ((opcode & 0xC0) == 0x00) {
84 uint32_t offset = (static_cast<uint32_t>(opcode & 0x3F) << 2U) + 4U;
85 io_sp += offset;
86 }
87 // 01xxxxxx: vsp = vsp - (xxxxxx << 2) - 4
88 else if ((opcode & 0xC0) == 0x40) {
89 uint32_t offset = (static_cast<uint32_t>(opcode & 0x3F) << 2U) + 4U;
90 io_sp -= offset;
91 }
92 // 1000iiii iiiiiiii: Pop integer registers (r4-r15)
93 else if ((opcode & 0xF0) == 0x80) {
94 if (idx >= 3)
95 break;
96 uint8_t low_mask = bytes[idx++];
97 uint16_t reg_mask =
98 static_cast<uint16_t>((static_cast<uint16_t>(opcode & 0x0F) << 8U) | static_cast<uint16_t>(low_mask));
99
100 for (int i = 0; i < 12; ++i) {
101 if ((reg_mask & (1U << static_cast<unsigned>(i))) != 0) {
102 uint32_t val = 0;
103 if (space.read_bytes(io_sp, &val, 4)) {
104 uint32_t dwarf_reg = static_cast<uint32_t>(dwarf::arm32::r4) + static_cast<uint32_t>(i);
105 if (!reg_ctx.write(dwarf_reg, val))
106 return false;
107 io_sp += 4;
108 }
109 }
110 }
111 }
112 // 1001nnnn (nnnn != 13,15): vsp = r[nnnn]. Extremely common with
113 // frame-pointer-based prologues (e.g. `vsp = r11`), where 0x9D/0x9F
114 // are reserved encodings that must be ignored.
115 else if ((opcode & 0xF0) == 0x90 && opcode != 0x9D && opcode != 0x9F) {
116 uint32_t reg_val = 0;
117 uint32_t reg_num = static_cast<uint32_t>(opcode & 0x0F);
118 if (!reg_ctx.read(reg_num, reg_val))
119 return false;
120 io_sp = reg_val;
121 }
122 // 10100nnn: Pop r4-r[4+nnn]. 10101nnn: Pop r4-r[4+nnn] and r14.
123 else if ((opcode & 0xF0) == 0xA0) {
124 uint8_t count = opcode & 0x07;
125 bool pop_lr = (opcode & 0x08) != 0;
126
127 for (int i = 0; i <= count; ++i, io_sp += 4) {
128 uint32_t val = 0;
129 if (space.read_bytes(io_sp, &val, 4)) {
130 uint32_t dwarf_reg = static_cast<uint32_t>(dwarf::arm32::r4) + static_cast<uint32_t>(i);
131 if (!reg_ctx.write(dwarf_reg, val))
132 return false;
133 }
134 }
135
136 if (pop_lr) {
137 uint32_t val = 0;
138 if (space.read_bytes(io_sp, &val, 4)) {
139 if (!reg_ctx.write(static_cast<uint32_t>(dwarf::arm32::r14), val))
140 return false;
141 }
142 io_sp += 4;
143 }
144 }
145 // 10111xxx: Pop VFP double-precision registers d(8) to d(8+xxx)
146 else if ((opcode & 0xF8) == 0xB8) {
147 uint8_t count = opcode & 0x07;
148 for (int i = 0; i <= count; ++i, io_sp += 8) {
149 uint64_t val = 0;
150 if (space.read_bytes(io_sp, &val, 8)) {
151 uint32_t dwarf_reg = static_cast<uint32_t>(dwarf::arm32::d0) + 8U + static_cast<uint32_t>(i);
152 if (!reg_ctx.write_raw(dwarf_reg, &val, 8))
153 return false;
154 }
155 }
156 }
157 }
158 return true;
159 }
160};
161
162} // namespace microfmt
Type-erased remote address-space access and remote string/object views.
Type-erased, two-word handle to a remote address space.
Definition address_space.hpp:204
Decoder for the inline unwind bytecode packed in an EXIDX word, writing restored register states dire...
Definition arm_exidx_decoder.hpp:20
static RELOCO_UNSAFE_BUFFER_USAGE bool execute_bytecode(address_space_ref space, uint32_t unwind_word, uintptr_t &io_sp, uintptr_t &, register_context_ref reg_ctx) noexcept
Executes up to 3 inline unwind bytes from an exidx/extab word.
Definition arm_exidx_decoder.hpp:34
Zero-allocation, type-erased handle for inspecting and modifying CPU registers from a frame context.
Definition register_context.hpp:118
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Definition assert.hpp:19
constexpr bytes_view bytes(uint64_t count) noexcept
Definition format_helpers.hpp:82