[PATCH v3 5/8] objtool: Add generic support for jump table annotations

From: Ard Biesheuvel
Date: Fri Oct 11 2024 - 13:11:04 EST


From: Ard Biesheuvel <ardb@xxxxxxxxxx>

Refactor the jump table handling code so that a generic code path is
provided that can identify jump tables attached to indirect jumps based
only on compiler provided annotations. This will be used by non-x86
architectures which do not support jump tables at all at this point.

Refactor the x86 code to share the logic that follows relocations on
instructions into the .rodata section and finds the associated symbols.

Signed-off-by: Ard Biesheuvel <ardb@xxxxxxxxxx>
---
tools/objtool/arch/x86/special.c | 46 ++++------
tools/objtool/check.c | 88 +++++++++++++++++++-
tools/objtool/include/objtool/check.h | 4 +
3 files changed, 106 insertions(+), 32 deletions(-)

diff --git a/tools/objtool/arch/x86/special.c b/tools/objtool/arch/x86/special.c
index cd964b85e2b1..08a5ce662974 100644
--- a/tools/objtool/arch/x86/special.c
+++ b/tools/objtool/arch/x86/special.c
@@ -112,46 +112,34 @@ static struct reloc *find_switch_table(struct objtool_file *file,
struct instruction *insn,
unsigned long *table_size)
{
- struct reloc *text_reloc, *rodata_reloc;
- struct section *table_sec;
- unsigned long table_offset;
-
- /* look for a relocation which references .rodata */
- text_reloc = find_reloc_by_dest_range(file->elf, insn->sec,
- insn->offset, insn->len);
- if (!text_reloc || text_reloc->sym->type != STT_SECTION ||
- !text_reloc->sym->sec->rodata)
- return NULL;
-
- table_offset = reloc_addend(text_reloc);
- table_sec = text_reloc->sym->sec;
+ struct reloc *rodata_reloc;
+ struct symbol *sym = NULL;

- if (reloc_type(text_reloc) == R_X86_64_PC32)
- table_offset += 4;
+ /*
+ * Each table entry has a rela associated with it. The rela
+ * should reference text in the same function as the original
+ * instruction.
+ */
+ rodata_reloc = find_rodata_sym_reference(file, insn, &sym);

/*
- * Make sure the .rodata address isn't associated with a
- * symbol. GCC jump tables are anonymous data.
+ * Annotations, if present, are attached to the indirect jump
+ * instruction directly. In this case, a symbol annotation is
+ * expected.
+ *
+ * Otherwise, make sure the .rodata address isn't associated with
+ * a symbol. Unannotated GCC jump tables are anonymous data.
*
* Also support C jump tables which are in the same format as
* switch jump tables. For objtool to recognize them, they
* need to be placed in the C_JUMP_TABLE_SECTION section. They
* have symbols associated with them.
*/
- if (find_symbol_containing(table_sec, table_offset) &&
- strcmp(table_sec->name, C_JUMP_TABLE_SECTION))
- return NULL;
-
- /*
- * Each table entry has a rela associated with it. The rela
- * should reference text in the same function as the original
- * instruction.
- */
- rodata_reloc = find_reloc_by_dest(file->elf, table_sec, table_offset);
- if (!rodata_reloc)
+ if (insn->type != INSN_JUMP_DYNAMIC && sym &&
+ strcmp(sym->sec->name, C_JUMP_TABLE_SECTION))
return NULL;

- *table_size = 0;
+ *table_size = sym ? sym->len : 0;
return rodata_reloc;
}

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 389475dde47c..b923d4a4efcb 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -1372,6 +1372,8 @@ __weak const char *arch_nop_fentry_call(int len)

static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn)
{
+ unsigned long offset = insn->offset;
+ unsigned int len = insn->len;
struct reloc *reloc;

if (insn->no_reloc)
@@ -1380,8 +1382,12 @@ static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *i
if (!file)
return NULL;

- reloc = find_reloc_by_dest_range(file->elf, insn->sec,
- insn->offset, insn->len);
+ do {
+ /* Skip any R_*_NONE relocations */
+ reloc = find_reloc_by_dest_range(file->elf, insn->sec,
+ offset++, len--);
+ } while (len && reloc && reloc_type(reloc) == R_NONE);
+
if (!reloc) {
insn->no_reloc = 1;
return NULL;
@@ -2169,10 +2175,86 @@ int add_jump_table(struct objtool_file *file, struct instruction *insn,
return 0;
}

+struct reloc *find_rodata_sym_reference(struct objtool_file *file,
+ struct instruction *insn,
+ struct symbol **table_sym)
+{
+ struct reloc *text_reloc, *rodata_reloc;
+ unsigned long addend;
+ struct symbol *sym;
+
+ /*
+ * Look for a relocation which references .rodata. We must use
+ * find_reloc_by_dest_range() directly here, as insn_reloc() filters
+ * out R_*_NONE relocations which are used for jump table annotations.
+ */
+ text_reloc = find_reloc_by_dest_range(file->elf, insn->sec,
+ insn->offset, insn->len);
+ if (!text_reloc) {
+ insn->no_reloc = 1;
+ return NULL;
+ }
+
+ sym = text_reloc->sym;
+ if (!sym->sec->rodata)
+ return NULL;
+
+ if (reloc_type(text_reloc) == elf_data_rela_type(file->elf))
+ addend = arch_dest_reloc_offset(reloc_addend(text_reloc));
+ else
+ addend = reloc_addend(text_reloc);
+
+ rodata_reloc = find_reloc_by_dest(file->elf, sym->sec,
+ sym->offset + addend);
+ if (!rodata_reloc)
+ return NULL;
+
+ /*
+ * Find the ELF symbol covering the destination of the relocation. This
+ * is trivial if the reloc refers to a STT_OBJECT directly, but it may
+ * have been emitted as section relative as well.
+ */
+ if (sym->type == STT_SECTION)
+ sym = find_symbol_containing(sym->sec, addend);
+
+ *table_sym = sym;
+ return rodata_reloc;
+}
+
+/*
+ * Generic version of jump table handling, relying strictly on annotations
+ * provided by the compiler. Overridden for x86 using heuristics that attempt
+ * to correlate indirect jump instructions with preceding .rodata references.
+ */
int __weak add_func_jump_tables(struct objtool_file *file,
struct symbol *func)
{
- return 0;
+ struct instruction *insn;
+ int ret = 0;
+
+ func_for_each_insn(file, func, insn) {
+ struct reloc *reloc;
+ struct symbol *sym;
+
+ if (insn->type != INSN_JUMP_DYNAMIC)
+ continue;
+
+ /*
+ * Look for a relocation attached to this indirect jump that
+ * references an ELF object in .rodata. This should be the jump
+ * table annotation emitted by the compiler.
+ */
+ reloc = find_rodata_sym_reference(file, insn, &sym);
+ if (reloc && sym && sym->len) {
+ insn->_jump_table = reloc;
+ insn->_jump_table_size = sym->len;
+
+ ret = add_jump_table(file, insn, NULL);
+ if (ret)
+ break;
+ }
+ }
+ return ret;
}

/*
diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h
index e2f755484c4a..7781100c9340 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -140,4 +140,8 @@ struct instruction *next_insn_same_func(struct objtool_file *file, struct instru
insn; \
insn = next_insn_same_func(file, insn))

+struct reloc *find_rodata_sym_reference(struct objtool_file *file,
+ struct instruction *insn,
+ struct symbol **sym);
+
#endif /* _CHECK_H */
--
2.47.0.rc1.288.g06298d1525-goog