From 88a4121d70bbae96ca555d7cd1570ca001445b4a Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Wed, 27 May 2026 09:32:52 +0000 Subject: [PATCH] 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. --- librz/bin/format/coff/coff.c | 44 ++++++++++++++- librz/bin/p/bin_coff.c | 22 ++++---- test/db/formats/coff | 100 +++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 10 deletions(-) diff --git a/librz/bin/format/coff/coff.c b/librz/bin/format/coff/coff.c index 532bc1cbfb..f8d38311ea 100644 --- a/librz/bin/format/coff/coff.c +++ b/librz/bin/format/coff/coff.c @@ -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); diff --git a/librz/bin/p/bin_coff.c b/librz/bin/p/bin_coff.c index 479f33c376..57dae7ad5e 100644 --- a/librz/bin/p/bin_coff.c +++ b/librz/bin/p/bin_coff.c @@ -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+"); diff --git a/test/db/formats/coff b/test/db/formats/coff index baad90d571..8258cca4bc 100644 --- a/test/db/formats/coff +++ b/test/db/formats/coff @@ -162,3 +162,103 @@ EXPECT=< cpu c28x +FILE=bins/tms320/emulateme_nostd.ccsv5.c28x.ticoff2.dbg.coff +CMDS=< cpu c54x +FILE=bins/tms320/emulateme_nostd.ccsv5.c54x.ticoff2.dbg.coff +CMDS=< cpu c55x +FILE=bins/tms320/emulateme_nostd.ccsv5.c55x.ticoff2.dbg.coff +CMDS=< cpu c64x +FILE=bins/tms320/emulateme_nostd.ccsv5.c62x.ticoff2.dbg.coff +CMDS=< cpu c64x +FILE=bins/tms320/emulateme_nostd.ccsv5.c64x.ticoff2.dbg.coff +CMDS=< cpu c64x +FILE=bins/tms320/emulateme_nostd.ccsv5.c64xp.ticoff2.dbg.coff +CMDS=<