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