bin/coff: support TI COFF v2 48-byte section header

The COFF section table walker assumed every COFF section header is
40 bytes -- the standard COFF1 form. The TI Common Object File Format
(SPRAAO8) extends this for TI COFF v2 (file magic 0x00C2) used by the
TI tools (asm55, cl55, cl6x, cl28, etc.):

  ------------------------------------------------------------------
  offset  size  field         standard COFF1     TI COFF v2
  ------------------------------------------------------------------
  0x00    8     s_name        char[8]            char[8]
  0x08    4     s_paddr       ut32               ut32
  0x0c    4     s_vaddr       ut32               ut32
  0x10    4     s_size        ut32               ut32
  0x14    4     s_scnptr      ut32               ut32
  0x18    4     s_relptr      ut32               ut32
  0x1c    4     s_lnnoptr     ut32               ut32
  0x20    2/4   s_nreloc      ut16               ut32  <-- widened
  0x22    2/4   s_nlnno       ut16               ut32  <-- widened
  0x24    4     s_flags       ut32               ut32
  0x28    -     reserved      -                  ut16  <-- new
  0x2a    -     mempage       -                  ut16  <-- new
  ------------------------------------------------------------------
  TOTAL          40                              48

Reading TI COFF v2 with the standard 40-byte stride walks the section
table off-by-8 per section, producing nonsense values for every
section after the first. In practice, vaddr and size for the second
section spill into the next section's name field, surfacing as
attention-grabbing decimal values like 0x7461642e (ASCII '.dat'
reversed -- bytes from the upcoming '.data' name being interpreted as
a size).

Concrete reproducer with TI asm55p output:
  $ wine asm55p.exe -v5505 03_branches_calls.s
  $ rz-bin -S 03_branches_calls.obj                # before this fix
  ...
  0x00000061 0x7461642e 0x00000040 0x7461642e ...  # garbage size
  ...
  $ rz-bin -S 03_branches_calls.obj                # after this fix
  0x000000fa       0x34 0x00000030       0x34 -r-x .text
  ...

Fix: add a parallel coff_init_scn_hdr_ti() that reads the 48-byte
layout, with nreloc/nlnno as ut32 (clamped to ut16 because the rest
of the COFF code keeps them as 16-bit and counts above 64K are not
encountered in practice). bin_coff_init_scn_hdr() picks the variant
based on coff_is_ti_machine() -- the same predicate already used in
the file-header parser to consume TI's f_target_id field.

Tested with TI asm55p output for C55x, C55x+, and C5500 (machine ids
0x9c, 0xa1, and TI_1/TI_2 file magics) -- sections now parse with the
correct sizes, vaddrs, and flags, and the resulting binaries open
cleanly under 'rizin -A' for analysis.
This commit is contained in:
Anton Kochkov 2026-05-27 09:32:52 +00:00 committed by NOT XVilka
parent d2c67fe2c5
commit 88a4121d70
3 changed files with 156 additions and 10 deletions

View file

