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 bool is_valid_list = false;
31
32 rsec = find_section_by_name(file->elf, ".rela.discard.tablejump_annotate");
33 if (!rsec)
34 return;
35
36 INIT_LIST_HEAD(&table_list);
37
38 for_each_reloc(rsec, reloc) {
39 if (reloc->sym->sec->rodata)
40 continue;
41
42 if (strcmp(insn->sec->name, reloc->sym->sec->name))
43 continue;
44
45 orig_table = malloc(sizeof(struct table_info));
46 if (!orig_table) {
47 WARN("malloc failed");
48 return;
49 }
50
51 orig_table->insn_offset = reloc->sym->offset + reloc_addend(reloc);
52 reloc++;
53 orig_table->rodata_offset = reloc->sym->offset + reloc_addend(reloc);
54
55 list_add_tail(&orig_table->jump_info, &table_list);
56
57 if (reloc_idx(reloc) + 1 == sec_num_entries(rsec))
58 break;
59
60 if (strcmp(insn->sec->name, (reloc + 1)->sym->sec->name)) {
61 list_for_each_entry(orig_table, &table_list, jump_info) {
62 if (orig_table->insn_offset == insn->offset) {
63 is_valid_list = true;
64 break;
65 }
66 }
67
68 if (!is_valid_list) {
69 list_del_init(&table_list);
70 continue;
71 }
72
73 break;
74 }
75 }
76
77 list_for_each_entry(orig_table, &table_list, jump_info) {
78 next_table = list_next_entry(orig_table, jump_info);
79 list_for_each_entry_from(next_table, &table_list, jump_info) {
80 if (next_table->rodata_offset < orig_table->rodata_offset) {
81 tmp_insn_offset = next_table->insn_offset;
82 tmp_rodata_offset = next_table->rodata_offset;
83 next_table->insn_offset = orig_table->insn_offset;
84 next_table->rodata_offset = orig_table->rodata_offset;
85 orig_table->insn_offset = tmp_insn_offset;
86 orig_table->rodata_offset = tmp_rodata_offset;
87 }
88 }
89 }
90
91 list_for_each_entry(orig_table, &table_list, jump_info) {
92 if (insn->offset == orig_table->insn_offset) {
93 next_table = list_next_entry(orig_table, jump_info);
94 if (&next_table->jump_info == &table_list) {
95 *table_size = 0;
96 return;
97 }
98
99 while (next_table->rodata_offset == orig_table->rodata_offset) {
100 next_table = list_next_entry(next_table, jump_info);
101 if (&next_table->jump_info == &table_list) {
102 *table_size = 0;
103 return;
104 }
105 }
106
107 *table_size = next_table->rodata_offset - orig_table->rodata_offset;
108 }
109 }
110 }
111
find_reloc_by_table_annotate(struct objtool_file * file,struct instruction * insn,unsigned long * table_size)112 static struct reloc *find_reloc_by_table_annotate(struct objtool_file *file,
113 struct instruction *insn,
114 unsigned long *table_size)
115 {
116 struct section *rsec;
117 struct reloc *reloc;
118 unsigned long offset;
119
120 rsec = find_section_by_name(file->elf, ".rela.discard.tablejump_annotate");
121 if (!rsec)
122 return NULL;
123
124 for_each_reloc(rsec, reloc) {
125 if (reloc->sym->sec->rodata)
126 continue;
127
128 if (strcmp(insn->sec->name, reloc->sym->sec->name))
129 continue;
130
131 offset = reloc->sym->offset + reloc_addend(reloc);
132 if (insn->offset == offset) {
133 get_rodata_table_size_by_table_annotate(file, insn, table_size);
134 reloc++;
135 return reloc;
136 }
137 }
138
139 return NULL;
140 }
141
find_reloc_of_rodata_c_jump_table(struct section * sec,unsigned long offset,unsigned long * table_size)142 static struct reloc *find_reloc_of_rodata_c_jump_table(struct section *sec,
143 unsigned long offset,
144 unsigned long *table_size)
145 {
146 struct section *rsec;
147 struct reloc *reloc;
148
149 rsec = sec->rsec;
150 if (!rsec)
151 return NULL;
152
153 for_each_reloc(rsec, reloc) {
154 if (reloc_offset(reloc) > offset)
155 break;
156
157 if (!strcmp(reloc->sym->sec->name, C_JUMP_TABLE_SECTION)) {
158 *table_size = 0;
159 return reloc;
160 }
161 }
162
163 return NULL;
164 }
165
arch_find_switch_table(struct objtool_file * file,struct instruction * insn,unsigned long * table_size)166 struct reloc *arch_find_switch_table(struct objtool_file *file,
167 struct instruction *insn,
168 unsigned long *table_size)
169 {
170 struct reloc *annotate_reloc;
171 struct reloc *rodata_reloc;
172 struct section *table_sec;
173 unsigned long table_offset;
174
175 annotate_reloc = find_reloc_by_table_annotate(file, insn, table_size);
176 if (!annotate_reloc) {
177 annotate_reloc = find_reloc_of_rodata_c_jump_table(
178 insn->sec, insn->offset, table_size);
179 if (!annotate_reloc)
180 return NULL;
181 }
182
183 table_sec = annotate_reloc->sym->sec;
184 table_offset = annotate_reloc->sym->offset + reloc_addend(annotate_reloc);
185
186 /*
187 * Each table entry has a rela associated with it. The rela
188 * should reference text in the same function as the original
189 * instruction.
190 */
191 rodata_reloc = find_reloc_by_dest(file->elf, table_sec, table_offset);
192 if (!rodata_reloc)
193 return NULL;
194
195 return rodata_reloc;
196 }
197