microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
arm_extab_stream.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 "arm_exidx_decoder.hpp"
12#include <cstdint>
13
14namespace microfmt {
15
33public:
39 constexpr extab_byte_stream(address_space_ref space, uintptr_t extab_addr) noexcept
40 : space_(space), current_word_addr_(extab_addr), bytes_remaining_in_word_(0) {}
41
47 [[nodiscard]] constexpr bool failed() const noexcept { return failed_; }
48
56 bool next_byte(uint8_t &out_byte) noexcept {
57 if (failed_)
58 return false;
59
60 if (bytes_remaining_in_word_ == 0) {
61 if (words_remaining_ == 0)
62 return false;
63
64 uint32_t word = 0;
65 if (!space_.read_bytes(current_word_addr_, &word, 4)) {
66 return false;
67 }
68 current_word_addr_ += 4;
69 --words_remaining_;
70
71 if (is_first_word_) {
72 is_first_word_ = false;
73
74 // Bit 31 clear means this word is instead a PREL31 pointer to an
75 // out-of-line personality-routine function -- not bytecode we can
76 // execute.
77 if ((word & 0x80000000U) == 0U) {
78 failed_ = true;
79 return false;
80 }
81
82 const uint8_t personality_index = static_cast<uint8_t>((word >> 24) & 0x7FU);
83 buffer_idx_ = 0;
84
85 if (personality_index == 0) {
86 // Personality 0 (Su16, Short): 3 opcode bytes packed into bits
87 // 23:16, 15:8, 7:0.
88 current_word_buffer_[0] = static_cast<uint8_t>((word >> 16) & 0xFF);
89 current_word_buffer_[1] = static_cast<uint8_t>((word >> 8) & 0xFF);
90 current_word_buffer_[2] = static_cast<uint8_t>(word & 0xFF);
91 bytes_remaining_in_word_ = 3;
92 } else {
93 // Personality 1/2 (Lu16/Lu32, Long): bits 23:16 hold the count N
94 // of additional 4-byte words carrying more opcode bytes; only 2
95 // opcode bytes come from this word (bits 15:8, 7:0).
96 words_remaining_ = static_cast<uint8_t>((word >> 16) & 0xFF);
97 current_word_buffer_[0] = static_cast<uint8_t>((word >> 8) & 0xFF);
98 current_word_buffer_[1] = static_cast<uint8_t>(word & 0xFF);
99 bytes_remaining_in_word_ = 2;
100 }
101 } else {
102 // Subsequent Extab Words: 4 sequential opcode bytes packed big-endian
103 current_word_buffer_[0] = static_cast<uint8_t>((word >> 24) & 0xFF);
104 current_word_buffer_[1] = static_cast<uint8_t>((word >> 16) & 0xFF);
105 current_word_buffer_[2] = static_cast<uint8_t>((word >> 8) & 0xFF);
106 current_word_buffer_[3] = static_cast<uint8_t>(word & 0xFF);
107 buffer_idx_ = 0;
108 bytes_remaining_in_word_ = 4;
109 }
110 }
111
112 if (bytes_remaining_in_word_ > 0) {
113 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
114
115 RELOCO_DEBUG_ASSERT(buffer_idx_ < std::size(current_word_buffer_), "Index out of range");
116 out_byte = current_word_buffer_[buffer_idx_++];
117
118 RELOCO_END_UNSAFE_BUFFER_USAGE;
119
120 bytes_remaining_in_word_--;
121 return true;
122 }
123 return false;
124 }
125
126private:
127 address_space_ref space_;
128 uintptr_t current_word_addr_;
129 uint8_t current_word_buffer_[4]{};
130 uint8_t buffer_idx_{0};
131 uint8_t bytes_remaining_in_word_{0};
132 bool is_first_word_{true};
133 bool failed_{false};
136 uint32_t words_remaining_{1};
137};
138
139// ============================================================================
140// Multi-Word Extab Bytecode Stream Executor
141// ============================================================================
142
147public:
162 static bool execute(address_space_ref space, uintptr_t extab_addr, uintptr_t &io_sp,
163 register_context_ref reg_ctx) noexcept {
164 extab_byte_stream stream(space, extab_addr);
165 uint8_t opcode = 0;
166
167 while (stream.next_byte(opcode)) {
168 // 0xB0 = FINISH opcode (terminate unwinding bytecode block)
169 if (opcode == 0xB0) {
170 return true;
171 }
172
173 // 00xxxxxx: vsp = vsp + (xxxxxx << 2) + 4
174 if ((opcode & 0xC0) == 0x00) {
175 uint32_t offset = (static_cast<uint32_t>(opcode & 0x3F) << 2U) + 4U;
176 io_sp += offset;
177 }
178 // 01xxxxxx: vsp = vsp - (xxxxxx << 2) - 4 (sub sp adjustment)
179 else if ((opcode & 0xC0) == 0x40) {
180 uint32_t offset = (static_cast<uint32_t>(opcode & 0x3F) << 2U) + 4U;
181 io_sp -= offset;
182 }
183 // 1000iiii iiiiiiii: Pop integer registers r4-r15
184 else if ((opcode & 0xF0) == 0x80) {
185 uint8_t opcode_low = 0;
186 if (!stream.next_byte(opcode_low))
187 break;
188 uint16_t reg_mask =
189 static_cast<uint16_t>((static_cast<uint16_t>(opcode & 0x0F) << 8U) | static_cast<uint16_t>(opcode_low));
190
191 for (int i = 0; i < 12; ++i) {
192 if ((reg_mask & (1U << static_cast<unsigned>(i))) != 0) {
193 uint32_t val = 0;
194 if (space.read_bytes(io_sp, &val, 4)) {
195 uint32_t dwarf_reg = static_cast<uint32_t>(dwarf::arm32::r4) + static_cast<uint32_t>(i);
196 if (!reg_ctx.write(dwarf_reg, val))
197 return false;
198 io_sp += 4;
199 }
200 }
201 }
202 }
203 // 1001nnnn (nnnn != 13,15): vsp = r[nnnn]. Extremely common with
204 // frame-pointer-based prologues (e.g. `vsp = r11`).
205 else if ((opcode & 0xF0) == 0x90 && opcode != 0x9D && opcode != 0x9F) {
206 uint32_t reg_val = 0;
207 uint32_t reg_num = static_cast<uint32_t>(opcode & 0x0F);
208 if (!reg_ctx.read(reg_num, reg_val))
209 return false;
210 io_sp = reg_val;
211 }
212 // 10100nnn: Pop r4-r[4+nnn]. 10101nnn: Pop r4-r[4+nnn] and r14.
213 else if ((opcode & 0xF0) == 0xA0) {
214 uint8_t count = opcode & 0x07;
215 bool pop_lr = (opcode & 0x08) != 0;
216
217 for (int i = 0; i <= count; ++i, io_sp += 4) {
218 uint32_t val = 0;
219 if (space.read_bytes(io_sp, &val, 4)) {
220 uint32_t dwarf_reg = static_cast<uint32_t>(dwarf::arm32::r4) + static_cast<uint32_t>(i);
221 if (!reg_ctx.write(dwarf_reg, val))
222 return false;
223 }
224 }
225
226 if (pop_lr) {
227 uint32_t val = 0;
228 if (space.read_bytes(io_sp, &val, 4)) {
229 if (!reg_ctx.write(static_cast<uint32_t>(dwarf::arm32::r14), val))
230 return false;
231 }
232 io_sp += 4;
233 }
234 }
235 // 10111xxx: Pop VFP double-precision registers d(8) to d(8+xxx)
236 else if ((opcode & 0xF8) == 0xB8) {
237 uint8_t count = opcode & 0x07;
238 for (int i = 0; i <= count; ++i, io_sp += 8) {
239 uint64_t val = 0;
240 if (space.read_bytes(io_sp, &val, 8)) {
241 uint32_t dwarf_reg = static_cast<uint32_t>(dwarf::arm32::d0) + 8U + static_cast<uint32_t>(i);
242 if (!reg_ctx.write_raw(dwarf_reg, &val, 8))
243 return false;
244 }
245 }
246 }
247 }
248 return !stream.failed();
249 }
250};
251
252} // namespace microfmt
Type-erased remote address-space access and remote string/object views.
ARM EXIDX inline unwind bytecode decoder.
Type-erased, two-word handle to a remote address space.
Definition address_space.hpp:204
Byte cursor pulling opcodes sequentially across .ARM.extab words.
Definition arm_extab_stream.hpp:32
bool next_byte(uint8_t &out_byte) noexcept
Pulls the next bytecode instruction byte across word boundaries.
Definition arm_extab_stream.hpp:56
constexpr extab_byte_stream(address_space_ref space, uintptr_t extab_addr) noexcept
Constructs a stream rooted at the first extab word.
Definition arm_extab_stream.hpp:39
constexpr bool failed() const noexcept
Reports whether the stream failed to parse a supported word-0 header (e.g. an out-of-line personality...
Definition arm_extab_stream.hpp:47
Executes a multi-word .ARM.extab bytecode program.
Definition arm_extab_stream.hpp:146
static bool execute(address_space_ref space, uintptr_t extab_addr, uintptr_t &io_sp, register_context_ref reg_ctx) noexcept
Runs the extab bytecode stream, updating SP and writing restored registers.
Definition arm_extab_stream.hpp:162
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