librz/bin/coff: scale addresses on word-addressed TI targets

The C54x and C28x address 16-bit words, so their loadable section sizes
and every address in the file count words. Rizin's address space is
byte-based, so reading those unscaled mapped only half of each loadable
section and placed every symbol at half its true offset: _main in the
c54x emulateme object landed mid-instruction instead of on its prologue.

Scale section VAs, loadable section sizes and symbol addresses by the
target's address unit. Debug sections are byte streams even on these
targets and keep a scale of one, matching the loadable mask already used
for mapping. The C55x addresses program memory by byte and is left
alone; the ids were checked against the objects in rizin-testbins by
comparing each section's declared size against its extent in the file.

Branch and call targets on the C54x count program words too, and reached
analysis and RzIL unscaled. That went unnoticed while the sections were
half-mapped: the emulateme RzIL test ran from a mid-instruction address
whose decode happened to lift, so it pinned values produced by garbage.
Scale those as well and drive the test the way its C55x sibling does,
decrypting seckrit with the real key.

With the sections fully mapped, analysis now finds every routine the
c54x fixtures declare, so the function counts change.
This commit is contained in:
Anton Kochkov 2026-08-11 04:34:40 +00:00 committed by NOT XVilka
parent f82d6e2104
commit fee84c7f5c
8 changed files with 96 additions and 56 deletions

View file

@ -116,6 +116,9 @@ static RzILOpPure *c54x_ea(const C55Operand *m) {
}
// Byte address of the current stack top: SP is a 16-bit word pointer.
// Program addresses count 16-bit words; the VM runs in the byte address space.
#define C54X_WORD_BYTES 2
static RzILOpPure *c54x_sp_ea(void) {
return MUL(UNSIGNED(24, VARG("sp")), UN(24, C54X_WORD_BYTES));
}
@ -1152,7 +1155,7 @@ RZ_IPI RzILOpEffect *c54x_lift(const C55Insn *insn, ut64 pc) {
if (!pred) {
return NULL;
}
return BRANCH(pred, JMP(UN(24, (ut32)op[0]->imm)), NOP());
return BRANCH(pred, JMP(UN(24, (ut32)op[0]->imm * C54X_WORD_BYTES)), NOP());
}
// conditional return
@ -1185,7 +1188,7 @@ RZ_IPI RzILOpEffect *c54x_lift(const C55Insn *insn, ut64 pc) {
}
// Test the AR before its post-modify, which happens regardless of branch.
RzILOpEffect *grab = SETL("take", BOOL_TO_BV(INV(IS_ZERO(VARG(ar))), 1));
RzILOpEffect *br = BRANCH(NON_ZERO(VARL("take")), JMP(UN(24, (ut32)op[0]->imm)), NOP());
RzILOpEffect *br = BRANCH(NON_ZERO(VARL("take")), JMP(UN(24, (ut32)op[0]->imm * C54X_WORD_BYTES)), NOP());
RzILOpEffect *post = c55_post_effect(a, op[1]);
return post ? SEQ3(grab, post, br) : SEQ2(grab, br);
}

View file

