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