tm-trap.c (9475B)
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 51pthread_t t0_ping; 52pthread_t t1_pong; 53 54int exit_from_pong; 55 56int trap_event; 57int le; 58 59bool success; 60 61void 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 179void 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 185void *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 221failure: 222 success = false; 223 goto exit_from_ping; 224 225success: 226 success = true; 227 228exit_from_ping: 229 /* Tell pong() to exit before leaving */ 230 pthread_kill(t1_pong, SIGUSR1); 231 return NULL; 232} 233 234void *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 247int 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 331int main(int argc, char **argv) 332{ 333 return test_harness(tm_trap_test, "tm_trap_test"); 334}