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