ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
emu_seam_div0.c
Go to the documentation of this file.
1
19
20#include <stdio.h>
21#include <string.h>
22
23#include "emu_elf.h"
25#include "emu_engine.h"
26#include "emu_exc.h"
28#include "emu_seams.h"
29
31static bool s_div0_fault;
32
34static uint32_t s_div0_fault_pc;
35
37static uint64_t s_div0_traps;
38
39/*
40 * =============================================================================
41 * Divide-by-zero UsageFault (CCR.DIV_0_TRP) -- CPU-model seam.
42 *
43 * Unicorn's Cortex-M core executes UDIV/SDIV with the Arm default divide-by-zero
44 * result (quotient 0) and never raises the UsageFault that real Armv7-M/Armv8-M
45 * silicon takes when CCR.DIV_0_TRP is set. ra8_emulator closes that gap by scanning
46 * the image for every UDIV/SDIV site and -- only after the firmware sets
47 * CCR.DIV_0_TRP (watched via on_scb_ctrl_write) -- overwriting those sites with an
48 * undefined instruction (UDF). Each divide then traps through the already-armed
49 * ::on_invalid_insn hook into ::emulate_div0_patched, which either takes a decoded
50 * UsageFault (divisor zero) or emulates the divide in software (::internal_div0_quotient)
51 * and continues. Patching -- rather than a per-site UC_HOOK_CODE -- is the point:
52 * a code hook disables Unicorn's engine-wide block chaining and would roughly
53 * quarter throughput for ANY busy firmware that arms the trap, whereas the invalid
54 * hook has no such cost. A firmware that never opts in keeps its native divides
55 * and pays nothing. Faithful in both directions: no faked trap, no masked one.
56 * =============================================================================
57 */
58
66static const uint8_t s_k_div0_udf[k_div0_insn_len] = {
67 (uint8_t)k_div0_udf_b0,
68 (uint8_t)k_div0_udf_b1,
69 0U,
70 (uint8_t)k_div0_udf_b3,
71};
72
79typedef struct {
80 uint32_t va;
81 uint16_t hw1;
82 uint16_t hw2;
84
86enum : uint32_t {
88};
90static uint32_t s_div0_site_n;
91static bool s_div0_armed;
92
133 uint16_t hw2,
134 uint32_t* out_rn,
135 uint32_t* out_rd,
136 uint32_t* out_rm,
137 bool* out_signed)
138{
139 if ((out_rn == nullptr) || (out_rd == nullptr) || (out_rm == nullptr) ||
140 (out_signed == nullptr)) {
141 return false;
142 }
143 const bool is_udiv = ((uint32_t)hw1 & (uint32_t)k_div0_hw1_mask) == (uint32_t)k_div0_hw1_udiv;
144 const bool is_sdiv = ((uint32_t)hw1 & (uint32_t)k_div0_hw1_mask) == (uint32_t)k_div0_hw1_sdiv;
145 if (!is_udiv && !is_sdiv) {
146 return false;
147 }
148 if (((uint32_t)hw2 & (uint32_t)k_div0_hw2_mask) != (uint32_t)k_div0_hw2_fixed) {
149 return false;
150 }
151 const uint32_t rn = (uint32_t)hw1 & (uint32_t)k_div0_reg_mask;
152 const uint32_t rd = ((uint32_t)hw2 >> (uint32_t)k_div0_rd_shift) & (uint32_t)k_div0_reg_mask;
153 const uint32_t rm = (uint32_t)hw2 & (uint32_t)k_div0_reg_mask;
154 /* d/n/m == SP or PC is UNPREDICTABLE for UDIV/SDIV: never a real divide, so
155 * this window is a false positive straddling two instructions. Reject it so
156 * the arming pass does not overwrite executed code with UDF. */
157 if ((rn == (uint32_t)k_div0_reg_sp) || (rn == (uint32_t)k_div0_reg_pc) ||
158 (rd == (uint32_t)k_div0_reg_sp) || (rd == (uint32_t)k_div0_reg_pc) ||
159 (rm == (uint32_t)k_div0_reg_sp) || (rm == (uint32_t)k_div0_reg_pc)) {
160 return false;
161 }
162 *out_rn = rn;
163 *out_rd = rd;
164 *out_rm = rm;
165 *out_signed = is_sdiv;
166 return true;
167}
168
187RA8_INTERNAL static uint32_t internal_div0_quotient(uint32_t vn, uint32_t vm, bool is_signed)
188{
189 if (vm == 0U) {
190 return 0U; /* Arm default when DIV_0_TRP is clear. */
191 }
192 if (!is_signed) {
193 return vn / vm;
194 }
195 if ((vn == (uint32_t)k_div0_int32_min) && ((int32_t)vm == -1)) {
196 return (uint32_t)k_div0_int32_min; /* Arm SDIV overflow: INT32_MIN / -1 = INT32_MIN. */
197 }
198 return (uint32_t)((int32_t)vn / (int32_t)vm);
199}
200
232bool emulate_div0_patched(uc_engine* uc, uint32_t pc, const uint8_t code[4])
233{
234 (void)code;
235 const div0_site_t* site = nullptr;
236 for (uint32_t i = 0U; i < s_div0_site_n; i++) {
237 if (s_div0_site[i].va == pc) {
238 site = &s_div0_site[i];
239 break;
240 }
241 }
242 if (site == nullptr) {
243 return false; /* not an armed divide -- let the other invalid-insn handlers try. */
244 }
245 uint32_t rn = 0U;
246 uint32_t rd = 0U;
247 uint32_t rm = 0U;
248 bool sign = false;
249 if (!internal_udiv_sdiv_decode(site->hw1, site->hw2, &rn, &rd, &rm, &sign)) {
250 return false; /* a tracked non-divide (scan false-positive): not ours to service. */
251 }
252 uint32_t vn = 0U;
253 uint32_t vm = 0U;
254 (void)uc_reg_read(uc, k_arm_reg_id[rn], &vn);
255 (void)uc_reg_read(uc, k_arm_reg_id[rm], &vm);
256 const uint32_t ccr = rd32(uc, (uint64_t)k_scb_ccr);
257 if ((vm == 0U) && ((ccr & (uint32_t)k_ccr_div_0_trp) != 0U)) {
258 s_div0_fault = true;
259 s_div0_fault_pc = pc; /* stacked PC == the divide, as real hardware does. */
260 return true; /* PC left at the site; the run loop synthesises UsageFault. */
261 }
262 const uint32_t q = internal_div0_quotient(vn, vm, sign);
263 (void)uc_reg_write(uc, k_arm_reg_id[rd], &q);
264 uint32_t next = pc + (uint32_t)k_div0_insn_len;
265 (void)uc_reg_write(uc, UC_ARM_REG_PC, &next);
266 /* Emulating one divide consumes no modelled time: mark the stop a zero-time
267 * seam relaunch so the inner run loop re-enters at `next` WITHOUT advancing
268 * SysTick or charging an outer chunk (like on_sdmmc_read_block). Without this,
269 * a divide in a hot loop (e.g. ra8_keycache_put's modulo hashing) charges one
270 * of the 40000 chunks per execution, exhausting the budget mid-render. The
271 * zero-divisor trap path above returns earlier and is handled by the
272 * s_div0_fault branch, which is already zero-time. */
274 return true;
275}
276
301void div0_patch_sites(uc_engine* uc)
302{
303 if (s_div0_armed) {
304 return;
305 }
306 for (uint32_t i = 0U; i < s_div0_site_n; i++) {
307 (void)emu_mem_write(uc, (uint64_t)s_div0_site[i].va, s_k_div0_udf, sizeof(s_k_div0_udf));
308 }
309 s_div0_armed = true;
310 (void)priv_emu_io_errf(" div-0 seam: CCR.DIV_0_TRP set -- patched %u UDIV/SDIV site(s)\n",
311 (unsigned)s_div0_site_n);
312}
313
340{
341 (void)ctx;
342 enum : size_t {
343 k_div0_scan_scratch = 4096U,
344 };
345 uint8_t bytes[k_div0_scan_scratch];
346 uint32_t base = 0U;
347 while ((base + (uint32_t)k_div0_insn_len) <= seg->filesz) {
348 const uint32_t left = seg->filesz - base;
349 const size_t length = (left < sizeof(bytes)) ? (size_t)left : sizeof(bytes);
350 emu_elf_view_t view = {};
351 if (priv_emu_elf_read(seg->source,
352 (uint64_t)seg->offset + base,
353 length,
354 bytes,
355 sizeof(bytes),
356 &view)
358 return false;
359 }
360 for (size_t local = 0U; (local + k_div0_insn_len) <= length; local += 2U) {
361 const uint8_t* p = &bytes[local];
362 const uint16_t hw1 = (uint16_t)(p[0] | ((uint16_t)p[1] << 8));
363 const uint16_t hw2 = (uint16_t)(p[2] | ((uint16_t)p[3] << 8));
364 uint32_t rn = 0U;
365 uint32_t rd = 0U;
366 uint32_t rm = 0U;
367 bool sign = false;
368 if (!internal_udiv_sdiv_decode(hw1, hw2, &rn, &rd, &rm, &sign)) {
369 continue;
370 }
371 if (s_div0_site_n >= (uint32_t)k_div0_sites_max) {
372 (void)priv_emu_io_errf(" div-0 seam: site cap %u reached\n", (unsigned)k_div0_sites_max);
373 return false;
374 }
376 (div0_site_t){.va = seg->vaddr + base + (uint32_t)local, .hw1 = hw1, .hw2 = hw2};
378 }
379 if (length == left) {
380 break;
381 }
382 base += (uint32_t)length - 2U;
383 }
384 return true;
385}
386
410{
411 s_div0_site_n = 0U;
412 s_div0_armed = false;
414 /* The CCR write that arms these sites is caught by on_scb_ctrl_write; arming
415 * overwrites each with UDF so its execution traps through on_invalid_insn. */
416 if (s_div0_site_n > 0U) {
417 (void)priv_emu_io_errf(" div-0 seam: %u UDIV/SDIV site(s) tracked; armed on CCR.DIV_0_TRP\n",
418 (unsigned)s_div0_site_n);
419 }
420}
421
448void div0_synth_usagefault(uc_engine* uc, uint32_t vtor_base)
449{
450 const uint32_t cfsr = rd32(uc, (uint64_t)k_scb_cfsr) | (uint32_t)k_div0_cfsr_divzero;
451 wr32(uc, (uint64_t)k_scb_cfsr, cfsr);
452 const uint32_t fault_pc = emu_div0_fault_pc();
453 (void)uc_reg_write(uc, UC_ARM_REG_PC, &fault_pc);
454 const uint32_t handler = exc_vector(uc, vtor_base, (uint32_t)k_exc_usagefault);
455 if (handler != 0U) {
457 exc_enter(uc, (uint32_t)k_exc_usagefault, handler);
458 }
459}
460
463{
464 return s_div0_fault;
465}
466
469{
470 s_div0_fault = false;
471}
472
474uint32_t emu_div0_fault_pc(void)
475{
476 return s_div0_fault_pc;
477}
478
481{
482 s_div0_traps++;
483}
484
487{
488 s_div0_armed = false;
489}
ELF32 image services for the board emulator (load / symbols / vectors).
uint32_t elf_foreach_exec_segment(const emu_elf_source_t *elf, elf_exec_segment_fn fn, void *ctx)
Walk every executable PT_LOAD segment of an ELF32 image.
Definition emu_elf.c:265
@ k_emu_elf_io_ok
The complete operation succeeded.
Definition emu_elf.h:75
Private raw-descriptor ELF source operations.
emu_elf_io_result_t priv_emu_elf_read(const emu_elf_source_t *source, uint64_t offset, size_t required_bytes, void *scratch, size_t supplied_bytes, emu_elf_view_t *view)
Read one exact source range into caller-owned bounded scratch.
Shared Unicorn engine access utilities for the board emulator.
void emu_seam_request_relaunch(void)
Mark the pending engine stop as a zero-time seam relaunch.
Definition emu_engine.c:52
static uint32_t rd32(uc_engine *uc, uint64_t addr)
Read a 32-bit little-endian word from emulated memory.
Definition emu_engine.h:67
const int k_arm_reg_id[16]
ARM register index (0..15) -> Unicorn register id.
Definition emu_engine.c:17
static void wr32(uc_engine *uc, uint64_t addr, uint32_t v)
Write a 32-bit little-endian word to emulated memory.
Definition emu_engine.h:91
Cortex-M exception model constants and interfaces for ra8_emulator.
@ k_scb_cfsr
Config Fault Status (MMFSR low byte).
Definition emu_exc.h:52
@ k_scb_ccr
Configuration and Control (CCR).
Definition emu_exc.h:50
@ k_ccr_div_0_trp
CCR.DIV_0_TRP bit4: divide-by-0 traps.
Definition emu_exc.h:51
@ k_exc_usagefault
UsageFault exception / vector index.
Definition emu_exc.h:56
uint32_t exc_vector(uc_engine *uc, uint32_t vtor_base, uint32_t exc_num)
Read the handler address for an exception from the vector table.
Definition emu_exc.c:417
void exc_enter(uc_engine *uc, uint32_t exc_num, uint32_t handler)
Enter a Cortex-M exception: stack the basic frame and vector in.
Definition emu_exc.c:210
Bounded raw-descriptor I/O seam for the RA8 emulator.
emu_io_result_t priv_emu_io_errf(const char *format,...)
Format bounded text and write it to the injected error descriptor.
uc_err emu_mem_write(uc_engine *uc, uint64_t address, const void *bytes, size_t count)
Write guest memory through the central access seam.
static RA8_INTERNAL bool internal_udiv_sdiv_decode(uint16_t hw1, uint16_t hw2, uint32_t *out_rn, uint32_t *out_rd, uint32_t *out_rm, bool *out_signed)
Decode a Thumb-2 halfword pair as UDIV/SDIV, recovering all operands.
static bool s_div0_armed
Armed sites overwritten UDF.
bool emu_div0_fault_pending(void)
Implementation of emu_div0_fault_pending() – plain flag read.
void div0_seam_install(const emu_elf_source_t *elf)
Scan the image for UDIV/SDIV sites so the div-0 trap can arm later.
void emu_div0_count_trap(void)
Implementation of emu_div0_count_trap() – run-end telemetry bump.
void div0_synth_usagefault(uc_engine *uc, uint32_t vtor_base)
Synthesise a UsageFault (#6) for a trapped divide-by-zero.
void emu_div0_clear_fault(void)
Implementation of emu_div0_clear_fault() – plain flag clear.
void emu_div0_disarm(void)
Implementation of emu_div0_disarm() – warm reboot un-patches sites.
@ k_div0_sites_max
Div0 sites maximum.
uint32_t emu_div0_fault_pc(void)
Implementation of emu_div0_fault_pc() – plain state read.
static bool s_div0_fault
Trapping div-0 latched (the run loop synthesises the UsageFault).
bool emulate_div0_patched(uc_engine *uc, uint32_t pc, const uint8_t code[4])
Service an undefined-instruction trap that landed on an armed divide.
static RA8_INTERNAL bool internal_div0_scan_segment(const elf_exec_segment_t *seg, void *ctx)
Record every UDIV/SDIV site in one executable segment.
static uint32_t s_div0_site_n
Count of tracked sites.
void div0_patch_sites(uc_engine *uc)
Overwrite every tracked divide with UDF so divide-by-zero can trap.
static uint32_t s_div0_fault_pc
PC of the divide that trapped (stacked by the synthesised fault).
static uint64_t s_div0_traps
Count of divide-by-zero UsageFaults synthesised this run.
static div0_site_t s_div0_site[k_div0_sites_max]
Tracked divide sites.
static const uint8_t s_k_div0_udf[k_div0_insn_len]
UDF.W #0 – a permanently-undefined 32-bit Thumb-2 instruction (LE bytes).
static RA8_INTERNAL uint32_t internal_div0_quotient(uint32_t vn, uint32_t vm, bool is_signed)
Compute a UDIV/SDIV quotient with the Arm div-by-zero + overflow rules.
Armv8.1-M instruction-emulation seams (M85 ops on Unicorn's M33).
@ k_div0_hw2_mask
hw2[15:12] and hw2[7:4] must be 1111.
Definition emu_seams.h:234
@ k_div0_cfsr_divzero
CFSR.UFSR.DIVBYZERO (0x02000000).
Definition emu_seams.h:241
@ k_div0_insn_len
UDIV/SDIV are 32-bit Thumb-2.
Definition emu_seams.h:240
@ k_div0_udf_b0
UDF.W #0 little-endian byte 0.
Definition emu_seams.h:243
@ k_div0_udf_b1
UDF.W #0 little-endian byte 1.
Definition emu_seams.h:244
@ k_div0_hw1_udiv
UDIV T1: hw1[15:4] == 0xFBB.
Definition emu_seams.h:232
@ k_div0_rd_shift
hw2[11:8] = Rd (destination register).
Definition emu_seams.h:237
@ k_div0_hw1_sdiv
SDIV T1: hw1[15:4] == 0xFB9.
Definition emu_seams.h:233
@ k_div0_reg_sp
r13 (SP): UNPREDICTABLE as UDIV d/n/m.
Definition emu_seams.h:238
@ k_div0_int32_min
INT32_MIN: the SDIV / -1 overflow edge.
Definition emu_seams.h:242
@ k_div0_hw1_mask
hw1[15:4] selects the divide opcode.
Definition emu_seams.h:231
@ k_div0_reg_pc
r15 (PC): UNPREDICTABLE as UDIV d/n/m.
Definition emu_seams.h:239
@ k_div0_reg_mask
4-bit register field (Rn / Rm / Rd).
Definition emu_seams.h:236
@ k_div0_udf_b3
UDF.W #0 little-endian byte 3.
Definition emu_seams.h:245
@ k_div0_hw2_fixed
Their required value for a real divide.
Definition emu_seams.h:235
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
One tracked UDIV/SDIV site: its address and original encoding halfwords.
uint32_t va
Divide-instruction virtual address.
uint16_t hw2
Original second halfword.
uint16_t hw1
Original first halfword.
One executable PT_LOAD segment, already bounds-checked against the image.
Definition emu_elf.h:238
uint32_t offset
Segment file offset.
Definition emu_elf.h:240
uint32_t filesz
Segment file byte count.
Definition emu_elf.h:243
const emu_elf_source_t * source
Open source owning segment bytes.
Definition emu_elf.h:239
uint32_t vaddr
Segment virtual address.
Definition emu_elf.h:241
emu_elf_io_status_t status
Semantic completion status.
Definition emu_elf.h:84
One independently owned immutable raw-descriptor ELF source.
Definition emu_elf.h:91
Transient view into caller-owned bounded scratch.
Definition emu_elf.h:97