commit 5fced6f7c4c5295dfbfa8259fb79575e081f26f5
parent 8ace7b4efb4478f3c7a24fd0a4bccf7ab89cc54a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 06:09:53 -0700
arm32 as: BL/B.W/Bcc.W placeholders must match codegen's encoders
The standalone assembler emitted ad-hoc branch placeholders (0xf000d000 for BL,
0xf0009000 for B.W, 0xf0008000|cond for Bcc.W). The THM_CALL/JUMP24/JUMP19
relocations are REL and the patcher preserves the field's J1/J2 (the ~(^S)
inversion), so a placeholder whose J1/J2 imply a nonzero in-field offset skews
the applied target — a +0xC00000 error that sent every kit-assembled
cross-object `bl SYM` to garbage (e.g. the AEABI compare helpers' `bl __ltdf2`).
This is the first kit-assembled code with cross-object branches to be linked and
run (the bare-metal reset stub is clang-assembled), so it had never surfaced.
Emit arm_bl()/arm_b_w()/arm_b_cond_w() — the exact placeholders codegen uses (and
the linker round-trips). test-isa/test-asm green; AEABI dcmp/fcmp now run
correctly under qemu.
Diffstat:
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/arch/arm32/asm.c b/src/arch/arm32/asm.c
@@ -533,7 +533,11 @@ static void assemble_one(AsmDriver* d, const Arm32InsnDesc* desc, u32 cond) {
return;
}
case ARM_FMT_BL: {
- emit_t32(d, 0xf000d000u); /* placeholder */
+ /* Placeholder MUST match codegen's arm_bl(): the THM_CALL reloc is REL and
+ * the patcher preserves the field's J1/J2 (XOR-S) base, so an ad-hoc
+ * placeholder whose J1/J2 imply a nonzero in-field offset corrupts the
+ * applied target (a +0xC00000 skew on cross-object calls). */
+ emit_t32(d, arm_bl()); /* placeholder (== codegen) */
/* reloc rides the BL we just emitted (offset = its start). */
{
MCEmitter* mc = asm_driver_mc(d);
@@ -547,7 +551,7 @@ static void assemble_one(AsmDriver* d, const Arm32InsnDesc* desc, u32 cond) {
return;
}
case ARM_FMT_BRANCH_T4: {
- emit_t32(d, 0xf0009000u);
+ emit_t32(d, arm_b_w());
{
MCEmitter* mc = asm_driver_mc(d);
ObjSymId sym = OBJ_SYM_NONE;
@@ -560,7 +564,7 @@ static void assemble_one(AsmDriver* d, const Arm32InsnDesc* desc, u32 cond) {
return;
}
case ARM_FMT_BRANCH_T3: {
- emit_t32(d, 0xf0008000u | (cond << 22));
+ emit_t32(d, arm_b_cond_w(cond));
{
MCEmitter* mc = asm_driver_mc(d);
ObjSymId sym = OBJ_SYM_NONE;
@@ -577,7 +581,7 @@ static void assemble_one(AsmDriver* d, const Arm32InsnDesc* desc, u32 cond) {
/* 16-bit branches: emit the 32-bit wide form so the relocation has the
* full range (the disassembler still round-trips the wide encoding). */
if (cond == ARM_CC_AL) {
- emit_t32(d, 0xf0009000u);
+ emit_t32(d, arm_b_w());
{
MCEmitter* mc = asm_driver_mc(d);
ObjSymId sym = OBJ_SYM_NONE;
@@ -588,7 +592,7 @@ static void assemble_one(AsmDriver* d, const Arm32InsnDesc* desc, u32 cond) {
0, 0);
}
} else {
- emit_t32(d, 0xf0008000u | (cond << 22));
+ emit_t32(d, arm_b_cond_w(cond));
{
MCEmitter* mc = asm_driver_mc(d);
ObjSymId sym = OBJ_SYM_NONE;