Skip to content

Commit

Permalink
Add support for using Intel PT to collect control flow data to debug …
Browse files Browse the repository at this point in the history
…rr divergences
  • Loading branch information
rocallahan committed Oct 24, 2023
1 parent edbd058 commit 93767cb
Show file tree
Hide file tree
Showing 20 changed files with 853 additions and 39 deletions.
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,12 @@ else()
set(CMAKE_INSTALL_INCLUDEDIR "include")
endif()

option(pt_decoding "Build with Intel PT decoding enabled.")
if (pt_decoding)
set(RR_SOURCES ${RR_SOURCES} src/ProcessorTraceDecoder.cc)
add_definitions(-DINTEL_PT_DECODING=1)
endif()

add_executable(rr ${RR_SOURCES})
set_target_properties(rr PROPERTIES ENABLE_EXPORTS true)
post_build_executable(rr)
Expand All @@ -683,6 +689,11 @@ if(LOWERCASE_CMAKE_BUILD_TYPE STREQUAL "release")
endif()
endif()

if (pt_decoding)
find_library(LIBIPT ipt)
target_link_libraries(rr ${LIBIPT})
endif()

if(LIBRT)
target_link_libraries(rr ${LIBRT})
endif()
Expand Down
62 changes: 43 additions & 19 deletions src/AddressSpace.cc
Original file line number Diff line number Diff line change
Expand Up @@ -286,16 +286,10 @@ remote_code_ptr AddressSpace::find_syscall_instruction_in_vdso(Task* t) {
.as_int());
}

