Commit | Line | Data |
---|---|---|
1ccea77e | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
442f04c3 JP |
2 | /* |
3 | * Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com> | |
442f04c3 JP |
4 | */ |
5 | ||
6 | /* | |
7 | * This file reads all the special sections which have alternate instructions | |
8 | * which can be patched in or redirected to at runtime. | |
9 | */ | |
10 | ||
11 | #include <stdlib.h> | |
12 | #include <string.h> | |
13 | ||
7786032e VG |
14 | #include <arch/special.h> |
15 | #include <objtool/builtin.h> | |
16 | #include <objtool/special.h> | |
17 | #include <objtool/warn.h> | |
18 | #include <objtool/endianness.h> | |
442f04c3 | 19 | |
442f04c3 JP |
20 | struct special_entry { |
21 | const char *sec; | |
22 | bool group, jump_or_nop; | |
23 | unsigned char size, orig, new; | |
24 | unsigned char orig_len, new_len; /* group only */ | |
25 | unsigned char feature; /* ALTERNATIVE macro CPU feature */ | |
cbf82a3d | 26 | unsigned char key; /* jump_label key */ |
442f04c3 JP |
27 | }; |
28 | ||
29 | struct special_entry entries[] = { | |
30 | { | |
31 | .sec = ".altinstructions", | |
32 | .group = true, | |
33 | .size = ALT_ENTRY_SIZE, | |
34 | .orig = ALT_ORIG_OFFSET, | |
35 | .orig_len = ALT_ORIG_LEN_OFFSET, | |
36 | .new = ALT_NEW_OFFSET, | |
37 | .new_len = ALT_NEW_LEN_OFFSET, | |
38 | .feature = ALT_FEATURE_OFFSET, | |
39 | }, | |
40 | { | |
41 | .sec = "__jump_table", | |
42 | .jump_or_nop = true, | |
43 | .size = JUMP_ENTRY_SIZE, | |
44 | .orig = JUMP_ORIG_OFFSET, | |
45 | .new = JUMP_NEW_OFFSET, | |
cbf82a3d | 46 | .key = JUMP_KEY_OFFSET, |
442f04c3 JP |
47 | }, |
48 | { | |
49 | .sec = "__ex_table", | |
50 | .size = EX_ENTRY_SIZE, | |
51 | .orig = EX_ORIG_OFFSET, | |
52 | .new = EX_NEW_OFFSET, | |
53 | }, | |
54 | {}, | |
55 | }; | |
56 | ||
eda3dc90 JT |
57 | void __weak arch_handle_alternative(unsigned short feature, struct special_alt *alt) |
58 | { | |
59 | } | |
60 | ||
4d8b3596 JP |
61 | static void reloc_to_sec_off(struct reloc *reloc, struct section **sec, |
62 | unsigned long *off) | |
24ff6525 | 63 | { |
4d8b3596 JP |
64 | *sec = reloc->sym->sec; |
65 | *off = reloc->sym->offset + reloc->addend; | |
24ff6525 PZ |
66 | } |
67 | ||
442f04c3 JP |
68 | static int get_alt_entry(struct elf *elf, struct special_entry *entry, |
69 | struct section *sec, int idx, | |
70 | struct special_alt *alt) | |
71 | { | |
f1974222 | 72 | struct reloc *orig_reloc, *new_reloc; |
442f04c3 JP |
73 | unsigned long offset; |
74 | ||
75 | offset = idx * entry->size; | |
76 | ||
77 | alt->group = entry->group; | |
78 | alt->jump_or_nop = entry->jump_or_nop; | |
79 | ||
80 | if (alt->group) { | |
baa41469 | 81 | alt->orig_len = *(unsigned char *)(sec->data->d_buf + offset + |
442f04c3 | 82 | entry->orig_len); |
baa41469 | 83 | alt->new_len = *(unsigned char *)(sec->data->d_buf + offset + |
442f04c3 JP |
84 | entry->new_len); |
85 | } | |
86 | ||
87 | if (entry->feature) { | |
88 | unsigned short feature; | |
89 | ||
0646c28b CL |
90 | feature = bswap_if_needed(elf, |
91 | *(unsigned short *)(sec->data->d_buf + | |
8bfe2732 VG |
92 | offset + |
93 | entry->feature)); | |
eda3dc90 | 94 | arch_handle_alternative(feature, alt); |
442f04c3 JP |
95 | } |
96 | ||
f1974222 MH |
97 | orig_reloc = find_reloc_by_dest(elf, sec, offset + entry->orig); |
98 | if (!orig_reloc) { | |
99 | WARN_FUNC("can't find orig reloc", sec, offset + entry->orig); | |
442f04c3 JP |
100 | return -1; |
101 | } | |
4d8b3596 JP |
102 | |
103 | reloc_to_sec_off(orig_reloc, &alt->orig_sec, &alt->orig_off); | |
442f04c3 | 104 | |
442f04c3 | 105 | if (!entry->group || alt->new_len) { |
f1974222 MH |
106 | new_reloc = find_reloc_by_dest(elf, sec, offset + entry->new); |
107 | if (!new_reloc) { | |
108 | WARN_FUNC("can't find new reloc", | |
442f04c3 JP |
109 | sec, offset + entry->new); |
110 | return -1; | |
111 | } | |
112 | ||
4d8b3596 | 113 | reloc_to_sec_off(new_reloc, &alt->new_sec, &alt->new_off); |
442f04c3 JP |
114 | |
115 | /* _ASM_EXTABLE_EX hack */ | |
116 | if (alt->new_off >= 0x7ffffff0) | |
117 | alt->new_off -= 0x7ffffff0; | |
118 | } | |
119 | ||
cbf82a3d PZ |
120 | if (entry->key) { |
121 | struct reloc *key_reloc; | |
122 | ||
123 | key_reloc = find_reloc_by_dest(elf, sec, offset + entry->key); | |
124 | if (!key_reloc) { | |
125 | WARN_FUNC("can't find key reloc", | |
126 | sec, offset + entry->key); | |
127 | return -1; | |
128 | } | |
129 | alt->key_addend = key_reloc->addend; | |
130 | } | |
131 | ||
442f04c3 JP |
132 | return 0; |
133 | } | |
134 | ||
135 | /* | |
136 | * Read all the special sections and create a list of special_alt structs which | |
137 | * describe all the alternate instructions which can be patched in or | |
138 | * redirected to at runtime. | |
139 | */ | |
140 | int special_get_alts(struct elf *elf, struct list_head *alts) | |
141 | { | |
142 | struct special_entry *entry; | |
143 | struct section *sec; | |
144 | unsigned int nr_entries; | |
145 | struct special_alt *alt; | |
146 | int idx, ret; | |
147 | ||
148 | INIT_LIST_HEAD(alts); | |
149 | ||
150 | for (entry = entries; entry->sec; entry++) { | |
151 | sec = find_section_by_name(elf, entry->sec); | |
152 | if (!sec) | |
153 | continue; | |
154 | ||
fe255fe6 | 155 | if (sec->sh.sh_size % entry->size != 0) { |
442f04c3 JP |
156 | WARN("%s size not a multiple of %d", |
157 | sec->name, entry->size); | |
158 | return -1; | |
159 | } | |
160 | ||
fe255fe6 | 161 | nr_entries = sec->sh.sh_size / entry->size; |
442f04c3 JP |
162 | |
163 | for (idx = 0; idx < nr_entries; idx++) { | |
164 | alt = malloc(sizeof(*alt)); | |
165 | if (!alt) { | |
166 | WARN("malloc failed"); | |
167 | return -1; | |
168 | } | |
169 | memset(alt, 0, sizeof(*alt)); | |
170 | ||
171 | ret = get_alt_entry(elf, entry, sec, idx, alt); | |
50e7b4a1 PZ |
172 | if (ret > 0) |
173 | continue; | |
174 | if (ret < 0) | |
442f04c3 JP |
175 | return ret; |
176 | ||
177 | list_add_tail(&alt->list, alts); | |
178 | } | |
179 | } | |
180 | ||
181 | return 0; | |
182 | } |