xref: /openbmc/linux/tools/objtool/orc_gen.c (revision abe9af53)
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.h>
10 #include <asm/orc_types.h>
11 
12 #include "check.h"
13 #include "warn.h"
14 
15 int create_orc(struct objtool_file *file)
16 {
17 	struct instruction *insn;
18 
19 	for_each_insn(file, insn) {
20 		struct orc_entry *orc = &insn->orc;
21 		struct cfi_reg *cfa = &insn->cfi.cfa;
22 		struct cfi_reg *bp = &insn->cfi.regs[CFI_BP];
23 
24 		if (!insn->sec->text)
25 			continue;
26 
27 		orc->end = insn->cfi.end;
28 
29 		if (cfa->base == CFI_UNDEFINED) {
30 			orc->sp_reg = ORC_REG_UNDEFINED;
31 			continue;
32 		}
33 
34 		switch (cfa->base) {
35 		case CFI_SP:
36 			orc->sp_reg = ORC_REG_SP;
37 			break;
38 		case CFI_SP_INDIRECT:
39 			orc->sp_reg = ORC_REG_SP_INDIRECT;
40 			break;
41 		case CFI_BP:
42 			orc->sp_reg = ORC_REG_BP;
43 			break;
44 		case CFI_BP_INDIRECT:
45 			orc->sp_reg = ORC_REG_BP_INDIRECT;
46 			break;
47 		case CFI_R10:
48 			orc->sp_reg = ORC_REG_R10;
49 			break;
50 		case CFI_R13:
51 			orc->sp_reg = ORC_REG_R13;
52 			break;
53 		case CFI_DI:
54 			orc->sp_reg = ORC_REG_DI;
55 			break;
56 		case CFI_DX:
57 			orc->sp_reg = ORC_REG_DX;
58 			break;
59 		default:
60 			WARN_FUNC("unknown CFA base reg %d",
61 				  insn->sec, insn->offset, cfa->base);
62 			return -1;
63 		}
64 
65 		switch(bp->base) {
66 		case CFI_UNDEFINED:
67 			orc->bp_reg = ORC_REG_UNDEFINED;
68 			break;
69 		case CFI_CFA:
70 			orc->bp_reg = ORC_REG_PREV_SP;
71 			break;
72 		case CFI_BP:
73 			orc->bp_reg = ORC_REG_BP;
74 			break;
75 		default:
76 			WARN_FUNC("unknown BP base reg %d",
77 				  insn->sec, insn->offset, bp->base);
78 			return -1;
79 		}
80 
81 		orc->sp_offset = cfa->offset;
82 		orc->bp_offset = bp->offset;
83 		orc->type = insn->cfi.type;
84 	}
85 
86 	return 0;
87 }
88 
89 static int create_orc_entry(struct elf *elf, struct section *u_sec, struct section *ip_relocsec,
90 				unsigned int idx, struct section *insn_sec,
91 				unsigned long insn_off, struct orc_entry *o)
92 {
93 	struct orc_entry *orc;
94 	struct reloc *reloc;
95 
96 	/* populate ORC data */
97 	orc = (struct orc_entry *)u_sec->data->d_buf + idx;
98 	memcpy(orc, o, sizeof(*orc));
99 
100 	/* populate reloc for ip */
101 	reloc = malloc(sizeof(*reloc));
102 	if (!reloc) {
103 		perror("malloc");
104 		return -1;
105 	}
106 	memset(reloc, 0, sizeof(*reloc));
107 
108 	if (insn_sec->sym) {
109 		reloc->sym = insn_sec->sym;
110 		reloc->addend = insn_off;
111 	} else {
112 		/*
113 		 * The Clang assembler doesn't produce section symbols, so we
114 		 * have to reference the function symbol instead:
115 		 */
116 		reloc->sym = find_symbol_containing(insn_sec, insn_off);
117 		if (!reloc->sym) {
118 			/*
119 			 * Hack alert.  This happens when we need to reference
120 			 * the NOP pad insn immediately after the function.
121 			 */
122 			reloc->sym = find_symbol_containing(insn_sec,
123 							   insn_off - 1);
124 		}
125 		if (!reloc->sym) {
126 			WARN("missing symbol for insn at offset 0x%lx\n",
127 			     insn_off);
128 			return -1;
129 		}
130 
131 		reloc->addend = insn_off - reloc->sym->offset;
132 	}
133 
134 	reloc->type = R_X86_64_PC32;
135 	reloc->offset = idx * sizeof(int);
136 	reloc->sec = ip_relocsec;
137 
138 	elf_add_reloc(elf, reloc);
139 
140 	return 0;
141 }
142 
143 int create_orc_sections(struct objtool_file *file)
144 {
145 	struct instruction *insn, *prev_insn;
146 	struct section *sec, *u_sec, *ip_relocsec;
147 	unsigned int idx;
148 
149 	struct orc_entry empty = {
150 		.sp_reg = ORC_REG_UNDEFINED,
151 		.bp_reg  = ORC_REG_UNDEFINED,
152 		.type    = UNWIND_HINT_TYPE_CALL,
153 	};
154 
155 	sec = find_section_by_name(file->elf, ".orc_unwind");
156 	if (sec) {
157 		WARN("file already has .orc_unwind section, skipping");
158 		return -1;
159 	}
160 
161 	/* count the number of needed orcs */
162 	idx = 0;
163 	for_each_sec(file, sec) {
164 		if (!sec->text)
165 			continue;
166 
167 		prev_insn = NULL;
168 		sec_for_each_insn(file, sec, insn) {
169 			if (!prev_insn ||
170 			    memcmp(&insn->orc, &prev_insn->orc,
171 				   sizeof(struct orc_entry))) {
172 				idx++;
173 			}
174 			prev_insn = insn;
175 		}
176 
177 		/* section terminator */
178 		if (prev_insn)
179 			idx++;
180 	}
181 	if (!idx)
182 		return -1;
183 
184 
185 	/* create .orc_unwind_ip and .rela.orc_unwind_ip sections */
186 	sec = elf_create_section(file->elf, ".orc_unwind_ip", 0, sizeof(int), idx);
187 	if (!sec)
188 		return -1;
189 
190 	ip_relocsec = elf_create_reloc_section(file->elf, sec, SHT_RELA);
191 	if (!ip_relocsec)
192 		return -1;
193 
194 	/* create .orc_unwind section */
195 	u_sec = elf_create_section(file->elf, ".orc_unwind", 0,
196 				   sizeof(struct orc_entry), idx);
197 
198 	/* populate sections */
199 	idx = 0;
200 	for_each_sec(file, sec) {
201 		if (!sec->text)
202 			continue;
203 
204 		prev_insn = NULL;
205 		sec_for_each_insn(file, sec, insn) {
206 			if (!prev_insn || memcmp(&insn->orc, &prev_insn->orc,
207 						 sizeof(struct orc_entry))) {
208 
209 				if (create_orc_entry(file->elf, u_sec, ip_relocsec, idx,
210 						     insn->sec, insn->offset,
211 						     &insn->orc))
212 					return -1;
213 
214 				idx++;
215 			}
216 			prev_insn = insn;
217 		}
218 
219 		/* section terminator */
220 		if (prev_insn) {
221 			if (create_orc_entry(file->elf, u_sec, ip_relocsec, idx,
222 					     prev_insn->sec,
223 					     prev_insn->offset + prev_insn->len,
224 					     &empty))
225 				return -1;
226 
227 			idx++;
228 		}
229 	}
230 
231 	if (elf_rebuild_reloc_section(file->elf, ip_relocsec))
232 		return -1;
233 
234 	return 0;
235 }
236