1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | #include <byteswap.h> |
3 | #include <elf.h> |
4 | #include <endian.h> |
5 | #include <errno.h> |
6 | #include <fcntl.h> |
7 | #include <inttypes.h> |
8 | #include <stdbool.h> |
9 | #include <stdio.h> |
10 | #include <stdlib.h> |
11 | #include <string.h> |
12 | #include <sys/mman.h> |
13 | #include <sys/types.h> |
14 | #include <sys/stat.h> |
15 | #include <unistd.h> |
16 | |
17 | #ifdef be32toh |
18 | /* If libc provides le{16,32,64}toh() then we'll use them */ |
19 | #elif BYTE_ORDER == LITTLE_ENDIAN |
20 | # define le16toh(x) (x) |
21 | # define le32toh(x) (x) |
22 | # define le64toh(x) (x) |
23 | #elif BYTE_ORDER == BIG_ENDIAN |
24 | # define le16toh(x) bswap_16(x) |
25 | # define le32toh(x) bswap_32(x) |
26 | # define le64toh(x) bswap_64(x) |
27 | #endif |
28 | |
29 | /* MIPS opcodes, in bits 31:26 of an instruction */ |
30 | #define OP_SPECIAL 0x00 |
31 | #define OP_REGIMM 0x01 |
32 | #define OP_BEQ 0x04 |
33 | #define OP_BNE 0x05 |
34 | #define OP_BLEZ 0x06 |
35 | #define OP_BGTZ 0x07 |
36 | #define OP_BEQL 0x14 |
37 | #define OP_BNEL 0x15 |
38 | #define OP_BLEZL 0x16 |
39 | #define OP_BGTZL 0x17 |
40 | #define OP_LL 0x30 |
41 | #define OP_LLD 0x34 |
42 | #define OP_SC 0x38 |
43 | #define OP_SCD 0x3c |
44 | |
45 | /* Bits 20:16 of OP_REGIMM instructions */ |
46 | #define REGIMM_BLTZ 0x00 |
47 | #define REGIMM_BGEZ 0x01 |
48 | #define REGIMM_BLTZL 0x02 |
49 | #define REGIMM_BGEZL 0x03 |
50 | #define REGIMM_BLTZAL 0x10 |
51 | #define REGIMM_BGEZAL 0x11 |
52 | #define REGIMM_BLTZALL 0x12 |
53 | #define REGIMM_BGEZALL 0x13 |
54 | |
55 | /* Bits 5:0 of OP_SPECIAL instructions */ |
56 | #define SPECIAL_SYNC 0x0f |
57 | |
58 | static void usage(FILE *f) |
59 | { |
60 | fprintf(stream: f, format: "Usage: loongson3-llsc-check /path/to/vmlinux\n" ); |
61 | } |
62 | |
63 | static int se16(uint16_t x) |
64 | { |
65 | return (int16_t)x; |
66 | } |
67 | |
68 | static bool is_ll(uint32_t insn) |
69 | { |
70 | switch (insn >> 26) { |
71 | case OP_LL: |
72 | case OP_LLD: |
73 | return true; |
74 | |
75 | default: |
76 | return false; |
77 | } |
78 | } |
79 | |
80 | static bool is_sc(uint32_t insn) |
81 | { |
82 | switch (insn >> 26) { |
83 | case OP_SC: |
84 | case OP_SCD: |
85 | return true; |
86 | |
87 | default: |
88 | return false; |
89 | } |
90 | } |
91 | |
92 | static bool is_sync(uint32_t insn) |
93 | { |
94 | /* Bits 31:11 should all be zeroes */ |
95 | if (insn >> 11) |
96 | return false; |
97 | |
98 | /* Bits 5:0 specify the SYNC special encoding */ |
99 | if ((insn & 0x3f) != SPECIAL_SYNC) |
100 | return false; |
101 | |
102 | return true; |
103 | } |
104 | |
105 | static bool is_branch(uint32_t insn, int *off) |
106 | { |
107 | switch (insn >> 26) { |
108 | case OP_BEQ: |
109 | case OP_BEQL: |
110 | case OP_BNE: |
111 | case OP_BNEL: |
112 | case OP_BGTZ: |
113 | case OP_BGTZL: |
114 | case OP_BLEZ: |
115 | case OP_BLEZL: |
116 | *off = se16(x: insn) + 1; |
117 | return true; |
118 | |
119 | case OP_REGIMM: |
120 | switch ((insn >> 16) & 0x1f) { |
121 | case REGIMM_BGEZ: |
122 | case REGIMM_BGEZL: |
123 | case REGIMM_BGEZAL: |
124 | case REGIMM_BGEZALL: |
125 | case REGIMM_BLTZ: |
126 | case REGIMM_BLTZL: |
127 | case REGIMM_BLTZAL: |
128 | case REGIMM_BLTZALL: |
129 | *off = se16(x: insn) + 1; |
130 | return true; |
131 | |
132 | default: |
133 | return false; |
134 | } |
135 | |
136 | default: |
137 | return false; |
138 | } |
139 | } |
140 | |
141 | static int check_ll(uint64_t pc, uint32_t *code, size_t sz) |
142 | { |
143 | ssize_t i, max, sc_pos; |
144 | int off; |
145 | |
146 | /* |
147 | * Every LL must be preceded by a sync instruction in order to ensure |
148 | * that instruction reordering doesn't allow a prior memory access to |
149 | * execute after the LL & cause erroneous results. |
150 | */ |
151 | if (!is_sync(le32toh(code[-1]))) { |
152 | fprintf(stderr, format: "%" PRIx64 ": LL not preceded by sync\n" , pc); |
153 | return -EINVAL; |
154 | } |
155 | |
156 | /* Find the matching SC instruction */ |
157 | max = sz / 4; |
158 | for (sc_pos = 0; sc_pos < max; sc_pos++) { |
159 | if (is_sc(le32toh(code[sc_pos]))) |
160 | break; |
161 | } |
162 | if (sc_pos >= max) { |
163 | fprintf(stderr, format: "%" PRIx64 ": LL has no matching SC\n" , pc); |
164 | return -EINVAL; |
165 | } |
166 | |
167 | /* |
168 | * Check branches within the LL/SC loop target sync instructions, |
169 | * ensuring that speculative execution can't generate memory accesses |
170 | * due to instructions outside of the loop. |
171 | */ |
172 | for (i = 0; i < sc_pos; i++) { |
173 | if (!is_branch(le32toh(code[i]), off: &off)) |
174 | continue; |
175 | |
176 | /* |
177 | * If the branch target is within the LL/SC loop then we don't |
178 | * need to worry about it. |
179 | */ |
180 | if ((off >= -i) && (off <= sc_pos)) |
181 | continue; |
182 | |
183 | /* If the branch targets a sync instruction we're all good... */ |
184 | if (is_sync(le32toh(code[i + off]))) |
185 | continue; |
186 | |
187 | /* ...but if not, we have a problem */ |
188 | fprintf(stderr, format: "%" PRIx64 ": Branch target not a sync\n" , |
189 | pc + (i * 4)); |
190 | return -EINVAL; |
191 | } |
192 | |
193 | return 0; |
194 | } |
195 | |
196 | static int check_code(uint64_t pc, uint32_t *code, size_t sz) |
197 | { |
198 | int err = 0; |
199 | |
200 | if (sz % 4) { |
201 | fprintf(stderr, format: "%" PRIx64 ": Section size not a multiple of 4\n" , |
202 | pc); |
203 | err = -EINVAL; |
204 | sz -= (sz % 4); |
205 | } |
206 | |
207 | if (is_ll(le32toh(code[0]))) { |
208 | fprintf(stderr, format: "%" PRIx64 ": First instruction in section is an LL\n" , |
209 | pc); |
210 | err = -EINVAL; |
211 | } |
212 | |
213 | #define advance() ( \ |
214 | code++, \ |
215 | pc += 4, \ |
216 | sz -= 4 \ |
217 | ) |
218 | |
219 | /* |
220 | * Skip the first instruction, allowing check_ll to look backwards |
221 | * unconditionally. |
222 | */ |
223 | advance(); |
224 | |
225 | /* Now scan through the code looking for LL instructions */ |
226 | for (; sz; advance()) { |
227 | if (is_ll(le32toh(code[0]))) |
228 | err |= check_ll(pc, code, sz); |
229 | } |
230 | |
231 | return err; |
232 | } |
233 | |
234 | int main(int argc, char *argv[]) |
235 | { |
236 | int vmlinux_fd, status, err, i; |
237 | const char *vmlinux_path; |
238 | struct stat st; |
239 | Elf64_Ehdr *eh; |
240 | Elf64_Shdr *sh; |
241 | void *vmlinux; |
242 | |
243 | status = EXIT_FAILURE; |
244 | |
245 | if (argc < 2) { |
246 | usage(stderr); |
247 | goto out_ret; |
248 | } |
249 | |
250 | vmlinux_path = argv[1]; |
251 | vmlinux_fd = open(file: vmlinux_path, O_RDONLY); |
252 | if (vmlinux_fd == -1) { |
253 | perror(s: "Unable to open vmlinux" ); |
254 | goto out_ret; |
255 | } |
256 | |
257 | err = fstat(fd: vmlinux_fd, buf: &st); |
258 | if (err) { |
259 | perror(s: "Unable to stat vmlinux" ); |
260 | goto out_close; |
261 | } |
262 | |
263 | vmlinux = mmap(NULL, len: st.st_size, PROT_READ, MAP_PRIVATE, fd: vmlinux_fd, offset: 0); |
264 | if (vmlinux == MAP_FAILED) { |
265 | perror(s: "Unable to mmap vmlinux" ); |
266 | goto out_close; |
267 | } |
268 | |
269 | eh = vmlinux; |
270 | if (memcmp(s1: eh->e_ident, ELFMAG, SELFMAG)) { |
271 | fprintf(stderr, format: "vmlinux is not an ELF?\n" ); |
272 | goto out_munmap; |
273 | } |
274 | |
275 | if (eh->e_ident[EI_CLASS] != ELFCLASS64) { |
276 | fprintf(stderr, format: "vmlinux is not 64b?\n" ); |
277 | goto out_munmap; |
278 | } |
279 | |
280 | if (eh->e_ident[EI_DATA] != ELFDATA2LSB) { |
281 | fprintf(stderr, format: "vmlinux is not little endian?\n" ); |
282 | goto out_munmap; |
283 | } |
284 | |
285 | for (i = 0; i < le16toh(eh->e_shnum); i++) { |
286 | sh = vmlinux + le64toh(eh->e_shoff) + (i * le16toh(eh->e_shentsize)); |
287 | |
288 | if (sh->sh_type != SHT_PROGBITS) |
289 | continue; |
290 | if (!(sh->sh_flags & SHF_EXECINSTR)) |
291 | continue; |
292 | |
293 | err = check_code(le64toh(sh->sh_addr), |
294 | code: vmlinux + le64toh(sh->sh_offset), |
295 | le64toh(sh->sh_size)); |
296 | if (err) |
297 | goto out_munmap; |
298 | } |
299 | |
300 | status = EXIT_SUCCESS; |
301 | out_munmap: |
302 | munmap(addr: vmlinux, len: st.st_size); |
303 | out_close: |
304 | close(fd: vmlinux_fd); |
305 | out_ret: |
306 | fprintf(stdout, format: "loongson3-llsc-check returns %s\n" , |
307 | status ? "failure" : "success" ); |
308 | return status; |
309 | } |
310 | |