Skip to content

Commit d871f7b

Browse files
raphaelgaultjpoimboe
authored andcommitted
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. Reviewed-by: Miroslav Benes <mbenes@suse.cz> Signed-off-by: Raphael Gault <raphael.gault@arm.com> [J.T.: Move arm64 bits out of this patch, Have only one function to find the start of the jump table, for now assume that the jump table format will be the same as x86] Signed-off-by: Julien Thierry <jthierry@redhat.com> Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
1 parent 45245f5 commit d871f7b

4 files changed

Lines changed: 103 additions & 87 deletions

File tree

tools/objtool/arch/x86/special.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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,
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

Lines changed: 4 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
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,
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,
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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,4 @@ struct instruction *find_insn(struct objtool_file *file,
6666
insn->sec == sec; \
6767
insn = list_next_entry(insn, list))
6868

69-
7069
#endif /* _CHECK_H */

tools/objtool/special.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
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);
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 */

0 commit comments

Comments
 (0)