blob: 1f3b7b8228b64af0c2df99767b29081c49469e9a [file] [edit]
/* **********************************************************
* Copyright (c) 2015-2017 Google, Inc. All rights reserved.
* **********************************************************/
/* Dr. Memory: the memory debugger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License, and no later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/***************************************************************************
* fastpath.c: Dr. Memory cross-platform shadow instrumentation fastpath
*/
#include "dr_api.h"
#include "drmemory.h"
#include "slowpath.h"
#include "spill.h"
#include "fastpath.h"
#include "fastpath_arch.h"
#include "shadow.h"
#include "stack.h"
#ifdef TOOL_DR_MEMORY
# include "alloc_drmem.h"
# include "report.h"
#endif
#include "pattern.h"
#ifdef UNIX
# include <signal.h> /* for SIGSEGV */
#else
# include <stddef.h> /* for offsetof */
#endif
static uint share_xl8_num_flushes;
void
slow_path_xl8_sharing(app_loc_t *loc, size_t inst_sz, opnd_t memop, dr_mcontext_t *mc)
{
/* PR 493257: share shadow translation across multiple instrs */
uint xl8_sharing_cnt;
app_pc pc;
bool translated = true;
ASSERT(loc != NULL && loc->type == APP_LOC_PC, "invalid param");
pc = loc_to_pc(loc);
xl8_sharing_cnt = (uint)(ptr_uint_t) hashtable_lookup(&xl8_sharing_table, pc);
if (xl8_sharing_cnt > 0) {
STATS_INC(xl8_shared_slowpath_count);
ASSERT(!opnd_is_null(memop), "error in xl8 sharing");
/* Since we can't share across 64K boundaries we exit to slowpath.
* If this happens too often, abandon sharing.
*/
if (xl8_sharing_cnt > options.share_xl8_max_slow &&
/* 2* is signal to not flush again */
xl8_sharing_cnt < 2*options.share_xl8_max_slow) {
uint num_flushes;
STATS_INC(xl8_not_shared_slowpaths);
/* If this instr has other reasons to go to slowpath, don't flush
* repeatedly: only flush if it's actually due to addr sharing
*/
hashtable_add_replace(&xl8_sharing_table, pc,
(void *)(ptr_uint_t)
(2*options.share_xl8_max_slow));
/* We don't need a synchronous flush: go w/ most performant.
* dr_delay_flush_region() doesn't do any unlinking, so if in
* a loop we'll repeatedly flush => performance problem!
* So we go w/ dr_unlink_flush_region(): should be ok since
* we'll never want -coarse_units.
* XXX i#1426: actually we do have -coarse_units with -persist_code!
* To support full mode persistence we'll have to change this flush!
*/
/* Flushing can be expensive so we disable sharing
* completely if we flush too many times.
*
* XXX DRi#373: really, DR should split vm areas up to
* make these flushes more performant so each flush
* doesn't throw out entire executable's worth!
*/
num_flushes = atomic_add32_return_sum((int*)&share_xl8_num_flushes, 1);
if (num_flushes >= options.share_xl8_max_flushes/2) {
/* reduce the max diff to reduce # of 64K crossings.
* the flush will flush all in this module (at least
* until DRi#373) so this will affect other sharings.
*/
LOG(1, "reached %d flushes: shrinking -share_xl8_max_diff\n",
num_flushes);
options.share_xl8_max_diff /= 8;
} else if (num_flushes >= options.share_xl8_max_flushes) {
LOG(1, "reached %d flushes: disabling xl8 sharing\n", num_flushes);
options.share_xl8 = false;
}
LOG(3, "slow_path_xl8_sharing: flushing "PFX"\n", pc);
if (!translated) {
/* Now we have to xl8 */
pc = loc_to_pc(loc);
/* We've been incrementing the cache pc entry, but the
* hashtable_add_replace below will tell the
* instrumentation to not share this app pc again.
*/
}
dr_unlink_flush_region(pc, 1);
} else {
xl8_sharing_cnt++;
/* We don't care about races: threshold is low enough we won't overflow */
hashtable_add_replace(&xl8_sharing_table, pc,
(void *)(ptr_uint_t) xl8_sharing_cnt);
}
} else if (!translated && !opnd_is_null(memop)) {
LOG(3, "slow_path_xl8_sharing: adding entry "PFX"\n", pc);
hashtable_add(&xl8_sharing_table, pc, (void *)1);
STATS_INC(xl8_shared_slowpath_instrs);
}
/* We've clobbered the register holding the shared xl8 address, so we have to
* either restore or clear for subsequent sharers. We end up clearing in all
* cases for simplicity (see the commit history for a big comment on how complex
* it gets to try and restore instead of just clearing). That clear used to be
* done here, and it relied on writing to the spill slot xchg-ed back into the
* reg by the 2-step return. Moving to drreg, we do not want to rely on spill
* slots being constant, and we want to eliminate the 2-step return, so we moved
* the reg clear to the inlined code in instrument_slowpath() where the reg to
* write to is known.
*/
}
/***************************************************************************
* Fault handling
*/
#ifdef ARM
dr_isa_mode_t
get_isa_mode_from_fault_mc(dr_mcontext_t *mc)
{
return TEST(EFLAGS_T, mc->xflags) ? DR_ISA_ARM_THUMB : DR_ISA_ARM_A32;
}
#endif
#ifdef TOOL_DR_MEMORY
/* PR 448701: handle fault on write to a special shadow block */
static byte *
compute_app_address_on_shadow_fault(void *drcontext, byte *target,
dr_mcontext_t *raw_mc, dr_mcontext_t *mc,
byte *pc_post_fault, bb_saved_info_t *save)
{
app_pc addr;
uint memopidx;
bool write;
instr_t *app_inst = restore_mcontext_on_shadow_fault(drcontext, raw_mc,
mc, pc_post_fault, save);
for (memopidx = 0;
instr_compute_address_ex(app_inst, mc, memopidx, &addr, &write);
memopidx++) {
LOG(3, "considering emulated target %s "PFX" => shadow "PFX" vs fault "PFX"\n",
write ? "write" : "read", addr, shadow_translation_addr(addr), target);
if (shadow_translation_addr(addr) == target)
break;
}
ASSERT(shadow_translation_addr(addr) == target,
"unable to compute original address on shadow fault");
instr_destroy(drcontext, app_inst);
return addr;
}
static void
handle_special_shadow_fault(void *drcontext, byte *target,
dr_mcontext_t *raw_mc, dr_mcontext_t *mc, void *tag)
{
/* Re-execute the faulting instruction. We have to shift the instruction's
* base register from the special block to the new block. An alternative is
* to restore the app context and then re-direct execution to a new bb: but
* that's more complex (have to decode forward and emulate all xchg +
* restore-from-tls until next cti) and less efficient (new bb, and we'd
* have to change two-part sub-dword dst write to get final value in reg
* before committing). Actually we do have to restore app state to get app
* address -- but the other arguments still apply so sticking w/ re-execute.
*/
app_pc pc;
app_pc addr;
instr_t fault_inst;
byte *new_shadow;
opnd_t shadowop;
bb_saved_info_t *save;
#ifdef DEBUG
tls_util_t *pt = PT_GET(drcontext);
#endif
LOG(2, "write fault to special shadow @"PFX"\n", target);
STATS_INC(num_faults);
#ifdef TOOL_DR_HEAPSTAT
ASSERT(false, "should not get here");
#endif
if (is_in_gencode(raw_mc->pc)) {
/* PR 503778: the esp_adjust fastpath now touches more than just the 64K
* region containing the app esp. We could calculate its exact address
* by locating the precise routine and then use the count from tls and
* remaining count, and then below adjust the stored boundary, but
* simpler to bail to slowpath.
*/
byte *nxt_pc;
instr_t inst;
pc = raw_mc->pc;
instr_init(drcontext, &inst);
nxt_pc = decode(drcontext, pc, &inst);
do {
/* Look for the start of slowpath inside esp_adjust fastpath.
* For simplicity we insert a nop there.
*/
if (instr_get_opcode(&inst) == OP_nop) {
LOG(3, "write fault in gencode "PFX" => xl8 to slowpath "PFX"\n",
raw_mc->pc, pc);
raw_mc->pc = pc;
instr_free(drcontext, &inst);
return;
}
instr_reset(drcontext, &inst);
pc = nxt_pc;
nxt_pc = decode(drcontext, pc, &inst);
} while (pc - raw_mc->pc < PAGE_SIZE);
ASSERT(false, "cannot find slowpath sequence in esp fastpath!");
}
instr_init(drcontext, &fault_inst);
pc = decode(drcontext, raw_mc->pc, &fault_inst);
hashtable_lock(&bb_table);
save = (bb_saved_info_t *) hashtable_lookup(&bb_table, tag);
addr = compute_app_address_on_shadow_fault(drcontext, target, raw_mc, mc, pc,
save);
hashtable_unlock(&bb_table);
/* Create a non-special shadow block */
new_shadow = shadow_replace_special(addr);
/* Change base register to point at it */
shadowop = instr_get_dst(&fault_inst, 0);
ASSERT(opnd_is_base_disp(shadowop) && opnd_get_index(shadowop) == REG_NULL,
"emulation error");
ASSERT(opnd_compute_address(shadowop, raw_mc) == target, "emulation error");
reg_set_value(opnd_get_base(shadowop), raw_mc, (reg_t)new_shadow);
DOLOG(3, {
LOG(3, "changed base reg in ");
opnd_disassemble(drcontext, shadowop, pt->f);
LOG(3, " to new non-special translation "PFX"\n", new_shadow);
});
instr_free(drcontext, &fault_inst);
}
/* i#1015: report write-to-read-only as an unaddressable warning */
static bool
handle_possible_write_to_read_only(void *drcontext,
dr_mcontext_t *raw_mc,
dr_mcontext_t *mc)
{
app_pc addr;
bool is_write;
uint pos;
int memopidx;
app_loc_t loc;
size_t size;
dr_mem_info_t info;
instr_t inst;
bool res = false;
/* XXX: we could try to merge some code w/ pattern_handle_segv_fault() */
ASSERT(options.pattern == 0, "already handled in pattern_handle_segv_fault()");
instr_init(drcontext, &inst);
if (!safe_decode(drcontext, raw_mc->pc, &inst, NULL)) {
instr_free(drcontext, &inst);
return false;
}
/* someone could send a SIGSEGV signal, so we iterate instr opnds
* to check possible write-to-read-only errors
*/
for (memopidx = 0;
instr_compute_address_ex_pos(&inst, mc, memopidx,
&addr, &is_write, &pos);
memopidx++) {
if (!is_write)
continue;
if (!dr_query_memory_ex(addr, &info) ||
info.type == DR_MEMTYPE_FREE ||
TEST(DR_MEMPROT_WRITE, info.prot) ||
TEST(info.prot, DR_MEMPROT_PRETEND_WRITE))
continue;
size = opnd_size_in_bytes(opnd_get_size(instr_get_dst(&inst, pos)));
pc_to_loc(&loc, mc->pc);
/* XXX: how to avoid double report on write-to-unaddressable,
* as the shadow have been updated by the first report?
* It should be ok since it is unlikely to have an unaddressable
* read-only region.
*/
report_unaddr_warning(&loc, mc, "writing to readonly memory", addr, size, true);
res = true;
}
instr_free(drcontext, &inst);
return res;
}
#endif /* TOOL_DR_MEMORY */
/* PR 448701: we fault if we write to a special block */
#ifdef UNIX
dr_signal_action_t
event_signal_instrument(void *drcontext, dr_siginfo_t *info)
{
# ifdef TOOL_DR_MEMORY
/* Handle faults from writes to special shadow blocks */
/* i#1488: we sometimes get SIGBUS on Mac */
if (info->sig == SIGSEGV || info->sig == SIGBUS) {
byte *target = info->access_address;
/* We don't know whether a write since DR isn't providing that info but
* shouldn't matter enough to be worth our determining
*/
LOG(2, "SIGSEGV @"PFX" (xl8=>"PFX") accessing "PFX"\n",
info->raw_mcontext->pc, info->mcontext->pc, target);
if (options.pattern != 0 &&
pattern_handle_segv_fault(drcontext, info->raw_mcontext, info->mcontext)) {
return DR_SIGNAL_SUPPRESS;
} else if (ZERO_STACK() &&
handle_zeroing_fault(drcontext, target, info->raw_mcontext,
info->mcontext)) {
return DR_SIGNAL_SUPPRESS;
} else if (options.shadowing &&
is_in_special_shadow_block(target)) {
ASSERT(info->raw_mcontext_valid, "raw mc should always be valid for SEGV");
handle_special_shadow_fault(drcontext, target, info->raw_mcontext,
info->mcontext, info->fault_fragment_info.tag);
/* Re-execute the faulting cache instr. If we ever change to redirect
* to a new bb at the app instr we must change our two-part shadow
* write for sub-dword.
*/
return DR_SIGNAL_SUPPRESS;
} else if (options.report_write_to_read_only &&
options.pattern == 0 && /* vs pattern_handle_segv_fault() */
handle_possible_write_to_read_only(drcontext, info->raw_mcontext,
info->mcontext)) {
/* fall through to DR_SIGNAL_DELIVER */
} else if (options.check_pc &&
/* If the pc is the target, it's an exec fault */
info->mcontext->pc == target) {
/* i#1412: raise an error on executing invalid memory */
app_loc_t loc;
pc_to_loc(&loc, target);
report_unaddressable_access(&loc, target, 1, DR_MEMPROT_EXEC,
target, target + 1, info->mcontext);
}
} else if (info->sig == SIGILL) {
LOG(2, "SIGILL @"PFX" (xl8=>"PFX")\n",
info->raw_mcontext->pc, info->mcontext->pc);
if (options.pattern != 0) {
if (pattern_handle_ill_fault(drcontext, info->raw_mcontext,
info->mcontext))
return DR_SIGNAL_SUPPRESS;
return DR_SIGNAL_DELIVER;
} else if (handle_slowpath_fault(drcontext, info->raw_mcontext,
info->mcontext,
info->fault_fragment_info.tag))
return DR_SIGNAL_SUPPRESS;
}
# endif
return DR_SIGNAL_DELIVER;
}
#else
bool
event_exception_instrument(void *drcontext, dr_exception_t *excpt)
{
# ifdef TOOL_DR_MEMORY
if (options.pattern != 0 &&
(excpt->record->ExceptionCode == STATUS_ACCESS_VIOLATION ||
excpt->record->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION)) {
bool guard;
app_pc target = (app_pc) excpt->record->ExceptionInformation[1];
/* i#1070: pattern mode inserted code may cause one-shot alarm
* guard page violation, we need actual target for restoring
* the guard page.
*/
guard = excpt->record->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION;
if (pattern_handle_segv_fault(drcontext, excpt->raw_mcontext, excpt->mcontext
_IF_WINDOWS(target) _IF_WINDOWS(guard)))
return false;
}
if (excpt->record->ExceptionCode == STATUS_ACCESS_VIOLATION) {
app_pc target = (app_pc) excpt->record->ExceptionInformation[1];
if (ZERO_STACK() &&
excpt->record->ExceptionInformation[0] == 1 /* write */ &&
handle_zeroing_fault(drcontext, target, excpt->raw_mcontext,
excpt->mcontext)) {
return false;
} else if (options.shadowing &&
excpt->record->ExceptionInformation[0] == 1 /* write */ &&
is_in_special_shadow_block(target)) {
handle_special_shadow_fault(drcontext, target, excpt->raw_mcontext,
excpt->mcontext, excpt->fault_fragment_info.tag);
/* Re-execute the faulting cache instr. If we ever change to redirect
* to a new bb at the app instr we must change our two-part shadow
* write for sub-dword.
*/
return false;
} else if (options.report_write_to_read_only &&
options.pattern == 0 && /* vs pattern_handle_segv_fault() */
excpt->record->ExceptionCode != STATUS_GUARD_PAGE_VIOLATION &&
handle_possible_write_to_read_only(drcontext, excpt->raw_mcontext,
excpt->mcontext)) {
return false;
} else if (options.check_pc &&
/* If the pc is the target, it's an exec fault */
excpt->mcontext->pc == target) {
/* i#1412: raise an error on executing invalid memory */
app_loc_t loc;
pc_to_loc(&loc, target);
report_unaddressable_access(&loc, target, 1, DR_MEMPROT_EXEC,
target, target + 1, excpt->mcontext);
}
} else if (excpt->record->ExceptionCode == STATUS_ILLEGAL_INSTRUCTION) {
if (options.pattern != 0) {
return !pattern_handle_ill_fault(drcontext, excpt->raw_mcontext,
excpt->mcontext);
} else if (handle_slowpath_fault(drcontext, excpt->raw_mcontext,
excpt->mcontext,
excpt->fault_fragment_info.tag)) {
return false;
}
}
# endif
LOG(2, "application fault @"PFX" in module %s\n",
excpt->mcontext->pc, module_lookup_preferred_name(excpt->mcontext->pc));
return true;
}
#endif
/***************************************************************************
* For PR 578892: fastpath heap routine unaddr accesses
*/
void
client_entering_heap_routine(void)
{
#ifdef TOOL_DR_MEMORY
if (options.shadowing)
set_shadow_inheap(1);
#endif
}
void
client_exiting_heap_routine(void)
{
#ifdef TOOL_DR_MEMORY
if (options.shadowing)
set_shadow_inheap(0);
#endif
}