@@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <stdlib.h>
+
#include "../../special.h"
#include "../../builtin.h"
+#include "../../warn.h"
void arch_handle_alternative(unsigned short feature, struct special_alt *alt)
{
@@ -32,3 +35,148 @@ void arch_handle_alternative(unsigned short feature, struct special_alt *alt)
break;
}
}
+
+int arch_add_jump_table_dests(struct objtool_file *file,
+ struct instruction *insn)
+{
+ struct rela *table = insn->jump_table;
+ struct rela *rela = table;
+ struct instruction *dest_insn;
+ struct alternative *alt;
+ struct symbol *pfunc = insn->func->pfunc;
+ unsigned int prev_offset = 0;
+
+ /*
+ * Each @rela is a switch table relocation which points to the target
+ * instruction.
+ */
+ list_for_each_entry_from(rela, &table->sec->rela_list, list) {
+
+ /* Check for the end of the table: */
+ if (rela != table && rela->jump_table_start)
+ break;
+
+ /* Make sure the table entries are consecutive: */
+ if (prev_offset && rela->offset != prev_offset + 8)
+ break;
+
+ /* Detect function pointers from contiguous objects: */
+ if (rela->sym->sec == pfunc->sec &&
+ rela->addend == pfunc->offset)
+ break;
+
+ dest_insn = find_insn(file, rela->sym->sec, rela->addend);
+ if (!dest_insn)
+ break;
+
+ /* Make sure the destination is in the same function: */
+ if (!dest_insn->func || dest_insn->func->pfunc != pfunc)
+ break;
+
+ alt = malloc(sizeof(*alt));
+ if (!alt) {
+ WARN("malloc failed");
+ return -1;
+ }
+
+ alt->insn = dest_insn;
+ list_add_tail(&alt->list, &insn->alts);
+ prev_offset = rela->offset;
+ }
+
+ if (!prev_offset) {
+ WARN_FUNC("can't find switch jump table",
+ insn->sec, insn->offset);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * There are 3 basic jump table patterns:
+ *
+ * 1. jmpq *[rodata addr](,%reg,8)
+ *
+ * This is the most common case by far. It jumps to an address in a simple
+ * jump table which is stored in .rodata.
+ *
+ * 2. jmpq *[rodata addr](%rip)
+ *
+ * This is caused by a rare GCC quirk, currently only seen in three driver
+ * functions in the kernel, only with certain obscure non-distro configs.
+ *
+ * As part of an optimization, GCC makes a copy of an existing switch jump
+ * table, modifies it, and then hard-codes the jump (albeit with an indirect
+ * jump) to use a single entry in the table. The rest of the jump table and
+ * some of its jump targets remain as dead code.
+ *
+ * In such a case we can just crudely ignore all unreachable instruction
+ * warnings for the entire object file. Ideally we would just ignore them
+ * for the function, but that would require redesigning the code quite a
+ * bit. And honestly that's just not worth doing: unreachable instruction
+ * warnings are of questionable value anyway, and this is such a rare issue.
+ *
+ * 3. mov [rodata addr],%reg1
+ * ... some instructions ...
+ * jmpq *(%reg1,%reg2,8)
+ *
+ * This is a fairly uncommon pattern which is new for GCC 6. As of this
+ * writing, there are 11 occurrences of it in the allmodconfig kernel.
+ *
+ * As of GCC 7 there are quite a few more of these and the 'in between' code
+ * is significant. Esp. with KASAN enabled some of the code between the mov
+ * and jmpq uses .rodata itself, which can confuse things.
+ *
+ * TODO: Once we have DWARF CFI and smarter instruction decoding logic,
+ * ensure the same register is used in the mov and jump instructions.
+ *
+ * NOTE: RETPOLINE made it harder still to decode dynamic jumps.
+ */
+struct rela *arch_find_switch_table(struct objtool_file *file,
+ struct instruction *insn)
+{
+ struct rela *text_rela, *rodata_rela;
+ struct section *table_sec;
+ unsigned long table_offset;
+
+ /* look for a relocation which references .rodata */
+ text_rela = find_rela_by_dest_range(insn->sec, insn->offset,
+ insn->len);
+ if (!text_rela || text_rela->sym->type != STT_SECTION ||
+ !text_rela->sym->sec->rodata)
+ return NULL;
+ table_offset = text_rela->addend;
+ table_sec = text_rela->sym->sec;
+
+ if (text_rela->type == R_X86_64_PC32)
+ table_offset += 4;
+
+ /*
+ * Make sure the .rodata address isn't associated with a
+ * symbol. 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;
+
+ rodata_rela = find_rela_by_dest(table_sec, table_offset);
+ if (rodata_rela) {
+ /*
+ * Use of RIP-relative switch jumps is quite rare, and
+ * indicates a rare GCC quirk/bug which can leave dead
+ * code behind.
+ */
+ if (text_rela->type == R_X86_64_PC32)
+ file->ignore_unreachables = true;
+
+ return rodata_rela;
+ }
+
+ return NULL;
+}
@@ -18,14 +18,6 @@
#define FAKE_JUMP_OFFSET -1
-#define C_JUMP_TABLE_SECTION ".rodata..c_jump_table"
-
-struct alternative {
- struct list_head list;
- struct instruction *insn;
- bool skip_orig;
-};
-
const char *objname;
struct cfi_state initial_func_cfi;
@@ -914,113 +906,16 @@ static int add_special_section_alts(struct objtool_file *file)
return ret;
}
-static int add_jump_table(struct objtool_file *file, struct instruction *insn,
- struct rela *table)
-{
- struct rela *rela = table;
- struct instruction *dest_insn;
- struct alternative *alt;
- struct symbol *pfunc = insn->func->pfunc;
- unsigned int prev_offset = 0;
-
- /*
- * Each @rela is a switch table relocation which points to the target
- * instruction.
- */
- list_for_each_entry_from(rela, &table->sec->rela_list, list) {
-
- /* Check for the end of the table: */
- if (rela != table && rela->jump_table_start)
- break;
-
- /* Make sure the table entries are consecutive: */
- if (prev_offset && rela->offset != prev_offset + 8)
- break;
-
- /* Detect function pointers from contiguous objects: */
- if (rela->sym->sec == pfunc->sec &&
- rela->addend == pfunc->offset)
- break;
-
- dest_insn = find_insn(file, rela->sym->sec, rela->addend);
- if (!dest_insn)
- break;
-
- /* Make sure the destination is in the same function: */
- if (!dest_insn->func || dest_insn->func->pfunc != pfunc)
- break;
-
- alt = malloc(sizeof(*alt));
- if (!alt) {
- WARN("malloc failed");
- return -1;
- }
-
- alt->insn = dest_insn;
- list_add_tail(&alt->list, &insn->alts);
- prev_offset = rela->offset;
- }
-
- if (!prev_offset) {
- WARN_FUNC("can't find switch jump table",
- insn->sec, insn->offset);
- return -1;
- }
-
- return 0;
-}
-
/*
* find_jump_table() - Given a dynamic jump, find the switch jump table in
- * .rodata associated with it.
- *
- * There are 3 basic patterns:
- *
- * 1. jmpq *[rodata addr](,%reg,8)
- *
- * This is the most common case by far. It jumps to an address in a simple
- * jump table which is stored in .rodata.
- *
- * 2. jmpq *[rodata addr](%rip)
- *
- * This is caused by a rare GCC quirk, currently only seen in three driver
- * functions in the kernel, only with certain obscure non-distro configs.
- *
- * As part of an optimization, GCC makes a copy of an existing switch jump
- * table, modifies it, and then hard-codes the jump (albeit with an indirect
- * jump) to use a single entry in the table. The rest of the jump table and
- * some of its jump targets remain as dead code.
- *
- * In such a case we can just crudely ignore all unreachable instruction
- * warnings for the entire object file. Ideally we would just ignore them
- * for the function, but that would require redesigning the code quite a
- * bit. And honestly that's just not worth doing: unreachable instruction
- * warnings are of questionable value anyway, and this is such a rare issue.
- *
- * 3. mov [rodata addr],%reg1
- * ... some instructions ...
- * jmpq *(%reg1,%reg2,8)
- *
- * This is a fairly uncommon pattern which is new for GCC 6. As of this
- * writing, there are 11 occurrences of it in the allmodconfig kernel.
- *
- * As of GCC 7 there are quite a few more of these and the 'in between' code
- * is significant. Esp. with KASAN enabled some of the code between the mov
- * and jmpq uses .rodata itself, which can confuse things.
- *
- * TODO: Once we have DWARF CFI and smarter instruction decoding logic,
- * ensure the same register is used in the mov and jump instructions.
- *
- * NOTE: RETPOLINE made it harder still to decode dynamic jumps.
+ * associated with it.
*/
static struct rela *find_jump_table(struct objtool_file *file,
struct symbol *func,
struct instruction *insn)
{
- struct rela *text_rela, *table_rela;
+ struct rela *table_rela;
struct instruction *orig_insn = insn;
- struct section *table_sec;
- unsigned long table_offset;
/*
* Backward search using the @first_jump_src links, these help avoid
@@ -1044,45 +939,10 @@ static struct rela *find_jump_table(struct objtool_file *file,
insn->jump_dest->offset > orig_insn->offset))
break;
- /* look for a relocation which references .rodata */
- text_rela = find_rela_by_dest_range(insn->sec, insn->offset,
- insn->len);
- if (!text_rela || text_rela->sym->type != STT_SECTION ||
- !text_rela->sym->sec->rodata)
- continue;
-
- table_offset = text_rela->addend;
- table_sec = text_rela->sym->sec;
-
- if (text_rela->type == R_X86_64_PC32)
- table_offset += 4;
-
- /*
- * Make sure the .rodata address isn't associated with a
- * symbol. 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))
- continue;
-
- /* Each table entry has a rela associated with it. */
- table_rela = find_rela_by_dest(table_sec, table_offset);
+ table_rela = arch_find_switch_table(file, insn);
if (!table_rela)
continue;
- /*
- * Use of RIP-relative switch jumps is quite rare, and
- * indicates a rare GCC quirk/bug which can leave dead code
- * behind.
- */
- if (text_rela->type == R_X86_64_PC32)
- file->ignore_unreachables = true;
-
return table_rela;
}
@@ -1138,7 +998,7 @@ static int add_func_jump_tables(struct objtool_file *file,
if (!insn->jump_table)
continue;
- ret = add_jump_table(file, insn, insn->jump_table);
+ ret = arch_add_jump_table_dests(file, insn);
if (ret)
return ret;
}
@@ -49,6 +49,12 @@ struct instruction {
bool intra_group_jump;
};
+struct alternative {
+ struct list_head list;
+ struct instruction *insn;
+ bool skip_orig;
+};
+
struct objtool_file {
struct elf *elf;
struct list_head insn_list;
@@ -70,5 +76,6 @@ struct instruction *find_insn(struct objtool_file *file,
insn->sec == sec; \
insn = list_next_entry(insn, list))
+#define C_JUMP_TABLE_SECTION ".rodata..c_jump_table"
#endif /* _CHECK_H */
@@ -7,6 +7,7 @@
#define _SPECIAL_H
#include <stdbool.h>
+#include "check.h"
#include "elf.h"
#include "arch_special.h"
@@ -36,4 +37,8 @@ static inline void arch_handle_alternative(unsigned short feature,
}
#endif
+int arch_add_jump_table_dests(struct objtool_file *file,
+ struct instruction *insn);
+struct rela *arch_find_switch_table(struct objtool_file *file,
+ struct instruction *insn);
#endif /* _SPECIAL_H */