1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com> |
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 | |
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> |
19 | |
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 */ |
26 | unsigned char key; /* jump_label key */ |
27 | }; |
28 | |
29 | static const 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, |
46 | .key = JUMP_KEY_OFFSET, |
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 | |
57 | void __weak arch_handle_alternative(unsigned short feature, struct special_alt *alt) |
58 | { |
59 | } |
60 | |
61 | static void reloc_to_sec_off(struct reloc *reloc, struct section **sec, |
62 | unsigned long *off) |
63 | { |
64 | *sec = reloc->sym->sec; |
65 | *off = reloc->sym->offset + reloc_addend(reloc); |
66 | } |
67 | |
68 | static int get_alt_entry(struct elf *elf, const struct special_entry *entry, |
69 | struct section *sec, int idx, |
70 | struct special_alt *alt) |
71 | { |
72 | struct reloc *orig_reloc, *new_reloc; |
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) { |
81 | alt->orig_len = *(unsigned char *)(sec->data->d_buf + offset + |
82 | entry->orig_len); |
83 | alt->new_len = *(unsigned char *)(sec->data->d_buf + offset + |
84 | entry->new_len); |
85 | } |
86 | |
87 | if (entry->feature) { |
88 | unsigned short feature; |
89 | |
90 | feature = bswap_if_needed(elf, |
91 | *(unsigned short *)(sec->data->d_buf + |
92 | offset + |
93 | entry->feature)); |
94 | arch_handle_alternative(feature, alt); |
95 | } |
96 | |
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); |
100 | return -1; |
101 | } |
102 | |
103 | reloc_to_sec_off(reloc: orig_reloc, sec: &alt->orig_sec, off: &alt->orig_off); |
104 | |
105 | if (!entry->group || alt->new_len) { |
106 | new_reloc = find_reloc_by_dest(elf, sec, offset + entry->new); |
107 | if (!new_reloc) { |
108 | WARN_FUNC("can't find new reloc" , |
109 | sec, offset + entry->new); |
110 | return -1; |
111 | } |
112 | |
113 | reloc_to_sec_off(reloc: new_reloc, sec: &alt->new_sec, off: &alt->new_off); |
114 | |
115 | /* _ASM_EXTABLE_EX hack */ |
116 | if (alt->new_off >= 0x7ffffff0) |
117 | alt->new_off -= 0x7ffffff0; |
118 | } |
119 | |
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 = reloc_addend(key_reloc); |
130 | } |
131 | |
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 | const 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 | |
155 | if (sec->sh.sh_size % entry->size != 0) { |
156 | WARN("%s size not a multiple of %d" , |
157 | sec->name, entry->size); |
158 | return -1; |
159 | } |
160 | |
161 | nr_entries = sec->sh.sh_size / entry->size; |
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); |
172 | if (ret > 0) |
173 | continue; |
174 | if (ret < 0) |
175 | return ret; |
176 | |
177 | list_add_tail(&alt->list, alts); |
178 | } |
179 | } |
180 | |
181 | return 0; |
182 | } |
183 | |