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