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

DONTMERGE Rework of PR 1857 #1944

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CI-Examples/redis/redis-server.manifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

# Redis manifest file example

sgx.enable_stats = true

################################## GRAMINE ####################################

# Entrypoint binary which Gramine invokes.
Expand Down
10 changes: 1 addition & 9 deletions pal/src/host/linux-sgx/enclave_framework.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,27 +67,19 @@ bool sgx_is_valid_untrusted_ptr(const void* _addr, size_t size, size_t alignment
}

/*
* When DEBUG is enabled, we run sgx_profile_sample() during asynchronous enclave exit (AEX), which
* We run some functions (e.g. sgx_profile_sample()) during asynchronous enclave exit (AEX), which
* uses the stack. Make sure to update URSP so that the AEX handler does not overwrite the part of
* the stack that we just allocated.
*
* (Recall that URSP is an outside stack pointer, saved by EENTER and restored on AEX by the SGX
* hardware itself.)
*/
#ifdef DEBUG

#define UPDATE_USTACK(_ustack) \
do { \
SET_ENCLAVE_TCB(ustack, _ustack); \
GET_ENCLAVE_TCB(gpr)->ursp = (uint64_t)_ustack; \
} while(0)

#else

#define UPDATE_USTACK(_ustack) SET_ENCLAVE_TCB(ustack, _ustack)

#endif

void* sgx_prepare_ustack(void) {
void* old_ustack = GET_ENCLAVE_TCB(ustack);

Expand Down
3 changes: 2 additions & 1 deletion pal/src/host/linux-sgx/enclave_ocalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,9 @@ static long sgx_exitless_ocall(uint64_t code, void* ocall_args) {
}
}

long result = COPY_UNTRUSTED_VALUE(&req->result);
sgx_reset_ustack(old_ustack);
return COPY_UNTRUSTED_VALUE(&req->result);
return result;
}

__attribute_no_sanitize_address
Expand Down
27 changes: 17 additions & 10 deletions pal/src/host/linux-sgx/host_entry.S
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

.extern tcs_base
.extern g_in_aex_profiling
.extern dump_and_reset_stats

.global sgx_ecall
.type sgx_ecall, @function
Expand Down Expand Up @@ -70,9 +71,9 @@ async_exit_pointer:
# increment per-thread AEX counter for stats
lock incq %gs:PAL_HOST_TCB_AEX_CNT

#ifdef DEBUG
# Inform that we are in AEX profiling code
movb $1, %gs:PAL_HOST_TCB_IN_AEX_PROF

# Save ERESUME parameters
pushq %rax
.cfi_adjust_cfa_offset 8
Expand All @@ -81,18 +82,25 @@ async_exit_pointer:
pushq %rcx
.cfi_adjust_cfa_offset 8

# Align stack (required by System V AMD64 ABI)
pushq %rbp
.cfi_adjust_cfa_offset 8
movq %rsp, %rbp
.cfi_offset %rbp, -16
.cfi_def_cfa_register %rbp
andq $~0xF, %rsp
andq $~0xF, %rsp # Required by System V AMD64 ABI.

#ifdef DEBUG
# Call sgx_profile_sample_aex with %rdi = TCS
movq %rbx, %rdi
call sgx_profile_sample_aex
#endif

call dump_and_reset_stats

# Restore stack
movq %rbp, %rsp
.cfi_def_cfa_register %rsp
popq %rbp
.cfi_def_cfa %rsp, 8

# Restore ERESUME parameters
popq %rcx
Expand All @@ -101,9 +109,8 @@ async_exit_pointer:
.cfi_adjust_cfa_offset -8
popq %rax
.cfi_adjust_cfa_offset -8
movb $0, %gs:PAL_HOST_TCB_IN_AEX_PROF
#endif

movb $0, %gs:PAL_HOST_TCB_IN_AEX_PROF
.cfi_endproc

# fall-through to ERESUME
Expand Down Expand Up @@ -143,25 +150,25 @@ sgx_raise:
.cfi_offset %rbp, -16
.cfi_def_cfa_register %rbp

#if DEBUG
# Adjust stack and save RDI
subq $8, %rsp
andq $~0xF, %rsp # Required by System V AMD64 ABI.
movq %rdi, -8(%rbp)

