blob: d649871d7f914ef9a4b83475688d1975734d0feb [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2012-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 VMware, 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 VMWARE, 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.
*/
#include "dr_api.h"
#include "hashtable.h"
#include "client_tools.h" /* For ASSERT; DR_ASSERT raises a message box. */
static byte *mybase;
static uint bb_execs;
static uint resurrect_success;
static bool verbose;
/* test hashtable persistence via a table that contains one entry per
* pcache written or loaded. key is start, payload is size.
*/
static hashtable_t sample_inlined_table;
static hashtable_t sample_pointer_table;
static void
free_payload(void *entry)
{
dr_global_free(entry, sizeof(size_t));
}
static
void at_bb(app_pc bb_addr)
{
#ifdef X64
/* FIXME: abs ref is rip-rel so doesn't work w/o patching.
* need to impl patching here: finalize interface.
*/
#else
/* global reference => won't work w/o same base or relocation */
bb_execs++;
#endif
}
/* We want to persist our clean call, which will only work if our library
* is at the same base
*/
static size_t
event_persist_ro_size(void *drcontext, void *perscxt, size_t file_offs,
void **user_data OUT)
{
return sizeof(mybase) +
hashtable_persist_size(drcontext, &sample_pointer_table,
sizeof(size_t), perscxt, DR_HASHPERS_REBASE_KEY |
DR_HASHPERS_ONLY_IN_RANGE | DR_HASHPERS_ONLY_PERSISTED) +
hashtable_persist_size(drcontext, &sample_inlined_table,
sizeof(size_t), perscxt, DR_HASHPERS_REBASE_KEY |
DR_HASHPERS_ONLY_IN_RANGE | DR_HASHPERS_ONLY_PERSISTED);
}
static bool
event_persist_patch(void *drcontext, void *perscxt,
byte *bb_start, size_t bb_size, void *user_data)
{
/* XXX: add more sophisticated example that needs patching.
* For that, we want cti's to be allowed, which is i#665. Then
* we can have a jmp or call into our lib (e.g., change at_bb to
* not be inlinable) and go patch it to go through gencode or sthg,
* and patch the gencode at load, or sthg.
*/
return true;
}
static bool
event_persist_ro(void *drcontext, void *perscxt, file_t fd, void *user_data)
{
bool ok = true;
app_pc start = dr_persist_start(perscxt);
if (dr_write_file(fd, &mybase, sizeof(mybase)) != (ssize_t)sizeof(mybase))
return false;
ok = ok && hashtable_persist(drcontext, &sample_pointer_table,
sizeof(size_t), fd, perscxt,
DR_HASHPERS_PAYLOAD_IS_POINTER | DR_HASHPERS_REBASE_KEY |
DR_HASHPERS_ONLY_IN_RANGE | DR_HASHPERS_ONLY_PERSISTED);
ok = ok && hashtable_persist(drcontext, &sample_inlined_table,
sizeof(size_t), fd, perscxt, DR_HASHPERS_REBASE_KEY |
DR_HASHPERS_ONLY_IN_RANGE | DR_HASHPERS_ONLY_PERSISTED);
return ok;
}
static bool
event_resurrect_ro(void *drcontext, void *perscxt, byte **map INOUT)
{
bool ok = true;
uint i;
app_pc start = dr_persist_start(perscxt);
size_t size = dr_persist_size(perscxt);
byte *base = *(byte **)(*map);
*map += sizeof(base);
/* this test relies on having a preferred base and getting it both runs */
if (base != mybase) {
if (verbose) {
dr_fprintf(STDERR, "persisted base="PFX" does not match cur base="PFX"\n",
base, mybase);
}
return false;
}
ok = ok && hashtable_resurrect(drcontext, map, &sample_pointer_table,
sizeof(size_t), perscxt,
DR_HASHPERS_PAYLOAD_IS_POINTER |
DR_HASHPERS_REBASE_KEY | DR_HASHPERS_CLONE_PAYLOAD,
NULL);
ok = ok && hashtable_resurrect(drcontext, map, &sample_inlined_table,
sizeof(size_t), perscxt, DR_HASHPERS_REBASE_KEY, NULL);
/* we stored the 1st byte of every bb */
for (i = 0; i < HASHTABLE_SIZE(sample_pointer_table.table_bits); i++) {
hash_entry_t *he;
for (he = sample_pointer_table.table[i]; he != NULL; he = he->next) {
ASSERT(*((app_pc)he->key) == *((app_pc)he->payload) ||
/* XXX: we relax our assert to handle a syscall hook, which
* isn't in place yet at load time (i#1196).
*/
*((app_pc)he->payload) == 0xe9);
}
}
for (i = 0; i < HASHTABLE_SIZE(sample_inlined_table.table_bits); i++) {
hash_entry_t *he;
for (he = sample_inlined_table.table[i]; he != NULL; he = he->next) {
ASSERT(*((app_pc)he->key) == (byte)(ptr_uint_t)he->payload ||
/* see above */
(byte)(ptr_uint_t)he->payload == 0xe9);
}
}
resurrect_success++;
return true;
}
#define MINSERT instrlist_meta_preinsert
static dr_emit_flags_t
event_bb(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, bool translating)
{
/* test abs ref */
dr_insert_clean_call(drcontext, bb, instrlist_first(bb), at_bb, false, 1,
OPND_CREATE_INTPTR((ptr_uint_t)dr_fragment_app_pc(tag)));
/* test intra-bb cti (i#665)
* XXX: test in automated fashion whether these bbs are truly persisted.
* using the client-walk patch API I can do that: but consensus is it's better
* to switch to having DR do the cache walk, making it not clear how to test
* this other than adding to every single bb which will result in no
* pcaches at all if this intra-bb cti isn't handled.
*/
{
instr_t *skip = INSTR_CREATE_label(drcontext);
instr_t *inst = instrlist_last(bb);
/* XXX i#1032: move in one further to avoid assert on an unhandled
* intra vs inter distinction case.
*/
if (instr_get_prev(inst) != NULL)
inst = instr_get_prev(inst);
instrlist_meta_preinsert(bb, inst, INSTR_CREATE_jmp
(drcontext, opnd_create_instr(skip)));
instrlist_meta_preinsert(bb, inst, INSTR_CREATE_ud2a(drcontext));
instrlist_meta_preinsert(bb, inst, skip);
}
/* test hashtable persistence: store the 1st byte of every bb */
{
void *payload = dr_global_alloc(sizeof(size_t));
app_pc pc = dr_fragment_app_pc(tag);
*(byte*)payload = *pc; /* not bothering w/ safe_read */
if (!hashtable_add(&sample_pointer_table, (void *)pc, payload))
free_payload(payload);
hashtable_add(&sample_inlined_table, (void *)pc, (void *)(ptr_uint_t)(*pc));
}
return DR_EMIT_DEFAULT | DR_EMIT_PERSISTABLE;
}
static void
event_exit(void)
{
if (resurrect_success > 0)
dr_fprintf(STDERR, "successfully resurrected at least one pcache\n");
hashtable_delete(&sample_inlined_table);
hashtable_delete(&sample_pointer_table);
}
DR_EXPORT
void
dr_init(client_id_t id)
{
mybase = dr_get_client_base(id);
dr_fprintf(STDERR, "thank you for testing the client interface\n");
dr_register_exit_event(event_exit);
dr_register_bb_event(event_bb);
if (!dr_register_persist_ro(event_persist_ro_size,
event_persist_ro,
event_resurrect_ro))
dr_fprintf(STDERR, "failed to register ro");
if (!dr_register_persist_patch(event_persist_patch))
dr_fprintf(STDERR, "failed to register patch");
hashtable_init(&sample_inlined_table, 4, HASH_INTPTR, false/*!strdup*/);
hashtable_init_ex(&sample_pointer_table, 4, HASH_INTPTR, false/*!strdup*/,
true/*sync*/, free_payload, NULL, NULL);
}