bin/coff: apply TMS320C6000 (c6x) relocations

This commit is contained in:
Anton Kochkov
2026-09-08 01:55:32 +08:00
committed by NOT XVilka
parent 58e1b45063
commit 0fc2365b57
4 changed files with 257 additions and 0 deletions
+3
View File
@@ -52,4 +52,7 @@ RZ_API RZ_BORROW RzBuffer *rz_coff_get_patched_buf(struct rz_bin_coff_obj *bin);
#define RZ_COFF_RELOC_TARGET_SIZE 8
/// on-disk size of a TI COFF2 relocation entry (r_vaddr, r_symndx, r_disp, r_type)
#define RZ_COFF_TI_RELOC_SIZE 12
#endif /* COFF_H */
+201
View File
@@ -144,10 +144,211 @@ static ut8 handle_arm64_relocs(struct rz_bin_coff_obj *bin, RzBinReloc *reloc, u
return 0;
}
// Store `value` into the field of the little-endian word at `paddr` that a TI
// C6000 field spec describes: bits [start, start + size) of a `container`-bit
// word, other bits preserved. The patched word is written to patch_buf; returns
// the byte count (container / 8), or 0 if the spec or read is invalid.
static size_t ti_c6000_store_field(struct rz_bin_coff_obj *bin, ut64 paddr, ut32 fldspec, ut64 value, ut8 *patch_buf) {
ut8 start = fldspec & 0xff;
ut8 size = (fldspec >> 8) & 0xff;
ut8 container = (fldspec >> 16) & 0xff;
if (!size || size > container || start + size > container) {
return 0;
}
ut64 mask = size >= 64 ? UT64_MAX : (((ut64)1 << size) - 1);
ut64 field = (value & mask) << start;
ut64 clear = mask << start;
switch (container) {
case 32: {
ut32 word;
if (!rz_buf_read_le32_at(bin->b, paddr, &word)) {
return 0;
}
word = (word & ~(ut32)clear) | (ut32)field;
rz_write_le32(patch_buf, word);
return 4;
}
case 16: {
ut16 hw;
if (!rz_buf_read_le16_at(bin->b, paddr, &hw)) {
return 0;
}
hw = (hw & ~(ut16)clear) | (ut16)field;
rz_write_le16(patch_buf, hw);
return 2;
}
case 8: {
ut8 b;
if (rz_buf_read_at(bin->b, paddr, &b, 1) != 1) {
return 0;
}
patch_buf[0] = (b & ~(ut8)clear) | (ut8)field;
return 1;
}
default:
return 0;
}
}
// Evaluate the TI TMS320C6000 relocation expressions of every section. Each
// field is patched by a contiguous RPN sequence (operators in coff_specs.h)
// that ends in a store; the running stack is reset at each store so the next
// field starts clean. One RzBinReloc is emitted per store, carrying the field
// width and the symbol the expression referenced.
static void ti_c6000_relocs_foreach(struct rz_bin_coff_obj *bin, RelocsForeachCb cb, void *user) {
size_t i = 0;
CoffScnHdr *scn_hdr = NULL;
rz_vector_enumerate (bin->scn_hdrs, scn_hdr, i) {
if (!scn_hdr->s_nreloc || !bin->scn_va) {
continue;
}
if (scn_hdr->s_relptr > bin->size ||
scn_hdr->s_relptr + (ut64)scn_hdr->s_nreloc * RZ_COFF_TI_RELOC_SIZE > bin->size) {
continue;
}
ut64 off = scn_hdr->s_relptr;
ut64 stack[16];
int sp = 0;
RzBinSymbol *cur_sym = NULL;
for (ut32 j = 0; j < scn_hdr->s_nreloc; j++) {
ut32 r_vaddr, r_symndx;
ut16 r_disp, r_type;
if (!rz_buf_read_le32_offset(bin->b, &off, &r_vaddr) ||
!rz_buf_read_le32_offset(bin->b, &off, &r_symndx) ||
!rz_buf_read_le16_offset(bin->b, &off, &r_disp) ||
!rz_buf_read_le16_offset(bin->b, &off, &r_type)) {
break;
}
#define C6000_BINOP(expr) \
do { \
if (sp >= 2) { \
ut64 b = stack[--sp], a = stack[sp - 1]; \
stack[sp - 1] = (expr); \
} \
} while (0)
switch (r_type) {
case COFF_REL_C6000_PUSH: {
RzBinSymbol *sym = ht_up_find(bin->sym_ht, (ut64)r_symndx, NULL);
if (sp < 16) {
stack[sp++] = sym ? sym->vaddr : 0;
}
if (sym) {
cur_sym = sym;
}
break;
}
case COFF_REL_C6000_PUSHUK:
if (sp < 16) {
stack[sp++] = r_symndx;
}
break;
case COFF_REL_C6000_PUSHSK:
if (sp < 16) {
stack[sp++] = (ut64)(st64)(st32)r_symndx;
}
break;
case COFF_REL_C6000_ADD: C6000_BINOP(a + b); break;
case COFF_REL_C6000_SUB: C6000_BINOP(a - b); break;
case COFF_REL_C6000_MPY: C6000_BINOP(a * b); break;
case COFF_REL_C6000_DIV: C6000_BINOP(b ? a / b : 0); break;
case COFF_REL_C6000_MOD: C6000_BINOP(b ? a % b : 0); break;
case COFF_REL_C6000_SR: C6000_BINOP(b < 64 ? a >> b : 0); break;
case COFF_REL_C6000_ASR: C6000_BINOP(b < 64 ? (ut64)((st64)a >> b) : 0); break;
case COFF_REL_C6000_SL: C6000_BINOP(b < 64 ? a << b : 0); break;
case COFF_REL_C6000_AND: C6000_BINOP(a & b); break;
case COFF_REL_C6000_OR: C6000_BINOP(a | b); break;
case COFF_REL_C6000_XOR: C6000_BINOP(a ^ b); break;
case COFF_REL_C6000_NEG:
if (sp >= 1) {
stack[sp - 1] = (ut64)(-(st64)stack[sp - 1]);
}
break;
case COFF_REL_C6000_NOTB:
if (sp >= 1) {
stack[sp - 1] = ~stack[sp - 1];
}
break;
case COFF_REL_C6000_USTFLD:
case COFF_REL_C6000_SSTFLD:
case COFF_REL_C6000_XSTFLD: {
if (sp < 1) {
break;
}
ut64 value = stack[--sp];
ut8 patch_buf[8];
ut8 container = (r_symndx >> 16) & 0xff;
RzBinReloc reloc = { 0 };
reloc.paddr = scn_hdr->s_scnptr + r_vaddr;
reloc.vaddr = bin->scn_va[i] + r_vaddr;
reloc.symbol = cur_sym;
reloc.target_vaddr = cur_sym ? cur_sym->vaddr : value;
reloc.type = container == 16 ? RZ_BIN_RELOC_16 : (container == 8 ? RZ_BIN_RELOC_8 : RZ_BIN_RELOC_32);
reloc.print_name = "R_C6000_STFLD";
size_t plen = ti_c6000_store_field(bin, reloc.paddr, r_symndx, value, patch_buf);
cb(&reloc, plen ? patch_buf : NULL, plen, user);
sp = 0;
cur_sym = NULL;
break;
}
case COFF_REL_C6000_RELBYTE:
case COFF_REL_C6000_RELWORD:
case COFF_REL_C6000_RELLONG: {
// classic direct relocations: add the symbol value to the
// in-place field, no stack involved
RzBinSymbol *sym = ht_up_find(bin->sym_ht, (ut64)r_symndx, NULL);
sp = 0;
cur_sym = NULL;
if (!sym) {
break;
}
ut8 width = r_type == COFF_REL_C6000_RELLONG ? 32 : (r_type == COFF_REL_C6000_RELWORD ? 16 : 8);
ut8 patch_buf[8];
RzBinReloc reloc = { 0 };
reloc.paddr = scn_hdr->s_scnptr + r_vaddr;
reloc.vaddr = bin->scn_va[i] + r_vaddr;
reloc.symbol = sym;
reloc.additive = 1;
reloc.type = width == 32 ? RZ_BIN_RELOC_32 : (width == 16 ? RZ_BIN_RELOC_16 : RZ_BIN_RELOC_8);
reloc.print_name = r_type == COFF_REL_C6000_RELLONG ? "R_C6000_RELLONG" : (r_type == COFF_REL_C6000_RELWORD ? "R_C6000_RELWORD" : "R_C6000_RELBYTE");
ut32 fldspec = ((ut32)width << 16) | ((ut32)width << 8);
ut64 addend = 0;
if (width == 32) {
ut32 v;
addend = rz_buf_read_le32_at(bin->b, reloc.paddr, &v) ? v : 0;
} else if (width == 16) {
ut16 v;
addend = rz_buf_read_le16_at(bin->b, reloc.paddr, &v) ? v : 0;
} else {
ut8 v;
addend = rz_buf_read_at(bin->b, reloc.paddr, &v, 1) == 1 ? v : 0;
}
reloc.addend = addend;
reloc.target_vaddr = sym->vaddr;
size_t plen = ti_c6000_store_field(bin, reloc.paddr, fldspec, sym->vaddr + addend, patch_buf);
cb(&reloc, plen ? patch_buf : NULL, plen, user);
break;
}
default:
// framing/attribute relocations (alignment, fetch-packet
// header, no-compact): no operand, and they terminate any
// field expression, so drop the stack
sp = 0;
cur_sym = NULL;
break;
}
#undef C6000_BINOP
}
}
}
static void relocs_foreach(struct rz_bin_coff_obj *bin, RelocsForeachCb cb, void *user) {
if (!bin->scn_hdrs) {
return;
}
if (bin->target_id == COFF_FILE_TARGET_TI_TMS320C6000) {
ti_c6000_relocs_foreach(bin, cb, user);
return;
}
size_t i = 0;
CoffScnHdr *scn_hdr = NULL;
+33
View File
@@ -184,6 +184,39 @@
#define COFF_REL_ARM64_ADDR32NB 2
#define COFF_REL_ARM64_BRANCH26 3
/* TI TMS320C6000 COFF relocations are RPN expressions evaluated by a small
* stack machine (TI "Common Object File Format Specification", SPRAAO8): the
* RE_PUSH* operators load operands, the arithmetic operators fold the two top
* stack entries, and the RE_*STFLD operators store the result into a field the
* reloc's symbol-index word describes as (container_bits << 16) |
* (field_bits << 8) | start_bit. Framing relocations (type < 0x4000, e.g. the
* no-compact marker) carry no operand and patch nothing. */
#define COFF_REL_C6000_ADD 0x4000 /* pop b, a; push a + b */
#define COFF_REL_C6000_SUB 0x4001 /* pop b, a; push a - b */
#define COFF_REL_C6000_NEG 0x4002 /* pop a; push -a */
#define COFF_REL_C6000_MPY 0x4003 /* pop b, a; push a * b */
#define COFF_REL_C6000_DIV 0x4004 /* pop b, a; push a / b */
#define COFF_REL_C6000_MOD 0x4005 /* pop b, a; push a % b */
#define COFF_REL_C6000_SR 0x4006 /* pop b, a; push a >> b (unsigned) */
#define COFF_REL_C6000_ASR 0x4007 /* pop b, a; push a >> b (arithmetic) */
#define COFF_REL_C6000_SL 0x4008 /* pop b, a; push a << b */
#define COFF_REL_C6000_AND 0x4009 /* pop b, a; push a & b */
#define COFF_REL_C6000_OR 0x400a /* pop b, a; push a | b */
#define COFF_REL_C6000_XOR 0x400b /* pop b, a; push a ^ b */
#define COFF_REL_C6000_NOTB 0x400c /* pop a; push ~a */
#define COFF_REL_C6000_USTFLD 0x400f /* pop v; store unsigned v into field */
#define COFF_REL_C6000_SSTFLD 0x4010 /* pop v; store signed v into field */
#define COFF_REL_C6000_PUSH 0x4011 /* push value of symbol symndx */
#define COFF_REL_C6000_PUSHSK 0x4012 /* push signed constant symndx */
#define COFF_REL_C6000_PUSHUK 0x4013 /* push unsigned constant symndx */
#define COFF_REL_C6000_XSTFLD 0x4016 /* pop v; store v into a whole-word field */
/* Classic COFF direct data relocations reused by TI (not stack-machine ops):
* the referenced symbol value is added to the in-place field. */
#define COFF_REL_C6000_RELBYTE 0x000f /* symbol value added to an 8-bit field */
#define COFF_REL_C6000_RELWORD 0x0010 /* symbol value added to a 16-bit field */
#define COFF_REL_C6000_RELLONG 0x0011 /* symbol value added to a 32-bit field */
typedef struct coff_hdr {
ut16 f_magic; /* Magic number */
ut16 f_nscns; /* Number of Sections */
+20
View File
@@ -277,3 +277,23 @@ bcc 0x2F, tc1
mov xar0, ac3
EOF
RUN
NAME=TMS320C6000 COFF relocation application
FILE=bins/tms320/coff2/c6x_reloc.ticoff2.obj
CMDS=<<EOF
ir
s section..text
pxw 12
s section..data
pxw 8
EOF
EXPECT=<<EOF
vaddr paddr target type name
--------------------------------------------------------
0x00000030 0x000000fd 0x00000050 R_C6000_STFLD _value
0x00000034 0x00000101 0x00000050 R_C6000_STFLD _value
0x00000054 0x00000121 0x00000050 R_C6000_RELLONG _value
0x00000030 0x00002828 0x00000068 0x00800264 ((..h...d...
0x00000050 0x0000600d 0x00000050 .`..P...
EOF
RUN