ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
emu_seam_longshift.c
Go to the documentation of this file.
1
49
50#include <stdio.h>
51#include <string.h>
52
53#include "emu_elf.h"
55#include "emu_engine.h"
56#include "emu_exc.h"
58#include "emu_seams.h"
59
60/* Armv8.1-M long-shift family (LSLL / LSRL / ASRL), both the immediate form
61 * (`lsll r4, r5, #7`) and the register form (`lsll r0, r1, ip`).
62 *
63 * Encoding (T1, two halfwords, little-endian), verified by assembling every
64 * member with `arm-none-eabi-as -march=armv8.1-m.main+mve` and reading back the
65 * bytes -- the family is dense and three neighbouring groups alias it:
66 *
67 * hw1 = 0xEA5n, where n[3:1] = RdaLo (always an EVEN register) and
68 * n[0] = 1 selects the SATURATING/ROUNDING subfamily
69 * (UQSHLL / URSHRL / SRSHRL / SQSHLL /
70 * UQRSHLL / SQRSHRL)
71 * hw2[11:8] = RdaHi, except 0b1111 which selects the 32-bit (non-long)
72 * UQRSHL / SQRSHR
73 * hw2[3:0] = 0b1111 -> immediate form, imm5 = hw2[14:12]:hw2[7:6]
74 * (imm5 == 0 encodes a shift of 32)
75 * = 0b1101 -> register form, Rm = hw2[15:12], hw2[7:6] == 0
76 * op = hw2[5:4] (00 = LSLL, 01 = LSRL, 10 = ASRL)
77 *
78 * The two exclusions matter and are not cosmetic. `uqshll r0, r1, #7` encodes
79 * as EA51 11CF: it shares the immediate form's 0b1111 tail and its op field, so
80 * a decoder that masks the low FOUR bits of hw1 as RdaLo (as this one once did)
81 * accepts it as `lsll r1, r1, #7` and computes an unsaturated shift into the
82 * wrong register -- silently wrong 64-bit arithmetic, which is the exact defect
83 * this seam exists to prevent. Likewise `uqrshl r0, r2` (EA50 2F0D) has the
84 * register form's 0b1101 tail and a clear hw1[0], and is excluded only by its
85 * RdaHi == 0b1111. Both subfamilies are therefore rejected here and reported at
86 * install time rather than emulated approximately. */
116
133typedef struct {
134 uint32_t rdalo;
135 uint32_t rdahi;
136 uint32_t op;
137 bool by_reg;
138 uint32_t rm;
139 uint32_t imm;
141
166RA8_INTERNAL static bool
167internal_long_shift_decode(uint16_t hw1, uint16_t hw2, long_shift_insn_t* out)
168{
169 if ((hw1 & (uint16_t)k_lsh_hw1_mask) != (uint16_t)k_lsh_hw1_match) {
170 return false;
171 }
172 if ((hw1 & (uint16_t)k_lsh_hw1_sat_bit) != 0U) {
173 return false; /* UQSHLL / URSHRL / SRSHRL / SQSHLL / UQRSHLL / SQRSHRL. */
174 }
175 const uint32_t op = ((uint32_t)hw2 >> (uint32_t)k_lsh_op_shift) & (uint32_t)k_lsh_op_mask;
176 if (op > (uint32_t)k_lsh_op_asrl) {
177 return false; /* op == 0b11 is reserved -- not a long shift. */
178 }
179 const uint32_t rdahi = ((uint32_t)hw2 >> (uint32_t)k_lsh_rdahi_shift) & (uint32_t)k_lo4_mask;
180 if (rdahi == (uint32_t)k_lsh_rdahi_wide) {
181 return false; /* UQRSHL / SQRSHR -- the 32-bit, non-long forms. */
182 }
183 const uint32_t tail = (uint32_t)hw2 & (uint32_t)k_lsh_hw2_lo_mask;
184 *out = (long_shift_insn_t){
185 .rdalo = (uint32_t)hw1 & (uint32_t)k_lsh_rdalo_mask,
186 .rdahi = rdahi,
187 .op = op,
188 };
189 if (tail == (uint32_t)k_lsh_tail_imm) {
190 const uint32_t imm3 = ((uint32_t)hw2 >> (uint32_t)k_lsh_imm3_shift) & (uint32_t)k_lsh_imm3_mask;
191 const uint32_t imm2 = ((uint32_t)hw2 >> (uint32_t)k_lsh_imm2_shift) & (uint32_t)k_lsh_imm2_mask;
192 const uint32_t imm5 = (imm3 << (uint32_t)k_lsh_imm3_to_imm5) | imm2;
193 out->imm = (imm5 == 0U) ? (uint32_t)k_lsh_word_bits : imm5;
194 return true;
195 }
196 if (tail == (uint32_t)k_lsh_tail_reg) {
197 if (((uint32_t)hw2 & (uint32_t)k_lsh_reg_zero_msk) != 0U) {
198 return false; /* hw2[7:6] must be zero in the register form. */
199 }
200 if (out->op == (uint32_t)k_lsh_op_lsrl) {
201 return false; /* no LSRL register form exists in the ISA. */
202 }
203 out->by_reg = true;
204 out->rm = ((uint32_t)hw2 >> (uint32_t)k_lsh_rm_shift) & (uint32_t)k_lo4_mask;
205 return true;
206 }
207 return false;
208}
209
230RA8_INTERNAL static int32_t internal_long_shift_amount(uc_engine* uc, const long_shift_insn_t* insn)
231{
232 if (!insn->by_reg) {
233 return (int32_t)insn->imm;
234 }
235 uint32_t rm = 0U;
236 (void)uc_reg_read(uc, k_arm_reg_id[insn->rm], &rm);
237 const uint32_t byte = rm & (uint32_t)k_lsh_amount_mask;
238 if ((byte & (uint32_t)k_lsh_amount_sign) != 0U) {
239 return (int32_t)byte - (int32_t)k_lsh_amount_wrap;
240 }
241 return (int32_t)byte;
242}
243
265RA8_INTERNAL static uint64_t internal_long_shift_apply(uint64_t val, uint32_t op, int32_t shift)
266{
267 uint32_t left = (op == (uint32_t)k_lsh_op_lsll) ? 1U : 0U;
268 int32_t n = shift;
269 if (n < 0) {
270 n = -n;
271 left = (left != 0U) ? 0U : 1U;
272 }
273 const bool arith = (op == (uint32_t)k_lsh_op_asrl);
274 if (n >= (int32_t)k_lsh_pair_bits) {
275 if (left != 0U) {
276 return 0U;
277 }
278 return arith ? (uint64_t)((int64_t)val >> ((int32_t)k_lsh_pair_bits - 1)) : 0U;
279 }
280 if (left != 0U) {
281 return val << (uint32_t)n;
282 }
283 return arith ? (uint64_t)((int64_t)val >> (uint32_t)n) : (val >> (uint32_t)n);
284}
285
287enum : uint32_t {
290};
291
295static uint32_t s_lsh_site_count;
298
323typedef struct {
324 bool active;
325 uint32_t at;
326 uint32_t rdalo;
327 uint32_t rdahi;
328 uint32_t lo;
329 uint32_t hi;
330 uint32_t nzcv;
332
334enum : uint32_t {
336};
337
340
355RA8_INTERNAL static long_shift_pending_t* internal_long_shift_slot(uint32_t at, bool want_active)
356{
357 for (uint32_t i = 0U; i < (uint32_t)k_lsh_pending_max; i++) {
358 if (s_lsh_pending[i].active && (s_lsh_pending[i].at == at)) {
359 return &s_lsh_pending[i];
360 }
361 }
362 if (want_active) {
363 return nullptr;
364 }
365 for (uint32_t i = 0U; i < (uint32_t)k_lsh_pending_max; i++) {
366 if (!s_lsh_pending[i].active) {
367 return &s_lsh_pending[i];
368 }
369 }
370 return nullptr;
371}
372
388{
389 for (uint32_t i = 0U; i < s_lsh_site_count; i++) {
390 if (s_lsh_sites[i] == addr) {
391 return true;
392 }
393 }
394 return false;
395}
396
418RA8_INTERNAL static void internal_long_shift_begin(uc_engine* uc, uint32_t address)
419{
420 uint8_t code[k_lsh_insn_len] = {};
421 if (emu_mem_read(uc, address, code, sizeof(code)) != UC_ERR_OK) {
422 return;
423 }
424 const uint16_t hw1 = (uint16_t)(code[0] | ((uint16_t)code[1] << (uint16_t)k_byte_bits));
425 const uint16_t hw2 = (uint16_t)(code[2] | ((uint16_t)code[3] << (uint16_t)k_byte_bits));
426 long_shift_insn_t insn = {};
427 if (!internal_long_shift_decode(hw1, hw2, &insn)) {
428 return; /* scan false-positive at a never-executed address: leave it alone. */
429 }
430 uint32_t lo = 0U;
431 uint32_t hi = 0U;
432 (void)uc_reg_read(uc, k_arm_reg_id[insn.rdalo], &lo);
433 (void)uc_reg_read(uc, k_arm_reg_id[insn.rdahi], &hi);
434 const uint64_t val = ((uint64_t)hi << (uint64_t)k_lsh_word_bits) | (uint64_t)lo;
435 const uint64_t res =
437 uint32_t nzcv = 0U;
438 (void)uc_reg_read(uc, UC_ARM_REG_APSR_NZCV, &nzcv);
439 const uint32_t at = address + (uint32_t)k_lsh_insn_len;
441 if (slot == nullptr) {
442 return; /* table full: leave the core's (wrong) ORRS result rather than
443 * write back against the wrong instruction. Unreachable in practice
444 * -- it needs k_lsh_pending_max nested interrupts inside the
445 * one-instruction window. */
446 }
447 *slot = (long_shift_pending_t){
448 .active = true,
449 .at = at,
450 .rdalo = insn.rdalo,
451 .rdahi = insn.rdahi,
452 .lo = (uint32_t)res,
453 .hi = (uint32_t)(res >> (uint64_t)k_lsh_word_bits),
454 .nzcv = nzcv,
455 };
456}
457
477{
478 (void)uc_reg_write(uc, k_arm_reg_id[slot->rdalo], &slot->lo);
479 (void)uc_reg_write(uc, k_arm_reg_id[slot->rdahi], &slot->hi);
480 (void)uc_reg_write(uc, UC_ARM_REG_APSR_NZCV, &slot->nzcv);
481 slot->active = false;
482}
483
496RA8_INTERNAL static void
497internal_on_long_shift(uc_engine* uc, uint64_t address, uint32_t size, void* user)
498{
499 (void)size;
500 (void)user;
501 const uint32_t pc = (uint32_t)address;
503 if (slot != nullptr) {
505 }
508 }
509}
510
511bool emulate_long_shift_reg(uc_engine* uc, uint32_t pc, const uint8_t code[4])
512{
513 const uint16_t hw1 = (uint16_t)(code[0] | ((uint16_t)code[1] << (uint16_t)k_byte_bits));
514 const uint16_t hw2 = (uint16_t)(code[2] | ((uint16_t)code[3] << (uint16_t)k_byte_bits));
515 long_shift_insn_t insn = {};
516 if (!internal_long_shift_decode(hw1, hw2, &insn) || !insn.by_reg) {
517 return false; /* not ours: the immediate form never traps, it mis-executes. */
518 }
519 uint32_t lo = 0U;
520 uint32_t hi = 0U;
521 (void)uc_reg_read(uc, k_arm_reg_id[insn.rdalo], &lo);
522 (void)uc_reg_read(uc, k_arm_reg_id[insn.rdahi], &hi);
523 const uint64_t val = ((uint64_t)hi << (uint64_t)k_lsh_word_bits) | (uint64_t)lo;
524 const uint64_t res =
526 lo = (uint32_t)res;
527 hi = (uint32_t)(res >> (uint64_t)k_lsh_word_bits);
528 (void)uc_reg_write(uc, k_arm_reg_id[insn.rdalo], &lo);
529 (void)uc_reg_write(uc, k_arm_reg_id[insn.rdahi], &hi);
530 uint32_t next = pc + (uint32_t)k_lsh_insn_len;
531 (void)uc_reg_write(uc, UC_ARM_REG_PC, &next);
532 return true;
533}
534
536typedef struct {
537 uc_engine* uc;
538 uint32_t n_hooks;
540
559 const uint8_t* bytes,
560 size_t length,
561 uint32_t vaddr)
562{
563 for (size_t off = 0U; (off + k_lsh_insn_len) <= length; off += 2U) {
564 const uint8_t* p = &bytes[off];
565 const uint16_t hw1 = (uint16_t)(p[0] | ((uint16_t)p[1] << (uint16_t)k_byte_bits));
566 const uint16_t hw2 = (uint16_t)(p[2] | ((uint16_t)p[3] << (uint16_t)k_byte_bits));
567 long_shift_insn_t insn = {};
568 if (!internal_long_shift_decode(hw1, hw2, &insn)) {
569 continue;
570 }
571 if (insn.by_reg) {
572 /* The register form aliases to ORRS with Rm == SP, which the core refuses
573 * outright, so it arrives as a real undefined-instruction trap and is
574 * serviced by ::emulate_long_shift_reg off the seam dispatch chain. Only
575 * the immediate form -- which aliases to an ORRS the core happily
576 * MIS-EXECUTES -- has to be intercepted here. */
577 continue;
578 }
579 if (scan->n_hooks >= (uint32_t)k_lsh_sites_max) {
580 (void)priv_emu_io_errf(" long-shift seam: site cap %u reached\n", (unsigned)k_lsh_sites_max);
581 return false;
582 }
583 const uint64_t va = (uint64_t)vaddr + off;
584 const uint64_t tail = va + (uint64_t)k_lsh_insn_len;
585 s_lsh_sites[s_lsh_site_count] = (uint32_t)va;
587 (void)uc_hook_add(scan->uc,
588 &s_lsh_hooks[(size_t)scan->n_hooks * 2U],
589 UC_HOOK_CODE,
591 nullptr,
592 va,
593 va);
594 (void)uc_hook_add(scan->uc,
595 &s_lsh_hooks[((size_t)scan->n_hooks * 2U) + 1U],
596 UC_HOOK_CODE,
598 nullptr,
599 tail,
600 tail);
601 scan->n_hooks++;
602 }
603 return true;
604}
605
622 void* opaque)
623{
624 enum : size_t {
625 k_lsh_scan_scratch = 4096U,
626 };
627 long_shift_scan_t* const scan = (long_shift_scan_t*)opaque;
628 uint8_t bytes[k_lsh_scan_scratch];
629 uint32_t base = 0U;
630 while ((base + (uint32_t)k_lsh_insn_len) <= segment->filesz) {
631 const uint32_t left = segment->filesz - base;
632 const size_t length = (left < sizeof(bytes)) ? (size_t)left : sizeof(bytes);
633 emu_elf_view_t view = {};
634 if (priv_emu_elf_read(segment->source,
635 (uint64_t)segment->offset + base,
636 length,
637 bytes,
638 sizeof(bytes),
639 &view)
641 !internal_install_seg_hooks(scan, bytes, length, segment->vaddr + base)) {
642 return false;
643 }
644 if (length == left) {
645 break;
646 }
647 base += (uint32_t)length - 2U;
648 }
649 return true;
650}
651
675void long_shift_seam_install(uc_engine* uc, const emu_elf_source_t* elf)
676{
677 if (elf == nullptr) {
678 return;
679 }
680 s_lsh_site_count = 0U;
681 (void)memset(s_lsh_pending, 0, sizeof(s_lsh_pending));
682 long_shift_scan_t scan = {.uc = uc};
684 if (scan.n_hooks > 0U) {
685 (void)priv_emu_io_errf(" long-shift seam: emulating %u Armv8.1-M LSLL/LSRL/ASRL site(s)\n",
686 (unsigned)scan.n_hooks);
687 }
688}
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.
const int k_arm_reg_id[16]
ARM register index (0..15) -> Unicorn register id.
Definition emu_engine.c:17
Cortex-M exception model constants and interfaces for ra8_emulator.
@ k_lo4_mask
Low nibble (register / cond field).
Definition emu_exc.h:110
@ k_byte_bits
Bits per byte (SHPR field width).
Definition emu_exc.h:99
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_read(uc_engine *uc, uint64_t address, void *bytes, size_t count)
Read guest memory through the central access seam.
@ k_lsh_hw1_sat_bit
hw1[0]: 1 = saturating/rounding subfamily.
@ k_lsh_tail_reg
hw2[3:0] == 0b1101 -> register form.
@ k_lsh_rdahi_wide
RdaHi == 0b1111 -> 32-bit form, rejected.
@ k_lsh_imm3_to_imm5
imm5 = (imm3 << 2) | imm2.
@ k_lsh_tail_imm
hw2[3:0] == 0b1111 -> immediate form.
@ k_lsh_imm2_mask
imm2 width.
@ k_lsh_hw1_mask
hw1[15:4] selects the group.
@ k_lsh_insn_len
Thumb-2 instruction length, bytes.
@ k_lsh_amount_mask
Register form uses Rm[7:0] as the amount.
@ k_lsh_imm3_shift
imm3 at hw2[14:12].
@ k_lsh_hw1_match
hw1[15:4] == 0xEA5 for this family.
@ k_lsh_hw2_lo_mask
hw2[3:0] selects immediate vs register form.
@ k_lsh_reg_zero_msk
hw2[7:6] must be zero in the register form.
@ k_lsh_op_lsll
00: 64-bit logical shift left.
@ k_lsh_imm2_shift
imm2 at hw2[7:6].
@ k_lsh_word_bits
Word width; also the imm5==0 shift amount.
@ k_lsh_op_asrl
10: 64-bit arithmetic shift right.
@ k_lsh_rm_shift
Rm at hw2[15:12] in the register form.
@ k_lsh_op_lsrl
01: 64-bit logical shift right.
@ k_lsh_pair_bits
Width of the {RdaHi:RdaLo} pair, bits.
@ k_lsh_amount_sign
Sign bit of the 8-bit shift amount.
@ k_lsh_op_shift
op field at hw2[5:4].
@ k_lsh_imm3_mask
imm3 width.
@ k_lsh_amount_wrap
Rm[7:0] is signed: subtract to go negative.
@ k_lsh_op_mask
op width (value 3 is reserved -> rejected).
@ k_lsh_rdalo_mask
RdaLo at hw1[3:1] (an even register).
@ k_lsh_rdahi_shift
RdaHi at hw2[11:8].
static uint32_t s_lsh_site_count
Number of valid entries in s_lsh_sites.
static RA8_INTERNAL void internal_on_long_shift(uc_engine *uc, uint64_t address, uint32_t size, void *user)
Perform on long shift for the emu seam longshift model.
static uc_hook s_lsh_hooks[k_lsh_hooks_max]
Hook handles for the installed sites (kept alive for the whole run).
void long_shift_seam_install(uc_engine *uc, const emu_elf_source_t *elf)
Scan the loaded image and install a hook at every immediate long-shift.
static RA8_INTERNAL bool internal_long_shift_segment(const elf_exec_segment_t *segment, void *opaque)
Stream and scan one executable segment through bounded scratch.
static RA8_INTERNAL int32_t internal_long_shift_amount(uc_engine *uc, const long_shift_insn_t *insn)
Resolve the shift amount of a decoded long shift, in bits.
static RA8_INTERNAL uint64_t internal_long_shift_apply(uint64_t val, uint32_t op, int32_t shift)
Apply one long shift to a 64-bit value with correct host arithmetic.
static long_shift_pending_t s_lsh_pending[k_lsh_pending_max]
Outstanding long-shift write-backs (see long_shift_pending_t).
static RA8_INTERNAL bool internal_install_seg_hooks(long_shift_scan_t *scan, const uint8_t *bytes, size_t length, uint32_t vaddr)
Scan one transient segment chunk and arm long-shift hooks.
@ k_lsh_sites_max
Lsh sites maximum.
@ k_lsh_hooks_max
Two hooks per site: the site and its tail.
static uint32_t s_lsh_sites[k_lsh_sites_max]
Execution addresses of every immediate long shift found in the image.
static RA8_INTERNAL long_shift_pending_t * internal_long_shift_slot(uint32_t at, bool want_active)
Find the pending write-back slot for at, or a free slot.
static RA8_INTERNAL bool internal_long_shift_is_site(uint32_t addr)
Report whether addr is a long-shift site found by the image scan.
@ k_lsh_pending_max
Lsh pending maximum.
static RA8_INTERNAL void internal_long_shift_commit(uc_engine *uc, long_shift_pending_t *slot)
Write back the staged long-shift result over the ORRS's damage.
bool emulate_long_shift_reg(uc_engine *uc, uint32_t pc, const uint8_t code[4])
Emulate a register-form Armv8.1-M long shift (LSLL/ASRL) that trapped.
static RA8_INTERNAL bool internal_long_shift_decode(uint16_t hw1, uint16_t hw2, long_shift_insn_t *out)
Decode an Armv8.1-M long shift (LSLL/LSRL/ASRL), immediate or register.
static RA8_INTERNAL void internal_long_shift_begin(uc_engine *uc, uint32_t address)
Compute one long shift on the host and stage it for write-back.
Armv8.1-M instruction-emulation seams (M85 ops on Unicorn's M33).
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
void * memset(void *dst, int value, size_t n)
Fill memory with a constant byte value.
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
One decoded immediate-or-register Armv8.1-M long shift.
uint32_t imm
Shift amount 1..32 (immediate form only).
uint32_t rdahi
High destination register index.
uint32_t rdalo
Low destination register index (even).
uint32_t op
k_lsh_op_lsll / _lsrl / _asrl.
bool by_reg
true when the amount comes from rm.
uint32_t rm
Register holding the amount (register form only).
The correct result of one long shift, awaiting write-back.
uint32_t at
Address of the instruction after the long shift.
uint32_t rdalo
Low destination register index.
uint32_t lo
Correct low word of the shifted 64-bit value.
uint32_t rdahi
High destination register index.
uint32_t nzcv
NZCV as it stood before the ORRS overwrote it.
uint32_t hi
Correct high word of the shifted 64-bit value.
bool active
A captured result is waiting to be written back.
Engine and bounded installed-hook count for a streamed scan.
uc_engine * uc
Engine receiving targeted hooks.
uint32_t n_hooks
Running installed-site count.