Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions libr/bin/format/elf/elf.c
Original file line number Diff line number Diff line change
Expand Up @@ -5508,6 +5508,9 @@ RVecRBinSymbol *Elf_(load_plt_symbols_vec)(ELFOBJ *eo) {
}
RVecRBinSymbol_push_back (&eo->plt_symbols_cache, &sym);
}
#if R_BIN_ELF64
Elf_(plt_ppc64v1_load_text_stubs) (eo);
#endif
eo->plt_symbols_cached = true;
return &eo->plt_symbols_cache;
}
Expand Down
3 changes: 3 additions & 0 deletions libr/bin/format/elf/elf.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ bool Elf_(has_nobtcfi)(ELFOBJ *eo);
ut8 *Elf_(grab_regstate)(struct Elf_(obj_t) *bin, int *len);
RList *Elf_(get_maps)(ELFOBJ *bin);
ut64 Elf_(ppc64_get_plt_stub_for_slot)(ELFOBJ *eo, ut64 slot_vaddr);
#if R_BIN_ELF64
void Elf_(plt_ppc64v1_load_text_stubs)(ELFOBJ *eo);
#endif
/* plt.c */
ut64 Elf_(plt_get_import_addr)(ELFOBJ *eo, int sym);
int Elf_(plt_ppc64_abi)(ELFOBJ *eo);
Expand Down
258 changes: 222 additions & 36 deletions libr/bin/format/elf/plt.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,7 @@ ut64 Elf_(plt_ppc32_thunk)(ELFOBJ *eo, ut64 slot_vaddr) {
}

#define MIPS_PLT_OFFSET 0x20
#define RISCV_PLT_OFFSET 0x20
#define LOONGARCH_PLT_OFFSET 0x20
#define RISCV_PLT_ENTRY_SIZE 0x10
#define LOONGARCH_PLT_ENTRY_SIZE 0x10
#define PLT_HDR_SIZE 0x20
#define X86_PLT_ENTRY_SIZE 0x10
#define SPARC_OFFSET_PLT_ENTRY_FROM_GOT_ADDR -0x6
#define X86_OFFSET_PLT_ENTRY_FROM_GOT_ADDR -0x6
Expand All @@ -255,24 +252,22 @@ static ut64 get_got_entry(ELFOBJ *eo, RBinElfReloc *rel) {
return (!addr || addr == R_BIN_ELF_WORD_MAX) ? UT64_MAX : addr;
}