@ -500,7 +500,10 @@ static ut64 c55_branch_target(const C55Insn *insn, ut64 pc) {
if (o->abs_target) {
// Absolute target: the operand is the destination address
// itself (24-bit program space), not a pc-relative offset.
return v & 0xffffff;
// The C54x counts program words here, so scale the target
// into the byte address space the analysis works in.
ut64 target = v & 0xffffff;
return insn->arch == C55_ARCH_C54X ? target * 2 : target;
}
st64 soff = o->reltarget_unsigned
? (st64)v

View file

@ -124,6 +124,30 @@ RZ_API bool rz_coff_supported_arch(RzBuffer *b) {
return coff_guess_endianness(b, &big_endian);
}
/**
* \brief Bytes per target address unit of \p obj.
* \param obj COFF object
* \return 1 for byte-addressed targets, 2 for the 16-bit word-addressed TI DSPs
*
* The C54x and C28x address 16-bit words, so their loadable section
* sizes and every address in the file count words, not bytes. Rizin's address
* space is byte-based, so those have to be scaled to line up with the section
* data. Debug sections are byte streams even on these targets and are excluded
* by the caller.
*/
RZ_API ut32 rz_coff_addr_scale(RZ_NONNULL struct rz_bin_coff_obj *obj) {
rz_return_val_if_fail(obj, 1);
switch (obj->target_id) {
case COFF_FILE_TARGET_TI_TMS320C5400:
case COFF_FILE_TARGET_TI_TMS320C2800:
return 2;
default:
// The C55x addresses program memory by byte, so its objects need no
// scaling despite being a fixed-point DSP.
return 1;
}
}
RZ_API ut64 rz_coff_perms_from_section_flags(ut32 flags) {
ut32 r = 0;
if (flags & COFF_SCN_MEM_READ) {
@ -375,11 +399,15 @@ static bool bin_coff_init_scn_va(struct rz_bin_coff_obj *obj) {
CoffScnHdr *scn_hdr;
rz_vector_enumerate (obj->scn_hdrs, scn_hdr, i) {
if (is_exec) {
obj->scn_va[i] = scn_hdr->s_vaddr;
obj->scn_va[i] = (ut64)scn_hdr->s_vaddr * rz_coff_addr_scale(obj);
continue;
}
obj->scn_va[i] = va;
va += scn_hdr->s_size ? scn_hdr->s_size : 16;
// Advance by the mapped byte length so the synthetic bases cannot
// overlap once a word-counted section is scaled.
const ut32 loadable = COFF_SCN_CNT_CODE | COFF_SCN_CNT_INIT_DATA | COFF_SCN_CNT_UNIN_DATA;
const ut32 scale = (scn_hdr->s_flags & loadable) ? rz_coff_addr_scale(obj) : 1;
va += scn_hdr->s_size ? (ut64)scn_hdr->s_size * scale : 16;
va = RZ_ROUND(va, 16ULL);
}
return true;

View file

@ -37,6 +37,7 @@ struct rz_bin_coff_obj {
};
RZ_API bool rz_coff_supported_arch(RzBuffer *b);
RZ_API ut32 rz_coff_addr_scale(RZ_NONNULL struct rz_bin_coff_obj *obj);
RZ_API ut64 rz_coff_perms_from_section_flags(ut32 flags);
RZ_API struct rz_bin_coff_obj *rz_bin_coff_new_buf(RzBuffer *buf);
RZ_API void rz_bin_coff_free(struct rz_bin_coff_obj *obj);

View file

@ -82,8 +82,9 @@ static bool coff_fill_bin_symbol(RzBin *rbin, struct rz_bin_coff_obj *bin, size_
if ((bin->hdr.f_flags & COFF_FLAGS_TI_F_EXEC) != 0) {
const ut32 loadable = COFF_SCN_CNT_CODE | COFF_SCN_CNT_INIT_DATA | COFF_SCN_CNT_UNIN_DATA;
if (sc_hdr->s_flags & loadable) {
ptr->vaddr = s->n_value;
ptr->paddr = sc_hdr->s_scnptr + (s->n_value - sc_hdr->s_vaddr);
const ut32 scale = rz_coff_addr_scale(bin);
ptr->vaddr = (ut64)s->n_value * scale;
ptr->paddr = sc_hdr->s_scnptr + (ut64)(s->n_value - sc_hdr->s_vaddr) * scale;
} else {
// Symbol belongs to a non-loadable section (DWARF/debug, build
// attributes, .pinit). Its n_value is not an address in the
@ -97,9 +98,11 @@ static bool coff_fill_bin_symbol(RzBin *rbin, struct rz_bin_coff_obj *bin, size_
ptr->paddr = sc_hdr->s_scnptr + s->n_value;
}
} else {
ptr->paddr = sc_hdr->s_scnptr + s->n_value;
const ut32 loadable = COFF_SCN_CNT_CODE | COFF_SCN_CNT_INIT_DATA | COFF_SCN_CNT_UNIN_DATA;
const ut32 scale = (sc_hdr->s_flags & loadable) ? rz_coff_addr_scale(bin) : 1;
ptr->paddr = sc_hdr->s_scnptr + (ut64)s->n_value * scale;
if (bin->scn_va) {
ptr->vaddr = bin->scn_va[s->n_scnum - 1] + s->n_value;
ptr->vaddr = bin->scn_va[s->n_scnum - 1] + (ut64)s->n_value * scale;
}
}
}
@ -300,8 +303,11 @@ static RzPVector /*<RzBinMap *>*/ *coff_maps(RzBinFile *bf) {
return ret;
}
ptr->name = rz_coff_symbol_name(obj, (const ut8 *)hdr->s_name);
ptr->psize = hdr->s_size;
ptr->vsize = hdr->s_size;
// Only loadable sections are counted in target address units; debug
// sections are byte streams even on a word-addressed target.
const ut32 scale = (hdr->s_flags & loadable) ? rz_coff_addr_scale(obj) : 1;
ptr->psize = (ut64)hdr->s_size * scale;
ptr->vsize = (ut64)hdr->s_size * scale;
ptr->paddr = hdr->s_scnptr;
if (obj->scn_va) {
ptr->vaddr = obj->scn_va[i];
@ -355,8 +361,10 @@ static RzPVector /*<RzBinSection *>*/ *coff_sections(RzBinFile *bf) {
if (strstr(ptr->name, "data")) {
ptr->is_data = true;
}
ptr->size = scn_hdr->s_size;
ptr->vsize = scn_hdr->s_size;
const ut32 loadable = COFF_SCN_CNT_CODE | COFF_SCN_CNT_INIT_DATA | COFF_SCN_CNT_UNIN_DATA;
const ut32 scale = (scn_hdr->s_flags & loadable) ? rz_coff_addr_scale(obj) : 1;
ptr->size = (ut64)scn_hdr->s_size * scale;
ptr->vsize = (ut64)scn_hdr->s_size * scale;
ptr->paddr = scn_hdr->s_scnptr;
ptr->flags = scn_hdr->s_flags;
if (obj->scn_va) {

View file

@ -141,22 +141,22 @@ EXPECT=<<EOF
type: nop
size: 2
type: jmp
jump: 0x00000100
jump: 0x00000200
type: jmp
jump: 0x00000100
jump: 0x00000200
type: call
jump: 0x00000100
jump: 0x00000200
fail: 0x00000004
stackptr: 2
type: cjmp
jump: 0x00000120
jump: 0x00000240
fail: 0x00000004
type: ccall
jump: 0x00000120
jump: 0x00000240
fail: 0x00000004
stackptr: 2
type: cjmp
jump: 0x00000120
jump: 0x00000240
fail: 0x00000004
type: ujmp
fail: 0x00000002
@ -248,7 +248,7 @@ direction: exec
stackop: inc
stackptr: 2
type: call
jump: 0x00000100
jump: 0x00000200
direction: exec
EOF
RUN
@ -322,7 +322,7 @@ val: 0x00001234
val: 0x00001234
val: 0x0000000f
type: jmp
jump: 0x00002010
jump: 0x00004020
EOF
RUN
@ -391,7 +391,7 @@ arch tms320
cpu c54x
bintype coff
endian LE
12
13
1
EOF
RUN
@ -407,7 +407,7 @@ EOF
EXPECT=<<EOF
arch tms320
cpu c54x
8
9
EOF
RUN

View file

@ -1,8 +1,8 @@
d "nop" 95f4 0x0 nop
d "b 0x100" 73f00001 0x0 (jmp (bv 24 0x100))
d "bd 0x100" 73f20001 0x0 (jmp (bv 24 0x100))
d "call 0x100" 74f00001 0x0 (jmp (bv 24 0x100))
d "calld 0x100" 74f20001 0x0 (jmp (bv 24 0x100))
d "b 0x100" 73f00001 0x0 (jmp (bv 24 0x200))
d "bd 0x100" 73f20001 0x0 (jmp (bv 24 0x200))
d "call 0x100" 74f00001 0x0 (jmp (bv 24 0x200))
d "calld 0x100" 74f20001 0x0 (jmp (bv 24 0x200))
d "ret" 00fc 0x0 (seq (set sp (+ (var sp) (bv 16 0x1))) (jmp (cast 24 false (loadw 0 16 (* (cast 24 false (var sp)) (bv 24 0x2))))))
d "retd" 00fe 0x0 (seq (set sp (+ (var sp) (bv 16 0x1))) (jmp (cast 24 false (loadw 0 16 (* (cast 24 false (var sp)) (bv 24 0x2))))))
d "rete" ebf4 0x0 (seq (set ret_addr (loadw 0 24 (* (cast 24 false (var sp)) (bv 24 0x2)))) (set sp (+ (var sp) (bv 16 0x2))) (jmp (var ret_addr)))
@ -13,10 +13,10 @@ d "bacc a" e2f4 0x0 (jmp (cast 24 false (var a)))
d "baccd a" e2f6 0x0 (jmp (cast 24 false (var a)))
d "cala a" e3f4 0x0 (jmp (cast 24 false (var a)))
d "calad a" e3f6 0x0 (jmp (cast 24 false (var a)))
d "fb 0x2" 80f80200 0x0 (jmp (bv 24 0x2))
d "fbd 0x2" 80fa0200 0x0 (jmp (bv 24 0x2))
d "fcall 0xf" 80f90f00 0x0 (jmp (bv 24 0xf))
d "fcalld 0xf" 80fb0f00 0x0 (jmp (bv 24 0xf))
d "fb 0x2" 80f80200 0x0 (jmp (bv 24 0x4))
d "fbd 0x2" 80fa0200 0x0 (jmp (bv 24 0x4))
d "fcall 0xf" 80f90f00 0x0 (jmp (bv 24 0x1e))
d "fcalld 0xf" 80fb0f00 0x0 (jmp (bv 24 0x1e))
d "fret" e4f4 0x0 (seq (set ret_addr (loadw 0 24 (* (cast 24 false (var sp)) (bv 24 0x2)))) (set sp (+ (var sp) (bv 16 0x2))) (jmp (var ret_addr)))
d "fretd" e4f6 0x0 (seq (set ret_addr (loadw 0 24 (* (cast 24 false (var sp)) (bv 24 0x2)))) (set sp (+ (var sp) (bv 16 0x2))) (jmp (var ret_addr)))
d "frete" e5f4 0x0 (seq (set ret_addr (loadw 0 24 (* (cast 24 false (var sp)) (bv 24 0x2)))) (set sp (+ (var sp) (bv 16 0x2))) (jmp (var ret_addr)))
@ -89,13 +89,13 @@ d "frame #-2" feee 0x0 (set sp (+ (var sp) (bv 16 0xfffe)))
d "frame #10" 0aee 0x0 (set sp (+ (var sp) (bv 16 0xa)))
d "bitf *ar1, #0x1234" 81613412 0x0 (set st0 (| (& (var st0) (bv 16 0xefff)) (ite (! (is_zero (& (loadw 0 16 (* (cast 24 false (var ar1)) (bv 24 0x2))) (bv 16 0x1234)))) (bv 16 0x1000) (bv 16 0x0))))
d "cmpm *ar1, #0x1234" 81603412 0x0 (set st0 (| (& (var st0) (bv 16 0xefff)) (ite (== (loadw 0 16 (* (cast 24 false (var ar1)) (bv 24 0x2))) (bv 16 0x1234)) (bv 16 0x1000) (bv 16 0x0))))
d "bc 0x100, agt" 46f80001 0x0 (branch (! (sle (var a) (bv 40 0x0))) (jmp (bv 24 0x100)) nop)
d "bc 0x100, aeq" 45f80001 0x0 (branch (is_zero (var a)) (jmp (bv 24 0x100)) nop)
d "cc 0x200, bneq" 4cf90002 0x0 (branch (! (is_zero (var b))) (jmp (bv 24 0x200)) nop)
d "bcd 0x100, agt" 46fa0001 0x0 (branch (! (sle (var a) (bv 40 0x0))) (jmp (bv 24 0x100)) nop)
d "ccd 0x200, bneq" 4cfb0002 0x0 (branch (! (is_zero (var b))) (jmp (bv 24 0x200)) nop)
d "banz 0x100, *ar1-" 896c0001 0x0 (seq (set take (ite (! (is_zero (var ar1))) (bv 1 0x1) (bv 1 0x0))) (set ar1 (- (var ar1) (bv 16 0x1))) (branch (! (is_zero (var take))) (jmp (bv 24 0x100)) nop))
d "banzd 0x100, *ar1-" 896e0001 0x0 (seq (set take (ite (! (is_zero (var ar1))) (bv 1 0x1) (bv 1 0x0))) (set ar1 (- (var ar1) (bv 16 0x1))) (branch (! (is_zero (var take))) (jmp (bv 24 0x100)) nop))
d "bc 0x100, agt" 46f80001 0x0 (branch (! (sle (var a) (bv 40 0x0))) (jmp (bv 24 0x200)) nop)
d "bc 0x100, aeq" 45f80001 0x0 (branch (is_zero (var a)) (jmp (bv 24 0x200)) nop)
d "cc 0x200, bneq" 4cf90002 0x0 (branch (! (is_zero (var b))) (jmp (bv 24 0x400)) nop)
d "bcd 0x100, agt" 46fa0001 0x0 (branch (! (sle (var a) (bv 40 0x0))) (jmp (bv 24 0x200)) nop)
d "ccd 0x200, bneq" 4cfb0002 0x0 (branch (! (is_zero (var b))) (jmp (bv 24 0x400)) nop)
d "banz 0x100, *ar1-" 896c0001 0x0 (seq (set take (ite (! (is_zero (var ar1))) (bv 1 0x1) (bv 1 0x0))) (set ar1 (- (var ar1) (bv 16 0x1))) (branch (! (is_zero (var take))) (jmp (bv 24 0x200)) nop))
d "banzd 0x100, *ar1-" 896e0001 0x0 (seq (set take (ite (! (is_zero (var ar1))) (bv 1 0x1) (bv 1 0x0))) (set ar1 (- (var ar1) (bv 16 0x1))) (branch (! (is_zero (var take))) (jmp (bv 24 0x200)) nop))
d "stl a, *ar1(0xc)" e1800c00 0x0 (storew 0 (* (+ (cast 24 false (var ar1)) (bv 24 0xc)) (bv 24 0x2)) (cast 16 false (var a)))
d "ld *ar1(0x1), a" e1100100 0x0 (set a (cast 40 (msb (loadw 0 16 (* (+ (cast 24 false (var ar1)) (bv 24 0x1)) (bv 24 0x2)))) (loadw 0 16 (* (+ (cast 24 false (var ar1)) (bv 24 0x1)) (bv 24 0x2)))))
d "ldm ar1, a" 1148 0x0 (set a (cast 40 false (var ar1)))
@ -260,7 +260,7 @@ d "rc bgeq" 4afc 0x0 (branch (|| (! (sle (var b) (bv 40 0x0))) (== (var b) (bv 4
d "xc 1, aeq" 45fd
d "xc 2, aeq" 45ff
d "xc 1, tc, c" 3cfd
d "bc 0x1234, tc, c" 3cf83412 0x0 (branch (&& (! (is_zero (& (var st0) (bv 16 0x1000)))) (! (is_zero (& (var st0) (bv 16 0x800))))) (jmp (bv 24 0x1234)) nop)
d "bc 0x1234, tc, c" 3cf83412 0x0 (branch (&& (! (is_zero (& (var st0) (bv 16 0x1000)))) (! (is_zero (& (var st0) (bv 16 0x800))))) (jmp (bv 24 0x2468)) nop)
d "ldm imr, a" 0048 0x0 (set a (cast 40 false (var imr)))
d "ldm al, a" 0848
d "ldm ah, a" 0948

View file

@ -347,29 +347,26 @@ ar4 = 0x30
EOF
RUN
NAME=C54x RzIL VM: emulateme _decrypt writes hex digits to the UART buffer
# Key at word 0x280 (byte 0x500) in A, encrypted seckrit at _seckrit; the stack
# is kept clear of .text, which the linker places at byte 0x100.
NAME=C54x RzIL VM: emulateme _decrypt decrypts seckrit to "Hello from RzIL!"
FILE=bins/tms320/emulateme_nostd.ccsv5.c54x.ticoff2.dbg.coff
CMDS=<<EOF
e io.cache=true
wx 30003100320033003400350036003700380039006100620063006400650066 @ 0x900
wx 0006 @ 0x9a4
wx 00000000 @ 0x9a8
s 0xec
wx 510053004d0077005800140051005f0045006c0017007f006e0078007f001c000000 @ 0x9ac
wx 41006e00790043006f006c006f007500720059006f0075004c0069006b006500 @ 0x500
s 0x1d8
aezi
aezv a 0x5a
aezv b 0x5
aezv sp 0x100
aezsu 0x136
p8 4 @ 0xc00
p8 2 @ 0x9aa
aezv sp 0x800
aezv a 0x280
aezsu 0x270
p8 32 @ 0x9ac
ar a
EOF
EXPECT=<<EOF
a = 0x5a
b = 0x5
sp = 0x100
35006100
0200
a = 0x0000000002
sp = 0x800
a = 0x280
480065006c006c006f002000660072006f006d00200052007a0049004c002100
a = 0x0000000001
EOF
RUN