Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i#4014 dr$sim phys: Check privileges for pagemap #5531

Merged
merged 9 commits into from
Jun 16, 2022
5 changes: 4 additions & 1 deletion clients/drcachesim/reader/ipc_reader.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2015-2020 Google, Inc. All rights reserved.
* Copyright (c) 2015-2022 Google, Inc. All rights reserved.
* **********************************************************/

/*
Expand Down Expand Up @@ -96,6 +96,9 @@ ipc_reader_t::read_next_entry()
if (cur_buf_ >= end_buf_) {
ssize_t sz = pipe_.read(buf_, sizeof(buf_)); // blocking read
if (sz < 0 || sz % sizeof(*end_buf_) != 0) {
// If called again at eof, do not return the footer: return an error.
if (at_eof_)
return nullptr;
// We aren't able to easily distinguish truncation from a clean
// end (we could at least ensure the prior entry was a thread exit
// I suppose).
Expand Down
5 changes: 5 additions & 0 deletions clients/drcachesim/tests/offline-phys_nopriv.templatex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Unable to open pagemap for physical addresses: check privileges.
derekbruening marked this conversation as resolved.
Show resolved Hide resolved
Output format:
<record#>: T<tid> <record details>
------------------------------------------------------------
ERROR: failed to initialize analyzer: Failed to create analysis tool: Tool failed to initialize: Failed to load binaries: Failed to parse module file
8 changes: 4 additions & 4 deletions clients/drcachesim/tests/phys.templatex
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
Cache simulation results:
Core #0 \(1 thread\(s\)\)
L1I stats:
Hits: *[0-9]*[,\.]?...
Misses: *[0-9]..?
Hits: *[0-9,\.]*
Misses: *[0-9,\.]*
Compulsory misses: *[0-9,\.]*
Invalidations: *0
.* Miss rate: [0-3][,\.]..%
Expand All @@ -18,8 +18,8 @@ Core #1 \(0 thread\(s\)\)
Core #2 \(0 thread\(s\)\)
Core #3 \(0 thread\(s\)\)
LL stats:
Hits: *[0-9]..?
Misses: *[0-9]*[,\.]?..?.?
Hits: *[0-9,\.]*
Misses: *[0-9,\.]*
Compulsory misses: *[0-9,\.]*
Invalidations: *0
.* Local miss rate: *[0-9,.]*%
Expand Down
57 changes: 48 additions & 9 deletions clients/drcachesim/tracer/physaddr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
# include <unistd.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <linux/capability.h>
# include <fstream>
#endif
#include "physaddr.h"
#include "../common/options.h"
Expand All @@ -54,9 +56,10 @@
# define PAGEMAP_VALID 0x8000000000000000
# define PAGEMAP_SWAP 0x4000000000000000
# define PAGEMAP_PFN 0x007fffffffffffff
static const addr_t PAGE_INVALID = (addr_t)-1;
#endif

std::atomic<bool> physaddr_t::has_privileges_;

physaddr_t::physaddr_t()
#ifdef LINUX
: page_size_(dr_page_size())
Expand All @@ -83,19 +86,57 @@ physaddr_t::physaddr_t()
physaddr_t::~physaddr_t()
{
#ifdef LINUX
NOTIFY(1,
"physaddr: hit cache: " UINT64_FORMAT_STRING
", hit table " UINT64_FORMAT_STRING ", miss " UINT64_FORMAT_STRING "\n",
num_hit_cache_, num_hit_table_, num_miss_);
if (num_miss_ > 0) {
NOTIFY(1,
"physaddr: hit cache: " UINT64_FORMAT_STRING
", hit table " UINT64_FORMAT_STRING ", miss " UINT64_FORMAT_STRING "\n",
num_hit_cache_, num_hit_table_, num_miss_);
}
if (v2p_ != nullptr)
dr_hashtable_destroy(dr_get_current_drcontext(), v2p_);
#endif
}

bool
physaddr_t::global_init()
{
// This is invoked at process init time, so we can use heap in C++ classes
// without affecting statically-linked dr$sim.

// We need CAP_SYS_ADMIN to get valid data out of /proc/self/pagemap
// (the kernel lets us read but just feeds us zeroes otherwise).
constexpr int MAX_STATUS_FNAME = 64;
char statf[MAX_STATUS_FNAME];
dr_snprintf(statf, BUFFER_SIZE_ELEMENTS(statf), "/proc/%d/status", getpid());
NULL_TERMINATE_BUFFER(statf);
std::ifstream file(statf);
std::string line;
const std::string want("CapEff");
while (std::getline(file, line)) {
if (line.compare(0, want.size(), want) != 0)
continue;
std::istringstream ss(line);
std::string unused;
uint64_t bits;
if (ss >> unused >> std::hex >> bits) {
if (TESTALL(1 << CAP_SYS_ADMIN, bits)) {
has_privileges_ = true;
NOTIFY(1, "Has CAP_SYS_ADMIN\n");
} else
NOTIFY(1, "Does NOT have CAP_SYS_ADMIN\n");
}
}
DR_ASSERT(std::atomic_is_lock_free(&has_privileges_));
return has_privileges_;
}

bool
physaddr_t::init()
{
#ifdef LINUX
if (!has_privileges_)
return false;

// Some threads may not do much, so start out small.
constexpr int V2P_INITIAL_BITS = 9;
// The hashtable lookup performance is important.
Expand Down Expand Up @@ -205,11 +246,9 @@ physaddr_t::virtual2physical(void *drcontext, addr_t virt, OUT addr_t *phys,
NOTIFY(1, "v2p failure: entry is invalid for %p\n", vpage);
return false;
}
// TODO i#4014: On recent kernels unprivileged reads succeed but are passed
// a 0 PFN. Since that could be valid, we need to check our capabilities
// to decide. Until then, we're currently returning 0 for every unprivileged query.
// Store 0 as a sentinel since 0 means no entry.
addr_t ppage = (addr_t)((entry & PAGEMAP_PFN) << page_bits_);
// Despite the kernel handing out a 0 PFN for unprivileged reads, 0 is a valid
// possible PFN.
// Store 0 as a sentinel since 0 means no entry.
dr_hashtable_add(drcontext, v2p_, vpage,
reinterpret_cast<void *>(ppage == 0 ? ZERO_ADDR_PAYLOAD : ppage));
Expand Down
13 changes: 10 additions & 3 deletions clients/drcachesim/tracer/physaddr.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@
#ifndef _PHYSADDR_H_
#define _PHYSADDR_H_ 1

#include <fstream>
#include <unordered_map>
#include <atomic>
#include "dr_api.h"
#include "hashtable.h"
#include "../common/trace_entry.h"
Expand All @@ -61,6 +60,12 @@ class physaddr_t {
virtual2physical(void *drcontext, addr_t virt, OUT addr_t *phys,
OUT bool *from_cache = nullptr);

// This must be called once prior to any instance variables.
// (If this class weren't used in a DR client context we could use a C++
// mutex or pthread do-once but those are not safe here.)
static bool
global_init();

private:
#ifdef LINUX
inline addr_t
Expand Down Expand Up @@ -93,13 +98,15 @@ class physaddr_t {
// The drcontainers hashtable is too slow due to the extra dereferences:
// we need an open-addressed table.
void *v2p_;
static constexpr addr_t PAGE_INVALID = (addr_t)-1;
// With hashtable_t nullptr is how non-existence is shown, so we store
// an actual 0 address (can happen for physical) as this sentinel.
static constexpr addr_t ZERO_ADDR_PAYLOAD = 1;
static constexpr addr_t ZERO_ADDR_PAYLOAD = PAGE_INVALID;
unsigned int count_;
uint64_t num_hit_cache_;
uint64_t num_hit_table_;
uint64_t num_miss_;
static std::atomic<bool> has_privileges_;
#endif
};

Expand Down
39 changes: 21 additions & 18 deletions clients/drcachesim/tracer/tracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,6 @@ static uint64 num_v2p_writeouts;
static uint64 num_phys_markers;
static volatile bool exited_process;

/* virtual to physical translation */
static std::atomic<bool> have_phys;