static ut64 get_import_addr_qdsp6(ELFOBJ *eo, RBinElfReloc *rel) {
ut64 got_addr = eo->dyn_info.dt_pltgot;
// classic layout shared by several arches: a lazy got slot points back at the
// plt, whose header is followed by fixed-stride entries
static ut64 indexed_plt_entry(ELFOBJ *eo, RBinElfReloc *rel, int skipped_slots, ut64 esize) {
const ut64 got_addr = eo->dyn_info.dt_pltgot;
if (got_addr == R_BIN_ELF_ADDR_MAX) {
return UT64_MAX;
}

ut64 plt_addr = get_got_entry (eo, rel);
const ut64 plt_addr = get_got_entry (eo, rel);
if (plt_addr == UT64_MAX) {
return UT64_MAX;
}
return plt_addr + PLT_HDR_SIZE + COMPUTE_PLTGOT_POSITION (rel, got_addr, skipped_slots) * esize;
}

const ut64 pos = COMPUTE_PLTGOT_POSITION (rel, got_addr, 0x3);

switch (rel->type) {
case R_QDSP6_JUMP_SLOT:
return plt_addr + pos * 16 + 32;
}
return UT64_MAX;
static ut64 get_import_addr_qdsp6(ELFOBJ *eo, RBinElfReloc *rel) {
return (rel->type == R_QDSP6_JUMP_SLOT)? indexed_plt_entry (eo, rel, 3, 16): UT64_MAX;
}

static ut64 get_import_addr_arm(ELFOBJ *eo, RBinElfReloc *rel) {
Expand Down Expand Up @@ -358,29 +353,11 @@ static ut64 get_import_addr_mips(ELFOBJ *bin, RBinElfReloc *rel) {
return UT64_MAX;
}

// riscv, loongarch and vax share the same 16-byte entries after a 32-byte header
static ut64 get_import_addr_riscv(ELFOBJ *bin, RBinElfReloc *rel) {
ut64 got_addr = bin->dyn_info.dt_pltgot;
if (got_addr != R_BIN_ELF_ADDR_MAX) {
ut64 plt_addr = get_got_entry (bin, rel);
if (plt_addr != UT64_MAX) {
ut64 pos = COMPUTE_PLTGOT_POSITION (rel, got_addr, 2);
return plt_addr + RISCV_PLT_OFFSET + pos * RISCV_PLT_ENTRY_SIZE;
}
}
return UT64_MAX;
return indexed_plt_entry (bin, rel, 2, 16);
}

static ut64 get_import_addr_loongarch(ELFOBJ *bin, RBinElfReloc *rel) {
ut64 got_addr = bin->dyn_info.dt_pltgot;
if (got_addr != R_BIN_ELF_ADDR_MAX) {
ut64 plt_addr = get_got_entry (bin, rel);
if (plt_addr != UT64_MAX) {
ut64 pos = COMPUTE_PLTGOT_POSITION (rel, got_addr, 2);
return plt_addr + LOONGARCH_PLT_OFFSET + pos * LOONGARCH_PLT_ENTRY_SIZE;
}
}
return UT64_MAX;
}
static ut64 get_import_addr_sparc(ELFOBJ *eo, RBinElfReloc *rel) {
if (rel->type != R_SPARC_JMP_SLOT) {
R_LOG_DEBUG ("Unknown sparc reloc type %d", rel->type);
Expand Down Expand Up @@ -473,6 +450,215 @@ ut64 Elf_(ppc64_get_plt_stub_for_slot)(ELFOBJ *eo, ut64 slot_vaddr) {
return UT64_MAX;
}

#if R_BIN_ELF64
// an ELFv1 call dispatches through a 7-word plt_call stub in .text:
// std r2, 40(r1); ld rX, d(r2); mtctr rX; ld r2, d+8(r2);
// cmpldi r2, 0; bnectr+; b <glink stub>
// the trailing branch into a known glink stub names the plt slot it serves
#define PPC64_STD_R2_40R1 0xf8410028
#define PPC64_TEXT_STUB_MIN 24
// longest form: std, addis, ld, mtctr, ld env, ld r2, cmpldi, bnectr, b
#define PPC64_TEXT_STUB_MAX 36
// bytes of executable ranges worth scanning for stubs before giving up
#define PPC64_TEXT_SCAN_MAX 0x2000000

// reloc index + 1 of the slot a plt_call stub serves, 0 when it is not one;
// after the std comes an optional addis r11, r2, ha, then ld rX, d(base);
// mtctr rX; ld r2, d+8(base), ending either in bctr (eager: the slot is
// toc + disp) or in cmpldi r2, 0; bnectr+; b <glink> (lazy: the branch
// target names the slot)
static ut64 ppc64v1_text_stub_reloc(ELFOBJ *eo, const ut8 *w, ut64 vaddr,
HtUU *rel_by_glink, HtUU *rel_by_slot, ut64 toc, ut32 *size) {
int i = 1;
st64 ha = 0;
ut32 base = 2;
ut32 op = r_read_ble32 (w + 4 * i, eo->endian);
if ((op & 0xffff0000) == 0x3d620000) { // addis r11, r2, disp@ha
ha = (st64)(st16)op * 0x10000;
base = 11;
i++;
op = r_read_ble32 (w + 4 * i, eo->endian);
}
if ((op & 0xfc1f0003) != (0xe8000000 | (base << 16))) { // ld rX, d(base)
return 0;
}
const ut32 reg = (op >> 21) & 0x1f;
const st32 lo = (st16)(op & 0xfffc);
op = r_read_ble32 (w + 4 * ++i, eo->endian);
if (reg == 2 || reg == base
|| (op & 0xfc1fffff) != 0x7c0903a6 || ((op >> 21) & 0x1f) != reg) { // mtctr rX
return 0;
}
// --plt-static-chain also loads the descriptor env word, ld rX, d+16(base),
// before the toc restore or after it, depending on whether it clobbers base
op = r_read_ble32 (w + 4 * ++i, eo->endian);
bool env = false;
if ((op & 0xfc1f0003) == (0xe8000000 | (base << 16)) && ((op >> 21) & 0x1f) != base
&& ((op >> 21) & 0x1f) != 2 && (st32)(st16)(op & 0xfffc) == lo + 16) {
env = true;
op = r_read_ble32 (w + 4 * ++i, eo->endian);
}
// the code address and the toc it runs with come from one descriptor
if ((op & 0xffff0003) != (0xe8400000 | (base << 16)) // ld r2, d+8(base)
|| (st32)(st16)(op & 0xfffc) != lo + 8) {
return 0;
}
op = r_read_ble32 (w + 4 * ++i, eo->endian);
if (!env && (op & 0xfc1f0003) == (0xe8000000 | (base << 16)) && ((op >> 21) & 0x1f) != 2
&& (st32)(st16)(op & 0xfffc) == lo + 16) {
op = r_read_ble32 (w + 4 * ++i, eo->endian);
}
if (op == 0x4e800420) { // bctr: an eager stub, only the toc names the slot
*size = 4 * (i + 1);
return toc? ht_uu_find (rel_by_slot, toc + ha + lo, NULL): 0;
}
if (op != 0x28220000) { // cmpldi r2, 0
return 0;
}
op = r_read_ble32 (w + 4 * ++i, eo->endian);
if ((op & 0xff9fffff) != 0x4c820420) { // bnectr+
return 0;
}
op = r_read_ble32 (w + 4 * ++i, eo->endian);
if ((op & 0xfc000003) != 0x48000000) { // b <glink>
return 0;
}
*size = 4 * (i + 1);
const st32 boff = ((st32)((op & 0x03fffffc) << 6)) >> 6;
return ht_uu_find (rel_by_glink, vaddr + 4 * i + boff, NULL);
}

// name the .text call stubs plt.<target> so calls read bl sym.plt.strlen and
// the anal name join can recover forwarded arguments through them
// the stub target is a local symbol or an import, whichever owns the reloc
static const char *ppc64v1_stub_target(ELFOBJ *eo, RBinElfReloc *rel, const char **bind) {
if ((size_t)rel->sym < eo->symbols_by_ord_size && eo->symbols_by_ord[rel->sym]) {
RBinSymbol *t = eo->symbols_by_ord[rel->sym];
*bind = t->bind;
return r_bin_name_tostring2 (t->name, 'o');
}
if ((size_t)rel->sym < eo->imports_by_ord_size && eo->imports_by_ord[rel->sym]) {
RBinImport *t = eo->imports_by_ord[rel->sym];
*bind = t->bind;
return r_bin_name_tostring2 (t->name, 'o');
}
return NULL;
}

typedef struct {
HtUU *rel_by_glink;
HtUU *rel_by_slot;
ut64 toc;
ut64 budget;
} PPC64StubScan;

// scan one executable file range for plt_call stubs, clamped to the file and
// to the remaining scan budget so corrupt headers cannot make this expensive
static void ppc64v1_scan_stubs(ELFOBJ *eo, PPC64StubScan *sc, ut64 paddr, ut64 vaddr, ut64 size) {
const ut64 fsz = r_buf_size (eo->b);
if (paddr >= fsz || !sc->budget) {
return;
}
size = R_MIN (size, fsz - paddr);
size = R_MIN (size, sc->budget);
if (size < PPC64_TEXT_STUB_MIN) {
return;
}
sc->budget -= size;
// zero padding lets the matcher read a full stub window at the range end
ut8 *buf = calloc (size + PPC64_TEXT_STUB_MAX, 1);
if (!buf || r_buf_read_at (eo->b, paddr, buf, size) != (st64)size) {
free (buf);
return;
}
ut64 i;
for (i = 0; i + PPC64_TEXT_STUB_MIN <= size; i += 4) {
if (r_read_ble32 (buf + i, eo->endian) != PPC64_STD_R2_40R1) {
continue;
}
ut32 stub_size = 0;
const ut64 relnum = ppc64v1_text_stub_reloc (eo, buf + i, vaddr + i,
sc->rel_by_glink, sc->rel_by_slot, sc->toc, &stub_size);
RBinElfReloc *rel;
if (!relnum || !(rel = RVecRBinElfReloc_at (&eo->g_relocs, relnum - 1))) {
continue;
}
const char *tbind = NULL;
const char *tname = ppc64v1_stub_target (eo, rel, &tbind);
if (R_STR_ISEMPTY (tname)) {
continue;
}
RBinSymbol sym = {0};
sym.name = r_bin_name_new_from (r_str_newf ("plt.%s", tname));
sym.forwarder = "NONE";
sym.bind = tbind? tbind: R_BIN_BIND_LOCAL_STR;
sym.type = R_BIN_TYPE_FUNC_STR;
sym.attr.size = stub_size;
sym.ordinal = rel->sym;
sym.vaddr = vaddr + i;
sym.paddr = paddr + i;
RVecRBinSymbol_push_back (&eo->plt_symbols_cache, &sym);
}
free (buf);
}

void Elf_(plt_ppc64v1_load_text_stubs)(ELFOBJ *eo) {
if (Elf_(plt_ppc64_abi) (eo) != 1) {
return;
}
Elf_(load_symbols_vec) (eo);
// each glink stub or plt slot identifies its reloc, and that its target
RBinElfSection *got = Elf_(plt_section_by_name) (eo, ".got");
PPC64StubScan sc = {
.rel_by_glink = ht_uu_new0 (),
.rel_by_slot = ht_uu_new0 (),
.toc = got? got->rva + 0x8000: 0,
.budget = PPC64_TEXT_SCAN_MAX,
};
if (!sc.rel_by_glink || !sc.rel_by_slot) {
goto beach;
}
RBinElfReloc *rel;
ut64 nrel = 0;
bool any = false;
R_VEC_FOREACH (&eo->g_relocs, rel) {
nrel++;
if (rel->type == R_PPC64_JMP_SLOT && rel->sym > 0) {
const ut64 glink = Elf_(ppc64_get_plt_stub_for_slot) (eo, rel->rva);
any |= ht_uu_insert (sc.rel_by_slot, rel->rva, nrel);
if (glink != UT64_MAX) {
ht_uu_insert (sc.rel_by_glink, glink, nrel);
}
}
}
if (!any) {
goto beach;
}
// bfd emits stubs into every code section (.init holds __gmon_start__),
// and without section headers the executable segments are all we have
bool scanned = false;
RBinElfSection *s;
R_VEC_FOREACH (&eo->g_sections, s) {
if (s->type == SHT_PROGBITS && (s->flags & SHF_EXECINSTR)) {
ppc64v1_scan_stubs (eo, &sc, s->offset, s->rva, s->size);
scanned = true;
}
}
if (!scanned && eo->phdr) {
int i;
for (i = 0; i < eo->ehdr.e_phnum; i++) {
const Elf_(Phdr) *p = &eo->phdr[i];
if (p->p_type == PT_LOAD && (p->p_flags & PF_X)) {
ppc64v1_scan_stubs (eo, &sc, p->p_offset, p->p_vaddr, p->p_filesz);
}
}
}
beach:
ht_uu_free (sc.rel_by_glink);
ht_uu_free (sc.rel_by_slot);
}
#endif

static ut64 get_import_addr_ppc(ELFOBJ *eo, RBinElfReloc *rel) {
#if R_BIN_ELF64
if (Elf_(plt_ppc64_abi) (eo)) {
Expand Down Expand Up @@ -647,7 +833,7 @@ ut64 Elf_(plt_get_import_addr)(ELFOBJ *eo, int sym) {
case EM_IAMCU:
return get_import_addr_x86 (eo, rel);
case EM_LOONGARCH:
return get_import_addr_loongarch (eo, rel);
return get_import_addr_riscv (eo, rel);
case EM_SBPF:
// sBPF relocations are handled in patch_reloc, return the offset for imports
return rel->offset;
Expand Down
46 changes: 44 additions & 2 deletions test/db/anal/plt-local
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,15 @@ EXPECT=<<EOF
EOF
RUN

NAME=ppc64 ELFv1 glink stubs are not resolved by decoding (known limitation)
NAME=ppc64 ELFv1 text stubs come flagged from the bin layer
FILE=bins/elf/ppc64v1-libz.so
CMDS=<<EOF
aa
fs symbols
f~plt.~?
EOF
EXPECT=<<EOF
0
50
EOF
RUN

Expand All @@ -182,6 +182,48 @@ EXPECT=<<EOF2
EOF2
RUN

NAME=ppc64 ELFv1 caller inherits args through a plt call stub
FILE=bins/elf/ppc64v1-libz.so
CMDS=<<EOF2
aa
afs @ sym.crc32
afs @ sym.compress
afs @ sym.gzgetc_
afs @ sym.crc32_z
EOF2
EXPECT=<<EOF2
void sym.crc32 (int64_t arg1, int64_t arg2, int64_t arg3);
void sym.compress (int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4);
void sym.gzgetc_ (int64_t arg1);
void sym.crc32_z (int64_t arg1, int64_t arg2, int64_t arg3);
EOF2
RUN

NAME=ppc64 ELFv1 stub args survive a non default base address
FILE=bins/elf/ppc64v1-libz.so
ARGS=-B 0x400000
CMDS=<<EOF2
aa
afs @ sym.crc32
afs @ sym.compress
EOF2
EXPECT=<<EOF2
void sym.crc32 (int64_t arg1, int64_t arg2, int64_t arg3);
void sym.compress (int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4);
EOF2
RUN

NAME=ppc64 ELFv1 call through a text stub names its target
FILE=bins/elf/ppc64v1-libz.so
CMDS=<<EOF2
aa
pd 1 @ 0x52dc
EOF2
EXPECT=<<EOF2
| 0x000052dc 4bffe7e5 bl sym.plt.crc32_z
EOF2
RUN

# the scan flags 12 bytes here, up to the call; the 17 below is what the later
# function analysis resizes the flag to
NAME=x86_64 lazy retpoline: stubs calling the shared thunk get named
Expand Down
Loading
Loading