Skip to content

Commit 6badd51

Browse files
committed
Support the tpause instruction.
Fixes #4074
1 parent b835bbd commit 6badd51

6 files changed

Lines changed: 124 additions & 1 deletion

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,7 @@ set(BASIC_TESTS
14561456
timer
14571457
timerfd
14581458
times
1459+
x86/tpause
14591460
truncate_temp
14601461
tty_tostop
14611462
tun

src/Registers.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ namespace rr {
2222

2323
class ReplayTask;
2424

25+
const uintptr_t X86_CF_FLAG = 1 << 0;
2526
const uintptr_t X86_RESERVED_FLAG = 1 << 1;
2627
const uintptr_t X86_ZF_FLAG = 1 << 6;
2728
const uintptr_t X86_TF_FLAG = 1 << 8;
@@ -30,6 +31,8 @@ const uintptr_t X86_DF_FLAG = 1 << 10;
3031
const uintptr_t X86_RF_FLAG = 1 << 16;
3132
const uintptr_t X86_ID_FLAG = 1 << 21;
3233

34+
const uintptr_t X86_ALL_ARITH_FLAGS = 0x8D5;
35+
3336
const uintptr_t AARCH64_DBG_SPSR_SS = 1 << 21;
3437
const uintptr_t AARCH64_DBG_SPSR_11 = 1 << 11;
3538

src/record_signal.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ static bool try_handle_trapped_instruction(RecordTask* t, siginfo_t* si) {
8585
case SpecialInstOpcode::ARM_MRS_CNTVCTSS_EL0:
8686
case SpecialInstOpcode::X86_RDTSC:
8787
case SpecialInstOpcode::X86_RDTSCP:
88+
case SpecialInstOpcode::X86_TPAUSE:
89+
case SpecialInstOpcode::X86_TPAUSE_REX:
8890
if (t->tsc_mode == PR_TSC_SIGSEGV) {
8991
return false;
9092
}
@@ -136,6 +138,34 @@ static bool try_handle_trapped_instruction(RecordTask* t, siginfo_t* si) {
136138
r.set_cpuid_output(cpuid_data.eax, cpuid_data.ebx, cpuid_data.ecx,
137139
cpuid_data.edx);
138140
LOG(debug) << " trapped for cpuid: " << HEX(eax) << ":" << HEX(ecx);
141+
} else if (special_instruction.opcode == SpecialInstOpcode::X86_TPAUSE ||
142+
special_instruction.opcode == SpecialInstOpcode::X86_TPAUSE_REX) {
143+
// The Intel SDM states:
144+
//
145+
// > Prior to executing the TPAUSE instruction, an operating system may
146+
// > specify the maximum delay it allows the processor to suspend its
147+
// > operation. It can do so by writing TSC-quanta value to the following
148+
// > 32-bit MSR (IA32_UMWAIT_CONTROL at MSR index E1H):
149+
// >
150+
// > - IA32_UMWAIT_CONTROL[31:2] — Determines the maximum time in TSC-quanta
151+
// > that the processor can reside in either C0.1 or C0.2. A zero value
152+
// > indicates no maximum time. The maximum time value is a 32-bit value
153+
// > where the upper 30 bits come from this field and the lower two bits are zero.
154+
// > - IA32_UMWAIT_CONTROL[1] — Reserved.
155+
// > - IA32_UMWAIT_CONTROL[0] — C0.2 is not allowed by the OS. Value of “1”
156+
// > means all C0.2 requests revert to C0.1.
157+
// >
158+
// > If the processor that executed a TPAUSE instruction wakes due to the
159+
// > expiration of the operating system time-limit, the instructions sets
160+
// > RFLAGS.CF; otherwise, that flag is cleared.
161+
//
162+
// As the "operating system", we choose a time-limit of zero. The point of
163+
// TPAUSE is to make spin-wait loops more efficient without yielding to the
164+
// operating system scheduler and triggering a full context switch. But
165+
// because we serialize all execution to a single physical core, no progress
166+
// will ever be made without yielding, and of course we've already paid the
167+
// cost of a context switch and then some.
168+
r.set_flags((r.flags() & ~X86_ALL_ARITH_FLAGS) | X86_CF_FLAG);
139169
}
140170

141171
r.set_ip(r.ip() + len);

src/test/x86/tpause.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
2+
3+
#include "util.h"
4+
5+
enum cpuid_requests {
6+
CPUID_GETEXTENDEDFEATURES = 0x07,
7+
};
8+
9+
static void cpuid(int code, int subrequest, unsigned int* a, unsigned int* c,
10+
unsigned int* d) {
11+
asm volatile("cpuid"
12+
: "=a"(*a), "=c"(*c), "=d"(*d)
13+
: "a"(code), "c"(subrequest)
14+
: "ebx");
15+
}
16+
17+
int main(void) {
18+
uint32_t eax, ecx, edx, state = 0;
19+
#ifdef __x86_64__
20+
register uint32_t r15d __asm__("r15") = state;
21+
#endif
22+
uint64_t tsc;
23+
24+
cpuid(CPUID_GETEXTENDEDFEATURES, 0, &eax, &ecx, &edx);
25+
if (!(ecx & (1 << 5))) {
26+
// WAITPKG not present.
27+
atomic_puts("tpause not supported on this system, skipping");
28+
atomic_puts("EXIT-SUCCESS");
29+
return 0;
30+
}
31+
32+
tsc = __rdtsc() + 1000;
33+
eax = tsc;
34+
edx = tsc >> 32;
35+
36+
asm volatile(
37+
".byte 0x66, 0x0f, 0xae, 0xf7;" // tpause %edi
38+
"lahf;"
39+
: "+a"(eax), "+d"(edx)
40+
: "D"(state)
41+
: "cc", "memory");
42+
43+
// All the arith flags should be cleared except CF, which should be set.
44+
test_assert((uint8_t)(eax >> 8) == 0x3);
45+
46+
#ifdef __x86_64__
47+
tsc = __rdtsc() + 1000;
48+
eax = tsc;
49+
edx = tsc >> 32;
50+
51+
// Test tpause with a REX prefix since the instruction length
52+
// is different.
53+
asm volatile(
54+
".byte 0x66, 0x41, 0x0f, 0xae, 0xf7;" // tpause %r15d
55+
"lahf;"
56+
: "+a"(eax), "+d"(edx)
57+
: "r"(r15d)
58+
: "cc", "memory");
59+
60+
// All the arith flags should be cleared except CF, which should be set.
61+
test_assert((uint8_t)(eax >> 8) == 0x3);
62+
#endif
63+
64+
atomic_puts("EXIT-SUCCESS");
65+
return 0;
66+
}

src/util.cc

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1925,6 +1925,8 @@ vector<string> current_env() {
19251925
const uint8_t rdtsc_insn[2] = { 0x0f, 0x31 };
19261926

19271927
static const uint8_t rdtscp_insn[] = { 0x0f, 0x01, 0xf9 };
1928+
static const uint8_t tpause_insn[] = { 0x66, 0x0f, 0xae, 0xf0 };
1929+
static const uint8_t tpause_rex_insn[] = { 0x66, 0x41, 0x0f, 0xae, 0xf0 };
19281930
static const uint8_t cpuid_insn[] = { 0x0f, 0xa2 };
19291931
static const uint8_t int3_insn[] = { 0xcc };
19301932
static const uint8_t pushf_insn[] = { 0x9c };
@@ -1933,7 +1935,7 @@ static const uint8_t pushf16_insn[] = { 0x66, 0x9c };
19331935
// XXX this probably needs to be extended to decode ignored prefixes
19341936
SpecialInst special_instruction_at(Task* t, remote_code_ptr ip) {
19351937
if (is_x86ish(t->arch())) {
1936-
uint8_t insn[sizeof(rdtscp_insn)];
1938+
uint8_t insn[sizeof(tpause_rex_insn)];
19371939
ssize_t ret =
19381940
t->read_bytes_fallible(ip.to_data_ptr<uint8_t>(), sizeof(insn), insn);
19391941
if (ret < 0) {
@@ -1964,6 +1966,21 @@ SpecialInst special_instruction_at(Task* t, remote_code_ptr ip) {
19641966
!memcmp(insn, pushf16_insn, sizeof(pushf16_insn))) {
19651967
return {SpecialInstOpcode::X86_PUSHF16};
19661968
}
1969+
// NB: These need to be last because they're destructive!
1970+
if (len >= sizeof(tpause_rex_insn)) {
1971+
// Mask off the register selector.
1972+
insn[4] &= ~0x07;
1973+
if (!memcmp(insn, tpause_rex_insn, sizeof(tpause_rex_insn))) {
1974+
return {SpecialInstOpcode::X86_TPAUSE_REX};
1975+
}
1976+
}
1977+
if (len >= sizeof(tpause_insn)) {
1978+
// Mask off the register selector.
1979+
insn[3] &= ~0x07;
1980+
if (!memcmp(insn, tpause_insn, sizeof(tpause_insn))) {
1981+
return {SpecialInstOpcode::X86_TPAUSE};
1982+
}
1983+
}
19671984
} else if (t->arch() == aarch64) {
19681985
uint8_t insn[4];
19691986
ssize_t ret =
@@ -1999,6 +2016,10 @@ size_t special_instruction_len(SpecialInstOpcode insn) {
19992016
return sizeof(pushf_insn);
20002017
} else if (insn == SpecialInstOpcode::X86_PUSHF16) {
20012018
return sizeof(pushf16_insn);
2019+
} else if (insn == SpecialInstOpcode::X86_TPAUSE) {
2020+
return sizeof(tpause_insn);
2021+
} else if (insn == SpecialInstOpcode::X86_TPAUSE_REX) {
2022+
return sizeof(tpause_rex_insn);
20022023
} else if (insn == SpecialInstOpcode::ARM_MRS_CNTFRQ_EL0 ||
20032024
insn == SpecialInstOpcode::ARM_MRS_CNTVCT_EL0 ||
20042025
insn == SpecialInstOpcode::ARM_MRS_CNTVCTSS_EL0) {

src/util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,8 @@ enum class SpecialInstOpcode {
493493
X86_INT3,
494494
X86_PUSHF,
495495
X86_PUSHF16,
496+
X86_TPAUSE,
497+
X86_TPAUSE_REX,
496498
};
497499

498500
struct SpecialInst {

0 commit comments

Comments
 (0)