History

objtool: Refactor jump table code to support other architectures

The way to identify jump tables and retrieve all the data necessary to handle the different execution branches is not the same on all architectures. In order to be able to add other architecture support, define an arch-dependent function to process jump-tables.

Raphael Gault committed 6 years ago

Commit d871f7b1 parent 45245f54 files · +103 −87Browse files

Unified · Split

4 files
  1. tools/objtool/arch/x86/special.c +95 −0
  2. tools/objtool/check.c +4 −86
  3. tools/objtool/check.h +0 −1
  4. tools/objtool/special.h +4 −0
tools/objtool/arch/x86/special.c+95−0
@@ -1,4 +1,6 @@
11 // SPDX-License-Identifier: GPL-2.0-or-later
2+#include <string.h>
3+
24 #include "../../special.h"
35 #include "../../builtin.h"
46
@@ -48,3 +50,96 @@ bool arch_support_alt_relocation(struct special_alt *special_alt, · 43 unmodified lines
4850 return insn->offset == special_alt->new_off &&
4951 (insn->type == INSN_CALL || is_static_jump(insn));
5052 }
53+
54+/*
55+ * There are 3 basic jump table patterns:
56+ *
57+ * 1. jmpq *[rodata addr](,%reg,8)
58+ *
59+ * This is the most common case by far. It jumps to an address in a simple
60+ * jump table which is stored in .rodata.
61+ *
62+ * 2. jmpq *[rodata addr](%rip)
63+ *
64+ * This is caused by a rare GCC quirk, currently only seen in three driver
65+ * functions in the kernel, only with certain obscure non-distro configs.
66+ *
67+ * As part of an optimization, GCC makes a copy of an existing switch jump
68+ * table, modifies it, and then hard-codes the jump (albeit with an indirect
69+ * jump) to use a single entry in the table. The rest of the jump table and
70+ * some of its jump targets remain as dead code.
71+ *
72+ * In such a case we can just crudely ignore all unreachable instruction
73+ * warnings for the entire object file. Ideally we would just ignore them
74+ * for the function, but that would require redesigning the code quite a
75+ * bit. And honestly that's just not worth doing: unreachable instruction
76+ * warnings are of questionable value anyway, and this is such a rare issue.
77+ *
78+ * 3. mov [rodata addr],%reg1
79+ * ... some instructions ...
80+ * jmpq *(%reg1,%reg2,8)
81+ *
82+ * This is a fairly uncommon pattern which is new for GCC 6. As of this
83+ * writing, there are 11 occurrences of it in the allmodconfig kernel.
84+ *
85+ * As of GCC 7 there are quite a few more of these and the 'in between' code
86+ * is significant. Esp. with KASAN enabled some of the code between the mov
87+ * and jmpq uses .rodata itself, which can confuse things.
88+ *
89+ * TODO: Once we have DWARF CFI and smarter instruction decoding logic,
90+ * ensure the same register is used in the mov and jump instructions.
91+ *
92+ * NOTE: RETPOLINE made it harder still to decode dynamic jumps.
93+ */
94+struct reloc *arch_find_switch_table(struct objtool_file *file,
95+ struct instruction *insn)
96+{
97+ struct reloc *text_reloc, *rodata_reloc;
98+ struct section *table_sec;
99+ unsigned long table_offset;
100+
101+ /* look for a relocation which references .rodata */
102+ text_reloc = find_reloc_by_dest_range(file->elf, insn->sec,
103+ insn->offset, insn->len);
104+ if (!text_reloc || text_reloc->sym->type != STT_SECTION ||
105+ !text_reloc->sym->sec->rodata)
106+ return NULL;
107+
108+ table_offset = text_reloc->addend;
109+ table_sec = text_reloc->sym->sec;
110+
111+ if (text_reloc->type == R_X86_64_PC32)
112+ table_offset += 4;
113+
114+ /*
115+ * Make sure the .rodata address isn't associated with a
116+ * symbol. GCC jump tables are anonymous data.
117+ *
118+ * Also support C jump tables which are in the same format as
119+ * switch jump tables. For objtool to recognize them, they
120+ * need to be placed in the C_JUMP_TABLE_SECTION section. They
121+ * have symbols associated with them.
122+ */
123+ if (find_symbol_containing(table_sec, table_offset) &&
124+ strcmp(table_sec->name, C_JUMP_TABLE_SECTION))
125+ return NULL;
126+
127+ /*
128+ * Each table entry has a rela associated with it. The rela
129+ * should reference text in the same function as the original
130+ * instruction.
131+ */
132+ rodata_reloc = find_reloc_by_dest(file->elf, table_sec, table_offset);
133+ if (!rodata_reloc)
134+ return NULL;
135+
136+ /*
137+ * Use of RIP-relative switch jumps is quite rare, and
138+ * indicates a rare GCC quirk/bug which can leave dead
139+ * code behind.
140+ */
141+ if (text_reloc->type == R_X86_64_PC32)
142+ file->ignore_unreachables = true;
143+
144+ return rodata_reloc;
145+}
tools/objtool/check.c+4−86
@@ -20,8 +20,6 @@ · 19 unmodified lines
2020
2121 #define FAKE_JUMP_OFFSET -1
2222
23−#define C_JUMP_TABLE_SECTION ".rodata..c_jump_table"
24−
2523 struct alternative {
2624 struct list_head list;
2725 struct instruction *insn;
@@ -1190,56 +1188,15 @@ static int add_jump_table(struct objtool_file *file, struct instruction *insn, · 1162 unmodified lines
11901188 }
11911189
11921190 /*
1193− * find_jump_table() - Given a dynamic jump, find the switch jump table in
1194− * .rodata associated with it.
1195− *
1196− * There are 3 basic patterns:
1197− *
1198− * 1. jmpq *[rodata addr](,%reg,8)
1199− *
1200− * This is the most common case by far. It jumps to an address in a simple
1201− * jump table which is stored in .rodata.
1202− *
1203− * 2. jmpq *[rodata addr](%rip)
1204− *
1205− * This is caused by a rare GCC quirk, currently only seen in three driver
1206− * functions in the kernel, only with certain obscure non-distro configs.
1207− *
1208− * As part of an optimization, GCC makes a copy of an existing switch jump
1209− * table, modifies it, and then hard-codes the jump (albeit with an indirect
1210− * jump) to use a single entry in the table. The rest of the jump table and
1211− * some of its jump targets remain as dead code.
1212− *
1213− * In such a case we can just crudely ignore all unreachable instruction
1214− * warnings for the entire object file. Ideally we would just ignore them
1215− * for the function, but that would require redesigning the code quite a
1216− * bit. And honestly that's just not worth doing: unreachable instruction
1217− * warnings are of questionable value anyway, and this is such a rare issue.
1218− *
1219− * 3. mov [rodata addr],%reg1
1220− * ... some instructions ...
1221− * jmpq *(%reg1,%reg2,8)
1222− *
1223− * This is a fairly uncommon pattern which is new for GCC 6. As of this
1224− * writing, there are 11 occurrences of it in the allmodconfig kernel.
1225− *
1226− * As of GCC 7 there are quite a few more of these and the 'in between' code
1227− * is significant. Esp. with KASAN enabled some of the code between the mov
1228− * and jmpq uses .rodata itself, which can confuse things.
1229− *
1230− * TODO: Once we have DWARF CFI and smarter instruction decoding logic,
1231− * ensure the same register is used in the mov and jump instructions.
1232− *
1233− * NOTE: RETPOLINE made it harder still to decode dynamic jumps.
1191+ * find_jump_table() - Given a dynamic jump, find the switch jump table
1192+ * associated with it.
12341193 */
12351194 static struct reloc *find_jump_table(struct objtool_file *file,
12361195 struct symbol *func,
12371196 struct instruction *insn)
12381197 {
1239− struct reloc *text_reloc, *table_reloc;
1198+ struct reloc *table_reloc;
12401199 struct instruction *dest_insn, *orig_insn = insn;
1241− struct section *table_sec;
1242− unsigned long table_offset;
12431200
12441201 /*
12451202 * Backward search using the @first_jump_src links, these help avoid
@@ -1260,52 +1217,13 @@ static struct reloc *find_jump_table(struct objtool_file *file, · 14 unmodified lines
12601217 insn->jump_dest->offset > orig_insn->offset))
12611218 break;
12621219
1263− /* look for a relocation which references .rodata */
1264− text_reloc = find_reloc_by_dest_range(file->elf, insn->sec,
1265− insn->offset, insn->len);
1266− if (!text_reloc || text_reloc->sym->type != STT_SECTION ||
1267− !text_reloc->sym->sec->rodata)
1268− continue;
1269−
1270− table_offset = text_reloc->addend;
1271− table_sec = text_reloc->sym->sec;
1272−
1273− if (text_reloc->type == R_X86_64_PC32)
1274− table_offset += 4;
1275−
1276− /*
1277− * Make sure the .rodata address isn't associated with a
1278− * symbol. GCC jump tables are anonymous data.
1279− *
1280− * Also support C jump tables which are in the same format as
1281− * switch jump tables. For objtool to recognize them, they
1282− * need to be placed in the C_JUMP_TABLE_SECTION section. They
1283− * have symbols associated with them.
1284− */
1285− if (find_symbol_containing(table_sec, table_offset) &&
1286− strcmp(table_sec->name, C_JUMP_TABLE_SECTION))
1287− continue;
1288−
1289− /*
1290− * Each table entry has a reloc associated with it. The reloc
1291− * should reference text in the same function as the original
1292− * instruction.
1293− */
1294− table_reloc = find_reloc_by_dest(file->elf, table_sec, table_offset);
1220+ table_reloc = arch_find_switch_table(file, insn);
12951221 if (!table_reloc)
12961222 continue;
12971223 dest_insn = find_insn(file, table_reloc->sym->sec, table_reloc->addend);
12981224 if (!dest_insn || !dest_insn->func || dest_insn->func->pfunc != func)
12991225 continue;
13001226
1301− /*
1302− * Use of RIP-relative switch jumps is quite rare, and
1303− * indicates a rare GCC quirk/bug which can leave dead code
1304− * behind.
1305− */
1306− if (text_reloc->type == R_X86_64_PC32)
1307− file->ignore_unreachables = true;
1308−
13091227 return table_reloc;
13101228 }
13111229
tools/objtool/check.h+0−1
@@ -66,5 +66,4 @@ struct instruction *find_insn(struct objtool_file *file, · 65 unmodified lines
6666 insn->sec == sec; \
6767 insn = list_next_entry(insn, list))
6868
69−
7069 #endif /* _CHECK_H */
tools/objtool/special.h+4−0
@@ -10,6 +10,8 @@ · 9 unmodified lines
1010 #include "check.h"
1111 #include "elf.h"
1212
13+#define C_JUMP_TABLE_SECTION ".rodata..c_jump_table"
14+
1315 struct special_alt {
1416 struct list_head list;
1517
@@ -34,4 +36,6 @@ void arch_handle_alternative(unsigned short feature, struct special_alt *alt); · 18 unmodified lines
3436 bool arch_support_alt_relocation(struct special_alt *special_alt,
3537 struct instruction *insn,
3638 struct reloc *reloc);
39+struct reloc *arch_find_switch_table(struct objtool_file *file,
40+ struct instruction *insn);
3741 #endif /* _SPECIAL_H */

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t