Skip to content

Commit

Permalink
Record and replay architected timer accesses on arm64
Browse files Browse the repository at this point in the history
This works using the recently added support for prctl(PR_SET_TSC) on
arm64 which is due to be released in kernel version 6.12.

Fixes #3740
  • Loading branch information
pcc committed Oct 17, 2024
1 parent de6a8eb commit f185672
Show file tree
Hide file tree
Showing 17 changed files with 241 additions and 16 deletions.
26 changes: 24 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,6 @@ set(BASIC_TESTS
prctl_name
prctl_short_name
prctl_speculation_ctrl
x86/prctl_tsc
privileged_net_ioctl
proc_fds
proc_mem
Expand Down Expand Up @@ -1461,6 +1460,7 @@ set(BASIC_CPP_TESTS
set(TESTS_WITH_PROGRAM
abort_nonmain
alternate_thread_diversion
arm/arch_timer
args
async_kill_with_syscallbuf
async_kill_with_syscallbuf2
Expand Down Expand Up @@ -1574,6 +1574,7 @@ set(TESTS_WITH_PROGRAM
pack
patch_page_end
x86/patch_40_80_f6_81
prctl_tsc
priority
ptrace_remote_unmap
x86/rdtsc_loop
Expand Down Expand Up @@ -1817,6 +1818,9 @@ if(BUILD_TESTS)
if (NOT x86ish AND ${test} MATCHES "^x86/.*")
continue()
endif()
if (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64" AND ${test} MATCHES "^arm/.*")
continue()
endif()
get_filename_component(testname ${test} NAME)

add_executable(${testname} src/test/${test}.c)
Expand Down Expand Up @@ -1854,6 +1858,9 @@ if(BUILD_TESTS)
PROPERTIES COMPILE_FLAGS "${RR_TEST_FLAGS} -g -O3")
add_dependencies(watchpoint_unaligned2 Generated)

add_executable(prctl_tsc_supported src/test/prctl_tsc_supported.c)
post_build_executable(prctl_tsc_supported)

# Test disabled because it requires libuvc to be built and installed, and a
# working USB camera
# add_executable(usb src/test/usb.c)
Expand Down Expand Up @@ -1925,7 +1932,7 @@ if(BUILD_TESTS)
set(CTEST_TEST_TIMEOUT 1000)

function(configure_test test)
set_tests_properties(${test} PROPERTIES FAIL_REGULAR_EXPRESSION "FAILED")
set_tests_properties(${test} PROPERTIES FAIL_REGULAR_EXPRESSION "FAILED" SKIP_RETURN_CODE 77)
endfunction(configure_test)

if(INSTALL_TESTSUITE)
Expand All @@ -1947,6 +1954,9 @@ if(BUILD_TESTS)
if (NOT x86ish AND ${test} MATCHES "^x86/.*")
continue()
endif()
if (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64" AND ${test} MATCHES "^arm/.*")
continue()
endif()
get_filename_component(testname ${test} NAME)
add_test(${test}
bash source_dir/src/test/basic_test.run ${testname} "" bin_dir ${TEST_MONITOR_DEFAULT_TIMEOUT})
Expand All @@ -1960,6 +1970,9 @@ if(BUILD_TESTS)
if (NOT x86ish AND ${test} MATCHES "^x86/.*")
continue()
endif()
if (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64" AND ${test} MATCHES "^arm/.*")
continue()
endif()
get_filename_component(testname ${test} NAME)
add_test(${test}
bash source_dir/src/test/${test}.run ${testname} "" bin_dir ${TEST_MONITOR_DEFAULT_TIMEOUT})
Expand Down Expand Up @@ -2006,6 +2019,9 @@ if(BUILD_TESTS)
endforeach(file)

foreach(test ${BASIC_TESTS} ${BASIC_CPP_TESTS} ${TESTS_WITH_PROGRAM})
if (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64" AND ${test} MATCHES "^arm/.*")
continue()
endif()
get_filename_component(testname ${test} NAME)
if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/32/${test}.c")
add_executable(${testname}_32 "${CMAKE_CURRENT_BINARY_DIR}/32/${test}.c")
Expand Down Expand Up @@ -2084,6 +2100,9 @@ if(BUILD_TESTS)
endif(INSTALL_TESTSUITE)

foreach(test ${BASIC_TESTS} ${BASIC_CPP_TESTS} ${OTHER_TESTS})
if (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64" AND ${test} MATCHES "^arm/.*")
continue()
endif()
get_filename_component(testname ${test} NAME)
add_test(${test}-32
bash source_dir/src/test/basic_test.run ${testname}_32 "" bin_dir ${TEST_MONITOR_DEFAULT_TIMEOUT})
Expand All @@ -2094,6 +2113,9 @@ if(BUILD_TESTS)
endforeach(test)

foreach(test ${TESTS_WITH_PROGRAM} ${TESTS_WITHOUT_PROGRAM})
if (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64" AND ${test} MATCHES "^arm/.*")
continue()
endif()
get_filename_component(testname ${test} NAME)
add_test(${test}-32
bash source_dir/src/test/${test}.run ${testname}_32 "" bin_dir ${TEST_MONITOR_DEFAULT_TIMEOUT})
Expand Down
36 changes: 29 additions & 7 deletions src/DiversionSession.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ using namespace std;
namespace rr {

DiversionSession::DiversionSession(int cpu_binding) :
emu_fs(EmuFs::create()), fake_rdstc(uint64_t(1) << 60), cpu_binding_(cpu_binding) {}
emu_fs(EmuFs::create()), fake_timer_counter(uint64_t(1) << 60), cpu_binding_(cpu_binding) {}

DiversionSession::~DiversionSession() {
// We won't permanently leak any OS resources by not ensuring
Expand Down Expand Up @@ -50,10 +50,10 @@ static void execute_syscall(Task* t) {
remote.regs().set_syscall_result(t->regs().syscall_result());
}

uint64_t DiversionSession::next_rdtsc_value() {
uint64_t rdtsc_value = fake_rdstc;
fake_rdstc += 1 << 20; // 1M cycles
return rdtsc_value;
uint64_t DiversionSession::next_timer_counter() {
uint64_t value = fake_timer_counter;
fake_timer_counter += 1 << 20; // 1M cycles
return value;
}

template <typename Arch>
Expand All @@ -70,7 +70,7 @@ static void process_syscall_arch(Task* t, int syscallno) {
}

if (syscallno == t->session().syscall_number_for_rrcall_rdtsc()) {
uint64_t rdtsc_value = static_cast<DiversionSession*>(&t->session())->next_rdtsc_value();
uint64_t rdtsc_value = static_cast<DiversionSession*>(&t->session())->next_timer_counter();
LOG(debug) << "Faking rrcall_rdtsc syscall with value " << rdtsc_value;
remote_ptr<uint64_t> out_param(t->regs().arg1());
t->write_mem(out_param, rdtsc_value);
Expand Down Expand Up @@ -230,7 +230,7 @@ DiversionSession::DiversionResult DiversionSession::diversion_step(
auto special_instruction = special_instruction_at(t, t->ip());
if (special_instruction.opcode == SpecialInstOpcode::X86_RDTSC) {
size_t len = special_instruction_len(special_instruction.opcode);
uint64_t rdtsc_value = next_rdtsc_value();
uint64_t rdtsc_value = next_timer_counter();
LOG(debug) << "Faking RDTSC instruction with value " << rdtsc_value;
Registers r = t->regs();
r.set_ip(r.ip() + len);
Expand All @@ -239,6 +239,28 @@ DiversionSession::DiversionResult DiversionSession::diversion_step(
t->set_regs(r);
result.break_status = BreakStatus();
continue;
} else if (special_instruction.opcode == SpecialInstOpcode::ARM_MRS_CNTVCT_EL0 ||
special_instruction.opcode == SpecialInstOpcode::ARM_MRS_CNTVCTSS_EL0) {
size_t len = special_instruction_len(special_instruction.opcode);
uint64_t cntvct_value = next_timer_counter();
Registers r = t->regs();
r.set_ip(r.ip() + len);
if (special_instruction.regno != 31) {
r.set_x(special_instruction.regno, cntvct_value);
}
t->set_regs(r);
result.break_status = BreakStatus();
continue;
} else if (special_instruction.opcode == SpecialInstOpcode::ARM_MRS_CNTFRQ_EL0) {
size_t len = special_instruction_len(special_instruction.opcode);
Registers r = t->regs();
r.set_ip(r.ip() + len);
if (special_instruction.regno != 31) {
r.set_x(special_instruction.regno, cntfrq());
}
t->set_regs(r);
result.break_status = BreakStatus();
continue;
}
}
LOG(debug) << "Diversion break at ip=" << (void*)t->ip().register_value()
Expand Down
4 changes: 2 additions & 2 deletions src/DiversionSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ class DiversionSession final : public Session {
void set_tracee_fd_number(int fd_number) { tracee_socket_fd_number = fd_number; }
void on_create(Task *t) override { this->Session::on_create(t); }

uint64_t next_rdtsc_value();
uint64_t next_timer_counter();

private:
friend class ReplaySession;

std::shared_ptr<EmuFs> emu_fs;
uint64_t fake_rdstc;
uint64_t fake_timer_counter;
int cpu_binding_;
};

Expand Down
16 changes: 15 additions & 1 deletion src/Task.cc
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,21 @@ void Task::detach() {
}

void Task::reenable_cpuid_tsc() {
AutoRemoteSyscalls remote(this);
if (is_x86ish(arch())) {
AutoRemoteSyscalls remote(this);
if (session().has_cpuid_faulting()) {
remote.infallible_syscall(syscall_number_for_arch_prctl(arch()),
ARCH_SET_CPUID, 1);
}
remote.infallible_syscall(syscall_number_for_prctl(arch()),
PR_SET_TSC, PR_TSC_ENABLE);
}
if (arch() == aarch64) {
// Not infallible because the prctl is only available in 6.12+.
// We already warned about this in post_exec_syscall().
remote.syscall(syscall_number_for_prctl(arch()),
PR_SET_TSC, PR_TSC_ENABLE);
}
}

void Task::wait_exit() {
Expand Down Expand Up @@ -1114,6 +1120,14 @@ void Task::post_exec_syscall(const std::string& original_exe_file) {
remote.infallible_syscall(syscall_number_for_arch_prctl(arch()),
ARCH_SET_CPUID, 0);
}
if (arch() == aarch64) {
if (remote.syscall(syscall_number_for_prctl(remote.task()->arch()),
PR_SET_TSC, PR_TSC_SIGSEGV, 0, 0) != 0) {
LOG(warn) << "Missing kernel support for PR_SET_TSC; architected timer "
"accesses will not be replayed deterministically. It is "
"recommended to upgrade to kernel version 6.12";
}
}
}

bool Task::execed() const { return tg->execed; }
Expand Down
16 changes: 14 additions & 2 deletions src/record_signal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ static bool try_handle_trapped_instruction(RecordTask* t, siginfo_t* si) {

auto special_instruction = special_instruction_at(t, t->ip());
switch (special_instruction.opcode) {
case SpecialInstOpcode::ARM_MRS_CNTFRQ_EL0:
case SpecialInstOpcode::ARM_MRS_CNTVCT_EL0:
case SpecialInstOpcode::ARM_MRS_CNTVCTSS_EL0:
case SpecialInstOpcode::X86_RDTSC:
case SpecialInstOpcode::X86_RDTSCP:
if (t->tsc_mode == PR_TSC_SIGSEGV) {
Expand All @@ -99,8 +102,17 @@ static bool try_handle_trapped_instruction(RecordTask* t, siginfo_t* si) {
ASSERT(t, len > 0);

Registers r = t->regs();
if (special_instruction.opcode == SpecialInstOpcode::X86_RDTSC ||
special_instruction.opcode == SpecialInstOpcode::X86_RDTSCP) {
if (special_instruction.opcode == SpecialInstOpcode::ARM_MRS_CNTVCT_EL0 ||
special_instruction.opcode == SpecialInstOpcode::ARM_MRS_CNTVCTSS_EL0) {
if (special_instruction.regno != 31) {
r.set_x(special_instruction.regno, cntvct());
}
} else if (special_instruction.opcode == SpecialInstOpcode::ARM_MRS_CNTFRQ_EL0) {
if (special_instruction.regno != 31) {
r.set_x(special_instruction.regno, cntfrq());
}
} else if (special_instruction.opcode == SpecialInstOpcode::X86_RDTSC ||
special_instruction.opcode == SpecialInstOpcode::X86_RDTSCP) {
if (special_instruction.opcode == SpecialInstOpcode::X86_RDTSC &&
t->vm()->monkeypatcher().try_patch_trapping_instruction(t, len, true)) {
Event ev = Event::patch_syscall();
Expand Down
62 changes: 62 additions & 0 deletions src/test/arm/arch_timer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "util.h"

#include <sys/auxv.h>
#include <sys/prctl.h>
#include <stdio.h>

long cntfrq(void) {
long c;
__asm__ __volatile__("mrs %0, cntfrq_el0" : "=r"(c));
return c;
}

long cntvct(void) {
long c;
__asm__ __volatile__("mrs %0, cntvct_el0" : "=r"(c));
return c;
}

long cntvctss(void) {
long c;
if (getauxval(AT_HWCAP2) & HWCAP2_ECV) {
__asm__ __volatile__(".arch armv8.6-a\nmrs %0, cntvctss_el0" : "=r"(c));
} else {
__asm__ __volatile__("mrs %0, cntvct_el0" : "=r"(c));
}
return c;
}

long initial_cntfrq;
long initial_cntvct;

void arch_timer_nops(void) {
__asm__ __volatile__("mrs xzr, cntfrq_el0");
__asm__ __volatile__("mrs xzr, cntvct_el0");
if (getauxval(AT_HWCAP2) & HWCAP2_ECV) {
__asm__ __volatile__("mrs xzr, cntvctss_el0");
}
}

void diversion_check(void) {
arch_timer_nops();
test_assert(initial_cntfrq == cntfrq());
test_assert(initial_cntvct < cntvct());
test_assert(initial_cntvct < cntvctss());
atomic_puts("diversion_check passed");
}

void breakpoint(void) {}

int main(void) {
initial_cntfrq = cntfrq();
initial_cntvct = cntvct();
breakpoint();

atomic_printf("%ld\n", cntvct());
atomic_printf("%ld\n", cntvctss());
atomic_printf("%ld\n", cntfrq());

arch_timer_nops();

atomic_puts("EXIT-SUCCESS");
}
6 changes: 6 additions & 0 deletions src/test/arm/arch_timer.run
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source `dirname $0`/util.sh
if ! prctl_tsc_supported; then
exit 77
fi
compare_test EXIT-SUCCESS
debug_gdb_only arm/diversion_arch_timer
12 changes: 12 additions & 0 deletions src/test/arm/diversion_arch_timer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from util import *

send_gdb('break breakpoint')
expect_gdb('Breakpoint 1')

send_gdb('c')
expect_gdb('Breakpoint 1')

send_gdb('call diversion_check()')
expect_gdb('diversion_check passed')

ok()
1 change: 1 addition & 0 deletions src/test/arm/util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "../util.h"
1 change: 1 addition & 0 deletions src/test/arm/util.py
2 changes: 2 additions & 0 deletions src/test/arm/util.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
TEST_PREFIX=arm/
source `dirname $0`/../util.sh
6 changes: 4 additions & 2 deletions src/test/x86/prctl_tsc.c → src/test/prctl_tsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ static void skip_handler(__attribute__((unused)) int sig,
ctx->uc_mcontext.gregs[REG_EIP] += 2;
#elif defined(__x86_64__)
ctx->uc_mcontext.gregs[REG_RIP] += 2;
#elif defined(__aarch64__)
ctx->uc_mcontext.pc += 4;
#else
#error unknown architecture
#endif
Expand Down Expand Up @@ -38,7 +40,7 @@ int main(void) {
test_assert(0 == prctl(PR_GET_TSC, &status));
test_assert(PR_TSC_SIGSEGV == status);
signal(SIGSEGV, exit_handler);
rdtsc();
trigger_timer_counter_trap();
return 77;
}

Expand All @@ -48,6 +50,6 @@ int main(void) {
signal(SIGSEGV, print_handler);
test_assert(0 == prctl(PR_GET_TSC, &status));
test_assert(PR_TSC_SIGSEGV == status);
rdtsc();
trigger_timer_counter_trap();
return 1;
}
5 changes: 5 additions & 0 deletions src/test/prctl_tsc.run
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
source `dirname $0`/util.sh
if ! prctl_tsc_supported; then
exit 77
fi
compare_test EXIT-SUCCESS
6 changes: 6 additions & 0 deletions src/test/prctl_tsc_supported.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <stdlib.h>
#include <sys/prctl.h>

int main(void) {
return prctl(PR_SET_TSC, PR_TSC_ENABLE) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
Loading

0 comments on commit f185672

Please sign in to comment.