Skip to content

Commit

Permalink
i#1921 native sig: Deliver signals to unmanaged threads
Browse files Browse the repository at this point in the history
When a signal arrives in an completely unmanaged thread with no
dcontext, typically because DR is detaching, we now deliver the signal
if the application has a handler for it.  This requires adding support
for no dcontext to several parts of the frame setup code even beyond
what was added in PR #4603 for temporarily-native threads.

We have to save the app's handler when we detach a thread so we know
where to send a native signal.  Full support is complex when we're
cleaning up and have no dynamic storage, so we use a single global
handler per signal.  We detect whether multiple handlers are in
operation in this single DR instance (quite rare: only custom
non-pthread clones have this behavior) and in that case we abort like
before on a native signal.  Adds ATOMIC_READ_1BYTE() to complement the
existing atomic operations for a cleaner read of the new multi-handler
flag.

Delivering the frame often overlaps with DR's frame and even DR's
stack usage while delivering, if the app has no sigaltstack.  We add
logic to detect this overlap and avoid clobbering the stack memory.

Alarm signals are still dropped, since they can arrive mid-thread-init
when it is even harder to deliver.

Adds a new test api.detach_signal which creates 10 threads who all sit
in a loop sending 4 different alternating signals (SIGSEGV, SIGBUS,
SIGURG, SIGALRM) while the main thread attaches and then detaches.
When run in debug build, many many signals arrive in post-detach
threads, since detach takes a while to do debug cleanup, exercising
the new code.

Adds a new RSTAT for native signals so we can identify when this
happens in release build.  Exports the stat to the API and uses it to
check that at least some signals were delivered natively in the new
test.

Removes the fatal error on a signal arriving with no dcontext.  But,
non-ignore default signal actions when no handler is present are still
not fully handled, along with multi-sighand-processes as mentioned,
and the fatal error remains in those cases.  For default actions,
since the process is going to terminate anyway, the only shortcoming
of this is whether a core is generated and whether the proper process
exit code is raised.

Issue: #1921
  • Loading branch information
derekbruening committed Dec 23, 2020
1 parent d53fa0e commit 8fe0e9e
Show file tree
Hide file tree
Showing 10 changed files with 431 additions and 98 deletions.
34 changes: 34 additions & 0 deletions core/arch/arch_exports.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ emit_detach_callback_final_jmp(dcontext_t *dcontext,
/* note that the microsoft compiler will not enregister variables across asm
* blocks that touch those registers, so don't need to worry about clobbering
* eax and ebx */
# define ATOMIC_1BYTE_READ(addr_src, addr_res) \
do { \
*(BYTE *)(addr_res) = *(volatile BYTE *)(addr_src); \
} while (0)
# define ATOMIC_1BYTE_WRITE(target, value, hot_patch) \
do { \
ASSERT(sizeof(value) == 1); \
Expand Down Expand Up @@ -482,6 +486,13 @@ atomic_add_exchange_int64(volatile int64 *var, int64 value)
/* IA-32 vol 3 7.1.4: processor will internally suppress the bus lock
* if target is within cache line.
*/
# define ATOMIC_1BYTE_READ(addr_src, addr_res) \
do { \
__asm__ __volatile__("movb %1, %%al; movb %%al, %0" \
: "=m"(*(byte *)(addr_res)) \
: "m"(*(byte *)(addr_src)) \
: "al"); \
} while (0)
# define ATOMIC_1BYTE_WRITE(target, value, hot_patch) \
do { \
/* allow a constant to be passed in by supplying our own lvalue */ \
Expand Down Expand Up @@ -644,6 +655,14 @@ atomic_add_exchange_int64(volatile int64 *var, int64 value)

# elif defined(DR_HOST_AARCH64)

# define ATOMIC_1BYTE_READ(addr_src, addr_res) \
do { \
/* We use "load-acquire" to add a barrier. */ \
__asm__ __volatile__("ldarb w0, [%0]; strb w0, [%1]" \
: \
: "r"(addr_src), "r"(addr_res) \
: "w0", "memory"); \
} while (0)
# define ATOMIC_1BYTE_WRITE(target, value, hot_patch) \
do { \
ASSERT(sizeof(value) == 1); \
Expand Down Expand Up @@ -829,6 +848,13 @@ atomic_dec_becomes_zero(volatile int *var)

# elif defined(DR_HOST_ARM)

# define ATOMIC_1BYTE_READ(addr_src, addr_res) \
do { \
__asm__ __volatile__("ldrb r0, [%0]; dmb ish; strb r0, [%1]" \
: \
: "r"(addr_src), "r"(addr_res) \
: "r0", "memory"); \
} while (0)
# define ATOMIC_1BYTE_WRITE(target, value, hot_patch) \
do { \
ASSERT(sizeof(value) == 1); \
Expand Down Expand Up @@ -1116,6 +1142,14 @@ atomic_aligned_read_int(volatile int *var)
return temp;
}

static inline bool
atomic_read_bool(volatile bool *var)
{
bool temp;
ATOMIC_1BYTE_READ(var, &temp);
return temp;
}

#ifdef X64
static inline int64
atomic_aligned_read_int64(volatile int64 *var)
Expand Down
2 changes: 2 additions & 0 deletions core/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ typedef struct _dr_stats_t {
uint64 peak_vmm_blocks_reach_special_heap;
/** Peak number of memory blocks used for other reachable mappings. */
uint64 peak_vmm_blocks_reach_special_mmap;
/** Signals delivered to native threads. */
uint64 num_native_signals;
} dr_stats_t;

/**
Expand Down
1 change: 1 addition & 0 deletions core/lib/statsx.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ STATS_DEF("SetContextThread w/o CONTEXT_CONTROL", num_app_setcontext_no_control)
STATS_DEF("Re-takeovers after native", num_retakeover_after_native)
#else
RSTATS_DEF("Total signals delivered", num_signals)
RSTATS_DEF("Total signals delivered to native threads", num_native_signals)
RSTATS_DEF("Signals dropped", num_signals_dropped)
RSTATS_DEF("Signals in coarse units delayed", num_signals_coarse_delayed)
#endif
Expand Down
Loading

0 comments on commit 8fe0e9e

Please sign in to comment.