#if DEBUG
# Call sgx_profile_sample_ocall_outer with RBX (ocall handler)
movq %rbx, %rdi
call sgx_profile_sample_ocall_outer

# Call sgx_profile_sample_ocall_inner with RDX (pointer to in-enclave context)
movq %rdx, %rdi
call sgx_profile_sample_ocall_inner
#endif

call dump_and_reset_stats

# Restore RDI
movq -8(%rbp), %rdi
#else
andq $~0xF, %rsp # Required by System V AMD64 ABI.
#endif

callq *%rbx

Expand Down
109 changes: 105 additions & 4 deletions pal/src/host/linux-sgx/host_exception.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,47 @@
#include <stdbool.h>

#include "api.h"
#include "assert.h"
#include "cpu.h"
#include "debug_map.h"
#include "gdb_integration/sgx_gdb.h"
#include "host_internal.h"
#include "host_syscall.h"
#include "pal_rpc_queue.h"
#include "pal_tcb.h"
#include "sigreturn.h"
#include "ucontext.h"

static const int ASYNC_SIGNALS[] = {SIGTERM, SIGCONT};

/*
* If no SGX-stats reset is in flight, this variable is zero.
*
* Upon user-induced SIGUSR1 on some thread (below happens in signal handling context):
* 1. If `g_stats_reset_leader_tid == 0`, then it is set to the TID of this thread -- this thread
* is designated to be the "leader" of SGX-stats reset flow, and it will broadcast SIGUSR1 to
* all other threads on the first AEX (since we can't do any complex logic in signal handling
* context, we postpone to the normal context which starts right-after an AEX event).
* 2. If `g_stats_reset_leader_tid != 0`, then it means that an SGX-stats reset flow is in flight.
* Two cases are possible:
* a. If PID of sending process is the current PID, then the signal was sent by the "leader"
* and this thread is a "follower" -- it sets `reset_stats = true` in its TCB, so that
* this thread's statistics are dumped and reset on the next AEX.
* b. If PID of sending process is not the current PID, then the signal was sent by the user
* and this is a new "SGX-stats reset" event from the user. Since the previous flow is
* still in flight, the thread must ignore this signal.
*
* On each AEX, each thread checks (below happens in normal context):
* 1. If `g_stats_reset_leader_tid == 0`, do nothing (no SGX-stats reset is in flight).
* 2. If `g_stats_reset_leader_tid == gettid()`, then this is the "leader" thread and it must
* broadcast SIGUSR1 to all other threads and wait until they perform their SGX-stats resets.
* After all threads are done, the "leader" resets `g_stats_reset_leader_tid` to zero.
* 3. Else, this is the "follower" thread and it must perform its SGX-stats reset.
*
* Application threads on Linux can never be 0, so this "no-op" default is safe.
*/
static int g_stats_reset_leader_tid = 0;

static int block_signal(int sig, bool block) {
int how = block ? SIG_BLOCK : SIG_UNBLOCK;

Expand Down Expand Up @@ -188,17 +220,38 @@ static void handle_dummy_signal(int signum, siginfo_t* info, struct ucontext* uc
/* we need this handler to interrupt blocking syscalls in RPC threads */
}

#ifdef DEBUG
static void handle_sigusr1(int signum, siginfo_t* info, struct ucontext* uc) {
__UNUSED(signum);
__UNUSED(info);
__UNUSED(uc);

if (g_sgx_enable_stats) {
int expected_tid = 0;
if (__atomic_compare_exchange_n(&g_stats_reset_leader_tid, &expected_tid,
DO_SYSCALL(gettid), /*weak=*/false,
__ATOMIC_ACQ_REL, __ATOMIC_RELAXED) == true) {
/* first thread that gets SIGUSR1, the CAS above designated it as the "leader" */
PAL_HOST_TCB* tcb = pal_get_host_tcb();
tcb->reset_stats = true;
} else {
/* thread gets SIGUSR1, check if this is a signal from the "leader" */
if (info->si_pid == g_host_pid) {
PAL_HOST_TCB* tcb = pal_get_host_tcb();
assert(!tcb->reset_stats);
tcb->reset_stats = true;
} else {
log_warning("Received SIGUSR1 from user, but there is another SGX-stats reset "
"in flight; ignoring it");
}
}
}

#ifdef DEBUG
if (g_pal_enclave.profile_enable) {
__atomic_store_n(&g_trigger_profile_reinit, true, __ATOMIC_RELEASE);
}
}
#endif /* DEBUG */
}

