335 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright 2017, Gustavo Romero, IBM Corp.4 *5 * Check if thread endianness is flipped inadvertently to BE on trap6 * caught in TM whilst MSR.FP and MSR.VEC are zero (i.e. just after7 * load_fp and load_vec overflowed).8 *9 * The issue can be checked on LE machines simply by zeroing load_fp10 * and load_vec and then causing a trap in TM. Since the endianness11 * changes to BE on return from the signal handler, 'nop' is12 * thread as an illegal instruction in following sequence:13 * tbegin.14 * beq 1f15 * trap16 * tend.17 * 1: nop18 *19 * However, although the issue is also present on BE machines, it's a20 * bit trickier to check it on BE machines because MSR.LE bit is set21 * to zero which determines a BE endianness that is the native22 * endianness on BE machines, so nothing notably critical happens,23 * i.e. no illegal instruction is observed immediately after returning24 * from the signal handler (as it happens on LE machines). Thus to test25 * it on BE machines LE endianness is forced after a first trap and then26 * the endianness is verified on subsequent traps to determine if the27 * endianness "flipped back" to the native endianness (BE).28 */29 30#define _GNU_SOURCE31#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 1UL49#define LE 1UL50 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 Machine71 */72 73 if (le) {74 /* First trap event */75 if (trap_event == 0) {76 /* Do nothing. Since it is returning from this trap77 * event that endianness is flipped by the bug, so just78 * let the process return from the signal handler and79 * check on the second trap event if endianness is80 * 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, if87 * endianness was still LE (not flipped inadvertently)88 * after returning from the signal handler instruction89 * (1) is executed (basically a 'nop'), as it's located90 * at address of tbegin. +4 (rollback addr). As (1) on91 * LE endianness does in effect nothing, instruction (2)92 * is then executed again as 'trap', generating a second93 * trap event (note that in that case 'trap' is caught94 * not in transacional mode). On te other hand, if after95 * the return from the signal handler the endianness in-96 * advertently flipped, instruction (1) is tread as a97 * 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 bit101 * once in the trap handler to verify if endianness was102 * flipped or not after the return from the second trap103 * event. If endianness is flipped, the bug is present.104 * Finally, getting a trap in TM mode or not is just105 * worth noting because it affects the math to determine106 * the offset added to the NIP on return: the NIP for a107 * trap caught in TM is the rollback address, i.e. the108 * next instruction after 'tbegin.', whilst the NIP for109 * a trap caught in non-transactional mode is the very110 * same address of the 'trap' instruction that generated111 * 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 flipped120 * inadvertently. Thus we flip back to LE and121 * 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 Machine131 */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 second139 * 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 this147 * second trap event endianness will flip back "automat-148 * ically" to BE, otherwise thread endianness will149 * 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 executed157 * as LE, generating a third trap event. In that case158 * endianness is still LE as set on return from the159 * first trap event, hence no bug. Otherwise, bug160 * flipped back to BE on return from the second trap161 * event and instruction (4) was executed as 'tdi' (so162 * basically a 'nop') and branch to 'failure' in163 * instruction (5) was taken to indicate failure and we164 * never get here.165 */166 167 /*168 * Flip back to BE and go to instruction (6), i.e. go to169 * '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 overflows193 * 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 a201 * instruction is executed on machine's native endianness (in202 * other words, native endianness matches kernel endianness).203 * [OP] means "Opposite Endianness", i.e. on a BE machine, it204 * tells how a instruction is executed as a LE instruction; con-205 * versely, on a LE machine, it tells how a instruction is206 * executed as a BE instruction. When [NA] is omitted, it means207 * that the native interpretation of a given instruction is not208 * 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() thread239 * until ping() finishes its job and signs240 * 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 and284 * speed up context switches on ping() thread, speeding up the load_fp285 * 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}335