bin/elf: convert and patch TMS320C6000 (c6x) ELF relocations

The values follow SPRAB89 Table 13-5 -- S+A for the absolute forms, S+A-P
for the PC-relative branches, S+A-B against the static base for the
base-relative ones -- and Table 13-6 gives, per type, the container to edit
and the field's offset and width inside it. That is the whole encoding, so
the field layouts are a table here too rather than a case each.

PC-relative branches measure from the fetch packet the branch sits in, not
from the instruction, so the place is rounded down to a 32-byte boundary
before the difference is taken.
This commit is contained in:
Anton Kochkov
2026-09-08 01:55:32 +08:00
committed by NOT XVilka
parent 0fc2365b57
commit 1b7476c11d
5 changed files with 302 additions and 2 deletions
+15
View File
@@ -361,6 +361,7 @@ static const struct arch_translation arch_translation_table[] = {
{ EM_VIDEOCORE3, "vc4" },
{ EM_VIDEOCORE4, "vc4" },
{ EM_MSP430, "msp430" },
{ EM_TI_C6000, "tms320" },
{ EM_SH, "sh" },
{ EM_V810, "v810" },
{ EM_V800, "v850" },
@@ -926,6 +927,10 @@ static inline bool arch_is_parisc(ELFOBJ *bin) {
return arch_is(bin, EM_PARISC);
}
static inline bool arch_is_c6x(ELFOBJ *bin) {
return arch_is(bin, EM_TI_C6000);
}
static inline bool arch_is_riscv(ELFOBJ *bin) {
return arch_is(bin, EM_RISCV);
}
@@ -1500,6 +1505,14 @@ static char *get_cpu_hppa(ELFOBJ *bin) {
return strdup("Unknown HP PARISC ISA");
}
// EM_TI_C6000 objects (COFF ABI and the C6000 EABI, SPRAB89) do not record the
// exact C6x ISA variant in the ELF header, so default to the C674x superset (the
// unified C64x+/C67x+ ISA the EABI targets); a narrower variant can be forced
// with -c on the shared tms320 c6x decoder.
static RZ_OWN char *get_cpu_c6x(ELFOBJ *bin) {
return rz_str_dup("c674x");
}
static char *get_cpu_h8xx(ELFOBJ *bin) {
if (bin->ehdr.e_machine == EM_H8_300H) {
return rz_str_dup("h8300h");
@@ -2125,6 +2138,8 @@ RZ_OWN char *Elf_(rz_bin_elf_get_cpu)(RZ_NONNULL ELFOBJ *bin) {
return bin->ehdr.e_machine == EM_SPARC ? strdup("v8") : strdup("v9");
} else if (arch_is_parisc(bin)) {
return get_cpu_hppa(bin);
} else if (arch_is_c6x(bin)) {
return get_cpu_c6x(bin);
} else if (arch_is_arm(bin)) {
return get_cpu_arm(bin);
} else if (arch_is_h8xx(bin)) {
+57 -1
View File
@@ -1531,6 +1531,62 @@ static RzBinReloc *reloc_convert_avr(ELFOBJ *bin, RzBinElfReloc *rel, ut64 GOT)
}
}
// TMS320C6000 EABI. The relocation types and the value each computes are
// SPRAB89 Table 13-5, and the field each writes is Table 13-6:
// "C6000 Embedded Application Binary Interface", SPRAB89B, revised August 2025.
// https://web.archive.org/web/20260906194722/https://www.ti.com/lit/an/sprab89b/sprab89b.pdf
//
// Data relocations (ABS8/16/32) resolve to a flat symbol value; the many
// instruction-field relocations (MVKL/MVKH halves, static-base and PC-relative
// displacements) patch a bit field inside a 32-bit opcode, so they are exposed
// with their width but no directly resolvable value. Framing and attribute
// relocations (ALIGN/FPHEAD/NOCMP) patch nothing.
static RzBinReloc *reloc_convert_c6000(ELFOBJ *bin, RzBinElfReloc *rel, ut64 GOT) {
ut64 P = rel->vaddr;
switch (rel->type) {
case R_C6000_NONE:
return reloc_convert_set(bin, rel, 0, "R_C6000_NONE");
case R_C6000_ABS32: ADD(32, 0, "R_C6000_ABS32", RZ_RELOC_BASE_SYMBOL);
case R_C6000_ABS16: ADD(16, 0, "R_C6000_ABS16", RZ_RELOC_BASE_SYMBOL);
case R_C6000_ABS8: ADD(8, 0, "R_C6000_ABS8", RZ_RELOC_BASE_SYMBOL);
case R_C6000_PCR_S21: SET(32, "R_C6000_PCR_S21");
case R_C6000_PCR_S12: SET(16, "R_C6000_PCR_S12");
case R_C6000_PCR_S10: SET(16, "R_C6000_PCR_S10");
case R_C6000_PCR_S7: SET(16, "R_C6000_PCR_S7");
case R_C6000_PCR_H16: SET(16, "R_C6000_PCR_H16");
case R_C6000_PCR_L16: SET(16, "R_C6000_PCR_L16");
case R_C6000_ABS_S16: SET(16, "R_C6000_ABS_S16");
case R_C6000_ABS_L16: SET(16, "R_C6000_ABS_L16");
case R_C6000_ABS_H16: SET(16, "R_C6000_ABS_H16");
case R_C6000_SBR_U15_B: ADD(16, 0, "R_C6000_SBR_U15_B", RZ_RELOC_BASE_SYMBOL);
case R_C6000_SBR_U15_H: ADD(16, 0, "R_C6000_SBR_U15_H", RZ_RELOC_BASE_SYMBOL);
case R_C6000_SBR_U15_W: ADD(16, 0, "R_C6000_SBR_U15_W", RZ_RELOC_BASE_SYMBOL);
case R_C6000_SBR_S16: ADD(16, 0, "R_C6000_SBR_S16", RZ_RELOC_BASE_SYMBOL);
case R_C6000_SBR_L16_B: ADD(16, 0, "R_C6000_SBR_L16_B", RZ_RELOC_BASE_SYMBOL);
case R_C6000_SBR_L16_H: ADD(16, 0, "R_C6000_SBR_L16_H", RZ_RELOC_BASE_SYMBOL);
case R_C6000_SBR_L16_W: ADD(16, 0, "R_C6000_SBR_L16_W", RZ_RELOC_BASE_SYMBOL);
case R_C6000_SBR_H16_B: ADD(16, 0, "R_C6000_SBR_H16_B", RZ_RELOC_BASE_SYMBOL);
case R_C6000_SBR_H16_H: ADD(16, 0, "R_C6000_SBR_H16_H", RZ_RELOC_BASE_SYMBOL);
case R_C6000_SBR_H16_W: ADD(16, 0, "R_C6000_SBR_H16_W", RZ_RELOC_BASE_SYMBOL);
case R_C6000_SBR_GOT_U15_W: SET(16, "R_C6000_SBR_GOT_U15_W");
case R_C6000_SBR_GOT_L16_W: SET(16, "R_C6000_SBR_GOT_L16_W");
case R_C6000_SBR_GOT_H16_W: SET(16, "R_C6000_SBR_GOT_H16_W");
case R_C6000_DSBT_INDEX: SET(16, "R_C6000_DSBT_INDEX");
case R_C6000_PREL31: ADD(32, -P, "R_C6000_PREL31", RZ_RELOC_BASE_SYMBOL);
case R_C6000_COPY: ADD(32, 0, "R_C6000_COPY", RZ_RELOC_BASE_UNKNOWN);
case R_C6000_JUMP_SLOT: SET(32, "R_C6000_JUMP_SLOT");
case R_C6000_EHTYPE: SET(32, "R_C6000_EHTYPE");
case R_C6000_ALIGN:
return reloc_convert_set(bin, rel, 0, "R_C6000_ALIGN");
case R_C6000_FPHEAD:
return reloc_convert_set(bin, rel, 0, "R_C6000_FPHEAD");
case R_C6000_NOCMP:
return reloc_convert_set(bin, rel, 0, "R_C6000_NOCMP");
default: UNSUPP("C6000");
}
}
#undef UNSUPP
#undef UNHANDL
#undef SET
@@ -1683,7 +1739,7 @@ RZ_OWN RzBinReloc *Elf_(rz_bin_elf_convert_relocation)(RZ_NONNULL ELFOBJ *bin, R
case EM_VIDEOCORE3: ARCH_MISSING("EM_VIDEOCORE3");
case EM_LATTICEMICO32: ARCH_MISSING("EM_LATTICEMICO32");
case EM_SE_C17: ARCH_MISSING("EM_SE_C17");
case EM_TI_C6000: ARCH_MISSING("EM_TI_C6000");
case EM_TI_C6000: return reloc_convert_c6000(bin, rel, GOT);
case EM_TI_C2000: ARCH_MISSING("EM_TI_C2000");
case EM_TI_C5500: ARCH_MISSING("EM_TI_C5500");
case EM_TI_ARP32: ARCH_MISSING("EM_TI_ARP32");
+151 -1
View File
@@ -906,6 +906,154 @@ static ut32 convert_ldr_group_mask(ut32 X, int n) {
return 0;
}
/** A C6000 fetch packet, which PC-relative branches are measured from. */
#define C6000_FETCH_PACKET_SIZE 32
/**
* \brief How a C6000 relocation's field sits inside its container.
*
* SPRAB89 Table 13-6 gives each relocation a field as [CS, O, FS]: the size of
* the addressable container at r_offset, the offset of the field within it, and
* the field's size. The relocated value is scaled down by \p scale before it is
* deposited, which is how the branch and base-relative forms encode a target
* that is known to be aligned.
*/
typedef struct {
ut8 container; ///< CS, in bits: 8, 16 or 32
ut8 offset; ///< O, the field's least-significant bit within the container
ut8 size; ///< FS, the field's width in bits
ut8 scale; ///< right shift applied to the result before it is deposited
bool high; ///< deposit the upper half of the result rather than the low one
} C6000RelocField;
/**
* \brief The field layout of \p type, or NULL when it has none to patch.
*
* Only the static relocations are described. The GOT, DSBT and thread-local
* forms need a dynamic loader's view of the image rather than the file's, and
* R_C6000_COPY moves bytes at load time rather than editing a field.
*/
static const C6000RelocField *c6000_reloc_field(ut32 type) {
// clang-format off
static const C6000RelocField abs32 = { 32, 0, 32, 0, false };
static const C6000RelocField abs16 = { 16, 0, 16, 0, false };
static const C6000RelocField abs8 = { 8, 0, 8, 0, false };
static const C6000RelocField pcr_s21 = { 32, 7, 21, 2, false };
static const C6000RelocField pcr_s12 = { 32, 16, 12, 2, false };
static const C6000RelocField pcr_s10 = { 32, 13, 10, 2, false };
static const C6000RelocField pcr_s7 = { 32, 16, 7, 2, false };
static const C6000RelocField f16_lo = { 32, 7, 16, 0, false };
static const C6000RelocField f16_hi = { 32, 7, 16, 0, true };
static const C6000RelocField u15_b = { 32, 8, 15, 0, false };
static const C6000RelocField u15_h = { 32, 8, 15, 1, false };
static const C6000RelocField u15_w = { 32, 8, 15, 2, false };
static const C6000RelocField l16_h = { 32, 7, 16, 1, false };
static const C6000RelocField l16_w = { 32, 7, 16, 2, false };
static const C6000RelocField prel31 = { 32, 0, 31, 1, false };
// clang-format on
switch (type) {
case R_C6000_ABS32:
case R_C6000_JUMP_SLOT:
case R_C6000_EHTYPE: return &abs32;
case R_C6000_ABS16: return &abs16;
case R_C6000_ABS8: return &abs8;
case R_C6000_PCR_S21: return &pcr_s21;
case R_C6000_PCR_S12: return &pcr_s12;
case R_C6000_PCR_S10: return &pcr_s10;
case R_C6000_PCR_S7: return &pcr_s7;
case R_C6000_ABS_S16:
case R_C6000_ABS_L16:
case R_C6000_SBR_S16:
case R_C6000_SBR_L16_B:
case R_C6000_PCR_L16: return &f16_lo;
case R_C6000_ABS_H16:
case R_C6000_SBR_H16_B:
case R_C6000_SBR_H16_H:
case R_C6000_SBR_H16_W:
case R_C6000_PCR_H16: return &f16_hi;
case R_C6000_SBR_U15_B: return &u15_b;
case R_C6000_SBR_U15_H: return &u15_h;
case R_C6000_SBR_U15_W: return &u15_w;
case R_C6000_SBR_L16_H: return &l16_h;
case R_C6000_SBR_L16_W: return &l16_w;
case R_C6000_PREL31: return &prel31;
default: return NULL;
}
}
/**
* \brief Patch one TMS320C6000 relocation.
*
* The value comes from SPRAB89 Table 13-5 -- S+A for the absolute forms, S+A-P
* for the PC-relative branches, S+A-B against the static base for the
* base-relative ones -- and is deposited into the field Table 13-6 gives for
* the type, leaving the rest of the instruction word alone.
*
* \param buf_patched Image being patched.
* \param patch_addr Address of the container to edit.
* \param rel The relocation.
* \param big_endian Byte order of the image.
* \param fs Values the relocation formula is computed from.
*/
static void patch_reloc_c6000(RZ_INOUT RzBuffer *buf_patched, ut64 patch_addr,
const RzBinElfReloc *rel, bool big_endian, const RelocFormularSymbols *fs) {
rz_return_if_fail(buf_patched && rel && fs);
const C6000RelocField *f = c6000_reloc_field(rel->type);
if (!f) {
return;
}
ut64 val = 0;
switch (rel->type) {
case R_C6000_PCR_S21:
case R_C6000_PCR_S12:
case R_C6000_PCR_S10:
case R_C6000_PCR_S7:
// branches are relative to the fetch packet the branch sits in, not to
// the instruction, so the place is rounded down to the packet
val = fs->S + fs->A - (fs->P & ~(ut64)(C6000_FETCH_PACKET_SIZE - 1));
break;
case R_C6000_PREL31: val = fs->S + fs->A - fs->P; break;
case R_C6000_PCR_H16:
case R_C6000_PCR_L16: val = fs->S + fs->A - fs->P; break;
case R_C6000_SBR_U15_B:
case R_C6000_SBR_U15_H:
case R_C6000_SBR_U15_W:
case R_C6000_SBR_S16:
case R_C6000_SBR_L16_B:
case R_C6000_SBR_L16_H:
case R_C6000_SBR_L16_W:
case R_C6000_SBR_H16_B:
case R_C6000_SBR_H16_H:
case R_C6000_SBR_H16_W:
case R_C6000_EHTYPE: val = fs->S + fs->A - fs->B; break;
default: val = fs->S + fs->A; break;
}
val >>= f->scale;
if (f->high) {
val >>= 16; // the H16 forms carry the upper half of the same result
}
ut8 buf[4] = { 0 };
ut32 nbytes = f->container / 8;
if (rz_buf_read_at(buf_patched, patch_addr, buf, nbytes) != (int)nbytes) {
return;
}
ut32 mask = f->size >= 32 ? UT32_MAX : (((ut32)1 << f->size) - 1);
ut32 field = ((ut32)val & mask) << f->offset;
if (nbytes == 4) {
ut32 word = big_endian ? rz_read_be32(buf) : rz_read_le32(buf);
word = (word & ~(mask << f->offset)) | field;
big_endian ? rz_write_be32(buf, word) : rz_write_le32(buf, word);
} else if (nbytes == 2) {
ut16 word = big_endian ? rz_read_be16(buf) : rz_read_le16(buf);
word = (word & ~(mask << f->offset)) | field;
big_endian ? rz_write_be16(buf, word) : rz_write_le16(buf, word);
} else {
buf[0] = (buf[0] & ~(mask << f->offset)) | field;
}
rz_buf_write_at(buf_patched, patch_addr, buf, nbytes);
}
/**
* \brief Patches the opcode at a given address depending on the relocation type.
*
@@ -3643,7 +3791,9 @@ void Elf_(rz_bin_elf_patch_relocation)(RZ_NONNULL ELFOBJ *bin, RZ_NONNULL RzBinE
case EM_VIDEOCORE3: ARCH_MISSING("EM_VIDEOCORE3");
case EM_LATTICEMICO32: ARCH_MISSING("EM_LATTICEMICO32");
case EM_SE_C17: ARCH_MISSING("EM_SE_C17");
case EM_TI_C6000: ARCH_MISSING("EM_TI_C6000");
case EM_TI_C6000:
patch_reloc_c6000(bin->buf_patched, patch_addr, rel, big_endian, &formular_sym);
break;
case EM_TI_C2000: ARCH_MISSING("EM_TI_C2000");
case EM_TI_C5500: ARCH_MISSING("EM_TI_C5500");
case EM_TI_ARP32: ARCH_MISSING("EM_TI_ARP32");
+36
View File
@@ -4608,6 +4608,42 @@ ARM32 relocations:
#define R_AVR_PORT5 35
#define R_AVR_32_PCREL 36
/* TMS320C6000 (C6000 EABI) Relocations */
#define R_C6000_NONE 0
#define R_C6000_ABS32 1
#define R_C6000_ABS16 2
#define R_C6000_ABS8 3
#define R_C6000_PCR_S21 4 /* 21-bit PC-relative branch displacement */
#define R_C6000_PCR_S12 5
#define R_C6000_PCR_S10 6
#define R_C6000_PCR_S7 7
#define R_C6000_ABS_S16 8
#define R_C6000_ABS_L16 9 /* low 16 bits, MVKL */
#define R_C6000_ABS_H16 10 /* high 16 bits, MVKH */
#define R_C6000_SBR_U15_B 11 /* static-base-relative, unsigned, byte */
#define R_C6000_SBR_U15_H 12 /* halfword */
#define R_C6000_SBR_U15_W 13 /* word */
#define R_C6000_SBR_S16 14
#define R_C6000_SBR_L16_B 15
#define R_C6000_SBR_L16_H 16
#define R_C6000_SBR_L16_W 17
#define R_C6000_SBR_H16_B 18
#define R_C6000_SBR_H16_H 19
#define R_C6000_SBR_H16_W 20
#define R_C6000_SBR_GOT_U15_W 21
#define R_C6000_SBR_GOT_L16_W 22
#define R_C6000_SBR_GOT_H16_W 23
#define R_C6000_DSBT_INDEX 24
#define R_C6000_PREL31 25
#define R_C6000_COPY 26
#define R_C6000_JUMP_SLOT 27
#define R_C6000_EHTYPE 28
#define R_C6000_PCR_H16 29
#define R_C6000_PCR_L16 30
#define R_C6000_ALIGN 253
#define R_C6000_FPHEAD 254
#define R_C6000_NOCMP 255
/* Dynamic array tags */
#define DT_HEXAGON_SYMSZ 0x70000000
+43
View File
@@ -0,0 +1,43 @@
# Relocation patching for the C6000 EABI, checked by hand against SPRAB89
# Table 13-6. far_target resolves to 0x08000570.
#
# 0x08000040 R_C6000_PCR_S21 field [32,7,21] scaled by 4
# (S - P) >> 2 = (0x08000570 - 0x08000040) >> 2 = 0x14c
# word 0x00000010 -> 0x0000a610, since 0x14c << 7 = 0xa600
# 0x08000048 R_C6000_ABS_L16 field [32,7,16], low half = 0x0570
# word 0x00000028 -> 0x0002b828, since 0x570 << 7 = 0x2b800
# 0x0800004c R_C6000_ABS_H16 field [32,7,16], high half = 0x0800
# word 0x00000068 -> 0x00040068, since 0x800 << 7 = 0x40000
NAME=TMS320C6000 EABI relocation patching
FILE=bins/tms320/elf/c6x_reloc.eabi.elf
CMDS=<<EOF
e bin.relocs.apply=true
e scr.color=0
ir
s 0x08000040
p8 4
s 0x08000048
p8 8
EOF
EXPECT=<<EOF
vaddr paddr target type name
---------------------------------------------------------------
0x08000040 0x00000040 0x08000570 R_C6000_PCR_S21 far_target
0x08000048 0x00000048 0x08000570 R_C6000_ABS_L16 far_target
0x0800004c 0x0000004c 0x08000570 R_C6000_ABS_H16 far_target
0x08000066 0x00000066 0x080001dd R_C6000_ABS32 .debug_abbrev
0x08000072 0x00000072 0x08000186 R_C6000_ABS32 .debug_line
0x080000e8 0x000000e8 0x080001ee R_C6000_ABS32 .debug_abbrev
0x080000f4 0x000000f4 0x080001a6 R_C6000_ABS32 .debug_line
0x080000f8 0x000000f8 0x08000040 R_C6000_ABS32 .text
0x080000fc 0x000000fc 0x08000040 R_C6000_ABS32 .text
0x08000179 0x00000179 0x08000040 R_C6000_ABS32 .text
0x0800017d 0x0000017d 0x08000040 R_C6000_ABS32 .text
0x080001cf 0x000001cf 0x08000040 R_C6000_ABS32 .text
0x0800021b 0x0000021b 0x080000e2 R_C6000_ABS32 .debug_info
0x08000225 0x00000225 0x08000040 R_C6000_ABS32 .text
0x0800023b 0x0000023b 0x080000e2 R_C6000_ABS32 .debug_info
10a60000
28b8020068000400
EOF
RUN