.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
---|
1 | 2 | /* |
---|
2 | 3 | * Copyright (C) 2017 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 | 4 | */ |
---|
17 | 5 | |
---|
18 | 6 | #include <stdlib.h> |
---|
19 | 7 | #include <string.h> |
---|
20 | 8 | |
---|
21 | | -#include "orc.h" |
---|
| 9 | +#include <linux/objtool.h> |
---|
| 10 | +#include <asm/orc_types.h> |
---|
| 11 | + |
---|
22 | 12 | #include "check.h" |
---|
23 | 13 | #include "warn.h" |
---|
24 | 14 | |
---|
25 | | -int create_orc(struct objtool_file *file) |
---|
| 15 | +static int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi, |
---|
| 16 | + struct instruction *insn) |
---|
26 | 17 | { |
---|
27 | | - struct instruction *insn; |
---|
| 18 | + struct cfi_reg *bp = &cfi->regs[CFI_BP]; |
---|
28 | 19 | |
---|
29 | | - for_each_insn(file, insn) { |
---|
30 | | - struct orc_entry *orc = &insn->orc; |
---|
31 | | - struct cfi_reg *cfa = &insn->state.cfa; |
---|
32 | | - struct cfi_reg *bp = &insn->state.regs[CFI_BP]; |
---|
| 20 | + memset(orc, 0, sizeof(*orc)); |
---|
33 | 21 | |
---|
34 | | - orc->end = insn->state.end; |
---|
35 | | - |
---|
36 | | - if (cfa->base == CFI_UNDEFINED) { |
---|
37 | | - orc->sp_reg = ORC_REG_UNDEFINED; |
---|
38 | | - continue; |
---|
39 | | - } |
---|
40 | | - |
---|
41 | | - switch (cfa->base) { |
---|
42 | | - case CFI_SP: |
---|
43 | | - orc->sp_reg = ORC_REG_SP; |
---|
44 | | - break; |
---|
45 | | - case CFI_SP_INDIRECT: |
---|
46 | | - orc->sp_reg = ORC_REG_SP_INDIRECT; |
---|
47 | | - break; |
---|
48 | | - case CFI_BP: |
---|
49 | | - orc->sp_reg = ORC_REG_BP; |
---|
50 | | - break; |
---|
51 | | - case CFI_BP_INDIRECT: |
---|
52 | | - orc->sp_reg = ORC_REG_BP_INDIRECT; |
---|
53 | | - break; |
---|
54 | | - case CFI_R10: |
---|
55 | | - orc->sp_reg = ORC_REG_R10; |
---|
56 | | - break; |
---|
57 | | - case CFI_R13: |
---|
58 | | - orc->sp_reg = ORC_REG_R13; |
---|
59 | | - break; |
---|
60 | | - case CFI_DI: |
---|
61 | | - orc->sp_reg = ORC_REG_DI; |
---|
62 | | - break; |
---|
63 | | - case CFI_DX: |
---|
64 | | - orc->sp_reg = ORC_REG_DX; |
---|
65 | | - break; |
---|
66 | | - default: |
---|
67 | | - WARN_FUNC("unknown CFA base reg %d", |
---|
68 | | - insn->sec, insn->offset, cfa->base); |
---|
69 | | - return -1; |
---|
70 | | - } |
---|
71 | | - |
---|
72 | | - switch(bp->base) { |
---|
73 | | - case CFI_UNDEFINED: |
---|
74 | | - orc->bp_reg = ORC_REG_UNDEFINED; |
---|
75 | | - break; |
---|
76 | | - case CFI_CFA: |
---|
77 | | - orc->bp_reg = ORC_REG_PREV_SP; |
---|
78 | | - break; |
---|
79 | | - case CFI_BP: |
---|
80 | | - orc->bp_reg = ORC_REG_BP; |
---|
81 | | - break; |
---|
82 | | - default: |
---|
83 | | - WARN_FUNC("unknown BP base reg %d", |
---|
84 | | - insn->sec, insn->offset, bp->base); |
---|
85 | | - return -1; |
---|
86 | | - } |
---|
87 | | - |
---|
88 | | - orc->sp_offset = cfa->offset; |
---|
89 | | - orc->bp_offset = bp->offset; |
---|
90 | | - orc->type = insn->state.type; |
---|
| 22 | + if (!cfi) { |
---|
| 23 | + orc->end = 0; |
---|
| 24 | + orc->sp_reg = ORC_REG_UNDEFINED; |
---|
| 25 | + return 0; |
---|
91 | 26 | } |
---|
92 | 27 | |
---|
93 | | - return 0; |
---|
94 | | -} |
---|
| 28 | + orc->end = cfi->end; |
---|
95 | 29 | |
---|
96 | | -static int create_orc_entry(struct section *u_sec, struct section *ip_relasec, |
---|
97 | | - unsigned int idx, struct section *insn_sec, |
---|
98 | | - unsigned long insn_off, struct orc_entry *o) |
---|
99 | | -{ |
---|
100 | | - struct orc_entry *orc; |
---|
101 | | - struct rela *rela; |
---|
| 30 | + if (cfi->cfa.base == CFI_UNDEFINED) { |
---|
| 31 | + orc->sp_reg = ORC_REG_UNDEFINED; |
---|
| 32 | + return 0; |
---|
| 33 | + } |
---|
102 | 34 | |
---|
103 | | - /* populate ORC data */ |
---|
104 | | - orc = (struct orc_entry *)u_sec->data->d_buf + idx; |
---|
105 | | - memcpy(orc, o, sizeof(*orc)); |
---|
106 | | - |
---|
107 | | - /* populate rela for ip */ |
---|
108 | | - rela = malloc(sizeof(*rela)); |
---|
109 | | - if (!rela) { |
---|
110 | | - perror("malloc"); |
---|
| 35 | + switch (cfi->cfa.base) { |
---|
| 36 | + case CFI_SP: |
---|
| 37 | + orc->sp_reg = ORC_REG_SP; |
---|
| 38 | + break; |
---|
| 39 | + case CFI_SP_INDIRECT: |
---|
| 40 | + orc->sp_reg = ORC_REG_SP_INDIRECT; |
---|
| 41 | + break; |
---|
| 42 | + case CFI_BP: |
---|
| 43 | + orc->sp_reg = ORC_REG_BP; |
---|
| 44 | + break; |
---|
| 45 | + case CFI_BP_INDIRECT: |
---|
| 46 | + orc->sp_reg = ORC_REG_BP_INDIRECT; |
---|
| 47 | + break; |
---|
| 48 | + case CFI_R10: |
---|
| 49 | + orc->sp_reg = ORC_REG_R10; |
---|
| 50 | + break; |
---|
| 51 | + case CFI_R13: |
---|
| 52 | + orc->sp_reg = ORC_REG_R13; |
---|
| 53 | + break; |
---|
| 54 | + case CFI_DI: |
---|
| 55 | + orc->sp_reg = ORC_REG_DI; |
---|
| 56 | + break; |
---|
| 57 | + case CFI_DX: |
---|
| 58 | + orc->sp_reg = ORC_REG_DX; |
---|
| 59 | + break; |
---|
| 60 | + default: |
---|
| 61 | + WARN_FUNC("unknown CFA base reg %d", |
---|
| 62 | + insn->sec, insn->offset, cfi->cfa.base); |
---|
111 | 63 | return -1; |
---|
112 | 64 | } |
---|
113 | | - memset(rela, 0, sizeof(*rela)); |
---|
114 | 65 | |
---|
115 | | - if (insn_sec->sym) { |
---|
116 | | - rela->sym = insn_sec->sym; |
---|
117 | | - rela->addend = insn_off; |
---|
118 | | - } else { |
---|
119 | | - /* |
---|
120 | | - * The Clang assembler doesn't produce section symbols, so we |
---|
121 | | - * have to reference the function symbol instead: |
---|
122 | | - */ |
---|
123 | | - rela->sym = find_symbol_containing(insn_sec, insn_off); |
---|
124 | | - if (!rela->sym) { |
---|
125 | | - /* |
---|
126 | | - * Hack alert. This happens when we need to reference |
---|
127 | | - * the NOP pad insn immediately after the function. |
---|
128 | | - */ |
---|
129 | | - rela->sym = find_symbol_containing(insn_sec, |
---|
130 | | - insn_off - 1); |
---|
131 | | - } |
---|
132 | | - if (!rela->sym) { |
---|
133 | | - WARN("missing symbol for insn at offset 0x%lx\n", |
---|
134 | | - insn_off); |
---|
135 | | - return -1; |
---|
136 | | - } |
---|
137 | | - |
---|
138 | | - rela->addend = insn_off - rela->sym->offset; |
---|
| 66 | + switch (bp->base) { |
---|
| 67 | + case CFI_UNDEFINED: |
---|
| 68 | + orc->bp_reg = ORC_REG_UNDEFINED; |
---|
| 69 | + break; |
---|
| 70 | + case CFI_CFA: |
---|
| 71 | + orc->bp_reg = ORC_REG_PREV_SP; |
---|
| 72 | + break; |
---|
| 73 | + case CFI_BP: |
---|
| 74 | + orc->bp_reg = ORC_REG_BP; |
---|
| 75 | + break; |
---|
| 76 | + default: |
---|
| 77 | + WARN_FUNC("unknown BP base reg %d", |
---|
| 78 | + insn->sec, insn->offset, bp->base); |
---|
| 79 | + return -1; |
---|
139 | 80 | } |
---|
140 | 81 | |
---|
141 | | - rela->type = R_X86_64_PC32; |
---|
142 | | - rela->offset = idx * sizeof(int); |
---|
143 | | - |
---|
144 | | - list_add_tail(&rela->list, &ip_relasec->rela_list); |
---|
145 | | - hash_add(ip_relasec->rela_hash, &rela->hash, rela->offset); |
---|
| 82 | + orc->sp_offset = cfi->cfa.offset; |
---|
| 83 | + orc->bp_offset = bp->offset; |
---|
| 84 | + orc->type = cfi->type; |
---|
146 | 85 | |
---|
147 | 86 | return 0; |
---|
148 | 87 | } |
---|
149 | 88 | |
---|
150 | | -int create_orc_sections(struct objtool_file *file) |
---|
| 89 | +static int write_orc_entry(struct elf *elf, struct section *orc_sec, |
---|
| 90 | + struct section *ip_sec, unsigned int idx, |
---|
| 91 | + struct section *insn_sec, unsigned long insn_off, |
---|
| 92 | + struct orc_entry *o) |
---|
151 | 93 | { |
---|
152 | | - struct instruction *insn, *prev_insn; |
---|
153 | | - struct section *sec, *u_sec, *ip_relasec; |
---|
154 | | - unsigned int idx; |
---|
| 94 | + struct orc_entry *orc; |
---|
155 | 95 | |
---|
156 | | - struct orc_entry empty = { |
---|
157 | | - .sp_reg = ORC_REG_UNDEFINED, |
---|
| 96 | + /* populate ORC data */ |
---|
| 97 | + orc = (struct orc_entry *)orc_sec->data->d_buf + idx; |
---|
| 98 | + memcpy(orc, o, sizeof(*orc)); |
---|
| 99 | + |
---|
| 100 | + /* populate reloc for ip */ |
---|
| 101 | + if (elf_add_reloc_to_insn(elf, ip_sec, idx * sizeof(int), R_X86_64_PC32, |
---|
| 102 | + insn_sec, insn_off)) |
---|
| 103 | + return -1; |
---|
| 104 | + |
---|
| 105 | + return 0; |
---|
| 106 | +} |
---|
| 107 | + |
---|
| 108 | +struct orc_list_entry { |
---|
| 109 | + struct list_head list; |
---|
| 110 | + struct orc_entry orc; |
---|
| 111 | + struct section *insn_sec; |
---|
| 112 | + unsigned long insn_off; |
---|
| 113 | +}; |
---|
| 114 | + |
---|
| 115 | +static int orc_list_add(struct list_head *orc_list, struct orc_entry *orc, |
---|
| 116 | + struct section *sec, unsigned long offset) |
---|
| 117 | +{ |
---|
| 118 | + struct orc_list_entry *entry = malloc(sizeof(*entry)); |
---|
| 119 | + |
---|
| 120 | + if (!entry) { |
---|
| 121 | + WARN("malloc failed"); |
---|
| 122 | + return -1; |
---|
| 123 | + } |
---|
| 124 | + |
---|
| 125 | + entry->orc = *orc; |
---|
| 126 | + entry->insn_sec = sec; |
---|
| 127 | + entry->insn_off = offset; |
---|
| 128 | + |
---|
| 129 | + list_add_tail(&entry->list, orc_list); |
---|
| 130 | + return 0; |
---|
| 131 | +} |
---|
| 132 | + |
---|
| 133 | +static unsigned long alt_group_len(struct alt_group *alt_group) |
---|
| 134 | +{ |
---|
| 135 | + return alt_group->last_insn->offset + |
---|
| 136 | + alt_group->last_insn->len - |
---|
| 137 | + alt_group->first_insn->offset; |
---|
| 138 | +} |
---|
| 139 | + |
---|
| 140 | +int orc_create(struct objtool_file *file) |
---|
| 141 | +{ |
---|
| 142 | + struct section *sec, *orc_sec; |
---|
| 143 | + unsigned int nr = 0, idx = 0; |
---|
| 144 | + struct orc_list_entry *entry; |
---|
| 145 | + struct list_head orc_list; |
---|
| 146 | + |
---|
| 147 | + struct orc_entry null = { |
---|
| 148 | + .sp_reg = ORC_REG_UNDEFINED, |
---|
158 | 149 | .bp_reg = ORC_REG_UNDEFINED, |
---|
159 | | - .type = ORC_TYPE_CALL, |
---|
| 150 | + .type = UNWIND_HINT_TYPE_CALL, |
---|
160 | 151 | }; |
---|
161 | 152 | |
---|
| 153 | + /* Build a deduplicated list of ORC entries: */ |
---|
| 154 | + INIT_LIST_HEAD(&orc_list); |
---|
| 155 | + for_each_sec(file, sec) { |
---|
| 156 | + struct orc_entry orc, prev_orc = {0}; |
---|
| 157 | + struct instruction *insn; |
---|
| 158 | + bool empty = true; |
---|
| 159 | + |
---|
| 160 | + if (!sec->text) |
---|
| 161 | + continue; |
---|
| 162 | + |
---|
| 163 | + sec_for_each_insn(file, sec, insn) { |
---|
| 164 | + struct alt_group *alt_group = insn->alt_group; |
---|
| 165 | + int i; |
---|
| 166 | + |
---|
| 167 | + if (!alt_group) { |
---|
| 168 | + if (init_orc_entry(&orc, insn->cfi, insn)) |
---|
| 169 | + return -1; |
---|
| 170 | + if (!memcmp(&prev_orc, &orc, sizeof(orc))) |
---|
| 171 | + continue; |
---|
| 172 | + if (orc_list_add(&orc_list, &orc, sec, |
---|
| 173 | + insn->offset)) |
---|
| 174 | + return -1; |
---|
| 175 | + nr++; |
---|
| 176 | + prev_orc = orc; |
---|
| 177 | + empty = false; |
---|
| 178 | + continue; |
---|
| 179 | + } |
---|
| 180 | + |
---|
| 181 | + /* |
---|
| 182 | + * Alternatives can have different stack layout |
---|
| 183 | + * possibilities (but they shouldn't conflict). |
---|
| 184 | + * Instead of traversing the instructions, use the |
---|
| 185 | + * alt_group's flattened byte-offset-addressed CFI |
---|
| 186 | + * array. |
---|
| 187 | + */ |
---|
| 188 | + for (i = 0; i < alt_group_len(alt_group); i++) { |
---|
| 189 | + struct cfi_state *cfi = alt_group->cfi[i]; |
---|
| 190 | + if (!cfi) |
---|
| 191 | + continue; |
---|
| 192 | + /* errors are reported on the original insn */ |
---|
| 193 | + if (init_orc_entry(&orc, cfi, insn)) |
---|
| 194 | + return -1; |
---|
| 195 | + if (!memcmp(&prev_orc, &orc, sizeof(orc))) |
---|
| 196 | + continue; |
---|
| 197 | + if (orc_list_add(&orc_list, &orc, insn->sec, |
---|
| 198 | + insn->offset + i)) |
---|
| 199 | + return -1; |
---|
| 200 | + nr++; |
---|
| 201 | + prev_orc = orc; |
---|
| 202 | + empty = false; |
---|
| 203 | + } |
---|
| 204 | + |
---|
| 205 | + /* Skip to the end of the alt_group */ |
---|
| 206 | + insn = alt_group->last_insn; |
---|
| 207 | + } |
---|
| 208 | + |
---|
| 209 | + /* Add a section terminator */ |
---|
| 210 | + if (!empty) { |
---|
| 211 | + orc_list_add(&orc_list, &null, sec, sec->len); |
---|
| 212 | + nr++; |
---|
| 213 | + } |
---|
| 214 | + } |
---|
| 215 | + if (!nr) |
---|
| 216 | + return 0; |
---|
| 217 | + |
---|
| 218 | + /* Create .orc_unwind, .orc_unwind_ip and .rela.orc_unwind_ip sections: */ |
---|
162 | 219 | sec = find_section_by_name(file->elf, ".orc_unwind"); |
---|
163 | 220 | if (sec) { |
---|
164 | 221 | WARN("file already has .orc_unwind section, skipping"); |
---|
165 | 222 | return -1; |
---|
166 | 223 | } |
---|
167 | | - |
---|
168 | | - /* count the number of needed orcs */ |
---|
169 | | - idx = 0; |
---|
170 | | - for_each_sec(file, sec) { |
---|
171 | | - if (!sec->text) |
---|
172 | | - continue; |
---|
173 | | - |
---|
174 | | - prev_insn = NULL; |
---|
175 | | - sec_for_each_insn(file, sec, insn) { |
---|
176 | | - if (!prev_insn || |
---|
177 | | - memcmp(&insn->orc, &prev_insn->orc, |
---|
178 | | - sizeof(struct orc_entry))) { |
---|
179 | | - idx++; |
---|
180 | | - } |
---|
181 | | - prev_insn = insn; |
---|
182 | | - } |
---|
183 | | - |
---|
184 | | - /* section terminator */ |
---|
185 | | - if (prev_insn) |
---|
186 | | - idx++; |
---|
187 | | - } |
---|
188 | | - if (!idx) |
---|
| 224 | + orc_sec = elf_create_section(file->elf, ".orc_unwind", 0, |
---|
| 225 | + sizeof(struct orc_entry), nr); |
---|
| 226 | + if (!orc_sec) |
---|
189 | 227 | return -1; |
---|
190 | 228 | |
---|
191 | | - |
---|
192 | | - /* create .orc_unwind_ip and .rela.orc_unwind_ip sections */ |
---|
193 | | - sec = elf_create_section(file->elf, ".orc_unwind_ip", sizeof(int), idx); |
---|
| 229 | + sec = elf_create_section(file->elf, ".orc_unwind_ip", 0, sizeof(int), nr); |
---|
194 | 230 | if (!sec) |
---|
195 | 231 | return -1; |
---|
196 | 232 | |
---|
197 | | - ip_relasec = elf_create_rela_section(file->elf, sec); |
---|
198 | | - if (!ip_relasec) |
---|
199 | | - return -1; |
---|
200 | | - |
---|
201 | | - /* create .orc_unwind section */ |
---|
202 | | - u_sec = elf_create_section(file->elf, ".orc_unwind", |
---|
203 | | - sizeof(struct orc_entry), idx); |
---|
204 | | - |
---|
205 | | - /* populate sections */ |
---|
206 | | - idx = 0; |
---|
207 | | - for_each_sec(file, sec) { |
---|
208 | | - if (!sec->text) |
---|
209 | | - continue; |
---|
210 | | - |
---|
211 | | - prev_insn = NULL; |
---|
212 | | - sec_for_each_insn(file, sec, insn) { |
---|
213 | | - if (!prev_insn || memcmp(&insn->orc, &prev_insn->orc, |
---|
214 | | - sizeof(struct orc_entry))) { |
---|
215 | | - |
---|
216 | | - if (create_orc_entry(u_sec, ip_relasec, idx, |
---|
217 | | - insn->sec, insn->offset, |
---|
218 | | - &insn->orc)) |
---|
219 | | - return -1; |
---|
220 | | - |
---|
221 | | - idx++; |
---|
222 | | - } |
---|
223 | | - prev_insn = insn; |
---|
224 | | - } |
---|
225 | | - |
---|
226 | | - /* section terminator */ |
---|
227 | | - if (prev_insn) { |
---|
228 | | - if (create_orc_entry(u_sec, ip_relasec, idx, |
---|
229 | | - prev_insn->sec, |
---|
230 | | - prev_insn->offset + prev_insn->len, |
---|
231 | | - &empty)) |
---|
232 | | - return -1; |
---|
233 | | - |
---|
234 | | - idx++; |
---|
235 | | - } |
---|
| 233 | + /* Write ORC entries to sections: */ |
---|
| 234 | + list_for_each_entry(entry, &orc_list, list) { |
---|
| 235 | + if (write_orc_entry(file->elf, orc_sec, sec, idx++, |
---|
| 236 | + entry->insn_sec, entry->insn_off, |
---|
| 237 | + &entry->orc)) |
---|
| 238 | + return -1; |
---|
236 | 239 | } |
---|
237 | | - |
---|
238 | | - if (elf_rebuild_rela_section(ip_relasec)) |
---|
239 | | - return -1; |
---|
240 | 240 | |
---|
241 | 241 | return 0; |
---|
242 | 242 | } |
---|