blob: 9c98d55ca48d0f817b355c0961c6481ec191b948 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2014 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 drmgr extension */
#include "dr_api.h"
#include "drmgr.h"
#include "client_tools.h"
#include <string.h> /* memset */
#define CHECK(x, msg) do { \
if (!(x)) { \
dr_fprintf(STDERR, "CHECK failed %s:%d: %s\n", __FILE__, __LINE__, msg); \
dr_abort(); \
} \
} while (0);
/* CLS tests: easiest to assume a single thread (the 2nd thread the
* app creates, in this case) hitting callbacks and use global data to
* check preservation
*/
static int tls_idx;
static int cls_idx;
static thread_id_t main_thread;
static int cb_depth;
static bool in_syscall_A;
static bool in_syscall_B;
static bool in_post_syscall_A;
static bool in_post_syscall_B;
static void *syslock;
static uint one_time_exec;
#define MAGIC_NUMBER_FROM_CACHE 0x0eadbeef
static bool checked_tls_from_cache;
static bool checked_cls_from_cache;
static bool checked_tls_write_from_cache;
static bool checked_cls_write_from_cache;
static void event_exit(void);
static void event_thread_init(void *drcontext);
static void event_thread_exit(void *drcontext);
static void event_thread_context_init(void *drcontext, bool new_depth);
static void event_thread_context_exit(void *drcontext, bool process_exit);
static bool event_filter_syscall(void *drcontext, int sysnum);
static bool event_pre_sys_A(void *drcontext, int sysnum);
static bool event_pre_sys_B(void *drcontext, int sysnum);
static void event_post_sys_A(void *drcontext, int sysnum);
static void event_post_sys_B(void *drcontext, int sysnum);
static dr_emit_flags_t event_bb_analysis(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating,
OUT void **user_data);
static dr_emit_flags_t event_bb_insert(void *drcontext, void *tag, instrlist_t *bb,
instr_t *inst, bool for_trace, bool translating,
void *user_data);
static dr_emit_flags_t event_bb4_app2app(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating,
OUT void **user_data);
static dr_emit_flags_t event_bb4_analysis(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating,
void *user_data);
static dr_emit_flags_t event_bb4_insert(void *drcontext, void *tag, instrlist_t *bb,
instr_t *inst, bool for_trace, bool translating,
void *user_data);
static dr_emit_flags_t event_bb4_instru2instru(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating,
void *user_data);
static dr_emit_flags_t one_time_bb_event(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating);
DR_EXPORT void
dr_init(client_id_t id)
{
drmgr_priority_t priority = {sizeof(priority), "drmgr-test", NULL, NULL, 0};
drmgr_priority_t priority4 = {sizeof(priority), "drmgr-test4", NULL, NULL, 0};
drmgr_priority_t sys_pri_A = {sizeof(priority), "drmgr-test-A", NULL, NULL, 0};
drmgr_priority_t sys_pri_B = {sizeof(priority), "drmgr-test-B",
"drmgr-test-A", NULL, 0};
bool ok;
drmgr_init();
dr_register_exit_event(event_exit);
drmgr_register_thread_init_event(event_thread_init);
drmgr_register_thread_exit_event(event_thread_exit);
ok = drmgr_register_bb_instrumentation_event(event_bb_analysis,
event_bb_insert,
&priority);
CHECK(ok, "drmgr register bb failed");
/* test data passing among all 4 phases */
ok = drmgr_register_bb_instrumentation_ex_event(event_bb4_app2app,
event_bb4_analysis,
event_bb4_insert,
event_bb4_instru2instru,
&priority4);
tls_idx = drmgr_register_tls_field();
CHECK(tls_idx != -1, "drmgr_register_tls_field failed");
cls_idx = drmgr_register_cls_field(event_thread_context_init,
event_thread_context_exit);
CHECK(cls_idx != -1, "drmgr_register_tls_field failed");
dr_register_filter_syscall_event(event_filter_syscall);
ok = drmgr_register_pre_syscall_event_ex(event_pre_sys_A, &sys_pri_A) &&
drmgr_register_pre_syscall_event_ex(event_pre_sys_B, &sys_pri_B);
CHECK(ok, "drmgr register sys failed");
ok = drmgr_register_post_syscall_event_ex(event_post_sys_A, &sys_pri_A) &&
drmgr_register_post_syscall_event_ex(event_post_sys_B, &sys_pri_B);
CHECK(ok, "drmgr register sys failed");
syslock = dr_mutex_create();
ok = drmgr_register_bb_app2app_event(one_time_bb_event, NULL);
CHECK(ok, "drmgr app2app registration failed");
}
static void
event_exit(void)
{
dr_mutex_destroy(syslock);
CHECK(checked_tls_from_cache, "failed to hit clean call");
CHECK(checked_cls_from_cache, "failed to hit clean call");
CHECK(checked_tls_write_from_cache, "failed to hit clean call");
CHECK(checked_cls_write_from_cache, "failed to hit clean call");
CHECK(one_time_exec == 1, "failed to execute one-time event");
if (!drmgr_unregister_bb_instrumentation_event(event_bb_analysis))
CHECK(false, "drmgr unregistration failed");
if (!drmgr_unregister_bb_instrumentation_ex_event(event_bb4_app2app,
event_bb4_analysis,
event_bb4_insert,
event_bb4_instru2instru))
CHECK(false, "drmgr unregistration failed");
if (!drmgr_unregister_cls_field(event_thread_context_init,
event_thread_context_exit, cls_idx))
CHECK(false, "drmgr unregistration failed");
drmgr_exit();
dr_fprintf(STDERR, "all done\n");
}
static void
event_thread_init(void *drcontext)
{
if (main_thread == 0)
main_thread = dr_get_thread_id(drcontext);
drmgr_set_tls_field(drcontext, tls_idx,
(void *)(ptr_int_t)dr_get_thread_id(drcontext));
}
static void
event_thread_exit(void *drcontext)
{
CHECK(drmgr_get_tls_field(drcontext, tls_idx) ==
(void *)(ptr_int_t)dr_get_thread_id(drcontext),
"tls not preserved");
}
static void
event_thread_context_init(void *drcontext, bool new_depth)
{
if (dr_get_thread_id(drcontext) != main_thread) {
cb_depth++;
#if VERBOSE
/* # cbs differs on xp vs win7 so not part of output */
dr_fprintf(STDERR, "non-main thread entering callback depth=%d\n", cb_depth);
#endif
CHECK(new_depth || drmgr_get_cls_field(drcontext, cls_idx) ==
(void *)(ptr_int_t)cb_depth,
"not re-using prior callback value");
drmgr_set_cls_field(drcontext, cls_idx, (void *)(ptr_int_t)cb_depth);
CHECK(drmgr_get_tls_field(drcontext, tls_idx) ==
(void *)(ptr_int_t)dr_get_thread_id(drcontext), "tls not preserved");
}
}
static void
event_thread_context_exit(void *drcontext, bool thread_exit)
{
if (!thread_exit && dr_get_thread_id(drcontext) != main_thread) {
#if VERBOSE
dr_fprintf(STDERR, " non-main thread exiting callback depth=%d cls=%d\n",
cb_depth, (int)(ptr_int_t) drmgr_get_cls_field(drcontext, cls_idx));
#endif
CHECK(drmgr_get_cls_field(drcontext, cls_idx) ==
(void *)(ptr_int_t)cb_depth,
"cls not preserved");
cb_depth--;
CHECK(drmgr_get_tls_field(drcontext, tls_idx) ==
(void *)(ptr_int_t)dr_get_thread_id(drcontext), "tls not preserved");
}
}
static dr_emit_flags_t
event_bb_analysis(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating, OUT void **user_data)
{
/* point at first instr */
*user_data = (void *) instrlist_first(bb);
return DR_EMIT_DEFAULT;
}
static void
check_tls_from_cache(void *tls_val)
{
CHECK(tls_val == drmgr_get_tls_field(dr_get_current_drcontext(), tls_idx),
"tls read from cache incorrect");
checked_tls_from_cache = true;
}
static void
check_cls_from_cache(void *cls_val)
{
CHECK(cls_val == drmgr_get_cls_field(dr_get_current_drcontext(), cls_idx),
"cls read from cache incorrect");
checked_cls_from_cache = true;
}
static void
check_tls_write_from_cache(void)
{
void *drcontext = dr_get_current_drcontext();
CHECK(drmgr_get_tls_field(drcontext, tls_idx) == (void *) MAGIC_NUMBER_FROM_CACHE,
"cls write from cache incorrect");
/* now restore */
drmgr_set_tls_field(drcontext, tls_idx,
(void *)(ptr_int_t)dr_get_thread_id(drcontext));
checked_tls_write_from_cache = true;
}
static void
check_cls_write_from_cache(void)
{
CHECK(drmgr_get_cls_field(dr_get_current_drcontext(), cls_idx) ==
(void *) MAGIC_NUMBER_FROM_CACHE,
"cls write from cache incorrect");
/* now restore */
drmgr_set_cls_field(dr_get_current_drcontext(), cls_idx,
(void *)(ptr_int_t)cb_depth);
checked_cls_write_from_cache = true;
}
static dr_emit_flags_t
event_bb_insert(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst,
bool for_trace, bool translating, void *user_data)
{
/* hack to instrument every nth bb. assumes DR serializes bb events. */
static int freq;
freq++;
if (freq % 100 == 0 && inst == (instr_t*)user_data/*first instr*/) {
/* test read from cache */
dr_save_reg(drcontext, bb, inst, DR_REG_XAX, SPILL_SLOT_1);
drmgr_insert_read_tls_field(drcontext, tls_idx, bb, inst, DR_REG_XAX);
dr_insert_clean_call(drcontext, bb, inst, (void *)check_tls_from_cache,
false, 1, opnd_create_reg(DR_REG_XAX));
drmgr_insert_read_cls_field(drcontext, cls_idx, bb, inst, DR_REG_XAX);
dr_insert_clean_call(drcontext, bb, inst, (void *)check_cls_from_cache,
false, 1, opnd_create_reg(DR_REG_XAX));
dr_restore_reg(drcontext, bb, inst, DR_REG_XAX, SPILL_SLOT_1);
}
if (freq % 300 == 0 && inst == (instr_t*)user_data/*first instr*/) {
/* test write from cache */
dr_save_reg(drcontext, bb, inst, DR_REG_XAX, SPILL_SLOT_1);
dr_save_reg(drcontext, bb, inst, DR_REG_XCX, SPILL_SLOT_2);
instrlist_meta_preinsert(bb, inst, INSTR_CREATE_mov_imm
(drcontext, opnd_create_reg(DR_REG_EAX),
OPND_CREATE_INT32(MAGIC_NUMBER_FROM_CACHE)));
drmgr_insert_write_tls_field(drcontext, tls_idx, bb, inst, DR_REG_XAX,
DR_REG_XCX);
dr_insert_clean_call(drcontext, bb, inst, (void *)check_tls_write_from_cache,
false, 0);
drmgr_insert_write_cls_field(drcontext, cls_idx, bb, inst, DR_REG_XAX,
DR_REG_XCX);
dr_insert_clean_call(drcontext, bb, inst, (void *)check_cls_write_from_cache,
false, 0);
dr_restore_reg(drcontext, bb, inst, DR_REG_XCX, SPILL_SLOT_2);
dr_restore_reg(drcontext, bb, inst, DR_REG_XAX, SPILL_SLOT_1);
}
return DR_EMIT_DEFAULT;
}
/* test data passed among all 4 phases */
static dr_emit_flags_t
event_bb4_app2app(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating,
OUT void **user_data)
{
*user_data = (void *) ((ptr_uint_t)tag + 1);
return DR_EMIT_DEFAULT;
}
static dr_emit_flags_t
event_bb4_analysis(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating,
void *user_data)
{
CHECK(user_data == (void *) ((ptr_uint_t)tag + 1), "user data not preserved");
return DR_EMIT_DEFAULT;
}
static dr_emit_flags_t
event_bb4_insert(void *drcontext, void *tag, instrlist_t *bb,
instr_t *inst, bool for_trace, bool translating,
void *user_data)
{
CHECK(user_data == (void *) ((ptr_uint_t)tag + 1), "user data not preserved");
return DR_EMIT_DEFAULT;
}
static dr_emit_flags_t
event_bb4_instru2instru(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating,
void *user_data)
{
CHECK(user_data == (void *) ((ptr_uint_t)tag + 1), "user data not preserved");
return DR_EMIT_DEFAULT;
}
static bool
event_filter_syscall(void *drcontext, int sysnum)
{
return true;
}
static bool
event_pre_sys_A(void *drcontext, int sysnum)
{
if (!in_syscall_A) {
dr_mutex_lock(syslock);
if (!in_syscall_A) {
dr_fprintf(STDERR, "in pre_sys_A\n");
in_syscall_A = true;
}
dr_mutex_unlock(syslock);
}
return true;
}
static bool
event_pre_sys_B(void *drcontext, int sysnum)
{
if (!in_syscall_B) {
dr_mutex_lock(syslock);
if (!in_syscall_B) {
dr_fprintf(STDERR, "in pre_sys_B\n");
in_syscall_B = true;
}
dr_mutex_unlock(syslock);
}
return true;
}
static void
event_post_sys_A(void *drcontext, int sysnum)
{
if (!in_post_syscall_A) {
dr_mutex_lock(syslock);
if (!in_post_syscall_A) {
dr_fprintf(STDERR, "in post_sys_A\n");
in_post_syscall_A = true;
}
dr_mutex_unlock(syslock);
}
}
static void
event_post_sys_B(void *drcontext, int sysnum)
{
if (!in_post_syscall_B) {
dr_mutex_lock(syslock);
if (!in_post_syscall_B) {
dr_fprintf(STDERR, "in post_sys_B\n");
in_post_syscall_B = true;
}
dr_mutex_unlock(syslock);
}
}
/* test unregistering from inside an event */
static dr_emit_flags_t
one_time_bb_event(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating)
{
int i;
# define STRESS_REGISTER_ITERS 64
# define NAME_SZ 32
char *names[STRESS_REGISTER_ITERS];
drmgr_priority_t pri = { sizeof(pri), };
one_time_exec++;
if (!drmgr_unregister_bb_app2app_event(one_time_bb_event))
CHECK(false, "drmgr unregistration failed");
/* stress-test adding and removing */
for (i = 0; i < STRESS_REGISTER_ITERS; i++) {
/* force sorted insertion on each add */
pri.priority = STRESS_REGISTER_ITERS - i;
names[i] = dr_thread_alloc(drcontext, NAME_SZ);
dr_snprintf(names[i], NAME_SZ, "%d", pri.priority);
pri.name = names[i];
if (!drmgr_register_bb_app2app_event(one_time_bb_event, &pri))
CHECK(false, "drmgr app2app registration failed");
}
/* XXX: drmgr lets us add multiple instances of the same callback
* so long as they have different priority names (or use default
* priority) -- but on removal it only asks for callback and
* removes the first it finds. Thus we cannot free any memory
* tied up in a priority until we remove *all* of them.
* Normally priorities use string literals, so seems ok.
*/
for (i = 0; i < STRESS_REGISTER_ITERS; i++) {
if (!drmgr_unregister_bb_app2app_event(one_time_bb_event))
CHECK(false, "drmgr app2app unregistration failed");
}
for (i = 0; i < STRESS_REGISTER_ITERS; i++) {
dr_thread_free(drcontext, names[i], NAME_SZ);
}
return DR_EMIT_DEFAULT;
}