diff --git a/api/docs/release.dox b/api/docs/release.dox index 5a4f1dfab1d..f764d7bff56 100644 --- a/api/docs/release.dox +++ b/api/docs/release.dox @@ -143,6 +143,8 @@ The changes between version \DR_VERSION and 6.1.0 include: drreg_set_bb_properties(). - Added priority-controlled drmgr_register_thread_init_event_ex() and drmgr_register_thread_exit_event_ex(). + - Added \ref sec_drx_buf to drx: drx_buf_create_circular_buffer(), + drx_buf_create_trace_buffer(), and more. **************************************************
diff --git a/ext/drx/CMakeLists.txt b/ext/drx/CMakeLists.txt index edf1d8e6777..80963c6ece4 100755 --- a/ext/drx/CMakeLists.txt +++ b/ext/drx/CMakeLists.txt @@ -39,6 +39,7 @@ set(DynamoRIO_USE_LIBC OFF) set(srcs drx.c + drx_buf.c # add more here ) diff --git a/ext/drx/drx.c b/ext/drx/drx.c index c268ad6cbec..3b91f8dd119 100644 --- a/ext/drx/drx.c +++ b/ext/drx/drx.c @@ -87,6 +87,10 @@ static uint verbose = 0; } \ } while (0) +/* defined in drx_buf.c */ +bool drx_buf_init_library(void); +void drx_buf_exit_library(void); + /*************************************************************************** * INIT */ @@ -105,7 +109,7 @@ drx_init(void) note_base = drmgr_reserve_note_range(DRX_NOTE_COUNT); ASSERT(note_base != DRMGR_NOTE_NONE, "failed to reserve note range"); - return true; + return drx_buf_init_library(); } DR_EXPORT @@ -119,6 +123,7 @@ drx_exit() if (soft_kills_enabled) soft_kills_exit(); + drx_buf_exit_library(); drmgr_exit(); } diff --git a/ext/drx/drx.dox b/ext/drx/drx.dox index 55494c026ba..d4d4cdfb967 100644 --- a/ext/drx/drx.dox +++ b/ext/drx/drx.dox @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2013-2014 Google, Inc. All rights reserved. + * Copyright (c) 2013-2016 Google, Inc. All rights reserved. * **********************************************************/ /* @@ -71,4 +71,87 @@ parent to simply skip its own termination request. The nudge handler should normally handle multiple requests, as it is not uncommon for the parent to kill each child process through multiple mechanisms. +\section sec_drx_buf \p Buffer Filling API + +The \p drx library also demonstrates a minimalistic buffer API. Its API is +currently in flux. These buffers may contain traces of data gathered during +instrumentation, such as memory traces, instruction traces, etc. Note that +per-thread buffers are used for all implementations. There currently exist +three types of buffers. + +- \ref sec_drx_buf_trace +- \ref sec_drx_buf_circular +- \ref sec_drx_buf_circular_fast +- \ref sec_drx_buf_api +- \ref sec_drx_buf_no_api + +\section sec_drx_buf_trace Trace Buffer + +The trace buffer notifies the user when the buffer fills up and allows the +client to write the contents to disk or to a pipe, etc. Note that writing +multiple fields of a struct to the buffer runs the risk of the client being +notified that the buffer is filled before the entire struct has been written. +In order to circumvent this limitation, either write the element at the +highest offset in the struct first, so that the user never sees an +incompletely-written struct, or if this is not possible, allocate a buffer +whose size is a multiple of the size of the struct. + +\section sec_drx_buf_circular Circular Buffer + +This circular buffer will wrap around when it becomes full, and is used +when a client might only need to remember the most recent portion of a +sequence of events instead of recording an entire trace of events. This +circular buffer can be any size, but is specially optimized for a buffer +size of 65336. + +\section sec_drx_buf_circular_fast Fast Circular Buffer + +The only special case mentioned in \ref sec_drx_buf_circular is a buffer +of size 65336. Because the buffer is this size exactly, we can align it +to a 65336 byte boundary, and increment only the bottom two bytes of the +base pointer. By this method we are able to wrap around on overflow. + +Note that this buffer is very good for homogeneous writes, such as in the +sample client \p bbuf (see \ref API_samples), where we only write \p app_pc +sized values. Since the buffer cannot be a different size, when using a +structure it is a good idea to increment \p buf_ptr to a size that evenly +divides the size of the buffer. + +\section sec_drx_buf_api Using the Buffer API + +There is a single API for modifying the buffer which is compatible with each +of these buffer types. The user must generally load the buffer pointer into +a register, perform some store operations relative to the register, and then +finally update the buffer pointer to accommodate these stores. Using offsets +for subsequent fields in a structure is the most efficient method, but please +note the warning in \ref sec_drx_buf_trace, where one should either allocate +an integer multiple of the size of the struct, or always write the last field +of a struct first. + +\code +/* load the buffer pointer into reg_ptr */ +drx_buf_insert_load_buf_ptr(drcontext, buf, bb, inst, reg_ptr); +/* Store whatever is in the scratch reg to the buffer at offset 0, and then + * to offset 8. + */ +drx_buf_insert_buf_store(drcontext, buf, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch1), OPSZ_PTR, 0); +drx_buf_insert_buf_store(drcontext, buf, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch2), OPSZ_PTR, 8); +/* We wrote 16 bytes (8 bytes of scratch1 and 8 bytes of scratch2), so we + * increment the buffer pointer by that amount, using reg_tmp as a temporary + * scratch register. + */ +drx_buf_insert_update_buf_ptr(drcontext, buf, bb, inst, reg_ptr, + reg_tmp, sizeof(reg_t)*2); +\endcode + +\section sec_drx_buf_no_api Manually Modifying the Buffer + +It is possible to manually modify the buffer without calling +drx_buf_insert_buf_store(). The provided store routines are for convenience +only, to ensure that an app translation is set for each instruction. If a user +writes to the buffer without using the provided operations, please make sure an +app translation is set. + */ diff --git a/ext/drx/drx.h b/ext/drx/drx.h index 3fd7b956b47..f807d9845a9 100644 --- a/ext/drx/drx.h +++ b/ext/drx/drx.h @@ -258,6 +258,147 @@ drx_open_unique_appid_file(const char *dir, ptr_int_t id, const char *prefix, const char *suffix, uint extra_flags, char *result OUT, size_t result_len); +/*************************************************************************** + * BUFFER FILLING LIBRARY + */ + +/** + * Callback for \p drx_buf_init_trace_buffer(), called when the buffer has + * been filled. The valid buffer data is contained within the interval + * [buf_base..buf_base+size). + */ +typedef void (*drx_buf_full_cb_t)(void *drcontext, void *buf_base, size_t size); + +struct _drx_buf_t; + +/** Opaque handle which represents a buffer for use by the drx_buf framework. */ +typedef struct _drx_buf_t drx_buf_t; + +enum { + /** + * Buffer size to be specified in drx_buf_create_circular_buffer() in order + * to make use of the fast circular buffer optimization. + */ + DRX_BUF_FAST_CIRCULAR_BUFSZ = (1<<16) +}; + +/** + * Priorities of drmgr instrumentation passes used by drx_buf. Users + * of drx_buf can use the names #DRMGR_PRIORITY_NAME_DRX_BUF_INIT and + * #DRMGR_PRIORITY_NAME_DRX_BUF_EXIT in the drmgr_priority_t.before field + * or can use these numeric priorities in the drmgr_priority_t.priority + * field to ensure proper instrumentation pass ordering. + */ +enum { + /** Priority of drx_buf thread init event */ + DRMGR_PRIORITY_THREAD_INIT_DRX_BUF = -7500, + /** Priority of drx_buf thread exit event */ + DRMGR_PRIORITY_THREAD_EXIT_DRX_BUF = -7500, +}; + +/** Name of drx_buf thread init priority for buffer initialization. */ +#define DRMGR_PRIORITY_NAME_DRX_BUF_INIT "drx_buf.init" + +/** Name of drx_buf thread exit priority for buffer cleanup and full_cb callback. */ +#define DRMGR_PRIORITY_NAME_DRX_BUF_EXIT "drx_buf.exit" + +DR_EXPORT +/** + * Initializes the drx_buf extension with a circular buffer which wraps + * around when full. + * + * \note All buffer sizes are supported. However, a buffer size of + * #DRX_BUF_FAST_CIRCULAR_BUFSZ bytes is specially optimized for performance. + * This buffer is referred to explicitly in the documentation as the "fast + * circular buffer". + * + * \return NULL if unsuccessful, a valid opaque struct pointer if successful. + */ +drx_buf_t * +drx_buf_create_circular_buffer(size_t buf_size); + +DR_EXPORT +/** + * Initializes the drx_buf extension with a buffer; \p full_cb is called + * when the buffer becomes full. + * + * \return NULL if unsuccessful, a valid opaque struct pointer if successful. + */ +drx_buf_t * +drx_buf_create_trace_buffer(size_t buffer_size, + drx_buf_full_cb_t full_cb); + +DR_EXPORT +/** Cleans up the buffer associated with \p buf. \returns whether successful. */ +bool +drx_buf_free(drx_buf_t *buf); + +DR_EXPORT +/** + * Inserts instructions to load the address of the TLS buffer at \p where + * into \p buf_ptr. + */ +void +drx_buf_insert_load_buf_ptr(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr); + +DR_EXPORT +/** + * Inserts instructions to increment the buffer pointer by \p stride to accommodate + * the writes that occurred since the last time the base pointer was loaded. + * + * \note \p scratch is only necessary on ARM, in the case of the fast circular + * buffer. On x86 scratch is completely unused. + */ +void +drx_buf_insert_update_buf_ptr(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr, reg_id_t scratch, + ushort stride); + +DR_EXPORT +/** + * Inserts instructions to store \p opsz bytes of \p opnd at \p offset bytes + * from \p buf_ptr. \p opnd must be a register or an immediate opnd of some + * appropriate size. \return whether successful. + * + * \note \p opsz must be either \p OPSZ_1, \p OPSZ_2, \p OPSZ_4 or \p OPSZ_8. + * + * \note \p scratch is only necessary on ARM when storing an immediate operand. + * + * \note This method simply wraps a store that also sets an app translation. Make + * sure that \p where has a translation set. + */ +bool +drx_buf_insert_buf_store(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr, reg_id_t scratch, + opnd_t opnd, opnd_size_t opsz, short offset); + +DR_EXPORT +/** + * Retrieves a pointer to the top of the buffer, that is, returns the + * same value as would an invocation of drx_buf_insert_load_buf_ptr(). + */ +void * +drx_buf_get_buffer_ptr(void *drcontext, drx_buf_t *buf); + +DR_EXPORT +/** + * Allows one to set the buffer pointer so that subsequent invocations + * of drx_buf_insert_load_buf_ptr() will use this new value instead. + */ +void +drx_buf_set_buffer_ptr(void *drcontext, drx_buf_t *buf, void *new_ptr); + +DR_EXPORT +/** Retrieves a pointer to the base of the buffer. */ +void * +drx_buf_get_buffer_base(void *drcontext, drx_buf_t *buf); + +DR_EXPORT +/** Retrieves the capacity of the buffer. */ +size_t +drx_buf_get_buffer_size(void *drcontext, drx_buf_t *buf); + /*@}*/ /* end doxygen group */ #ifdef __cplusplus diff --git a/ext/drx/drx_buf.c b/ext/drx/drx_buf.c new file mode 100644 index 00000000000..d10eaf8c30a --- /dev/null +++ b/ext/drx/drx_buf.c @@ -0,0 +1,718 @@ +/* ********************************************************** + * Copyright (c) 2016 Google, Inc. All rights reserved. + * **********************************************************/ + +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Google, Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* DynamoRio eXtension Buffer Filling API */ + +#include "dr_api.h" +#include "drx.h" +#include "drmgr.h" +#include "drvector.h" +#include "../ext_utils.h" + +#ifdef UNIX +# include +#endif + +#define TLS_SLOT(tls_base, offs) (void **)((byte *)(tls_base)+(offs)) +#define BUF_PTR(tls_base, offs) *(byte **) TLS_SLOT(tls_base, offs) + +#define MINSERT instrlist_meta_preinsert + +/* denotes the possible buffer types */ +typedef enum { + DRX_BUF_CIRCULAR_FAST, + DRX_BUF_CIRCULAR, + DRX_BUF_TRACE +} drx_buf_type_t; + +typedef struct { + byte *seg_base; + byte *cli_base; /* the base of the buffer from the client's perspective */ + byte *buf_base; /* the actual base of the buffer */ + size_t total_size; /* the actual size of the buffer */ +} per_thread_t; + +struct _drx_buf_t { + drx_buf_type_t buf_type; + size_t buf_size; + uint vec_idx; /* index into the clients vector */ + drx_buf_full_cb_t full_cb; + /* tls implementation */ + int tls_idx; + uint tls_offs; + reg_id_t tls_seg; +}; + +/* drx_buf globals */ +drvector_t clients; + +/* called by drx_init() */ +bool drx_buf_init_library(void); +void drx_buf_exit_library(void); + +static drx_buf_t *drx_buf_init(drx_buf_type_t bt, size_t bsz, + drx_buf_full_cb_t full_cb); + +static per_thread_t *per_thread_init_2byte(void *drcontext, drx_buf_t *buf); +static per_thread_t *per_thread_init_fault(void *drcontext, drx_buf_t *buf); + +static void drx_buf_insert_update_buf_ptr_2byte(void *drcontext, drx_buf_t *buf, + instrlist_t *ilist, instr_t *where, + reg_id_t buf_ptr, reg_id_t scratch, + ushort stride); +static void drx_buf_insert_update_buf_ptr_fault(void *drcontext, drx_buf_t *buf, + instrlist_t *ilist, instr_t *where, + reg_id_t buf_ptr, ushort stride); + +static void restore_state_event(void *drcontext, void *tag, dr_mcontext_t *mcontext, + bool restore_memory, bool app_code_consistent); + +static void event_thread_init(void *drcontext); +static void event_thread_exit(void *drcontext); + +#ifdef WINDOWS +static bool exception_event(void *drcontext, dr_exception_t *excpt); +#else +static dr_signal_action_t signal_event(void *drcontext, dr_siginfo_t *info); +#endif + +static reg_id_t deduce_buf_ptr(instr_t *instr); +static bool reset_buf_ptr(void *drcontext, dr_mcontext_t *raw_mcontext, byte *seg_base, + byte *cli_base, drx_buf_t *buf); +static bool fault_event_helper(void *drcontext, byte *target, + dr_mcontext_t *raw_mcontext); + +bool +drx_buf_init_library(void) +{ + drmgr_priority_t exit_priority = { + sizeof(exit_priority), DRMGR_PRIORITY_NAME_DRX_BUF_EXIT, NULL, NULL, + DRMGR_PRIORITY_THREAD_EXIT_DRX_BUF}; + drmgr_priority_t init_priority = { + sizeof(init_priority), DRMGR_PRIORITY_NAME_DRX_BUF_INIT, NULL, NULL, + DRMGR_PRIORITY_THREAD_INIT_DRX_BUF}; + + /* We sync the vector manually, since we need to lock the vector ourselves + * when adding a client. + */ + if (!drvector_init(&clients, 1, false/*!synch*/, NULL) || + !drmgr_register_thread_init_event_ex(event_thread_init, &init_priority) || + !drmgr_register_thread_exit_event_ex(event_thread_exit, &exit_priority) || + !drmgr_register_restore_state_event(restore_state_event)) + return false; + +#ifdef WINDOWS + if (!drmgr_register_exception_event(exception_event)) + return false; +#else + if (!drmgr_register_signal_event(signal_event)) + return false; +#endif + return true; +} + +void +drx_buf_exit_library(void) +{ +#ifdef WINDOWS + drmgr_unregister_exception_event(exception_event); +#else + drmgr_unregister_signal_event(signal_event); +#endif + + drmgr_unregister_restore_state_event(restore_state_event); + drmgr_unregister_thread_init_event(event_thread_init); + drmgr_unregister_thread_exit_event(event_thread_exit); + drvector_delete(&clients); +} + +DR_EXPORT +drx_buf_t * +drx_buf_create_circular_buffer(size_t buf_size) +{ + /* We can optimize circular buffers that are this size */ + drx_buf_type_t buf_type = (buf_size == DRX_BUF_FAST_CIRCULAR_BUFSZ) ? + DRX_BUF_CIRCULAR_FAST : DRX_BUF_CIRCULAR; + return drx_buf_init(buf_type, buf_size, NULL); +} + +DR_EXPORT +drx_buf_t * +drx_buf_create_trace_buffer(size_t buf_size, + drx_buf_full_cb_t full_cb) +{ + return drx_buf_init(DRX_BUF_TRACE, buf_size, full_cb); +} + +static drx_buf_t * +drx_buf_init(drx_buf_type_t bt, size_t bsz, + drx_buf_full_cb_t full_cb) +{ + drx_buf_t *new_client; + int tls_idx; + uint tls_offs; + reg_id_t tls_seg; + + /* allocate raw TLS so we can access it from the code cache */ + if (!dr_raw_tls_calloc(&tls_seg, &tls_offs, 1, 0)) + return NULL; + + tls_idx = drmgr_register_tls_field(); + if (tls_idx == -1) + return NULL; + + /* init the client struct */ + new_client = dr_global_alloc(sizeof(*new_client)); + new_client->buf_type = bt; + new_client->buf_size = bsz; + new_client->tls_offs = tls_offs; + new_client->tls_seg = tls_seg; + new_client->tls_idx = tls_idx; + new_client->full_cb = full_cb; + drvector_lock(&clients); + /* We don't attempt to re-use NULL entries (presumably which + * have already been freed), for simplicity. + */ + new_client->vec_idx = clients.entries; + drvector_append(&clients, new_client); + drvector_unlock(&clients); + + return new_client; +} + +DR_EXPORT +bool +drx_buf_free(drx_buf_t *buf) +{ + drvector_lock(&clients); + if (!(buf != NULL && drvector_get_entry(&clients, buf->vec_idx) == buf)) { + drvector_unlock(&clients); + return false; + } + /* NULL out the entry in the vector */ + ((drx_buf_t **)clients.array)[buf->vec_idx] = NULL; + drvector_unlock(&clients); + + if (!drmgr_unregister_tls_field(buf->tls_idx) || + !dr_raw_tls_cfree(buf->tls_offs, 1)) + return false; + dr_global_free(buf, sizeof(*buf)); + + return true; +} + +DR_EXPORT +void * +drx_buf_get_buffer_ptr(void *drcontext, drx_buf_t *buf) +{ + per_thread_t *data = drmgr_get_tls_field(drcontext, buf->tls_idx); + return BUF_PTR(data->seg_base, buf->tls_offs); +} + +DR_EXPORT +void +drx_buf_set_buffer_ptr(void *drcontext, drx_buf_t *buf, void *new_ptr) +{ + per_thread_t *data = drmgr_get_tls_field(drcontext, buf->tls_idx); + BUF_PTR(data->seg_base, buf->tls_offs) = new_ptr; +} + +DR_EXPORT +void * +drx_buf_get_buffer_base(void *drcontext, drx_buf_t *buf) +{ + per_thread_t *data = drmgr_get_tls_field(drcontext, buf->tls_idx); + return data->cli_base; +} + +DR_EXPORT +size_t +drx_buf_get_buffer_size(void *drcontext, drx_buf_t *buf) +{ + return buf->buf_size; +} + +void +event_thread_init(void *drcontext) +{ + unsigned int i; + drvector_lock(&clients); + for (i = 0; i < clients.entries; ++i) { + per_thread_t *data; + drx_buf_t *buf = drvector_get_entry(&clients, i); + if (buf != NULL) { + if (buf->buf_type == DRX_BUF_CIRCULAR_FAST) + data = per_thread_init_2byte(drcontext, buf); + else + data = per_thread_init_fault(drcontext, buf); + drmgr_set_tls_field(drcontext, buf->tls_idx, data); + BUF_PTR(data->seg_base, buf->tls_offs) = data->cli_base; + } + } + drvector_unlock(&clients); +} + +void +event_thread_exit(void *drcontext) +{ + unsigned int i; + drvector_lock(&clients); + for (i = 0; i < clients.entries; ++i) { + drx_buf_t *buf = drvector_get_entry(&clients, i); + if (buf != NULL) { + per_thread_t *data = drmgr_get_tls_field(drcontext, buf->tls_idx); + byte *cli_ptr = BUF_PTR(data->seg_base, buf->tls_offs); + /* buffer has not yet been deleted, call user callback(s) */ + if (buf->full_cb != NULL) { + (*buf->full_cb)(drcontext, data->cli_base, + (size_t)(cli_ptr - data->cli_base)); + } + dr_raw_mem_free(data->buf_base, data->total_size); + dr_thread_free(drcontext, data, sizeof(per_thread_t)); + } + } + drvector_unlock(&clients); +} + +static per_thread_t * +per_thread_init_2byte(void *drcontext, drx_buf_t *buf) +{ + per_thread_t *per_thread = dr_thread_alloc(drcontext, sizeof(per_thread_t)); + byte *ret; + /* Keep seg_base in a per-thread data structure so we can get the TLS + * slot and find where the pointer points to in the buffer. + */ + per_thread->seg_base = dr_get_dr_segment_base(buf->tls_seg); + /* We allocate twice the amount necessary to make sure we + * have a buffer with a 65336-byte aligned starting address. + */ + per_thread->total_size = 2 * buf->buf_size; + ret = dr_raw_mem_alloc(per_thread->total_size, + DR_MEMPROT_READ | DR_MEMPROT_WRITE, + NULL); + per_thread->buf_base = ret; + per_thread->cli_base = (void *) ALIGN_FORWARD(ret, buf->buf_size); + return per_thread; +} + +static per_thread_t * +per_thread_init_fault(void *drcontext, drx_buf_t *buf) +{ + per_thread_t *per_thread = dr_thread_alloc(drcontext, sizeof(per_thread_t)); + byte *ret; + bool ok; + /* Keep seg_base in a per-thread data structure so we can get the TLS + * slot and find where the pointer points to in the buffer. + */ + per_thread->seg_base = dr_get_dr_segment_base(buf->tls_seg); + /* We construct a buffer right before a fault by allocating as + * many pages as needed to fit the buffer, plus another read-only + * page. Then, we return an address such that we have exactly + * buf_size bytes usable before we hit the ro page. + */ + per_thread->total_size = ALIGN_FORWARD(buf->buf_size, PAGE_SIZE) + PAGE_SIZE; + ret = dr_raw_mem_alloc(per_thread->total_size, + DR_MEMPROT_READ | DR_MEMPROT_WRITE, + NULL); + ok = dr_memory_protect(ret + per_thread->total_size - PAGE_SIZE, + PAGE_SIZE, DR_MEMPROT_READ); + DR_ASSERT(ok); + per_thread->buf_base = ret; + per_thread->cli_base = ret + ALIGN_FORWARD(buf->buf_size, PAGE_SIZE) - buf->buf_size; + return per_thread; +} + +DR_EXPORT +void +drx_buf_insert_load_buf_ptr(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr) +{ + dr_insert_read_raw_tls(drcontext, ilist, where, buf->tls_seg, + buf->tls_offs, buf_ptr); +} + +DR_EXPORT +void +drx_buf_insert_update_buf_ptr(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr, reg_id_t scratch, + ushort stride) +{ + if (buf->buf_type == DRX_BUF_CIRCULAR_FAST) { + drx_buf_insert_update_buf_ptr_2byte(drcontext, buf, ilist, where, + buf_ptr, scratch, stride); + } else { + drx_buf_insert_update_buf_ptr_fault(drcontext, buf, ilist, where, + buf_ptr, stride); + } +} + +static void +drx_buf_insert_update_buf_ptr_2byte(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr, reg_id_t scratch, + ushort stride) +{ +#ifdef X86 + /* To get the "rotating" effect, we update only the bottom bits of the register. */ + if (drx_aflags_are_dead(where)) { + /* if aflags are dead, we use add directly */ + MINSERT(ilist, where, INSTR_CREATE_add + (drcontext, + opnd_create_far_base_disp(buf->tls_seg, DR_REG_NULL, DR_REG_NULL, + 0, buf->tls_offs, OPSZ_2), + OPND_CREATE_INT16(stride))); + } else { + reg_id_t scratch = reg_resize_to_opsz(buf_ptr, OPSZ_2); + /* use lea to avoid dealing with aflags */ + MINSERT(ilist, where, INSTR_CREATE_lea + (drcontext, + opnd_create_reg(scratch), + opnd_create_base_disp(buf_ptr, DR_REG_NULL, 0, + stride, OPSZ_lea))); + dr_insert_write_raw_tls(drcontext, ilist, where, buf->tls_seg, + buf->tls_offs, buf_ptr); + } +#elif defined(ARM) + if (stride > 255) { + /* fall back to XINST_CREATE_load_int() if stride doesn't fit in 1 byte */ + MINSERT(ilist, where, XINST_CREATE_load_int + (drcontext, opnd_create_reg(scratch), OPND_CREATE_INT16(stride))); + MINSERT(ilist, where, XINST_CREATE_add + (drcontext, opnd_create_reg(buf_ptr), opnd_create_reg(scratch))); + } else { + MINSERT(ilist, where, XINST_CREATE_add + (drcontext, opnd_create_reg(buf_ptr), OPND_CREATE_INT16(stride))); + } + MINSERT(ilist, where, XINST_CREATE_store_2bytes + (drcontext, + OPND_CREATE_MEM16(buf->tls_seg, buf->tls_offs), + opnd_create_reg(buf_ptr))); +#endif +} + +static void +drx_buf_insert_update_buf_ptr_fault(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr, ushort stride) +{ + /* straightforward, just increment buf_ptr */ + MINSERT(ilist, where, XINST_CREATE_add + (drcontext, + opnd_create_reg(buf_ptr), + OPND_CREATE_INT16(stride))); + dr_insert_write_raw_tls(drcontext, ilist, where, buf->tls_seg, + buf->tls_offs, buf_ptr); +} + +static bool +drx_buf_insert_buf_store_1byte(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr, reg_id_t scratch, + opnd_t opnd, short offset) +{ + instr_t *instr; + if (!opnd_is_reg(opnd) && !opnd_is_immed(opnd)) + return false; + if (opnd_is_immed(opnd)) { +#ifdef X86 + instr = XINST_CREATE_store_1byte + (drcontext, + OPND_CREATE_MEM8(buf_ptr, offset), opnd); +#elif defined(ARM) + /* this will certainly not fault, so don't set a translation */ + MINSERT(ilist, where, XINST_CREATE_load_int + (drcontext, + opnd_create_reg(scratch), opnd)); + instr = XINST_CREATE_store_1byte + (drcontext, + OPND_CREATE_MEM8(buf_ptr, offset), + opnd_create_reg(scratch)); +#endif + } else { + instr = XINST_CREATE_store_1byte + (drcontext, + OPND_CREATE_MEM8(buf_ptr, offset), + opnd); + } + INSTR_XL8(instr, instr_get_app_pc(where)); + MINSERT(ilist, where, instr); + return true; +} + +static bool +drx_buf_insert_buf_store_2bytes(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr, reg_id_t scratch, + opnd_t opnd, short offset) +{ + instr_t *instr; + if (!opnd_is_reg(opnd) && !opnd_is_immed(opnd)) + return false; + if (opnd_is_immed(opnd)) { +#ifdef X86 + instr = XINST_CREATE_store_2bytes + (drcontext, + OPND_CREATE_MEM16(buf_ptr, offset), opnd); +#elif defined(ARM) + /* this will certainly not fault, so don't set a translation */ + MINSERT(ilist, where, XINST_CREATE_load_int + (drcontext, + opnd_create_reg(scratch), opnd)); + instr = XINST_CREATE_store_2bytes + (drcontext, + OPND_CREATE_MEM16(buf_ptr, offset), + opnd_create_reg(scratch)); +#endif + } else { + instr = XINST_CREATE_store_2bytes + (drcontext, + OPND_CREATE_MEM16(buf_ptr, offset), + opnd); + } + INSTR_XL8(instr, instr_get_app_pc(where)); + MINSERT(ilist, where, instr); + return true; +} + +#if defined(X86_64) || defined(AARCH64) +/* only valid on platforms where OPSZ_PTR != OPSZ_4 */ +static bool +drx_buf_insert_buf_store_4bytes(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr, reg_id_t scratch, + opnd_t opnd, short offset) +{ + instr_t *instr; + if (!opnd_is_reg(opnd) && !opnd_is_immed(opnd)) + return false; + if (opnd_is_immed(opnd)) { +#ifdef X86_64 + instr = XINST_CREATE_store + (drcontext, + OPND_CREATE_MEM32(buf_ptr, offset), opnd); +#elif defined(AARCH64) + /* this will certainly not fault, so don't set a translation */ + MINSERT(ilist, where, XINST_CREATE_load_int + (drcontext, + opnd_create_reg(scratch), opnd)); + instr = XINST_CREATE_store + (drcontext, + OPND_CREATE_MEM32(buf_ptr, offset), + opnd_create_reg(scratch)); +#endif + } else { + instr = XINST_CREATE_store + (drcontext, + OPND_CREATE_MEM32(buf_ptr, offset), + opnd); + } + INSTR_XL8(instr, instr_get_app_pc(where)); + MINSERT(ilist, where, instr); + return true; +} +#endif + +static bool +drx_buf_insert_buf_store_ptrsz(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr, reg_id_t scratch, + opnd_t opnd, short offset) +{ + if (!opnd_is_reg(opnd) && !opnd_is_immed(opnd)) + return false; + if (opnd_is_immed(opnd)) { + instr_t *first, *last; + ptr_int_t immed = opnd_get_immed_int(opnd); +#ifdef X86 + instrlist_insert_mov_immed_ptrsz(drcontext, immed, + OPND_CREATE_MEMPTR(buf_ptr, offset), + ilist, where, &first, &last); + for (;; first = instr_get_next(first)) { + INSTR_XL8(first, instr_get_app_pc(where)); + if (last == NULL || first == last) + break; + } +#elif defined(ARM) + instr_t *instr; + instrlist_insert_mov_immed_ptrsz(drcontext, immed, + opnd_create_reg(scratch), + ilist, where, &first, &last); + instr = XINST_CREATE_store + (drcontext, + OPND_CREATE_MEMPTR(buf_ptr, offset), + opnd_create_reg(scratch)); + INSTR_XL8(instr, instr_get_app_pc(where)); + MINSERT(ilist, where, instr); +#endif + } else { + instr_t *instr = XINST_CREATE_store + (drcontext, + OPND_CREATE_MEMPTR(buf_ptr, offset), + opnd); + INSTR_XL8(instr, instr_get_app_pc(where)); + MINSERT(ilist, where, instr); + } + return true; +} + +DR_EXPORT +bool +drx_buf_insert_buf_store(void *drcontext, drx_buf_t *buf, instrlist_t *ilist, + instr_t *where, reg_id_t buf_ptr, reg_id_t scratch, + opnd_t opnd, opnd_size_t opsz, short offset) +{ + switch (opsz) { + case OPSZ_1: + return drx_buf_insert_buf_store_1byte(drcontext, buf, ilist, where, buf_ptr, scratch, + opnd, offset); + case OPSZ_2: + return drx_buf_insert_buf_store_2bytes(drcontext, buf, ilist, where, buf_ptr, scratch, + opnd, offset); +#if defined(X86_64) || defined(AARCH64) + case OPSZ_4: + return drx_buf_insert_buf_store_4bytes(drcontext, buf, ilist, where, buf_ptr, + scratch, opnd, offset); +#endif + case OPSZ_PTR: + return drx_buf_insert_buf_store_ptrsz(drcontext, buf, ilist, where, buf_ptr, + scratch, opnd, offset); + default: + return false; + } +} + +/* assumes that the instruction writes memory relative to some buffer pointer */ +static reg_id_t +deduce_buf_ptr(instr_t *instr) +{ + int i; + for (i = 0; i < instr_num_dsts(instr); ++i) { + opnd_t dst = instr_get_dst(instr, i); + if (opnd_is_memory_reference(dst)) + return opnd_get_base(dst); + } + /* if we got here, then it's possible that the write had no base reg */ + return DR_REG_NULL; +} + +/* returns true if we won't intercept the fault, false otherwise */ +static bool +reset_buf_ptr(void *drcontext, dr_mcontext_t *raw_mcontext, byte *seg_base, + byte *cli_base, drx_buf_t *buf) +{ + instr_t *instr; + reg_id_t buf_ptr; + app_pc tmp_base; + + /* decode the instruction to extract the base register */ + instr = instr_create(drcontext); + decode(drcontext, raw_mcontext->pc, instr); + buf_ptr = deduce_buf_ptr(instr); + instr_destroy(drcontext, instr); + if (buf_ptr == DR_REG_NULL) + return true; + + /* We set the buffer pointer before the callback so it's easier + * for the user to override it in the callback. + */ + tmp_base = BUF_PTR(seg_base, buf->tls_offs); + BUF_PTR(seg_base, buf->tls_offs) = cli_base; + if (buf->full_cb != NULL) + (*buf->full_cb)(drcontext, cli_base, (size_t)(tmp_base - cli_base)); + + /* change contents of buf_ptr and retry the instruction */ + reg_set_value(buf_ptr, raw_mcontext, + (reg_t)BUF_PTR(seg_base, buf->tls_offs)); + return false; +} + +/* returns true if we won't intercept the fault, false otherwise */ +static bool +fault_event_helper(void *drcontext, byte *target, + dr_mcontext_t *raw_mcontext) +{ + per_thread_t *data; + drx_buf_t *buf; + unsigned int i; + + /* were we executing instead of writing? */ + if (raw_mcontext->pc == target) + return true; + + /* check bounds of write to see which buffer this event belongs to */ + drvector_lock(&clients); + for (i = 0; i < clients.entries; ++i) { + buf = drvector_get_entry(&clients, i); + if (buf != NULL && buf->buf_type != DRX_BUF_CIRCULAR_FAST) { + data = drmgr_get_tls_field(drcontext, buf->tls_idx); + byte *ro_lo = data->cli_base + buf->buf_size; + + /* we found the right client */ + if (target >= ro_lo && target < ro_lo + PAGE_SIZE) { + drvector_unlock(&clients); + return reset_buf_ptr(drcontext, raw_mcontext, data->seg_base, + data->cli_base, buf); + } + } + } + drvector_unlock(&clients); + return true; +} + +#ifdef WINDOWS +bool +exception_event(void *drcontext, dr_exception_t *excpt) +{ + /* fast fail if it wasn't a seg fault */ + if (excpt->record->ExceptionCode != STATUS_ACCESS_VIOLATION) + return true; + + /* The second entry in the exception information array handily holds the target + * write address. + */ + return fault_event_helper(drcontext, (byte *)excpt->record->ExceptionInformation[1], + excpt->raw_mcontext); +} +#else +dr_signal_action_t +signal_event(void *drcontext, dr_siginfo_t *info) +{ + /* fast fail if it wasn't a seg fault */ + if (info->sig != SIGSEGV) + return DR_SIGNAL_DELIVER; + DR_ASSERT(info->raw_mcontext_valid); + + return fault_event_helper(drcontext, info->access_address, info->raw_mcontext) ? + DR_SIGNAL_DELIVER : DR_SIGNAL_SUPPRESS; +} +#endif + +void +restore_state_event(void *drcontext, void *tag, dr_mcontext_t *mcontext, + bool restore_memory, bool app_code_consistent) +{ + /* no-op */ +} diff --git a/suite/tests/CMakeLists.txt b/suite/tests/CMakeLists.txt index 71aa4779383..00a1ffdc28b 100644 --- a/suite/tests/CMakeLists.txt +++ b/suite/tests/CMakeLists.txt @@ -2019,6 +2019,15 @@ if (CLIENT_INTERFACE) target_link_libraries(client.drmgr-test ${libpthread}) endif () + tobuild_ci(client.drx_buf-test client-interface/drx_buf-test.c "" "" "") + use_DynamoRIO_extension(client.drx_buf-test.dll drmgr) + use_DynamoRIO_extension(client.drx_buf-test.dll drx) + if (UNIX AND NOT ANDROID) # pthreads is inside Bionic on Android + target_link_libraries(client.drx_buf-test ${libpthread}) + endif () + target_include_directories(client.drx_buf-test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/client-interface) + tobuild_ci(client.drreg-test client-interface/drreg-test.c "" "" "") use_DynamoRIO_extension(client.drreg-test.dll drmgr) use_DynamoRIO_extension(client.drreg-test.dll drreg) diff --git a/suite/tests/client-interface/drx_buf-test-shared.h b/suite/tests/client-interface/drx_buf-test-shared.h new file mode 100644 index 00000000000..3d00ad0be38 --- /dev/null +++ b/suite/tests/client-interface/drx_buf-test-shared.h @@ -0,0 +1,68 @@ +/* ********************************************************** + * Copyright (c) 2016 Google, Inc. All rights reserved. + * **********************************************************/ + +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Google, Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +#ifndef IF_X86_ELSE +# ifdef X86 +# define IF_X86_ELSE(x, y) x +# else +# define IF_X86_ELSE(x, y) y +# endif +#endif + +#define TEST_REG IF_X86_ELSE(DR_REG_XDX, DR_REG_R4) +#define TEST_REG_ASM IF_X86_ELSE(REG_XDX, r4) + +/* Immediates that we look for in the app code to identify places for + * specific tests in the client. + * We limit to 16 bits to work on ARM. + */ +#define DRX_BUF_TEST_CONST(num) f1f##num +#define MAKE_HEX_ASM(n) HEX(n) +#define MAKE_HEX(n) 0x##n +#define MAKE_HEX_C(n) MAKE_HEX(n) + +#define DRX_BUF_TEST_1_ASM MAKE_HEX_ASM(DRX_BUF_TEST_CONST(1)) +#define DRX_BUF_TEST_1_C MAKE_HEX_C(DRX_BUF_TEST_CONST(1)) + +#define DRX_BUF_TEST_2_ASM MAKE_HEX_ASM(DRX_BUF_TEST_CONST(2)) +#define DRX_BUF_TEST_2_C MAKE_HEX_C(DRX_BUF_TEST_CONST(2)) + +#define DRX_BUF_TEST_3_ASM MAKE_HEX_ASM(DRX_BUF_TEST_CONST(3)) +#define DRX_BUF_TEST_3_C MAKE_HEX_C(DRX_BUF_TEST_CONST(3)) + +#define DRX_BUF_TEST_4_ASM MAKE_HEX_ASM(DRX_BUF_TEST_CONST(4)) +#define DRX_BUF_TEST_4_C MAKE_HEX_C(DRX_BUF_TEST_CONST(4)) + +#define DRX_BUF_TEST_5_ASM MAKE_HEX_ASM(DRX_BUF_TEST_CONST(5)) +#define DRX_BUF_TEST_5_C MAKE_HEX_C(DRX_BUF_TEST_CONST(5)) + +#define NUM_ITER 100 diff --git a/suite/tests/client-interface/drx_buf-test.c b/suite/tests/client-interface/drx_buf-test.c new file mode 100644 index 00000000000..8d48a4ddec4 --- /dev/null +++ b/suite/tests/client-interface/drx_buf-test.c @@ -0,0 +1,252 @@ +/* ********************************************************** + * Copyright (c) 2016 Google, Inc. All rights reserved. + * **********************************************************/ + +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Google, Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +#ifndef ASM_CODE_ONLY /* C code */ +#include "tools.h" +#include "drx_buf-test-shared.h" +#include + +#define CHECK(x, msg) do { \ + if (!(x)) { \ + fprintf(stderr, "CHECK failed %s:%d: %s\n", __FILE__, __LINE__, msg); \ + abort(); \ + } \ +} while (0); + +/* asm routines */ +void test_asm_123(); +void test_asm_45(); + +static SIGJMP_BUF mark; + +#if defined(UNIX) +# include +# include +static void +handle_signal(int signal, siginfo_t *siginfo, ucontext_t *ucxt) +{ + print("drx_buf signal test PASS\n"); + SIGLONGJMP(mark, 1); +} + +void * +thread_asm_test(void *unused) +{ + int i; + /* tests 1, 2 and 3 */ + for (i = 0; i < NUM_ITER; ++i) + test_asm_123(); + /* tests 4 and 5 */ + test_asm_45(); + return NULL; +} +#elif defined(WINDOWS) +# include +static LONG WINAPI +handle_exception(struct _EXCEPTION_POINTERS *ep) +{ + print("drx_buf signal test PASS\n"); + SIGLONGJMP(mark, 1); +} + +DWORD WINAPI +thread_asm_test(LPVOID lpParam) +{ + int i; + /* tests 1, 2 and 3 */ + for (i = 0; i < NUM_ITER; ++i) + test_asm_123(); + /* tests 4 and 5 */ + test_asm_45(); + return 0; +} +#endif + +int +main(void) +{ + /* XXX: We can also trigger a segfault by trying to execute the buffer. We could + * get the address of the buffer in the app using some annotation-based approach. + */ +#if defined(UNIX) + pthread_t thread; + intercept_signal(SIGSEGV, (handler_3_t)&handle_signal, false); +#elif defined(WINDOWS) + HANDLE thread; + DWORD threadId; + SetUnhandledExceptionFilter(&handle_exception); +#endif + + print("Starting drx_buf threaded test\n"); +#if defined(UNIX) + CHECK(!pthread_create(&thread, NULL, thread_asm_test, NULL), "create failed"); + /* make sure that the buffers are threadsafe */ + (void)thread_asm_test(NULL); + CHECK(!pthread_join(thread, NULL), "join failed"); +#elif defined(WINDOWS) + CHECK((thread = CreateThread(NULL, 0, thread_asm_test, NULL, 0, &threadId)) != NULL, + "CreateThread failed"); + /* make sure that the buffers are threadsafe */ + (void)thread_asm_test(NULL); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); +#endif + print("Ending drx_buf threaded test\n"); + print("Starting drx_buf signal test\n"); + /* try to cause a segfault and make sure it didn't trigger the buffer to dump */ + if (SIGSETJMP(mark) == 0) { + int *x = NULL; + return *x; + } + print("Ending drx_buf signal test\n"); + return 0; +} + +#else /* asm code *************************************************************/ +#include "asm_defines.asm" +#include "drx_buf-test-shared.h" +START_FILE + +#ifdef X64 +# define FRAME_PADDING 8 +#else +# define FRAME_PADDING 0 +#endif + +#define FUNCNAME test_asm_123 + DECLARE_FUNC_SEH(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) +#ifdef X86 + /* push callee-saved registers */ + PUSH_SEH(REG_XBX) + PUSH_SEH(REG_XBP) + PUSH_SEH(REG_XSI) + PUSH_SEH(REG_XDI) + sub REG_XSP, FRAME_PADDING /* align */ + END_PROLOG + + jmp test1 + /* Test 1: test the fast circular buffer */ + test1: + mov TEST_REG_ASM, DRX_BUF_TEST_1_ASM + mov TEST_REG_ASM, DRX_BUF_TEST_1_ASM + jmp test2 + /* Test 2: test the slow circular buffer */ + test2: + mov TEST_REG_ASM, DRX_BUF_TEST_2_ASM + mov TEST_REG_ASM, DRX_BUF_TEST_2_ASM + jmp test3 + /* Test 3: test the faulting buffer */ + test3: + mov TEST_REG_ASM, DRX_BUF_TEST_3_ASM + mov TEST_REG_ASM, DRX_BUF_TEST_3_ASM + jmp epilog1 + epilog1: + add REG_XSP, FRAME_PADDING /* make a legal SEH64 epilog */ + pop REG_XDI + pop REG_XSI + pop REG_XBP + pop REG_XBX + ret +#elif defined(ARM) + b test1 + /* Test 1: test the fast circular buffer */ + test1: + movw TEST_REG_ASM, DRX_BUF_TEST_1_ASM + movw TEST_REG_ASM, DRX_BUF_TEST_1_ASM + b test2 + /* Test 2: test the slow circular buffer */ + test2: + movw TEST_REG_ASM, DRX_BUF_TEST_2_ASM + movw TEST_REG_ASM, DRX_BUF_TEST_2_ASM + b test3 + /* Test 3: test the faulting buffer */ + test3: + movw TEST_REG_ASM, DRX_BUF_TEST_3_ASM + movw TEST_REG_ASM, DRX_BUF_TEST_3_ASM + b epilog1 + epilog1: + bx lr +#endif + END_FUNC(FUNCNAME) +#undef FUNCNAME + +#define FUNCNAME test_asm_45 + DECLARE_FUNC_SEH(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) +#ifdef X86 + /* push callee-saved registers */ + PUSH_SEH(REG_XBX) + PUSH_SEH(REG_XBP) + PUSH_SEH(REG_XSI) + PUSH_SEH(REG_XDI) + sub REG_XSP, FRAME_PADDING /* align */ + END_PROLOG + + jmp test4 + /* Test 4: test store registers */ + test4: + mov TEST_REG_ASM, DRX_BUF_TEST_4_ASM + mov TEST_REG_ASM, DRX_BUF_TEST_4_ASM + jmp test5 + /* Test 5: test store immediates */ + test5: + mov TEST_REG_ASM, DRX_BUF_TEST_5_ASM + mov TEST_REG_ASM, DRX_BUF_TEST_5_ASM + jmp epilog2 + epilog2: + add REG_XSP, FRAME_PADDING /* make a legal SEH64 epilog */ + pop REG_XDI + pop REG_XSI + pop REG_XBP + pop REG_XBX + ret +#elif defined(ARM) + b test4 + /* Test 4: test store registers */ + test4: + movw TEST_REG_ASM, DRX_BUF_TEST_4_ASM + movw TEST_REG_ASM, DRX_BUF_TEST_4_ASM + b test5 + /* Test 5: test store immediates */ + test5: + movw TEST_REG_ASM, DRX_BUF_TEST_5_ASM + movw TEST_REG_ASM, DRX_BUF_TEST_5_ASM + b epilog2 + epilog2: + bx lr +#endif + END_FUNC(FUNCNAME) +#undef FUNCNAME + +END_FILE +#endif diff --git a/suite/tests/client-interface/drx_buf-test.dll.c b/suite/tests/client-interface/drx_buf-test.dll.c new file mode 100644 index 00000000000..f1fc0f001f4 --- /dev/null +++ b/suite/tests/client-interface/drx_buf-test.dll.c @@ -0,0 +1,386 @@ +/* ********************************************************** + * Copyright (c) 2016 Google, Inc. All rights reserved. + * **********************************************************/ + +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Google, Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* Tests the drx_buf extension */ +#include +#include +#include "dr_api.h" +#include "drmgr.h" +#include "drx.h" +#include "drx_buf-test-shared.h" + +#define CHECK(x, msg) do { \ + if (!(x)) { \ + dr_fprintf(STDERR, "CHECK failed %s:%d: %s\n", __FILE__, __LINE__, msg); \ + dr_abort(); \ + } \ +} while (0); + +#define CIRCULAR_FAST_SZ DRX_BUF_FAST_CIRCULAR_BUFSZ +#define CIRCULAR_SLOW_SZ 256 +#define TRACE_SZ 256 + +#define MINSERT instrlist_meta_preinsert + +#ifdef X64 +static char cmp[] = "ABCDEFGHABCDEFGH"; +#else +static char cmp[] = "ABCDEFGH"; +#endif + +static drx_buf_t *circular_fast; +static drx_buf_t *circular_slow; +static drx_buf_t *trace; +static volatile int num_faults; + +static void +event_thread_init(void *drcontext) +{ + /* memset all of the buffers, to verify + * that they've already been initialized + */ + byte *buf_base; + + buf_base = drx_buf_get_buffer_base(drcontext, circular_fast); + memset(buf_base, 0, CIRCULAR_FAST_SZ); + + buf_base = drx_buf_get_buffer_base(drcontext, circular_slow); + memset(buf_base, 0, CIRCULAR_SLOW_SZ); + + buf_base = drx_buf_get_buffer_base(drcontext, trace); + memset(buf_base, 0, TRACE_SZ); +} + +static void +verify_buffers_empty(drx_buf_t *client) +{ + void *drcontext = dr_get_current_drcontext(); + byte *buf_base, *buf_ceil; + + buf_base = drx_buf_get_buffer_base(drcontext, client); + buf_ceil = drx_buf_get_buffer_ptr(drcontext, client); + CHECK(buf_base == buf_ceil, "buffer not empty"); +} + +static void +verify_buffers_dirty(drx_buf_t *client, int32_t test) +{ + void *drcontext = dr_get_current_drcontext(); + byte *buf_base, *buf_ceil; + + buf_base = drx_buf_get_buffer_base(drcontext, client); + buf_ceil = drx_buf_get_buffer_ptr(drcontext, client); + CHECK(buf_base + sizeof(int32_t) == buf_ceil, "buffer not dirty"); + CHECK(*(int32_t *)buf_base == test, "buffer has wrong value"); +} + +static void +verify_trace_buffer(void *drcontext, void *buf_base, size_t size) +{ + dr_atomic_add32_return_sum(&num_faults, 1); +} + +static void +verify_store(drx_buf_t *client) +{ + void *drcontext = dr_get_current_drcontext(); + char *buf_base = drx_buf_get_buffer_base(drcontext, client); + CHECK(strcmp(buf_base, cmp) == 0, + "Store immediate or Store register failed to copy right value"); + memset(buf_base, 0, drx_buf_get_buffer_size(drcontext, client)); +} + +static dr_emit_flags_t +event_app_analysis(void *drcontext, void *tag, instrlist_t *bb, + bool for_trace, bool translating, OUT void **user_data) +{ + instr_t *inst, *label; + bool prev_was_mov_const = false; + ptr_int_t val1, val2; + *user_data = NULL; + /* Look for duplicate mov immediates telling us which subtest we're in */ + for (inst = instrlist_first_app(bb); inst != NULL; inst = instr_get_next_app(inst)) { + if (instr_is_mov_constant(inst, prev_was_mov_const ? &val2 : &val1)) { + if (prev_was_mov_const && val1 == val2 && + val1 != 0 && /* rule out xor w/ self */ + opnd_is_reg(instr_get_dst(inst, 0)) && + opnd_get_reg(instr_get_dst(inst, 0)) == TEST_REG) { + *user_data = (void *) val1; + label = INSTR_CREATE_label(drcontext); + instr_set_translation(label, instr_get_app_pc(inst)); + instrlist_meta_postinsert(bb, inst, label); + } else + prev_was_mov_const = true; + } else + prev_was_mov_const = false; + } + return DR_EMIT_DEFAULT; +} + +static dr_emit_flags_t +event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst, + bool for_trace, bool translating, void *user_data) +{ + reg_id_t reg_ptr = IF_X86_ELSE(DR_REG_XDX, DR_REG_R4); + reg_id_t reg_tmp = IF_X86_ELSE(DR_REG_XCX, DR_REG_R3); + /* We need a third register on ARM, because updating the buf pointer + * requires a second scratch reg. + */ + reg_id_t scratch = IF_X86_ELSE(reg_tmp, DR_REG_R5); + ptr_int_t subtest = (ptr_int_t) user_data; + + if (!instr_is_label(inst)) + return DR_EMIT_DEFAULT; + + scratch = reg_resize_to_opsz(scratch, OPSZ_4); + if (subtest == DRX_BUF_TEST_1_C) { + /* testing fast circular buffer */ + /* test to make sure that on first invocation, the buffer is empty */ + dr_insert_clean_call(drcontext, bb, inst, verify_buffers_empty, false, 1, + OPND_CREATE_INTPTR(circular_fast)); + + /* load the buf pointer, and then write a garbage element to the buffer */ + drx_buf_insert_load_buf_ptr(drcontext, circular_fast, bb, inst, reg_ptr); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch), OPSZ_4, 0); + drx_buf_insert_update_buf_ptr(drcontext, circular_fast, bb, inst, reg_ptr, + reg_tmp, sizeof(int)); + + /* verify the buffer was written to */ + dr_insert_clean_call(drcontext, bb, inst, verify_buffers_dirty, false, 2, + OPND_CREATE_INTPTR(circular_fast), + opnd_create_reg(scratch)); + + /* fast circular buffer: trigger an overflow */ + drx_buf_insert_load_buf_ptr(drcontext, circular_fast, bb, inst, reg_ptr); + drx_buf_insert_update_buf_ptr(drcontext, circular_fast, bb, inst, reg_ptr, + reg_tmp, CIRCULAR_FAST_SZ - sizeof(int)); + + /* the buffer is now clean */ + dr_insert_clean_call(drcontext, bb, inst, verify_buffers_empty, false, 1, + OPND_CREATE_INTPTR(circular_fast)); + } else if (subtest == DRX_BUF_TEST_2_C) { + /* testing slow circular buffer */ + /* test to make sure that on first invocation, the buffer is empty */ + dr_insert_clean_call(drcontext, bb, inst, verify_buffers_empty, false, 1, + OPND_CREATE_INTPTR(circular_slow)); + + /* load the buf pointer, and then write an element to the buffer */ + drx_buf_insert_load_buf_ptr(drcontext, circular_slow, bb, inst, reg_ptr); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch), OPSZ_4, 0); + drx_buf_insert_update_buf_ptr(drcontext, circular_slow, bb, inst, reg_ptr, + DR_REG_NULL, sizeof(int)); + + /* verify the buffer was written to */ + dr_insert_clean_call(drcontext, bb, inst, verify_buffers_dirty, false, 2, + OPND_CREATE_INTPTR(circular_slow), + opnd_create_reg(scratch)); + + /* slow circular buffer: trigger a fault */ + drx_buf_insert_load_buf_ptr(drcontext, circular_slow, bb, inst, reg_ptr); + drx_buf_insert_update_buf_ptr(drcontext, circular_slow, bb, inst, reg_ptr, + DR_REG_NULL, CIRCULAR_SLOW_SZ - sizeof(int)); + /* the "trigger" is a write, so we write whatever garbage is in reg_tmp */ + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch), OPSZ_4, 0); + + /* the buffer is now clean */ + dr_insert_clean_call(drcontext, bb, inst, verify_buffers_empty, false, 1, + OPND_CREATE_INTPTR(circular_slow)); + } else if (subtest == DRX_BUF_TEST_3_C) { + /* testing trace buffer */ + /* test to make sure that on first invocation, the buffer is empty */ + dr_insert_clean_call(drcontext, bb, inst, verify_buffers_empty, false, 1, + OPND_CREATE_INTPTR(trace)); + + /* load the buf pointer, and then write an element to the buffer */ + drx_buf_insert_load_buf_ptr(drcontext, trace, bb, inst, reg_ptr); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch), OPSZ_4, 0); + drx_buf_insert_update_buf_ptr(drcontext, trace, bb, inst, reg_ptr, + DR_REG_NULL, sizeof(int)); + + /* verify the buffer was written to */ + dr_insert_clean_call(drcontext, bb, inst, verify_buffers_dirty, false, 2, + OPND_CREATE_INTPTR(trace), + opnd_create_reg(scratch)); + + /* trace buffer: trigger a fault and verify */ + drx_buf_insert_load_buf_ptr(drcontext, trace, bb, inst, reg_ptr); + drx_buf_insert_update_buf_ptr(drcontext, trace, bb, inst, reg_ptr, + DR_REG_NULL, TRACE_SZ - sizeof(int)); + /* the "trigger" is a write, so we write whatever garbage is in reg_tmp */ + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch), OPSZ_4, 0); + + /* the buffer is now clean */ + dr_insert_clean_call(drcontext, bb, inst, verify_buffers_empty, false, 1, + OPND_CREATE_INTPTR(trace)); + } else if (subtest == DRX_BUF_TEST_4_C) { + /* test immediate store: 8 bytes (if possible), 4 bytes, 2 bytes and 1 byte */ + /* "ABCDEFGH\x00" (x2 for x64) */ + drx_buf_insert_load_buf_ptr(drcontext, circular_fast, bb, inst, reg_ptr); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + scratch, opnd_create_immed_int(0x41, OPSZ_1), + OPSZ_1, 0); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + scratch, opnd_create_immed_int(0x42, OPSZ_1), + OPSZ_1, 1); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + scratch, opnd_create_immed_int(0x4443, OPSZ_2), + OPSZ_2, 2); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + scratch, opnd_create_immed_int(0x48474645, OPSZ_4), + OPSZ_4, 4); +#ifdef X64 + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + scratch, opnd_create_immed_int(0x4847464544434241, + OPSZ_8), + OPSZ_8, 8); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + scratch, opnd_create_immed_int(0x00, OPSZ_1), + OPSZ_1, 17); +#else + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + scratch, opnd_create_immed_int(0x00, OPSZ_1), + OPSZ_1, 9); +#endif + dr_insert_clean_call(drcontext, bb, inst, verify_store, false, 1, + OPND_CREATE_INTPTR(circular_fast)); + } else if (subtest == DRX_BUF_TEST_5_C) { + /* test register store: 8 bytes (if possible), 4 bytes, 2 bytes and 1 byte */ + /* "ABCDEFGH\x00" (x2 for x64) */ + drx_buf_insert_load_buf_ptr(drcontext, circular_fast, bb, inst, reg_ptr); + scratch = reg_resize_to_opsz(scratch, OPSZ_1); + MINSERT(bb, inst, XINST_CREATE_load_int + (drcontext, + opnd_create_reg(scratch), + opnd_create_immed_int(0x41, OPSZ_1))); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch), + OPSZ_1, 0); + MINSERT(bb, inst, XINST_CREATE_load_int + (drcontext, + opnd_create_reg(scratch), + opnd_create_immed_int(0x42, OPSZ_1))); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch), + OPSZ_1, 1); + scratch = reg_resize_to_opsz(scratch, OPSZ_2); + MINSERT(bb, inst, XINST_CREATE_load_int + (drcontext, + opnd_create_reg(scratch), + opnd_create_immed_int(0x4443, OPSZ_2))); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch), + OPSZ_2, 2); + scratch = reg_resize_to_opsz(scratch, OPSZ_4); +#ifdef X86 + MINSERT(bb, inst, XINST_CREATE_load_int + (drcontext, + opnd_create_reg(scratch), + opnd_create_immed_int(0x48474645, OPSZ_4))); +#else + instrlist_insert_mov_immed_ptrsz(drcontext, 0x48474645, + opnd_create_reg(scratch), + bb, inst, NULL, NULL); +#endif + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch), + OPSZ_4, 4); +#ifdef X64 + scratch = reg_resize_to_opsz(scratch, OPSZ_8); + /* only way to reliably move a 64 bit int into a register */ + instrlist_insert_mov_immed_ptrsz(drcontext, 0x4847464544434241, + opnd_create_reg(scratch), + bb, inst, NULL, NULL); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + DR_REG_NULL, opnd_create_reg(scratch), + OPSZ_8, 8); + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + scratch, opnd_create_immed_int(0x00, OPSZ_1), + OPSZ_1, 17); +#else + drx_buf_insert_buf_store(drcontext, circular_fast, bb, inst, reg_ptr, + scratch, opnd_create_immed_int(0x00, OPSZ_1), + OPSZ_1, 9); +#endif + dr_insert_clean_call(drcontext, bb, inst, verify_store, false, 1, + OPND_CREATE_INTPTR(circular_fast)); + } + + return DR_EMIT_DEFAULT; +} + +static void +event_exit(void) +{ + /* we are supposed to have faulted NUM_ITER times per thread, plus 2 more + * because the callback is called on thread_exit(). + */ + CHECK(num_faults == NUM_ITER * 2 + 2, "the number of faults don't match up"); + if (!drmgr_unregister_bb_insertion_event(event_app_instruction)) + CHECK(false, "exit failed"); + drx_buf_free(circular_fast); + drx_buf_free(circular_slow); + drx_buf_free(trace); + drmgr_unregister_thread_init_event(event_thread_init); + drmgr_exit(); + drx_exit(); +} + +DR_EXPORT void +dr_init(client_id_t id) +{ + if (!drmgr_init()) + CHECK(false, "init failed"); + + /* init buffer routines */ + drx_init(); + circular_fast = drx_buf_create_circular_buffer(DRX_BUF_FAST_CIRCULAR_BUFSZ); + circular_slow = drx_buf_create_circular_buffer(CIRCULAR_SLOW_SZ); + trace = drx_buf_create_trace_buffer(TRACE_SZ, verify_trace_buffer); + CHECK(circular_fast != NULL, "circular fast failed"); + CHECK(circular_slow != NULL, "circular slow failed"); + CHECK(trace != NULL, "trace failed"); + + CHECK(drmgr_register_thread_init_event(event_thread_init), + "event thread init failed"); + + /* register events */ + dr_register_exit_event(event_exit); + if (!drmgr_register_bb_instrumentation_event(event_app_analysis, + event_app_instruction, + NULL)) + CHECK(false, "init failed"); +} diff --git a/suite/tests/client-interface/drx_buf-test.expect b/suite/tests/client-interface/drx_buf-test.expect new file mode 100644 index 00000000000..e59eece48ed --- /dev/null +++ b/suite/tests/client-interface/drx_buf-test.expect @@ -0,0 +1,5 @@ +Starting drx_buf threaded test +Ending drx_buf threaded test +Starting drx_buf signal test +drx_buf signal test PASS +Ending drx_buf signal test