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