1 // SPDX-License-Identifier: GPL-2.0-or-later
2 #include <string.h>
3 #include <objtool/special.h>
4 #include <objtool/warn.h>
5
arch_support_alt_relocation(struct special_alt * special_alt,struct instruction * insn,struct reloc * reloc)6 bool arch_support_alt_relocation(struct special_alt *special_alt,
7 struct instruction *insn,
8 struct reloc *reloc)
9 {
10 return false;
11 }
12
13 struct table_info {
14 struct list_head jump_info;
15 unsigned long insn_offset;
16 unsigned long rodata_offset;
17 };
18
get_rodata_table_size_by_table_annotate(struct objtool_file * file,struct instruction * insn,unsigned long * table_size)19 static void get_rodata_table_size_by_table_annotate(struct objtool_file *file,
20 struct instruction *insn,
21 unsigned long *table_size)
22 {
23 struct section *rsec;
24 struct reloc *reloc;
25 struct list_head table_list;
26 struct table_info *orig_table;
27 struct table_info *next_table;
28 unsigned long tmp_insn_offset;
29 unsigned long tmp_rodata_offset;
30
31 rsec = find_section_by_name(file->elf, ".rela.discard.tablejump_annotate");
32 if (!rsec)
33 return;
34
35 INIT_LIST_HEAD(&table_list);
36
37 for_each_reloc(rsec, reloc) {
38 orig_table = malloc(sizeof(struct table_info));
39 if (!orig_table) {
40 WARN("malloc failed");
41 return;
42 }
43
44 orig_table->insn_offset = reloc->sym->offset + reloc_addend(reloc);
45 reloc++;
46 orig_table->rodata_offset = reloc->sym->offset + reloc_addend(reloc);
47
48 list_add_tail(&orig_table->jump_info, &table_list);
49
50 if (reloc_idx(reloc) + 1 == sec_num_entries(rsec))
51 break;
52 }
53
54 list_for_each_entry(orig_table, &table_list, jump_info) {
55 next_table = list_next_entry(orig_table, jump_info);
56 list_for_each_entry_from(next_table, &table_list, jump_info) {
57 if (next_table->rodata_offset < orig_table->rodata_offset) {
58 tmp_insn_offset = next_table->insn_offset;
59 tmp_rodata_offset = next_table->rodata_offset;
60 next_table->insn_offset = orig_table->insn_offset;
61 next_table->rodata_offset = orig_table->rodata_offset;
62 orig_table->insn_offset = tmp_insn_offset;
63 orig_table->rodata_offset = tmp_rodata_offset;
64 }
65 }
66 }
67
68 list_for_each_entry(orig_table, &table_list, jump_info) {
69 if (insn->offset == orig_table->insn_offset) {
70 next_table = list_next_entry(orig_table, jump_info);
71 if (&next_table->jump_info == &table_list) {
72 *table_size = 0;
73 return;
74 }
75
76 while (next_table->rodata_offset == orig_table->rodata_offset) {
77 next_table = list_next_entry(next_table, jump_info);
78 if (&next_table->jump_info == &table_list) {
79 *table_size = 0;
80 return;
81 }
82 }
83
84 *table_size = next_table->rodata_offset - orig_table->rodata_offset;
85 }
86 }
87 }
88
find_reloc_by_table_annotate(struct objtool_file * file,struct instruction * insn,unsigned long * table_size)89 static struct reloc *find_reloc_by_table_annotate(struct objtool_file *file,
90 struct instruction *insn,
91 unsigned long *table_size)
92 {
93 struct section *rsec;
94 struct reloc *reloc;
95 unsigned long offset;
96
97 rsec = find_section_by_name(file->elf, ".rela.discard.tablejump_annotate");
98 if (!rsec)
99 return NULL;
100
101 for_each_reloc(rsec, reloc) {
102 if (reloc->sym->sec->rodata)
103 continue;
104
105 if (strcmp(insn->sec->name, reloc->sym->sec->name))
106 continue;
107
108 offset = reloc->sym->offset + reloc_addend(reloc);
109 if (insn->offset == offset) {
110 get_rodata_table_size_by_table_annotate(file, insn, table_size);
111 reloc++;
112 return reloc;
113 }
114 }
115
116 return NULL;
117 }
118
find_reloc_of_rodata_c_jump_table(struct section * sec,unsigned long offset,unsigned long * table_size)119 static struct reloc *find_reloc_of_rodata_c_jump_table(struct section *sec,
120 unsigned long offset,
121 unsigned long *table_size)
122 {
123 struct section *rsec;
124 struct reloc *reloc;
125
126 rsec = sec->rsec;
127 if (!rsec)
128 return NULL;
129
130 for_each_reloc(rsec, reloc) {
131 if (reloc_offset(reloc) > offset)
132 break;
133
134 if (!strcmp(reloc->sym->sec->name, C_JUMP_TABLE_SECTION)) {
135 *table_size = 0;
136 return reloc;
137 }
138 }
139
140 return NULL;
141 }
142
arch_find_switch_table(struct objtool_file * file,struct instruction * insn,unsigned long * table_size)143 struct reloc *arch_find_switch_table(struct objtool_file *file,
144 struct instruction *insn,
145 unsigned long *table_size)
146 {
147 struct reloc *annotate_reloc;
148 struct reloc *rodata_reloc;
149 struct section *table_sec;
150 unsigned long table_offset;
151
152 annotate_reloc = find_reloc_by_table_annotate(file, insn, table_size);
153 if (!annotate_reloc) {
154 annotate_reloc = find_reloc_of_rodata_c_jump_table(
155 insn->sec, insn->offset, table_size);
156 if (!annotate_reloc)
157 return NULL;
158 }
159
160 table_sec = annotate_reloc->sym->sec;
161 table_offset = annotate_reloc->sym->offset + reloc_addend(annotate_reloc);
162
163 /*
164 * Each table entry has a rela associated with it. The rela
165 * should reference text in the same function as the original
166 * instruction.
167 */
168 rodata_reloc = find_reloc_by_dest(file->elf, table_sec, table_offset);
169 if (!rodata_reloc)
170 return NULL;
171
172 return rodata_reloc;
173 }
174