void AddressSpace::map_rr_page(AutoRemoteSyscalls& remote) {
int prot = PROT_EXEC | PROT_READ;
int flags = MAP_PRIVATE | MAP_FIXED;

static string rr_page_file_name(SupportedArch arch, const char** fname_out) {
string file_name;
Task* t = remote.task();
SupportedArch arch = t->arch();

const char *fname = nullptr;
switch (t->arch()) {
switch (arch) {
case x86_64:
case aarch64:
fname = RRPAGE_LIB_FILENAME;
Expand All @@ -308,13 +302,29 @@ void AddressSpace::map_rr_page(AutoRemoteSyscalls& remote) {
#endif
break;
}
*fname_out = fname;

string path = find_helper_library(fname);
if (path.empty()) {
FATAL() << "Failed to locate " << fname << "; needed by "
<< t->exe_path() << " (" << arch_name(t->arch()) << ")";
return path;
}
path += fname;
return path;
}

void AddressSpace::map_rr_page(AutoRemoteSyscalls& remote) {
int prot = PROT_EXEC | PROT_READ;
int flags = MAP_PRIVATE | MAP_FIXED;

Task* t = remote.task();
SupportedArch arch = t->arch();
const char* fname;
string path = rr_page_file_name(arch, &fname);
if (path.empty()) {
FATAL() << "Failed to locate " << fname << "; needed by "
<< t->exe_path() << " (" << arch_name(arch) << ")";
}

size_t offset_pages = t->session().is_recording() ?
RRPAGE_RECORD_PAGE_OFFSET : RRPAGE_REPLAY_PAGE_OFFSET;
size_t offset_bytes = offset_pages * PRELOAD_LIBRARY_PAGE_SIZE;
Expand All @@ -332,7 +342,7 @@ void AddressSpace::map_rr_page(AutoRemoteSyscalls& remote) {
child_fd, offset_bytes);

struct stat fstat = t->stat_fd(child_fd);
file_name = t->file_name_of_fd(child_fd);
string file_name = t->file_name_of_fd(child_fd);

remote.infallible_close_syscall_if_alive(child_fd);

Expand All @@ -356,6 +366,25 @@ void AddressSpace::map_rr_page(AutoRemoteSyscalls& remote) {
}
}

vector<uint8_t> AddressSpace::read_rr_page_for_recording(SupportedArch arch) {
const char* fname;
string path = rr_page_file_name(arch, &fname);
if (path.empty()) {
FATAL() << "Failed to locate " << fname;
}

ScopedFd page(path.c_str(), O_RDONLY);
char buf[PRELOAD_LIBRARY_PAGE_SIZE];
ssize_t ret = read_to_end(page,
RRPAGE_RECORD_PAGE_OFFSET * PRELOAD_LIBRARY_PAGE_SIZE, buf, sizeof(buf));
if (ret != PRELOAD_LIBRARY_PAGE_SIZE) {
FATAL() << "Failed to read full page from " << path;
}
vector<uint8_t> result;
result.insert(result.end(), buf, buf + sizeof(buf));
return result;
}

void AddressSpace::unmap_all_but_rr_mappings(AutoRemoteSyscalls& remote,
UnmapOptions options) {
vector<MemoryRange> unmaps;
Expand Down Expand Up @@ -693,14 +722,9 @@ bool AddressSpace::is_breakpoint_in_private_read_only_memory(
void AddressSpace::replace_breakpoints_with_original_values(
uint8_t* dest, size_t length, remote_ptr<uint8_t> addr) {
for (auto& it : breakpoints) {
remote_ptr<uint8_t> bkpt_location = it.first.to_data_ptr<uint8_t>();
remote_ptr<uint8_t> start = max(addr, bkpt_location);
remote_ptr<uint8_t> end =
min(addr + length, bkpt_location + bkpt_instruction_length(arch()));
if (start < end) {
memcpy(dest + (start - addr),
it.second.original_data() + (start - bkpt_location), end - start);
}
replace_in_buffer(MemoryRange(it.first.to_data_ptr<void>(), bkpt_instruction_length(arch())),
it.second.original_data(),
MemoryRange(addr, length), dest);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/AddressSpace.h
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,7 @@ class AddressSpace : public HasTaskSet {
};

void map_rr_page(AutoRemoteSyscalls& remote);
static std::vector<uint8_t> read_rr_page_for_recording(SupportedArch arch);
struct UnmapOptions {
bool exclude_vdso_vvar;
UnmapOptions() : exclude_vdso_vvar(false) {}
Expand Down
149 changes: 148 additions & 1 deletion src/PerfCounters.cc
Original file line number Diff line number Diff line change
Expand Up @@ -608,12 +608,16 @@ uint32_t PerfCounters::skid_size() {
}

PerfCounters::PerfCounters(pid_t tid, int cpu_binding,
TicksSemantics ticks_semantics, bool enable)
TicksSemantics ticks_semantics, bool enable,
bool enable_pt)
: tid(tid), pmu_index(get_pmu_index(cpu_binding)), ticks_semantics_(ticks_semantics),
enable(enable), started(false), counting(false) {
if (!supports_ticks_semantics(ticks_semantics)) {
FATAL() << "Ticks semantics " << ticks_semantics << " not supported";
}
if (enable_pt) {
pt_state = make_unique<PTState>();
}
}

static void make_counter_async(ScopedFd& fd, int signal) {
Expand Down Expand Up @@ -647,6 +651,131 @@ static void infallible_perf_event_disable_if_open(ScopedFd& fd) {
}
}

static uint32_t pt_event_type() {
static const char file_name[] = "/sys/bus/event_source/devices/intel_pt/type";
ScopedFd fd(file_name, O_RDONLY);
if (!fd.is_open()) {
FATAL() << "Can't open " << file_name << ", PT events not available";
}
char buf[6];
ssize_t ret = read(fd, buf, sizeof(buf));
if (ret < 1 || ret > 5) {
FATAL() << "Invalid value in " << file_name;
}
char* end_ptr;
unsigned long value = strtoul(buf, &end_ptr, 10);
if (end_ptr < buf + ret && *end_ptr && *end_ptr != '\n') {
FATAL() << "Invalid value in " << file_name;
}
return value;
}

static const size_t PT_PERF_DATA_SIZE = 8*1024*1024;
static const size_t PT_PERF_AUX_SIZE = 32*1024*1024;

// See https://github.com/intel/libipt/blob/master/doc/howto_capture.md
static void start_pt(pid_t tid, PerfCounters::PTState& state) {
static uint32_t event_type = pt_event_type();

struct perf_event_attr attr;
init_perf_event_attr(&attr, event_type, 0);
state.pt_perf_event_fd = start_counter(tid, -1, &attr);

size_t page_size = sysconf(_SC_PAGESIZE);
void* base = mmap(NULL, page_size + PT_PERF_DATA_SIZE,
PROT_READ | PROT_WRITE, MAP_SHARED, state.pt_perf_event_fd, 0);
if (base == MAP_FAILED) {
FATAL() << "Can't allocate memory for PT DATA area";
}
auto header = static_cast<struct perf_event_mmap_page*>(base);
state.mmap_header = header;

header->aux_offset = header->data_offset + header->data_size;
header->aux_size = PT_PERF_AUX_SIZE;

void* aux = mmap(NULL, header->aux_size, PROT_READ | PROT_WRITE, MAP_SHARED,
state.pt_perf_event_fd, header->aux_offset);
if (aux == MAP_FAILED) {
FATAL() << "Can't allocate memory for PT AUX area";
}
state.mmap_aux_buffer = static_cast<char*>(aux);
}

// I wish I knew why this type isn't defined in perf_event.h but is just
// commented out there...
struct PerfEventAux {
struct perf_event_header header;
uint64_t aux_offset;
uint64_t aux_size;
uint64_t flags;
uint64_t sample_id;
};

static void flush_pt(PerfCounters::PTState& state) {
uint64_t data_end = state.mmap_header->data_head;
__sync_synchronize();
char* data_buf = reinterpret_cast<char*>(state.mmap_header) +
state.mmap_header->data_offset;

vector<char> packet;
while (state.mmap_header->data_tail < data_end) {
uint64_t data_start = state.mmap_header->data_tail;
size_t start_offset = data_start % state.mmap_header->data_size;
auto header = reinterpret_cast<struct perf_event_header*>(data_buf + start_offset);
packet.resize(header->size);
size_t first_chunk_size = min<size_t>(header->size,
state.mmap_header->data_size - start_offset);
memcpy(packet.data(), header, first_chunk_size);
memcpy(packet.data() + first_chunk_size, data_buf, header->size - first_chunk_size);
state.mmap_header->data_tail += header->size;

switch (header->type) {
case PERF_RECORD_LOST:
FATAL() << "PT records lost!";
break;
case PERF_RECORD_ITRACE_START:
break;
case PERF_RECORD_AUX: {
auto aux_packet = reinterpret_cast<PerfEventAux*>(packet.data());
if (aux_packet->flags) {
FATAL() << "Unexpected AUX packet flags " << aux_packet->flags;
}
size_t current_size = state.pt_data.data.size();
state.pt_data.data.resize(current_size + aux_packet->aux_size);
uint8_t* current = state.pt_data.data.data() + current_size;
size_t aux_start_offset = aux_packet->aux_offset % PT_PERF_AUX_SIZE;
first_chunk_size = min<size_t>(aux_packet->aux_size, PT_PERF_AUX_SIZE - aux_start_offset);
memcpy(current, state.mmap_aux_buffer + aux_start_offset, first_chunk_size);
memcpy(current + first_chunk_size, state.mmap_aux_buffer,
aux_packet->aux_size - first_chunk_size);
break;
}
default:
FATAL() << "Unknown record " << header->type;
break;
}
}
}

PTData PerfCounters::extract_pt_data() {
PTData result;
if (pt_state) {
result = std::move(pt_state->pt_data);
}
return result;
}

void PerfCounters::PTState::stop() {
pt_perf_event_fd.close();
if (mmap_aux_buffer) {
size_t page_size = sysconf(_SC_PAGESIZE);
munmap(mmap_aux_buffer, mmap_header->aux_size);
mmap_aux_buffer = nullptr;
munmap(mmap_header, page_size + PT_PERF_DATA_SIZE);
mmap_header = nullptr;
}
}

void PerfCounters::reset(Ticks ticks_period) {
if (!enable) {
return;
Expand Down Expand Up @@ -689,6 +818,10 @@ void PerfCounters::reset(Ticks ticks_period) {
FATAL() << "Failed to SETOWN_EX ticks event fd";
}
make_counter_async(fd_ticks_interrupt, PerfCounters::TIME_SLICE_SIGNAL);

if (pt_state) {
start_pt(tid, *pt_state);
}
} else {
LOG(debug) << "Resetting counters with period " << ticks_period;

Expand All @@ -707,6 +840,10 @@ void PerfCounters::reset(Ticks ticks_period) {

infallible_perf_event_reset_if_open(fd_ticks_in_transaction);
infallible_perf_event_enable_if_open(fd_ticks_in_transaction);

if (pt_state) {
infallible_perf_event_enable_if_open(pt_state->pt_perf_event_fd);
}
}

started = true;
Expand All @@ -724,6 +861,9 @@ void PerfCounters::stop() {
return;
}
started = false;
if (pt_state) {
pt_state->stop();
}

fd_ticks_interrupt.close();
fd_ticks_measure.close();
Expand All @@ -744,6 +884,9 @@ void PerfCounters::stop_counting() {
infallible_perf_event_disable_if_open(fd_minus_ticks_measure);
infallible_perf_event_disable_if_open(fd_ticks_measure);
infallible_perf_event_disable_if_open(fd_ticks_in_transaction);
if (pt_state) {
infallible_perf_event_disable_if_open(pt_state->pt_perf_event_fd);
}
}
}

Expand All @@ -768,6 +911,10 @@ Ticks PerfCounters::read_ticks(Task* t) {
return 0;
}

if (pt_state) {
flush_pt(*pt_state);
}

if (fd_ticks_in_transaction.is_open()) {
uint64_t transaction_ticks = read_counter(fd_ticks_in_transaction);
if (transaction_ticks > 0) {
Expand Down
Loading

0 comments on commit 93767cb

Please sign in to comment.