int sgx_signal_setup(void) {
int ret;
Expand Down Expand Up @@ -238,11 +291,9 @@ int sgx_signal_setup(void) {
if (ret < 0)
goto err;

#ifdef DEBUG
ret = set_signal_handler(SIGUSR1, handle_sigusr1);
if (ret < 0)
goto err;
#endif /* DEBUG */

/* SIGUSR2 is reserved for Gramine usage: interrupting blocking syscalls in RPC threads.
* We block SIGUSR2 in enclave threads; it is unblocked by each RPC thread explicitly. */
Expand Down Expand Up @@ -274,3 +325,53 @@ void pal_describe_location(uintptr_t addr, char* buf, size_t buf_size) {
#endif
default_describe_location(addr, buf, buf_size);
}

static size_t send_sigusr1_to_followers(pid_t leader_tid) {
size_t followers_num = 0;

/* we re-use DBGINFO_ADDR special variable (that is primarily used by GDB for debugging),
* fortunately this variable is set up even in non-debug builds */
for (size_t i = 0; i < MAX_DBG_THREADS; i++) {
int follower_tid = ((struct enclave_dbginfo*)DBGINFO_ADDR)->thread_tids[i];
if (!follower_tid || follower_tid == leader_tid)
continue;

DO_SYSCALL(tkill, follower_tid, SIGUSR1);
followers_num++;
}
return followers_num;
}

/* called on each AEX and OCALL (in normal context), see host_entry.S */
void dump_and_reset_stats(void) {
static size_t followers_visited_num = 0; /* note `static`, it is a global var */

if (!g_sgx_enable_stats)
return;

int leader_tid = __atomic_load_n(&g_stats_reset_leader_tid, __ATOMIC_ACQUIRE);
if (!leader_tid)
return;

PAL_HOST_TCB* tcb = pal_get_host_tcb();
if (!tcb->reset_stats)
return;

if (DO_SYSCALL(gettid) == leader_tid) {
log_always("----- DUMPING and RESETTING SGX STATS -----");
size_t followers_num = send_sigusr1_to_followers(leader_tid);

while ((__atomic_load_n(&followers_visited_num, __ATOMIC_ACQUIRE)) < followers_num)
DO_SYSCALL(sched_yield);

update_and_print_stats(/*process_wide=*/true);
pal_host_tcb_reset_stats();
__atomic_store_n(&followers_visited_num, 0, __ATOMIC_RELEASE);

__atomic_store_n(&g_stats_reset_leader_tid, 0, __ATOMIC_RELEASE);
} else {
update_and_print_stats(/*process_wide=*/false);
pal_host_tcb_reset_stats();
__atomic_fetch_add(&followers_visited_num, 1, __ATOMIC_ACQ_REL);
}
}
44 changes: 32 additions & 12 deletions pal/src/host/linux-sgx/host_thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ bool g_sgx_enable_stats = false;

/* this function is called only on thread/process exit (never in the middle of thread exec) */
void update_and_print_stats(bool process_wide) {
static atomic_ulong g_eenter_cnt = 0;
static atomic_ulong g_eexit_cnt = 0;
static atomic_ulong g_aex_cnt = 0;
static atomic_ulong g_sync_signal_cnt = 0;
static atomic_ulong g_async_signal_cnt = 0;
static uint64_t g_eenter_cnt = 0;
static uint64_t g_eexit_cnt = 0;
static uint64_t g_aex_cnt = 0;
static uint64_t g_sync_signal_cnt = 0;
static uint64_t g_async_signal_cnt = 0;

if (!g_sgx_enable_stats)
return;
Expand All @@ -50,11 +50,11 @@ void update_and_print_stats(bool process_wide) {
tid, tcb->eenter_cnt, tcb->eexit_cnt, tcb->aex_cnt,
tcb->sync_signal_cnt, tcb->async_signal_cnt);

g_eenter_cnt += tcb->eenter_cnt;
g_eexit_cnt += tcb->eexit_cnt;
g_aex_cnt += tcb->aex_cnt;
g_sync_signal_cnt += tcb->sync_signal_cnt;
g_async_signal_cnt += tcb->async_signal_cnt;
__atomic_fetch_add(&g_eenter_cnt, tcb->eenter_cnt, __ATOMIC_ACQ_REL);
__atomic_fetch_add(&g_eexit_cnt, tcb->eexit_cnt, __ATOMIC_ACQ_REL);
__atomic_fetch_add(&g_aex_cnt, tcb->aex_cnt, __ATOMIC_ACQ_REL);
__atomic_fetch_add(&g_sync_signal_cnt, tcb->sync_signal_cnt, __ATOMIC_ACQ_REL);
__atomic_fetch_add(&g_async_signal_cnt, tcb->async_signal_cnt, __ATOMIC_ACQ_REL);

if (process_wide) {
int pid = g_host_pid;
Expand All @@ -65,8 +65,17 @@ void update_and_print_stats(bool process_wide) {
" # of AEXs: %lu\n"
" # of sync signals: %lu\n"
" # of async signals: %lu",
pid, g_eenter_cnt, g_eexit_cnt, g_aex_cnt,
g_sync_signal_cnt, g_async_signal_cnt);
pid, __atomic_load_n(&g_eenter_cnt, __ATOMIC_ACQUIRE),
__atomic_load_n(&g_eexit_cnt, __ATOMIC_ACQUIRE),
__atomic_load_n(&g_aex_cnt, __ATOMIC_ACQUIRE),
__atomic_load_n(&g_sync_signal_cnt, __ATOMIC_ACQUIRE),
__atomic_load_n(&g_async_signal_cnt, __ATOMIC_ACQUIRE));

__atomic_store_n(&g_eenter_cnt, 0, __ATOMIC_RELEASE);
__atomic_store_n(&g_eexit_cnt, 0, __ATOMIC_RELEASE);
__atomic_store_n(&g_aex_cnt, 0, __ATOMIC_RELEASE);
__atomic_store_n(&g_sync_signal_cnt, 0, __ATOMIC_RELEASE);
__atomic_store_n(&g_async_signal_cnt, 0, __ATOMIC_RELEASE);
}
}

Expand All @@ -81,12 +90,23 @@ void pal_host_tcb_init(PAL_HOST_TCB* tcb, void* stack, void* alt_stack) {
tcb->aex_cnt = 0;
tcb->sync_signal_cnt = 0;
tcb->async_signal_cnt = 0;
tcb->reset_stats = false;

tcb->profile_sample_time = 0;

tcb->last_async_event = PAL_EVENT_NO_EVENT;
}

void pal_host_tcb_reset_stats(void) {
PAL_HOST_TCB* tcb = pal_get_host_tcb();
tcb->eenter_cnt = 0;
tcb->eexit_cnt = 0;
tcb->aex_cnt = 0;
tcb->sync_signal_cnt = 0;
tcb->async_signal_cnt = 0;
tcb->reset_stats = false;
}

int create_tcs_mapper(void* tcs_base, unsigned int thread_num) {
sgx_arch_tcs_t* enclave_tcs = tcs_base;

Expand Down
3 changes: 3 additions & 0 deletions pal/src/host/linux-sgx/pal_tcb.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,12 @@ typedef struct pal_host_tcb {
uint64_t profile_sample_time; /* last time sgx_profile_sample() recorded a sample */
int32_t last_async_event; /* last async signal, reported to the enclave on ocall return */
int* start_status_ptr; /* pointer to return value of clone_thread */
bool reset_stats; /* if true, dump SGX stats and reset them on next AEX event */
} PAL_HOST_TCB;

extern void pal_host_tcb_init(PAL_HOST_TCB* tcb, void* stack, void* alt_stack);
extern void pal_host_tcb_reset_stats(void);
void dump_and_reset_stats(void);

static inline PAL_HOST_TCB* pal_get_host_tcb(void) {
PAL_HOST_TCB* tcb;
Expand Down