]>
Commit | Line | Data |
---|---|---|
442f04c3 JP |
1 | /* |
2 | * Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com> | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License | |
6 | * as published by the Free Software Foundation; either version 2 | |
7 | * of the License, or (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | |
16 | */ | |
17 | ||
18 | /* | |
19 | * This file reads all the special sections which have alternate instructions | |
20 | * which can be patched in or redirected to at runtime. | |
21 | */ | |
22 | ||
23 | #include <stdlib.h> | |
24 | #include <string.h> | |
25 | ||
26 | #include "special.h" | |
27 | #include "warn.h" | |
28 | ||
29 | #define EX_ENTRY_SIZE 12 | |
30 | #define EX_ORIG_OFFSET 0 | |
31 | #define EX_NEW_OFFSET 4 | |
32 | ||
33 | #define JUMP_ENTRY_SIZE 24 | |
34 | #define JUMP_ORIG_OFFSET 0 | |
35 | #define JUMP_NEW_OFFSET 8 | |
36 | ||
37 | #define ALT_ENTRY_SIZE 13 | |
38 | #define ALT_ORIG_OFFSET 0 | |
39 | #define ALT_NEW_OFFSET 4 | |
40 | #define ALT_FEATURE_OFFSET 8 | |
41 | #define ALT_ORIG_LEN_OFFSET 10 | |
42 | #define ALT_NEW_LEN_OFFSET 11 | |
43 | ||
44 | #define X86_FEATURE_POPCNT (4*32+23) | |
45 | ||
46 | struct special_entry { | |
47 | const char *sec; | |
48 | bool group, jump_or_nop; | |
49 | unsigned char size, orig, new; | |
50 | unsigned char orig_len, new_len; /* group only */ | |
51 | unsigned char feature; /* ALTERNATIVE macro CPU feature */ | |
52 | }; | |
53 | ||
54 | struct special_entry entries[] = { | |
55 | { | |
56 | .sec = ".altinstructions", | |
57 | .group = true, | |
58 | .size = ALT_ENTRY_SIZE, | |
59 | .orig = ALT_ORIG_OFFSET, | |
60 | .orig_len = ALT_ORIG_LEN_OFFSET, | |
61 | .new = ALT_NEW_OFFSET, | |
62 | .new_len = ALT_NEW_LEN_OFFSET, | |
63 | .feature = ALT_FEATURE_OFFSET, | |
64 | }, | |
65 | { | |
66 | .sec = "__jump_table", | |
67 | .jump_or_nop = true, | |
68 | .size = JUMP_ENTRY_SIZE, | |
69 | .orig = JUMP_ORIG_OFFSET, | |
70 | .new = JUMP_NEW_OFFSET, | |
71 | }, | |
72 | { | |
73 | .sec = "__ex_table", | |
74 | .size = EX_ENTRY_SIZE, | |
75 | .orig = EX_ORIG_OFFSET, | |
76 | .new = EX_NEW_OFFSET, | |
77 | }, | |
78 | {}, | |
79 | }; | |
80 | ||
81 | static int get_alt_entry(struct elf *elf, struct special_entry *entry, | |
82 | struct section *sec, int idx, | |
83 | struct special_alt *alt) | |
84 | { | |
85 | struct rela *orig_rela, *new_rela; | |
86 | unsigned long offset; | |
87 | ||
88 | offset = idx * entry->size; | |
89 | ||
90 | alt->group = entry->group; | |
91 | alt->jump_or_nop = entry->jump_or_nop; | |
92 | ||
93 | if (alt->group) { | |
baa41469 | 94 | alt->orig_len = *(unsigned char *)(sec->data->d_buf + offset + |
442f04c3 | 95 | entry->orig_len); |
baa41469 | 96 | alt->new_len = *(unsigned char *)(sec->data->d_buf + offset + |
442f04c3 JP |
97 | entry->new_len); |
98 | } | |
99 | ||
100 | if (entry->feature) { | |
101 | unsigned short feature; | |
102 | ||
baa41469 | 103 | feature = *(unsigned short *)(sec->data->d_buf + offset + |
442f04c3 JP |
104 | entry->feature); |
105 | ||
106 | /* | |
107 | * It has been requested that we don't validate the !POPCNT | |
108 | * feature path which is a "very very small percentage of | |
109 | * machines". | |
110 | */ | |
111 | if (feature == X86_FEATURE_POPCNT) | |
112 | alt->skip_orig = true; | |
113 | } | |
114 | ||
115 | orig_rela = find_rela_by_dest(sec, offset + entry->orig); | |
116 | if (!orig_rela) { | |
117 | WARN_FUNC("can't find orig rela", sec, offset + entry->orig); | |
118 | return -1; | |
119 | } | |
120 | if (orig_rela->sym->type != STT_SECTION) { | |
121 | WARN_FUNC("don't know how to handle non-section rela symbol %s", | |
122 | sec, offset + entry->orig, orig_rela->sym->name); | |
123 | return -1; | |
124 | } | |
125 | ||
126 | alt->orig_sec = orig_rela->sym->sec; | |
127 | alt->orig_off = orig_rela->addend; | |
128 | ||
129 | if (!entry->group || alt->new_len) { | |
130 | new_rela = find_rela_by_dest(sec, offset + entry->new); | |
131 | if (!new_rela) { | |
132 | WARN_FUNC("can't find new rela", | |
133 | sec, offset + entry->new); | |
134 | return -1; | |
135 | } | |
136 | ||
137 | alt->new_sec = new_rela->sym->sec; | |
138 | alt->new_off = (unsigned int)new_rela->addend; | |
139 | ||
140 | /* _ASM_EXTABLE_EX hack */ | |
141 | if (alt->new_off >= 0x7ffffff0) | |
142 | alt->new_off -= 0x7ffffff0; | |
143 | } | |
144 | ||
145 | return 0; | |
146 | } | |
147 | ||
148 | /* | |
149 | * Read all the special sections and create a list of special_alt structs which | |
150 | * describe all the alternate instructions which can be patched in or | |
151 | * redirected to at runtime. | |
152 | */ | |
153 | int special_get_alts(struct elf *elf, struct list_head *alts) | |
154 | { | |
155 | struct special_entry *entry; | |
156 | struct section *sec; | |
157 | unsigned int nr_entries; | |
158 | struct special_alt *alt; | |
159 | int idx, ret; | |
160 | ||
161 | INIT_LIST_HEAD(alts); | |
162 | ||
163 | for (entry = entries; entry->sec; entry++) { | |
164 | sec = find_section_by_name(elf, entry->sec); | |
165 | if (!sec) | |
166 | continue; | |
167 | ||
168 | if (sec->len % entry->size != 0) { | |
169 | WARN("%s size not a multiple of %d", | |
170 | sec->name, entry->size); | |
171 | return -1; | |
172 | } | |
173 | ||
174 | nr_entries = sec->len / entry->size; | |
175 | ||
176 | for (idx = 0; idx < nr_entries; idx++) { | |
177 | alt = malloc(sizeof(*alt)); | |
178 | if (!alt) { | |
179 | WARN("malloc failed"); | |
180 | return -1; | |
181 | } | |
182 | memset(alt, 0, sizeof(*alt)); | |
183 | ||
184 | ret = get_alt_entry(elf, entry, sec, idx, alt); | |
185 | if (ret) | |
186 | return ret; | |
187 | ||
188 | list_add_tail(&alt->list, alts); | |
189 | } | |
190 | } | |
191 | ||
192 | return 0; | |
193 | } |