static drmgr_priority_t pri_pre_bbdup = { sizeof(drmgr_priority_t),
DRMGR_PRIORITY_NAME_MEMTRACE, NULL, NULL,
DRMGR_PRIORITY_APP2APP_DRBBDUP - 1 };
Expand Down Expand Up @@ -990,14 +987,15 @@ output_buffer(void *drcontext, per_thread_t *data, byte *buf_base, byte *buf_ptr
return current_num_refs;
}

// Should be called only for have_phys and -use_physical.
// Should be called only for -use_physical.
// Returns the byte count to skip in the trace buffer (due to shifting some headers
// to the v2p buffer).
static size_t
process_buffer_for_physaddr(void *drcontext, per_thread_t *data, size_t header_size,
byte *buf_ptr)
{
ASSERT(have_phys, "Caller must check for use_physical being enabled");
ASSERT(op_use_physical.get_value(),
"Caller must check for use_physical being enabled");
byte *v2p_ptr = data->v2p_buf;
size_t skip = 0;
bool emitted = false;
Expand Down Expand Up @@ -1207,7 +1205,7 @@ memtrace(void *drcontext, bool skip_size_cap)
}
}
size_t skip = 0;
if (have_phys && op_use_physical.get_value()) {
if (op_use_physical.get_value()) {
skip = process_buffer_for_physaddr(drcontext, data, header_size, buf_ptr);
}
current_num_refs +=
Expand Down Expand Up @@ -1566,7 +1564,7 @@ instrument_delay_instrs(void *drcontext, void *tag, instrlist_t *ilist, user_dat
// Instrument to add a full instr entry for the first instr.
adjust = instru->instrument_instr(drcontext, tag, &ud->instru_field, ilist, where,
reg_ptr, adjust, ud->delay_instrs[0]);
if (have_phys && op_use_physical.get_value()) {
if (op_use_physical.get_value()) {
// No instr bundle if physical-2-virtual since instr bundle may
// cross page bundary.
int i;
Expand Down Expand Up @@ -2719,12 +2717,20 @@ event_inscount_app_instruction(void *drcontext, void *tag, instrlist_t *bb,
* Top level.
*/

static bool
optimizations_disabled()
derekbruening marked this conversation as resolved.
Show resolved Hide resolved
{
// We cannot elide addresses or ignore offsets when we need to translate
// all addresses during tracing.
return op_disable_optimizations.get_value() || op_use_physical.get_value();
}

static offline_file_type_t
get_file_type()
{
offline_file_type_t file_type =
op_L0_filter.get_value() ? OFFLINE_FILE_TYPE_FILTERED : OFFLINE_FILE_TYPE_DEFAULT;
if (op_disable_optimizations.get_value()) {
if (optimizations_disabled()) {
file_type = static_cast<offline_file_type_t>(file_type |
OFFLINE_FILE_TYPE_NO_OPTIMIZATIONS);
}
Expand Down Expand Up @@ -2770,9 +2776,9 @@ init_thread_in_process(void *drcontext)

if (op_use_physical.get_value()) {
if (!data->physaddr.init()) {
derekbruening marked this conversation as resolved.
Show resolved Hide resolved
have_phys = false;
NOTIFY(0, "Unable to open pagemap: using virtual addresses for thread T%d.\n",
dr_get_thread_id(drcontext));
FATAL("Unable to open pagemap for physical addresses in thread T%d: check "
"privileges.\n",
dr_get_thread_id(drcontext));
}
}

Expand Down Expand Up @@ -3170,7 +3176,6 @@ drmemtrace_client_main(client_id_t id, int argc, const char *argv[])
DR_ASSERT(std::atomic_is_lock_free(&reached_trace_after_instrs));
DR_ASSERT(std::atomic_is_lock_free(&tracing_disabled));
DR_ASSERT(std::atomic_is_lock_free(&tracing_window));
DR_ASSERT(std::atomic_is_lock_free(&have_phys));

drreg_init_and_fill_vector(&scratch_reserve_vec, true);
#ifdef X86
Expand All @@ -3180,11 +3185,6 @@ drmemtrace_client_main(client_id_t id, int argc, const char *argv[])
}
#endif

if (op_use_physical.get_value()) {
// This will be set to false if any thread fails.
have_phys = true;
}

if (op_offline.get_value()) {
void *placement;
if (!init_offline_dir()) {
Expand All @@ -3195,7 +3195,7 @@ drmemtrace_client_main(client_id_t id, int argc, const char *argv[])
placement = dr_global_alloc(MAX_INSTRU_SIZE);
instru = new (placement) offline_instru_t(
insert_load_buf_ptr, op_L0_filter.get_value(), &scratch_reserve_vec,
file_ops_func.write_file, module_file, op_disable_optimizations.get_value());
file_ops_func.write_file, module_file, optimizations_disabled());
} else {
void *placement;
/* we use placement new for better isolation */
Expand Down Expand Up @@ -3330,6 +3330,9 @@ drmemtrace_client_main(client_id_t id, int argc, const char *argv[])
/* We need the same is-buffer-zero checks in the instrumentation. */
thread_filtering_enabled = true;
}

if (op_use_physical.get_value() && !physaddr_t::global_init())
FATAL("Unable to open pagemap for physical addresses: check privileges.\n");
}

/* To support statically linked multiple clients, we add drmemtrace_client_main
Expand Down
12 changes: 10 additions & 2 deletions suite/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,7 @@ function(torun test key source native standalone_dr dr_ops exe_ops added_out pas
-D postcmd=${${key}_postcmd}
-D postcmd2=${${key}_postcmd2}
-D postcmd3=${${key}_postcmd3}
-D failok=${${key}_failok}
-D cmp=${CMAKE_CURRENT_BINARY_DIR}/${expectbase}.expect
-D code=${${key}_code}
-D capture=${${key}_runcmp_capture}
Expand Down Expand Up @@ -3348,8 +3349,12 @@ if (BUILD_CLIENTS)
# would need an analyzer to go examine addresses, or to use the view tool.
# For now these are just sanity tests that flipping on the option doesn't
# cause outright failure.
torunonly_drcachesim(phys ${ci_shared_app} "-use_physical" "")
torunonly_drcachesim(phys-threads client.annotation-concurrency "-use_physical"
set(tool.drcachesim.phys_SUDO_sudo ON)
set(tool.drcachesim.phys_SUDO_expectbase "phys")
torunonly_drcachesim(phys_SUDO ${ci_shared_app} "-use_physical" "")
set(tool.drcachesim.phys-threads_SUDO_sudo ON)
set(tool.drcachesim.phys-threads_SUDO_expectbase "phys-threads")
torunonly_drcachesim(phys-threads_SUDO client.annotation-concurrency "-use_physical"
"${annotation_test_args_shorter}")
endif ()

Expand Down Expand Up @@ -3756,6 +3761,9 @@ if (BUILD_CLIENTS)
set(tool.drcacheoff.phys_SUDO_expectbase "offline-phys")
torunonly_drcacheoff(phys_SUDO allasm_x86_64
"-use_physical" "@${test_mode_flag}@-simulator_type@view" "")
set(tool.drcacheoff.phys_nopriv_failok ON)
torunonly_drcacheoff(phys_nopriv allasm_x86_64
"-use_physical" "@${test_mode_flag}@-simulator_type@view" "")
endif ()

if (UNIX AND ZLIB_FOUND)
Expand Down
7 changes: 4 additions & 3 deletions suite/tests/runmulti.cmake
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# **********************************************************
# Copyright (c) 2015-2018 Google, Inc. All rights reserved.
# Copyright (c) 2015-2022 Google, Inc. All rights reserved.
# **********************************************************

# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -35,6 +35,7 @@
# * postcmd = post processing command to run
# * postcmdN (for N=2+) = additional post processing commands to run
# * cmp = the file containing the expected output
# * failok = if ON then non-zero exit codes are ignored for cmd
#
# A "*" in any command line will be glob-expanded right before running.
# If the command starts with "foreach@", instead of passing the glob-expansion
Expand Down Expand Up @@ -102,9 +103,9 @@ macro(process_cmdline line skip_empty err_and_out)
RESULT_VARIABLE cmd_result
ERROR_VARIABLE cmd_err
OUTPUT_VARIABLE cmd_out)
if (cmd_result)
if (cmd_result AND NOT failok)
derekbruening marked this conversation as resolved.
Show resolved Hide resolved
message(FATAL_ERROR "*** ${line} failed (${cmd_result}): ${cmd_err}***\n")
endif (cmd_result)
endif ()
endif ()
endif ()
set(${err_and_out} "${${err_and_out}}${cmd_err}${cmd_out}")
Expand Down