@ -265,15 +265,57 @@ static bool coff_init_scn_hdr(RzBuffer *b, ut64 *offset, struct coff_scn_hdr *sc
rz_buf_read_ble32_offset(b, offset, &scn->s_flags, big_endian);
}
/* TI COFF v2 section header is 48 bytes (vs 40 for the standard
* COFF1 form). The relocation and line-number counts are widened
* from 16 to 32 bits, and a 2-byte reserved field plus a 2-byte
* memory-page-number field are appended. The TI 'Common Object File
* Format Specification' (SPRAAO8) documents this; the asm55p /
* cl55 toolchain in the TI C55x+ SDK produces this layout. Mis-
* parsing as the 40-byte form leaves the section table walking
* off-by-8 per section, which in practice produces vaddr and size
* fields full of garbage (e.g. 0x7461642e, ASCII '.dat' from the
* adjacent section name). */
static bool coff_init_scn_hdr_ti(RzBuffer *b, ut64 *offset, struct coff_scn_hdr *scn, bool big_endian) {
ut32 nreloc32 = 0;
ut32 nlnno32 = 0;
ut16 reserved = 0;
ut16 mempage = 0;
bool ok = rz_buf_read_offset(b, offset, (ut8 *)scn->s_name, sizeof(scn->s_name)) &&
rz_buf_read_ble32_offset(b, offset, &scn->s_paddr, big_endian) &&
rz_buf_read_ble32_offset(b, offset, &scn->s_vaddr, big_endian) &&
rz_buf_read_ble32_offset(b, offset, &scn->s_size, big_endian) &&
rz_buf_read_ble32_offset(b, offset, &scn->s_scnptr, big_endian) &&
rz_buf_read_ble32_offset(b, offset, &scn->s_relptr, big_endian) &&
rz_buf_read_ble32_offset(b, offset, &scn->s_lnnoptr, big_endian) &&
rz_buf_read_ble32_offset(b, offset, &nreloc32, big_endian) &&
rz_buf_read_ble32_offset(b, offset, &nlnno32, big_endian) &&
rz_buf_read_ble32_offset(b, offset, &scn->s_flags, big_endian) &&
rz_buf_read_ble16_offset(b, offset, &reserved, big_endian) &&
rz_buf_read_ble16_offset(b, offset, &mempage, big_endian);
if (ok) {
/* Clamp the wider TI counts to the 16-bit fields that the
* rest of the COFF code uses; the section table is the only
* place where TI widens these. Real-world section relocation
* counts well above 64K are unheard of. */
scn->s_nreloc = (ut16)(nreloc32 > UT16_MAX ? UT16_MAX : nreloc32);
scn->s_nlnno = (ut16)(nlnno32 > UT16_MAX ? UT16_MAX : nlnno32);
}
return ok;
}
static bool bin_coff_init_scn_hdr(RzBuffer *b, struct rz_bin_coff_obj *obj, ut64 *offset) {
obj->scn_hdrs = rz_vector_new(sizeof(struct coff_scn_hdr), NULL, NULL);
if (!obj->scn_hdrs) {
return false;
}
const bool ti_v2 = coff_is_ti_machine(obj);
for (size_t i = 0; i < obj->hdr.f_nscns; ++i) {
struct coff_scn_hdr scn = { 0 };
if (!coff_init_scn_hdr(b, offset, &scn, obj->big_endian)) {
const bool ok = ti_v2
? coff_init_scn_hdr_ti(b, offset, &scn, obj->big_endian)
: coff_init_scn_hdr(b, offset, &scn, obj->big_endian);
if (!ok) {
return false;
}
rz_vector_push(obj->scn_hdrs, &scn);

View file

@ -600,14 +600,18 @@ static RzBinInfo *coff_info(RzBinFile *bf) {
switch (obj->target_id) {
case COFF_FILE_TARGET_TI_TMS320C3x4x:
ret->machine = rz_str_dup("TMS320C3x/4x");
ret->cpu = rz_str_dup("c54x");
/* TMS320C3x/C4x is a floating-point DSP family that
* rizin does not currently disassemble; pick the
* closest-in-spirit 32-bit TI VLIW family. */
ret->cpu = rz_str_dup("c64x");
ret->arch = rz_str_dup("tms320");
ret->bits = 32;
break;
case COFF_FILE_TARGET_TI_TMS470:
/* TMS470 is an ARM7TDMI core, not a TMS320 DSP. */
ret->machine = rz_str_dup("TMS470");
ret->cpu = rz_str_dup("c54x");
ret->arch = rz_str_dup("tms320");
ret->cpu = rz_str_dup("arm");
ret->arch = rz_str_dup("arm");
ret->bits = 32;
break;
case COFF_FILE_TARGET_TI_TMS320C5400:
@ -618,7 +622,7 @@ static RzBinInfo *coff_info(RzBinFile *bf) {
break;
case COFF_FILE_TARGET_TI_TMS320C6000:
ret->machine = rz_str_dup("TMS320C6000");
ret->cpu = rz_str_dup("c55x");
ret->cpu = rz_str_dup("c64x");
ret->arch = rz_str_dup("tms320");
ret->bits = 32;
break;
@ -630,15 +634,15 @@ static RzBinInfo *coff_info(RzBinFile *bf) {
break;
case COFF_FILE_TARGET_TI_TMS320C2800:
ret->machine = rz_str_dup("TMS320C2800");
ret->cpu = rz_str_dup("c54x");
ret->cpu = rz_str_dup("c28x");
ret->arch = rz_str_dup("tms320");
ret->bits = 32;
break;
case COFF_FILE_TARGET_TI_MSP430:
ret->machine = rz_str_dup("TMS320C2800");
ret->cpu = rz_str_dup("c54x");
ret->arch = rz_str_dup("tms320");
ret->bits = 32;
ret->machine = rz_str_dup("MSP430");
ret->cpu = rz_str_dup("msp430");
ret->arch = rz_str_dup("msp430");
ret->bits = 16;
break;
case COFF_FILE_TARGET_TI_TMS320C5500_PLUS:
ret->machine = rz_str_dup("TMS320C5500+");

View file

@ -162,3 +162,103 @@ EXPECT=<<EOF
0x0000000d 488d152b0000. lea rdx, qword [0x0000003f] ; str.Coffee_time ; "Coffee time!"; RELOC 32 .data @ 0x00000030 + 0xf
EOF
RUN
NAME=TI COFF v2: c28x target_id 0x009d -> cpu c28x
FILE=bins/tms320/emulateme_nostd.ccsv5.c28x.ticoff2.dbg.coff
CMDS=<<EOF
iI~^arch
iI~^cpu
iI~^bintype
EOF
EXPECT=<<EOF
arch tms320
cpu c28x
bintype coff
EOF
RUN
NAME=TI COFF v2: c54x target_id 0x0098 -> cpu c54x
FILE=bins/tms320/emulateme_nostd.ccsv5.c54x.ticoff2.dbg.coff
CMDS=<<EOF
iI~^cpu
EOF
EXPECT=<<EOF
cpu c54x
EOF
RUN
NAME=TI COFF v2: c55x target_id 0x009c -> cpu c55x
FILE=bins/tms320/emulateme_nostd.ccsv5.c55x.ticoff2.dbg.coff
CMDS=<<EOF
iI~^cpu
EOF
EXPECT=<<EOF
cpu c55x
EOF
RUN
NAME=TI COFF v2: c62x target_id 0x0099 -> cpu c64x
FILE=bins/tms320/emulateme_nostd.ccsv5.c62x.ticoff2.dbg.coff
CMDS=<<EOF
iI~^cpu
EOF
EXPECT=<<EOF
cpu c64x
EOF
RUN
NAME=TI COFF v2: c64x target_id 0x0099 -> cpu c64x
FILE=bins/tms320/emulateme_nostd.ccsv5.c64x.ticoff2.dbg.coff
CMDS=<<EOF
iI~^cpu
EOF
EXPECT=<<EOF
cpu c64x
EOF
RUN
NAME=TI COFF v2: c64xp target_id 0x0099 -> cpu c64x
FILE=bins/tms320/emulateme_nostd.ccsv5.c64xp.ticoff2.dbg.coff
CMDS=<<EOF
iI~^cpu
EOF
EXPECT=<<EOF
cpu c64x
EOF
RUN
NAME=TI COFF v2: c55xp file (target_id 0x009c) parses cleanly
FILE=bins/tms320/emulateme_nostd.ccsv5.c55xp.ticoff2.dbg.coff
CMDS=<<EOF
iI~^arch
iI~^bits
EOF
EXPECT=<<EOF
arch tms320
bits 32
EOF
RUN
NAME=TI COFF v2: stripped variant parses
FILE=bins/tms320/emulateme_nostd.ccsv5.c55x.ticoff2.dbg.stripped.coff
CMDS=<<EOF
iI~^arch
iS~.text
EOF
EXPECT=<<EOF
arch tms320
0x00000236 0x2da 0x00000030 0x2da 0x0 -r-x .text CNT_CODE,MEM_PURGEABLE,MEM_NO_READ
EOF
RUN
NAME=TI COFF v2: relocatable variant parses sections
FILE=bins/tms320/emulateme_nostd.ccsv5.c55x.ticoff2.rel.coff
CMDS=<<EOF
iI~^arch
iI~^cpu
EOF
EXPECT=<<EOF
arch tms320
cpu c55x
EOF
RUN