xref: /openbmc/linux/tools/objtool/special.c (revision 8730046c)
1 /*
2  * Copyright (C) 2015 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  */
17 
18 /*
19  * This file reads all the special sections which have alternate instructions
20  * which can be patched in or redirected to at runtime.
21  */
22 
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "special.h"
27 #include "warn.h"
28 
29 #define EX_ENTRY_SIZE		12
30 #define EX_ORIG_OFFSET		0
31 #define EX_NEW_OFFSET		4
32 
33 #define JUMP_ENTRY_SIZE		24
34 #define JUMP_ORIG_OFFSET	0
35 #define JUMP_NEW_OFFSET		8
36 
37 #define ALT_ENTRY_SIZE		13
38 #define ALT_ORIG_OFFSET		0
39 #define ALT_NEW_OFFSET		4
40 #define ALT_FEATURE_OFFSET	8
41 #define ALT_ORIG_LEN_OFFSET	10
42 #define ALT_NEW_LEN_OFFSET	11
43 
44 #define X86_FEATURE_POPCNT (4*32+23)
45 
46 struct special_entry {
47 	const char *sec;
48 	bool group, jump_or_nop;
49 	unsigned char size, orig, new;
50 	unsigned char orig_len, new_len; /* group only */
51 	unsigned char feature; /* ALTERNATIVE macro CPU feature */
52 };
53 
54 struct special_entry entries[] = {
55 	{
56 		.sec = ".altinstructions",
57 		.group = true,
58 		.size = ALT_ENTRY_SIZE,
59 		.orig = ALT_ORIG_OFFSET,
60 		.orig_len = ALT_ORIG_LEN_OFFSET,
61 		.new = ALT_NEW_OFFSET,
62 		.new_len = ALT_NEW_LEN_OFFSET,
63 		.feature = ALT_FEATURE_OFFSET,
64 	},
65 	{
66 		.sec = "__jump_table",
67 		.jump_or_nop = true,
68 		.size = JUMP_ENTRY_SIZE,
69 		.orig = JUMP_ORIG_OFFSET,
70 		.new = JUMP_NEW_OFFSET,
71 	},
72 	{
73 		.sec = "__ex_table",
74 		.size = EX_ENTRY_SIZE,
75 		.orig = EX_ORIG_OFFSET,
76 		.new = EX_NEW_OFFSET,
77 	},
78 	{},
79 };
80 
81 static int get_alt_entry(struct elf *elf, struct special_entry *entry,
82 			 struct section *sec, int idx,
83 			 struct special_alt *alt)
84 {
85 	struct rela *orig_rela, *new_rela;
86 	unsigned long offset;
87 
88 	offset = idx * entry->size;
89 
90 	alt->group = entry->group;
91 	alt->jump_or_nop = entry->jump_or_nop;
92 
93 	if (alt->group) {
94 		alt->orig_len = *(unsigned char *)(sec->data + offset +
95 						   entry->orig_len);
96 		alt->new_len = *(unsigned char *)(sec->data + offset +
97 						  entry->new_len);
98 	}
99 
100 	if (entry->feature) {
101 		unsigned short feature;
102 
103 		feature = *(unsigned short *)(sec->data + offset +
104 					      entry->feature);
105 
106 		/*
107 		 * It has been requested that we don't validate the !POPCNT
108 		 * feature path which is a "very very small percentage of
109 		 * machines".
110 		 */
111 		if (feature == X86_FEATURE_POPCNT)
112 			alt->skip_orig = true;
113 	}
114 
115 	orig_rela = find_rela_by_dest(sec, offset + entry->orig);
116 	if (!orig_rela) {
117 		WARN_FUNC("can't find orig rela", sec, offset + entry->orig);
118 		return -1;
119 	}
120 	if (orig_rela->sym->type != STT_SECTION) {
121 		WARN_FUNC("don't know how to handle non-section rela symbol %s",
122 			   sec, offset + entry->orig, orig_rela->sym->name);
123 		return -1;
124 	}
125 
126 	alt->orig_sec = orig_rela->sym->sec;
127 	alt->orig_off = orig_rela->addend;
128 
129 	if (!entry->group || alt->new_len) {
130 		new_rela = find_rela_by_dest(sec, offset + entry->new);
131 		if (!new_rela) {
132 			WARN_FUNC("can't find new rela",
133 				  sec, offset + entry->new);
134 			return -1;
135 		}
136 
137 		alt->new_sec = new_rela->sym->sec;
138 		alt->new_off = (unsigned int)new_rela->addend;
139 
140 		/* _ASM_EXTABLE_EX hack */
141 		if (alt->new_off >= 0x7ffffff0)
142 			alt->new_off -= 0x7ffffff0;
143 	}
144 
145 	return 0;
146 }
147 
148 /*
149  * Read all the special sections and create a list of special_alt structs which
150  * describe all the alternate instructions which can be patched in or
151  * redirected to at runtime.
152  */
153 int special_get_alts(struct elf *elf, struct list_head *alts)
154 {
155 	struct special_entry *entry;
156 	struct section *sec;
157 	unsigned int nr_entries;
158 	struct special_alt *alt;
159 	int idx, ret;
160 
161 	INIT_LIST_HEAD(alts);
162 
163 	for (entry = entries; entry->sec; entry++) {
164 		sec = find_section_by_name(elf, entry->sec);
165 		if (!sec)
166 			continue;
167 
168 		if (sec->len % entry->size != 0) {
169 			WARN("%s size not a multiple of %d",
170 			     sec->name, entry->size);
171 			return -1;
172 		}
173 
174 		nr_entries = sec->len / entry->size;
175 
176 		for (idx = 0; idx < nr_entries; idx++) {
177 			alt = malloc(sizeof(*alt));
178 			if (!alt) {
179 				WARN("malloc failed");
180 				return -1;
181 			}
182 			memset(alt, 0, sizeof(*alt));
183 
184 			ret = get_alt_entry(elf, entry, sec, idx, alt);
185 			if (ret)
186 				return ret;
187 
188 			list_add_tail(&alt->list, alts);
189 		}
190 	}
191 
192 	return 0;
193 }
194