1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright 2017, Gustavo Romero, IBM Corp. |
4 | * |
5 | * Check if thread endianness is flipped inadvertently to BE on trap |
6 | * caught in TM whilst MSR.FP and MSR.VEC are zero (i.e. just after |
7 | * load_fp and load_vec overflowed). |
8 | * |
9 | * The issue can be checked on LE machines simply by zeroing load_fp |
10 | * and load_vec and then causing a trap in TM. Since the endianness |
11 | * changes to BE on return from the signal handler, 'nop' is |
12 | * thread as an illegal instruction in following sequence: |
13 | * tbegin. |
14 | * beq 1f |
15 | * trap |
16 | * tend. |
17 | * 1: nop |
18 | * |
19 | * However, although the issue is also present on BE machines, it's a |
20 | * bit trickier to check it on BE machines because MSR.LE bit is set |
21 | * to zero which determines a BE endianness that is the native |
22 | * endianness on BE machines, so nothing notably critical happens, |
23 | * i.e. no illegal instruction is observed immediately after returning |
24 | * from the signal handler (as it happens on LE machines). Thus to test |
25 | * it on BE machines LE endianness is forced after a first trap and then |
26 | * the endianness is verified on subsequent traps to determine if the |
27 | * endianness "flipped back" to the native endianness (BE). |
28 | */ |
29 | |
30 | #define _GNU_SOURCE |
31 | #include <error.h> |
32 | #include <stdio.h> |
33 | #include <stdlib.h> |
34 | #include <unistd.h> |
35 | #include <htmintrin.h> |
36 | #include <inttypes.h> |
37 | #include <pthread.h> |
38 | #include <sched.h> |
39 | #include <signal.h> |
40 | #include <stdbool.h> |
41 | |
42 | #include "tm.h" |
43 | #include "utils.h" |
44 | |
45 | #define pr_error(error_code, format, ...) \ |
46 | error_at_line(1, error_code, __FILE__, __LINE__, format, ##__VA_ARGS__) |
47 | |
48 | #define MSR_LE 1UL |
49 | #define LE 1UL |
50 | |
51 | pthread_t t0_ping; |
52 | pthread_t t1_pong; |
53 | |
54 | int exit_from_pong; |
55 | |
56 | int trap_event; |
57 | int le; |
58 | |
59 | bool success; |
60 | |
61 | void trap_signal_handler(int signo, siginfo_t *si, void *uc) |
62 | { |
63 | ucontext_t *ucp = uc; |
64 | uint64_t thread_endianness; |
65 | |
66 | /* Get thread endianness: extract bit LE from MSR */ |
67 | thread_endianness = MSR_LE & ucp->uc_mcontext.gp_regs[PT_MSR]; |
68 | |
69 | /* |
70 | * Little-Endian Machine |
71 | */ |
72 | |
73 | if (le) { |
74 | /* First trap event */ |
75 | if (trap_event == 0) { |
76 | /* Do nothing. Since it is returning from this trap |
77 | * event that endianness is flipped by the bug, so just |
78 | * let the process return from the signal handler and |
79 | * check on the second trap event if endianness is |
80 | * flipped or not. |
81 | */ |
82 | } |
83 | /* Second trap event */ |
84 | else if (trap_event == 1) { |
85 | /* |
86 | * Since trap was caught in TM on first trap event, if |
87 | * endianness was still LE (not flipped inadvertently) |
88 | * after returning from the signal handler instruction |
89 | * (1) is executed (basically a 'nop'), as it's located |
90 | * at address of tbegin. +4 (rollback addr). As (1) on |
91 | * LE endianness does in effect nothing, instruction (2) |
92 | * is then executed again as 'trap', generating a second |
93 | * trap event (note that in that case 'trap' is caught |
94 | * not in transacional mode). On te other hand, if after |
95 | * the return from the signal handler the endianness in- |
96 | * advertently flipped, instruction (1) is tread as a |
97 | * branch instruction, i.e. b .+8, hence instruction (3) |
98 | * and (4) are executed (tbegin.; trap;) and we get sim- |
99 | * ilaly on the trap signal handler, but now in TM mode. |
100 | * Either way, it's now possible to check the MSR LE bit |
101 | * once in the trap handler to verify if endianness was |
102 | * flipped or not after the return from the second trap |
103 | * event. If endianness is flipped, the bug is present. |
104 | * Finally, getting a trap in TM mode or not is just |
105 | * worth noting because it affects the math to determine |
106 | * the offset added to the NIP on return: the NIP for a |
107 | * trap caught in TM is the rollback address, i.e. the |
108 | * next instruction after 'tbegin.', whilst the NIP for |
109 | * a trap caught in non-transactional mode is the very |
110 | * same address of the 'trap' instruction that generated |
111 | * the trap event. |
112 | */ |
113 | |
114 | if (thread_endianness == LE) { |
115 | /* Go to 'success', i.e. instruction (6) */ |
116 | ucp->uc_mcontext.gp_regs[PT_NIP] += 16; |
117 | } else { |
118 | /* |
119 | * Thread endianness is BE, so it flipped |
120 | * inadvertently. Thus we flip back to LE and |
121 | * set NIP to go to 'failure', instruction (5). |
122 | */ |
123 | ucp->uc_mcontext.gp_regs[PT_MSR] |= 1UL; |
124 | ucp->uc_mcontext.gp_regs[PT_NIP] += 4; |
125 | } |
126 | } |
127 | } |
128 | |
129 | /* |
130 | * Big-Endian Machine |
131 | */ |
132 | |
133 | else { |
134 | /* First trap event */ |
135 | if (trap_event == 0) { |
136 | /* |
137 | * Force thread endianness to be LE. Instructions (1), |
138 | * (3), and (4) will be executed, generating a second |
139 | * trap in TM mode. |
140 | */ |
141 | ucp->uc_mcontext.gp_regs[PT_MSR] |= 1UL; |
142 | } |
143 | /* Second trap event */ |
144 | else if (trap_event == 1) { |
145 | /* |
146 | * Do nothing. If bug is present on return from this |
147 | * second trap event endianness will flip back "automat- |
148 | * ically" to BE, otherwise thread endianness will |
149 | * continue to be LE, just as it was set above. |
150 | */ |
151 | } |
152 | /* A third trap event */ |
153 | else { |
154 | /* |
155 | * Once here it means that after returning from the sec- |
156 | * ond trap event instruction (4) (trap) was executed |
157 | * as LE, generating a third trap event. In that case |
158 | * endianness is still LE as set on return from the |
159 | * first trap event, hence no bug. Otherwise, bug |
160 | * flipped back to BE on return from the second trap |
161 | * event and instruction (4) was executed as 'tdi' (so |
162 | * basically a 'nop') and branch to 'failure' in |
163 | * instruction (5) was taken to indicate failure and we |
164 | * never get here. |
165 | */ |
166 | |
167 | /* |
168 | * Flip back to BE and go to instruction (6), i.e. go to |
169 | * 'success'. |
170 | */ |
171 | ucp->uc_mcontext.gp_regs[PT_MSR] &= ~1UL; |
172 | ucp->uc_mcontext.gp_regs[PT_NIP] += 8; |
173 | } |
174 | } |
175 | |
176 | trap_event++; |
177 | } |
178 | |
179 | void usr1_signal_handler(int signo, siginfo_t *si, void *not_used) |
180 | { |
181 | /* Got a USR1 signal from ping(), so just tell pong() to exit */ |
182 | exit_from_pong = 1; |
183 | } |
184 | |
185 | void *ping(void *not_used) |
186 | { |
187 | uint64_t i; |
188 | |
189 | trap_event = 0; |
190 | |
191 | /* |
192 | * Wait an amount of context switches so load_fp and load_vec overflows |
193 | * and MSR_[FP|VEC|V] is 0. |
194 | */ |
195 | for (i = 0; i < 1024*1024*512; i++) |
196 | ; |
197 | |
198 | asm goto( |
199 | /* |
200 | * [NA] means "Native Endianness", i.e. it tells how a |
201 | * instruction is executed on machine's native endianness (in |
202 | * other words, native endianness matches kernel endianness). |
203 | * [OP] means "Opposite Endianness", i.e. on a BE machine, it |
204 | * tells how a instruction is executed as a LE instruction; con- |
205 | * versely, on a LE machine, it tells how a instruction is |
206 | * executed as a BE instruction. When [NA] is omitted, it means |
207 | * that the native interpretation of a given instruction is not |
208 | * relevant for the test. Likewise when [OP] is omitted. |
209 | */ |
210 | |
211 | " tbegin. ;" /* (0) tbegin. [NA] */ |
212 | " tdi 0, 0, 0x48;" /* (1) nop [NA]; b (3) [OP] */ |
213 | " trap ;" /* (2) trap [NA] */ |
214 | ".long 0x1D05007C;" /* (3) tbegin. [OP] */ |
215 | ".long 0x0800E07F;" /* (4) trap [OP]; nop [NA] */ |
216 | " b %l[failure] ;" /* (5) b [NA]; MSR.LE flipped (bug) */ |
217 | " b %l[success] ;" /* (6) b [NA]; MSR.LE did not flip (ok)*/ |
218 | |
219 | : : : : failure, success); |
220 | |
221 | failure: |
222 | success = false; |
223 | goto exit_from_ping; |
224 | |
225 | success: |
226 | success = true; |
227 | |
228 | exit_from_ping: |
229 | /* Tell pong() to exit before leaving */ |
230 | pthread_kill(t1_pong, SIGUSR1); |
231 | return NULL; |
232 | } |
233 | |
234 | void *pong(void *not_used) |
235 | { |
236 | while (!exit_from_pong) |
237 | /* |
238 | * Induce context switches on ping() thread |
239 | * until ping() finishes its job and signs |
240 | * to exit from this loop. |
241 | */ |
242 | sched_yield(); |
243 | |
244 | return NULL; |
245 | } |
246 | |
247 | int tm_trap_test(void) |
248 | { |
249 | uint16_t k = 1; |
250 | int cpu, rc; |
251 | |
252 | pthread_attr_t attr; |
253 | cpu_set_t cpuset; |
254 | |
255 | struct sigaction trap_sa; |
256 | |
257 | SKIP_IF(!have_htm()); |
258 | SKIP_IF(htm_is_synthetic()); |
259 | |
260 | trap_sa.sa_flags = SA_SIGINFO; |
261 | trap_sa.sa_sigaction = trap_signal_handler; |
262 | sigaction(SIGTRAP, &trap_sa, NULL); |
263 | |
264 | struct sigaction usr1_sa; |
265 | |
266 | usr1_sa.sa_flags = SA_SIGINFO; |
267 | usr1_sa.sa_sigaction = usr1_signal_handler; |
268 | sigaction(SIGUSR1, &usr1_sa, NULL); |
269 | |
270 | cpu = pick_online_cpu(); |
271 | FAIL_IF(cpu < 0); |
272 | |
273 | // Set only one CPU in the mask. Both threads will be bound to that CPU. |
274 | CPU_ZERO(&cpuset); |
275 | CPU_SET(cpu, &cpuset); |
276 | |
277 | /* Init pthread attribute */ |
278 | rc = pthread_attr_init(&attr); |
279 | if (rc) |
280 | pr_error(rc, "pthread_attr_init()" ); |
281 | |
282 | /* |
283 | * Bind thread ping() and pong() both to CPU 0 so they ping-pong and |
284 | * speed up context switches on ping() thread, speeding up the load_fp |
285 | * and load_vec overflow. |
286 | */ |
287 | rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset); |
288 | if (rc) |
289 | pr_error(rc, "pthread_attr_setaffinity()" ); |
290 | |
291 | /* Figure out the machine endianness */ |
292 | le = (int) *(uint8_t *)&k; |
293 | |
294 | printf("%s machine detected. Checking if endianness flips %s" , |
295 | le ? "Little-Endian" : "Big-Endian" , |
296 | "inadvertently on trap in TM... " ); |
297 | |
298 | rc = fflush(0); |
299 | if (rc) |
300 | pr_error(rc, "fflush()" ); |
301 | |
302 | /* Launch ping() */ |
303 | rc = pthread_create(&t0_ping, &attr, ping, NULL); |
304 | if (rc) |
305 | pr_error(rc, "pthread_create()" ); |
306 | |
307 | exit_from_pong = 0; |
308 | |
309 | /* Launch pong() */ |
310 | rc = pthread_create(&t1_pong, &attr, pong, NULL); |
311 | if (rc) |
312 | pr_error(rc, "pthread_create()" ); |
313 | |
314 | rc = pthread_join(t0_ping, NULL); |
315 | if (rc) |
316 | pr_error(rc, "pthread_join()" ); |
317 | |
318 | rc = pthread_join(t1_pong, NULL); |
319 | if (rc) |
320 | pr_error(rc, "pthread_join()" ); |
321 | |
322 | if (success) { |
323 | printf("no.\n" ); /* no, endianness did not flip inadvertently */ |
324 | return EXIT_SUCCESS; |
325 | } |
326 | |
327 | printf("yes!\n" ); /* yes, endianness did flip inadvertently */ |
328 | return EXIT_FAILURE; |
329 | } |
330 | |
331 | int main(int argc, char **argv) |
332 | { |
333 | return test_harness(tm_trap_test, "tm_trap_test" ); |
334 | } |
335 | |