mirror of
https://github.com/DynamoRIO/dynamorio.git
synced 2026-10-12 02:02:04 +00:00
- Include source files as dependencies for kernel module:
- 3 remaining files in `core/`: `translate.c`, `utils.c`, and
`vmareas.c`.
- Relevant files in `core/ir/`
- Relevant files in `core/ir/x86/`
- Exclude functions and logic that don't apply in kernel space, such as
rseq and floating point arithmetic.
- Update include headers. This includes using wrapper headers or
excluding headers that don't have an equivalent in kernel.
- Some bug fixes that are uncovered by Kbuild compiler flags.
Issue: #8021
4654 lines
168 KiB
C
4654 lines
168 KiB
C
/* **********************************************************
|
|
* Copyright (c) 2010-2026 Google, Inc. All rights reserved.
|
|
* Copyright (c) 2017 ARM Limited. All rights reserved.
|
|
* Copyright (c) 2000-2010 VMware, 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.
|
|
*/
|
|
|
|
/* Copyright (c) 2003-2007 Determina Corp. */
|
|
/* Copyright (c) 2001-2003 Massachusetts Institute of Technology */
|
|
/* Copyright (c) 2000-2001 Hewlett-Packard Company */
|
|
|
|
/*
|
|
* utils.c - miscellaneous utilities
|
|
*/
|
|
|
|
#include "globals.h"
|
|
#include "configure_defines.h"
|
|
#include "dr_tools.h"
|
|
#include "utils.h"
|
|
#include "module_shared.h"
|
|
|
|
#ifndef LINUX_KERNEL
|
|
# include <math.h>
|
|
#endif
|
|
|
|
#ifdef PROCESS_CONTROL
|
|
# include "moduledb.h" /* for process control macros */
|
|
#endif
|
|
|
|
#ifdef UNIX
|
|
# include "types_wrapper.h"
|
|
# ifndef LINUX_KERNEL
|
|
# include <sys/stat.h>
|
|
# include <fcntl.h>
|
|
# include <stdio.h>
|
|
# include <stdlib.h>
|
|
# include <unistd.h>
|
|
# include <errno.h>
|
|
# endif
|
|
#else
|
|
# include <errno.h>
|
|
/* XXX : remove when syslog macros fixed */
|
|
# include "events.h"
|
|
#endif
|
|
|
|
#ifdef SHARING_STUDY
|
|
# include "fragment.h" /* print_shared_stats */
|
|
#endif
|
|
#ifdef DEBUG
|
|
# include "fcache.h"
|
|
# include "synch.h" /* all_threads_synch_lock */
|
|
#endif
|
|
|
|
#include "stdarg_wrapper.h" /* for varargs */
|
|
#include "stddef_wrapper.h" /* for offsetof */
|
|
|
|
try_except_t global_try_except;
|
|
#ifdef UNIX
|
|
thread_id_t global_try_tid = INVALID_THREAD_ID;
|
|
#endif
|
|
|
|
int do_once_generation = 1;
|
|
|
|
#ifdef SIDELINE
|
|
extern void
|
|
sideline_exit(void);
|
|
#endif
|
|
|
|
/* use for soft errors that can handle some cleanup: assertions and apichecks
|
|
* performs some cleanup and then calls os_terminate
|
|
*/
|
|
static void
|
|
soft_terminate(void)
|
|
{
|
|
#ifdef SIDELINE
|
|
/* kill child threads */
|
|
if (dynamo_options.sideline) {
|
|
sideline_stop();
|
|
sideline_exit();
|
|
}
|
|
#endif
|
|
/* set exited status for shared memory watchers, other threads */
|
|
DOSTATS({
|
|
if (d_r_stats != NULL)
|
|
GLOBAL_STAT(exited) = true;
|
|
});
|
|
|
|
/* do not try to clean up */
|
|
os_terminate(NULL, TERMINATE_PROCESS);
|
|
}
|
|
|
|
#if defined(INTERNAL) || defined(DEBUG)
|
|
/* checks whether an assert statement should be ignored,
|
|
* produces a warning if so and returns true,
|
|
* otherwise returns false
|
|
*/
|
|
bool
|
|
ignore_assert(const char *assert_stmt, const char *expr)
|
|
{
|
|
bool ignore = false;
|
|
if (!IS_STRING_OPTION_EMPTY(ignore_assert_list)) {
|
|
string_option_read_lock();
|
|
ignore = check_filter(DYNAMO_OPTION(ignore_assert_list), assert_stmt);
|
|
string_option_read_unlock();
|
|
}
|
|
if (IS_LISTSTRING_OPTION_FORALL(ignore_assert_list)) {
|
|
ignore = true;
|
|
}
|
|
if (ignore) {
|
|
/* XXX: could have passed message around */
|
|
SYSLOG_INTERNAL_WARNING("Ignoring assert %s %s", assert_stmt, expr);
|
|
}
|
|
return ignore;
|
|
}
|
|
|
|
/* Hand-made DO_ONCE used in d_r_internal_error b/c ifdefs in
|
|
* report_dynamorio_problem prevent DO_ONCE itself
|
|
*/
|
|
DECLARE_FREQPROT_VAR(static bool do_once_internal_error, false);
|
|
|
|
/* abort on internal dynamo error */
|
|
void
|
|
d_r_internal_error(const char *file, int line, const char *expr)
|
|
{
|
|
/* note that we no longer obfuscate filenames in non-internal builds
|
|
* xref PR 303817 */
|
|
|
|
/* need to produce a SYSLOG Ignore Error here and return right away */
|
|
/* to avoid adding another ?: in the ASSERT messsage
|
|
* we'll reconstruct file and line # here
|
|
*/
|
|
if (!IS_STRING_OPTION_EMPTY(ignore_assert_list)) {
|
|
char assert_stmt[MAXIMUM_PATH]; /* max unique identifier */
|
|
/* note the ignore checks are done with a possible recursive
|
|
* infinte loop if any asserts fail. Not very safe to set and
|
|
* unset a static bool either since we'll be noisy.
|
|
*/
|
|
|
|
/* Assert identifiers should be an exact match of message
|
|
* after Internal Error. Most common look like 'arch/arch.c:142',
|
|
* but could also look like 'Not implemented @arch/arch.c:142'
|
|
* or 'Bug #4809 @arch/arch.c:145;Ignore message
|
|
* @arch/arch.c:146'
|
|
*/
|
|
snprintf(assert_stmt, BUFFER_SIZE_ELEMENTS(assert_stmt), "%s:%d", file, line);
|
|
NULL_TERMINATE_BUFFER(assert_stmt);
|
|
ASSERT_CURIOSITY((strlen(assert_stmt) + 1) != BUFFER_SIZE_ELEMENTS(assert_stmt));
|
|
if (ignore_assert(assert_stmt, expr))
|
|
return;
|
|
/* we can ignore multiple asserts without triggering the do_once */
|
|
}
|
|
|
|
if (do_once_internal_error) /* recursing, bail out */
|
|
return;
|
|
else
|
|
do_once_internal_error = true;
|
|
|
|
report_dynamorio_problem(NULL, DUMPCORE_ASSERTION, NULL, NULL,
|
|
PRODUCT_NAME " debug check failure: %s:%d %s"
|
|
# if defined(DEBUG) && defined(INTERNAL)
|
|
"\n(Error occurred @%d frags in tid " TIDFMT ")"
|
|
# endif
|
|
,
|
|
file, line, expr
|
|
# if defined(DEBUG) && defined(INTERNAL)
|
|
,
|
|
d_r_stats == NULL ? -1 : GLOBAL_STAT(num_fragments),
|
|
IF_UNIX_ELSE(get_sys_thread_id(), d_r_get_thread_id())
|
|
# endif
|
|
);
|
|
|
|
soft_terminate();
|
|
}
|
|
#endif /* defined(INTERNAL) || defined(DEBUG) */
|
|
|
|
/* abort on external application created error, i.e. apicheck */
|
|
void
|
|
external_error(const char *file, int line, const char *msg)
|
|
{
|
|
DO_ONCE({
|
|
/* this syslog is before any core dump, unlike our other reports, but
|
|
* not worth fixing
|
|
*/
|
|
SYSLOG(SYSLOG_ERROR, EXTERNAL_ERROR, 4, get_application_name(),
|
|
get_application_pid(), PRODUCT_NAME, msg);
|
|
report_dynamorio_problem(NULL, DUMPCORE_FATAL_USAGE_ERROR, NULL, NULL,
|
|
"Usage error: %s (%s, line %d)", msg, file, line);
|
|
});
|
|
soft_terminate();
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* SYNCHRONIZATION */
|
|
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
/* Keeps the head of a linked list of all mutexes currently
|
|
held by a thread. We also require a LIFO lock unlock order
|
|
to keep things simpler (and stricter).
|
|
*/
|
|
struct _thread_locks_t {
|
|
mutex_t *last_lock;
|
|
};
|
|
|
|
/* These two locks are never deleted, although innermost_lock is grabbed */
|
|
DECLARE_CXTSWPROT_VAR(mutex_t outermost_lock, INIT_LOCK_FREE(outermost_lock));
|
|
DECLARE_CXTSWPROT_VAR(mutex_t innermost_lock, INIT_LOCK_FREE(innermost_lock));
|
|
|
|
/* Case 8075: For selfprot we have no way to put a local-scope mutex into
|
|
* .cspdata, and {add,remove}_process_lock need to write to the
|
|
* do_threshold_mutex when managing adjacent entries in the lock list, so
|
|
* we use a global lock instead. Could be a contention issue but it's only
|
|
* DEADLOCK_AVOIDANCE builds and there are few uses of DO_THRESHOLD_SAFE.
|
|
*/
|
|
DECLARE_CXTSWPROT_VAR(mutex_t do_threshold_mutex, INIT_LOCK_FREE(do_threshold_mutex));
|
|
|
|
/* structure field dumper for both name and value, format with %*s%d */
|
|
# define DUMP_NONZERO(v, field) \
|
|
strlen(#field) + 1, (v->field ? #field "=" : ""), v->field
|
|
# ifdef MACOS
|
|
# define DUMP_CONTENDED(v, field) \
|
|
strlen(#field) + 1, (ksynch_var_initialized(&v->field) ? #field "=" : ""), \
|
|
v->field.sem
|
|
# else
|
|
# define DUMP_CONTENDED DUMP_NONZERO
|
|
# endif
|
|
|
|
/* common format string used for different log files and loglevels */
|
|
# define DUMP_LOCK_INFO_ARGS(depth, cur_lock, prev) \
|
|
"%d lock " PFX ": name=%s\nrank=%d owner=" TIDFMT " owning_dc=" PFX " " \
|
|
"%*s" PIFX " prev=" PFX "\n" \
|
|
"lock %*s%8d %*s%8d %*s%8d %*s%8d %*s%8d+2 %s\n", \
|
|
depth, cur_lock, cur_lock->name, cur_lock->rank, cur_lock->owner, \
|
|
cur_lock->owning_dcontext, DUMP_CONTENDED(cur_lock, contended_event), prev, \
|
|
DUMP_NONZERO(cur_lock, count_times_acquired), \
|
|
DUMP_NONZERO(cur_lock, count_times_contended), \
|
|
DUMP_NONZERO(cur_lock, count_times_spin_pause), \
|
|
DUMP_NONZERO(cur_lock, count_times_spin_only), \
|
|
DUMP_NONZERO(cur_lock, max_contended_requests), cur_lock->name
|
|
|
|
# ifdef INTERNAL
|
|
static void
|
|
dump_mutex_callstack(mutex_t *lock)
|
|
{
|
|
/* from windbg proper command is
|
|
* 0:001> dds @@(&lock->callstack) L4
|
|
*/
|
|
# ifdef MUTEX_CALLSTACK
|
|
uint i;
|
|
if (INTERNAL_OPTION(mutex_callstack) == 0)
|
|
return;
|
|
LOG(GLOBAL, LOG_THREADS, 1, "dump_mutex_callstack %s\n", lock->name);
|
|
for (i = 0; i < INTERNAL_OPTION(mutex_callstack); i++) {
|
|
/* some macro's call this function, so it is easier to ifdef
|
|
* only references to callstack */
|
|
LOG(GLOBAL, LOG_THREADS, 1, " " PFX "\n", lock->callstack[i]);
|
|
}
|
|
# endif /* MUTEX_CALLSTACK */
|
|
}
|
|
# endif
|
|
|
|
void
|
|
dump_owned_locks(dcontext_t *dcontext)
|
|
{ /* LIFO order even though order in releasing doesn't matter */
|
|
mutex_t *cur_lock;
|
|
uint depth = 0;
|
|
cur_lock = dcontext->thread_owned_locks->last_lock;
|
|
LOG(THREAD, LOG_THREADS, 1, "Owned locks for thread " TIDFMT " dcontext=" PFX "\n",
|
|
dcontext->owning_thread, dcontext);
|
|
while (cur_lock != &outermost_lock) {
|
|
depth++;
|
|
LOG(THREAD, LOG_THREADS, 1,
|
|
DUMP_LOCK_INFO_ARGS(depth, cur_lock, cur_lock->prev_owned_lock));
|
|
ASSERT(cur_lock->owner == dcontext->owning_thread);
|
|
cur_lock = cur_lock->prev_owned_lock;
|
|
}
|
|
}
|
|
|
|
bool
|
|
thread_owns_no_locks(dcontext_t *dcontext)
|
|
{
|
|
ASSERT(dcontext != NULL);
|
|
if (!INTERNAL_OPTION(deadlock_avoidance))
|
|
return true; /* can't verify since we aren't keeping track of owned locks */
|
|
return (dcontext->thread_owned_locks->last_lock == &outermost_lock);
|
|
}
|
|
|
|
bool
|
|
thread_owns_one_lock(dcontext_t *dcontext, mutex_t *lock)
|
|
{
|
|
mutex_t *cur_lock;
|
|
ASSERT(dcontext != NULL);
|
|
if (!INTERNAL_OPTION(deadlock_avoidance))
|
|
return true; /* can't verify since we aren't keeping track of owned locks */
|
|
cur_lock = dcontext->thread_owned_locks->last_lock;
|
|
return (cur_lock == lock && cur_lock->prev_owned_lock == &outermost_lock);
|
|
}
|
|
|
|
/* Returns true if dcontext thread owns lock1 and lock2 and no other locks */
|
|
bool
|
|
thread_owns_two_locks(dcontext_t *dcontext, mutex_t *lock1, mutex_t *lock2)
|
|
{
|
|
mutex_t *cur_lock;
|
|
ASSERT(dcontext != NULL);
|
|
if (!INTERNAL_OPTION(deadlock_avoidance))
|
|
return true; /* can't verify since we aren't keeping track of owned locks */
|
|
cur_lock = dcontext->thread_owned_locks->last_lock;
|
|
return (cur_lock == lock1 && cur_lock->prev_owned_lock == lock2 &&
|
|
lock2->prev_owned_lock == &outermost_lock);
|
|
}
|
|
|
|
/* Returns true if dcontext thread owns lock1 and optionally lock2
|
|
* (acquired before lock1) and no other locks. */
|
|
bool
|
|
thread_owns_first_or_both_locks_only(dcontext_t *dcontext, mutex_t *lock1, mutex_t *lock2)
|
|
{
|
|
mutex_t *cur_lock;
|
|
ASSERT(dcontext != NULL);
|
|
if (!INTERNAL_OPTION(deadlock_avoidance))
|
|
return true; /* can't verify since we aren't keeping track of owned locks */
|
|
cur_lock = dcontext->thread_owned_locks->last_lock;
|
|
return (cur_lock == lock1 &&
|
|
(cur_lock->prev_owned_lock == &outermost_lock ||
|
|
(cur_lock->prev_owned_lock == lock2 &&
|
|
lock2->prev_owned_lock == &outermost_lock)));
|
|
}
|
|
|
|
/* dump process locks that have been acquired at least once */
|
|
/* XXX: since most mutexes are global we don't have thread private lock lists */
|
|
void
|
|
dump_process_locks(void)
|
|
{
|
|
mutex_t *cur_lock;
|
|
uint depth = 0;
|
|
uint total_acquired = 0;
|
|
uint total_contended = 0;
|
|
|
|
LOG(GLOBAL, LOG_STATS, 2, "Currently live process locks:\n");
|
|
/* global list access needs to be synchronized */
|
|
d_r_mutex_lock(&innermost_lock);
|
|
cur_lock = &innermost_lock;
|
|
do {
|
|
depth++;
|
|
LOG(GLOBAL, LOG_STATS,
|
|
(cur_lock->count_times_contended ? 1U : 2U), /* elevate contended ones */
|
|
DUMP_LOCK_INFO_ARGS(depth, cur_lock, cur_lock->next_process_lock));
|
|
DOLOG((cur_lock->count_times_contended ? 2U : 3U), /* elevate contended ones */
|
|
LOG_THREADS, {
|
|
/* last recorded callstack, not necessarily the most contended path */
|
|
dump_mutex_callstack(cur_lock);
|
|
});
|
|
cur_lock = cur_lock->next_process_lock;
|
|
total_acquired += cur_lock->count_times_acquired;
|
|
total_contended += cur_lock->count_times_contended;
|
|
ASSERT(cur_lock);
|
|
ASSERT(cur_lock->next_process_lock->prev_process_lock == cur_lock);
|
|
ASSERT(cur_lock->prev_process_lock->next_process_lock == cur_lock);
|
|
ASSERT(cur_lock->prev_process_lock != cur_lock || cur_lock == &innermost_lock);
|
|
ASSERT(cur_lock->next_process_lock != cur_lock || cur_lock == &innermost_lock);
|
|
} while (cur_lock != &innermost_lock);
|
|
d_r_mutex_unlock(&innermost_lock);
|
|
LOG(GLOBAL, LOG_STATS, 1,
|
|
"Currently live process locks: %d, acquired %d, contended %d (current only)\n",
|
|
depth, total_acquired, total_contended);
|
|
}
|
|
|
|
uint
|
|
locks_not_closed(void)
|
|
{
|
|
mutex_t *cur_lock;
|
|
uint forgotten = 0;
|
|
uint ignored = 0;
|
|
/* Case 8075: we use a global do_threshold_mutex for DEADLOCK_AVOIDANCE.
|
|
* Leaving the code for a local var here via this bool in case we run
|
|
* this routine in release build while somehow avoiding the global lock.
|
|
*/
|
|
static const bool allow_do_threshold_leaks = false;
|
|
|
|
/* we assume that we would have removed them from the process list in mutex_close */
|
|
|
|
/* locks assigned with do_threshold_mutex are 'leaked' because it
|
|
* is too much hassle to find a good place to DELETE them -- though we
|
|
* now use a global mutex for DEADLOCK_AVOIDANCE so that's not the case.
|
|
*/
|
|
d_r_mutex_lock(&innermost_lock);
|
|
/* innermost will stay */
|
|
cur_lock = innermost_lock.next_process_lock;
|
|
while (cur_lock != &innermost_lock) {
|
|
if (allow_do_threshold_leaks && cur_lock->rank == LOCK_RANK(do_threshold_mutex)) {
|
|
ignored++;
|
|
} else if (cur_lock->deleted &&
|
|
(IF_WINDOWS(cur_lock->rank == LOCK_RANK(debugbox_lock) ||
|
|
cur_lock->rank == LOCK_RANK(dump_core_lock) ||)
|
|
cur_lock->rank == LOCK_RANK(report_buf_lock) ||
|
|
cur_lock->rank == LOCK_RANK(datasec_selfprot_lock) ||
|
|
cur_lock->rank == LOCK_RANK(logdir_mutex) ||
|
|
cur_lock->rank ==
|
|
LOCK_RANK(options_lock)
|
|
/* This lock can be used parallel to detach cleanup. */
|
|
IF_UNIX(|| cur_lock->rank == LOCK_RANK(detached_sigact_lock)))) {
|
|
/* i#1058: curiosities during exit re-acquire these locks. */
|
|
ignored++;
|
|
} else {
|
|
LOG(GLOBAL, LOG_STATS, 1, "missing DELETE_LOCK on lock " PFX " %s\n",
|
|
cur_lock, cur_lock->name);
|
|
forgotten++;
|
|
}
|
|
cur_lock = cur_lock->next_process_lock;
|
|
}
|
|
d_r_mutex_unlock(&innermost_lock);
|
|
LOG(GLOBAL, LOG_STATS, 3, "locks_not_closed= %d remaining, %d ignored\n", forgotten,
|
|
ignored);
|
|
return forgotten;
|
|
}
|
|
|
|
void
|
|
locks_thread_init(dcontext_t *dcontext)
|
|
{
|
|
thread_locks_t *new_thread_locks;
|
|
new_thread_locks = (thread_locks_t *)UNPROTECTED_GLOBAL_ALLOC(
|
|
sizeof(thread_locks_t) HEAPACCT(ACCT_OTHER));
|
|
LOG(THREAD, LOG_STATS, 2, "thread_locks=" PFX " size=%d\n", new_thread_locks,
|
|
sizeof(thread_locks_t));
|
|
/* initialize any thread bookkeeping fields before assigning to dcontext */
|
|
new_thread_locks->last_lock = &outermost_lock;
|
|
dcontext->thread_owned_locks = new_thread_locks;
|
|
}
|
|
|
|
void
|
|
locks_thread_exit(dcontext_t *dcontext)
|
|
{
|
|
/* using global heap and always have to clean up */
|
|
if (dcontext->thread_owned_locks) {
|
|
thread_locks_t *old_thread_locks = dcontext->thread_owned_locks;
|
|
/* when exiting, another thread may be holding the lock instead of the current,
|
|
CHECK: is this true for detaching */
|
|
ASSERT(dcontext->thread_owned_locks->last_lock == &thread_initexit_lock ||
|
|
dcontext->thread_owned_locks->last_lock == &outermost_lock
|
|
/* PR 546016: sideline client thread might hold client lock */
|
|
|| dcontext->thread_owned_locks->last_lock->rank == dr_client_mutex_rank);
|
|
/* disable thread lock checks before freeing memory */
|
|
dcontext->thread_owned_locks = NULL;
|
|
UNPROTECTED_GLOBAL_FREE(old_thread_locks,
|
|
sizeof(thread_locks_t) HEAPACCT(ACCT_OTHER));
|
|
}
|
|
}
|
|
|
|
static void
|
|
add_process_lock(mutex_t *lock)
|
|
{
|
|
/* add to global locks circular double linked list */
|
|
d_r_mutex_lock(&innermost_lock);
|
|
if (lock->prev_process_lock != NULL) {
|
|
/* race: someone already added (can only happen for read locks) */
|
|
LOG(THREAD_GET, LOG_THREADS, 2, "\talready added\n");
|
|
ASSERT(lock->next_process_lock != NULL);
|
|
d_r_mutex_unlock(&innermost_lock);
|
|
return;
|
|
}
|
|
LOG(THREAD_GET, LOG_THREADS, 2,
|
|
"add_process_lock: " DUMP_LOCK_INFO_ARGS(0, lock, lock->prev_process_lock));
|
|
ASSERT(lock->next_process_lock == NULL || lock == &innermost_lock);
|
|
ASSERT(lock->prev_process_lock == NULL || lock == &innermost_lock);
|
|
if (innermost_lock.prev_process_lock == NULL) {
|
|
innermost_lock.next_process_lock = &innermost_lock;
|
|
innermost_lock.prev_process_lock = &innermost_lock;
|
|
}
|
|
lock->next_process_lock = &innermost_lock;
|
|
innermost_lock.prev_process_lock->next_process_lock = lock;
|
|
lock->prev_process_lock = innermost_lock.prev_process_lock;
|
|
innermost_lock.prev_process_lock = lock;
|
|
ASSERT(lock->next_process_lock->prev_process_lock == lock);
|
|
ASSERT(lock->prev_process_lock->next_process_lock == lock);
|
|
ASSERT(lock->prev_process_lock != lock || lock == &innermost_lock);
|
|
ASSERT(lock->next_process_lock != lock || lock == &innermost_lock);
|
|
d_r_mutex_unlock(&innermost_lock);
|
|
}
|
|
|
|
static void
|
|
remove_process_lock(mutex_t *lock)
|
|
{
|
|
LOG(THREAD_GET, LOG_THREADS, 3,
|
|
"remove_process_lock " DUMP_LOCK_INFO_ARGS(0, lock, lock->prev_process_lock));
|
|
STATS_ADD(total_acquired, lock->count_times_acquired);
|
|
STATS_ADD(total_contended, lock->count_times_contended);
|
|
if (lock->count_times_acquired == 0) {
|
|
ASSERT(lock->prev_process_lock == NULL);
|
|
LOG(THREAD_GET, LOG_THREADS, 3, "\tnever acquired\n");
|
|
return;
|
|
}
|
|
ASSERT(lock->prev_process_lock && "if ever acquired should be on the list");
|
|
ASSERT(lock != &innermost_lock && "innermost will be 'leaked'");
|
|
/* remove from global locks list */
|
|
d_r_mutex_lock(&innermost_lock);
|
|
/* innermost should always have both fields set here */
|
|
lock->next_process_lock->prev_process_lock = lock->prev_process_lock;
|
|
lock->prev_process_lock->next_process_lock = lock->next_process_lock;
|
|
lock->next_process_lock = NULL;
|
|
lock->prev_process_lock = NULL; /* so we catch uses after closing */
|
|
d_r_mutex_unlock(&innermost_lock);
|
|
}
|
|
|
|
# ifdef MUTEX_CALLSTACK
|
|
/* XXX: generalize and merge w/ CALL_PROFILE? */
|
|
static void
|
|
mutex_collect_callstack(mutex_t *lock)
|
|
{
|
|
uint max_depth = INTERNAL_OPTION(mutex_callstack);
|
|
uint depth = 0;
|
|
uint skip = 2; /* ignore calls from deadlock_avoidance() and d_r_mutex_lock() */
|
|
/* XXX: write_lock could ignore one level further */
|
|
byte *fp;
|
|
dcontext_t *dcontext = get_thread_private_dcontext();
|
|
|
|
GET_FRAME_PTR(fp);
|
|
|
|
/* only interested in DR addresses which should all be readable */
|
|
while (depth < max_depth && (is_on_initstack(fp) || is_on_dstack(dcontext, fp)) &&
|
|
/* is_on_initstack() and is_on_dstack() do include the guard pages
|
|
* yet we cannot afford to call is_readable_without_exception()
|
|
*/
|
|
!is_stack_overflow(dcontext, fp)) {
|
|
app_pc our_ret = *((app_pc *)fp + 1);
|
|
fp = *(byte **)fp;
|
|
if (skip) {
|
|
skip--;
|
|
continue;
|
|
}
|
|
lock->callstack[depth] = our_ret;
|
|
depth++;
|
|
}
|
|
}
|
|
# endif /* MUTEX_CALLSTACK */
|
|
|
|
enum { LOCK_NOT_OWNABLE, LOCK_OWNABLE };
|
|
|
|
/* if not acquired only update statistics,
|
|
if not ownable (i.e. read lock) only check against previous locks,
|
|
but don't add to thread owned list
|
|
*/
|
|
static void
|
|
deadlock_avoidance_lock(mutex_t *lock, bool acquired, bool ownable)
|
|
{
|
|
if (acquired) {
|
|
lock->count_times_acquired++;
|
|
/* CHECK: everything here works without mutex_trylock's */
|
|
LOG(GLOBAL, LOG_THREADS, 6,
|
|
"acquired lock " PFX " %s rank=%d, %s dcontext, tid:%d, %d time\n", lock,
|
|
lock->name, lock->rank, get_thread_private_dcontext() ? "valid" : "not valid",
|
|
d_r_get_thread_id(), lock->count_times_acquired);
|
|
LOG(THREAD_GET, LOG_THREADS, 6, "acquired lock " PFX " %s rank=%d\n", lock,
|
|
lock->name, lock->rank);
|
|
ASSERT(lock->rank > 0 && "initialize with INIT_LOCK_FREE");
|
|
if (ownable) {
|
|
ASSERT(!lock->owner);
|
|
lock->owner = d_r_get_thread_id();
|
|
lock->owning_dcontext = get_thread_private_dcontext();
|
|
}
|
|
/* add to global list */
|
|
if (lock->prev_process_lock == NULL && lock != &innermost_lock) {
|
|
add_process_lock(lock);
|
|
}
|
|
|
|
/* cannot hold thread_initexit_lock while couldbelinking, else will
|
|
* deadlock with flushers
|
|
*/
|
|
ASSERT(lock != &thread_initexit_lock || !is_self_couldbelinking());
|
|
|
|
if (INTERNAL_OPTION(deadlock_avoidance) &&
|
|
get_thread_private_dcontext() != NULL &&
|
|
get_thread_private_dcontext() != GLOBAL_DCONTEXT) {
|
|
dcontext_t *dcontext = get_thread_private_dcontext();
|
|
if (dcontext->thread_owned_locks != NULL) {
|
|
/* PR 198871: same label used for all client locks so allow same rank.
|
|
* For now we ignore rank order when client lock is 1st, as well,
|
|
* to support decode_trace() for 0.9.6 release PR 198871 covers safer
|
|
* long-term fix.
|
|
*/
|
|
bool first_client = (dcontext->thread_owned_locks->last_lock->rank ==
|
|
dr_client_mutex_rank);
|
|
bool both_client = (first_client && lock->rank == dr_client_mutex_rank);
|
|
if (dcontext->thread_owned_locks->last_lock->rank >= lock->rank &&
|
|
!first_client /*XXX PR 198871: remove */ && !both_client) {
|
|
/* report rank order violation */
|
|
SYSLOG_INTERNAL_NO_OPTION_SYNCH(
|
|
SYSLOG_CRITICAL,
|
|
"rank order violation %s acquired after %s in tid:%x", lock->name,
|
|
dcontext->thread_owned_locks->last_lock->name,
|
|
d_r_get_thread_id());
|
|
dump_owned_locks(dcontext);
|
|
/* like syslog don't synchronize options for dumpcore_mask */
|
|
if (TESTANY(DUMPCORE_DEADLOCK, DYNAMO_OPTION(dumpcore_mask)))
|
|
os_dump_core("rank order violation");
|
|
}
|
|
ASSERT((dcontext->thread_owned_locks->last_lock->rank < lock->rank ||
|
|
first_client /*XXX PR 198871: remove */
|
|
|| both_client) &&
|
|
"rank order violation");
|
|
if (ownable) {
|
|
lock->prev_owned_lock = dcontext->thread_owned_locks->last_lock;
|
|
dcontext->thread_owned_locks->last_lock = lock;
|
|
}
|
|
DOLOG(6, LOG_THREADS, { dump_owned_locks(dcontext); });
|
|
}
|
|
}
|
|
if (INTERNAL_OPTION(mutex_callstack) != 0 && ownable &&
|
|
get_thread_private_dcontext() != NULL) {
|
|
# ifdef MUTEX_CALLSTACK
|
|
mutex_collect_callstack(lock);
|
|
# endif
|
|
}
|
|
} else {
|
|
/* NOTE check_wait_at_safe_spot makes the assumption that no system
|
|
* calls are made on the non acquired path here */
|
|
ASSERT(lock->rank > 0 && "initialize with INIT_LOCK_FREE");
|
|
if (INTERNAL_OPTION(deadlock_avoidance) && ownable) {
|
|
ASSERT(lock->owner != d_r_get_thread_id() &&
|
|
"deadlock on recursive mutex_lock");
|
|
}
|
|
lock->count_times_contended++;
|
|
}
|
|
}
|
|
|
|
/* XXX: exported only for the linux hack -- make static once that's fixed */
|
|
void
|
|
deadlock_avoidance_unlock(mutex_t *lock, bool ownable)
|
|
{
|
|
if (INTERNAL_OPTION(simulate_contention)) {
|
|
/* with higher chances another thread will have to wait */
|
|
os_thread_yield();
|
|
}
|
|
|
|
LOG(GLOBAL, LOG_THREADS, 6,
|
|
"released lock " PFX " %s rank=%d, %s dcontext, tid:%d \n", lock, lock->name,
|
|
lock->rank, get_thread_private_dcontext() ? "valid" : "not valid",
|
|
d_r_get_thread_id());
|
|
LOG(THREAD_GET, LOG_THREADS, 6, "released lock " PFX " %s rank=%d\n", lock,
|
|
lock->name, lock->rank);
|
|
if (!ownable)
|
|
return;
|
|
|
|
ASSERT(lock->owner == d_r_get_thread_id());
|
|
if (INTERNAL_OPTION(deadlock_avoidance) && lock->owning_dcontext != NULL &&
|
|
lock->owning_dcontext != GLOBAL_DCONTEXT) {
|
|
dcontext_t *dcontext = get_thread_private_dcontext();
|
|
if (dcontext == NULL) {
|
|
# ifdef DEBUG
|
|
/* thread_initexit_lock and all_threads_synch_lock
|
|
* are unlocked after tearing down thread structures
|
|
*/
|
|
# if defined(UNIX) && !defined(HAVE_TLS)
|
|
extern mutex_t tls_lock;
|
|
# endif
|
|
bool null_ok =
|
|
(lock == &thread_initexit_lock || lock == &all_threads_synch_lock
|
|
# if defined(UNIX) && !defined(HAVE_TLS)
|
|
|| lock == &tls_lock
|
|
# endif
|
|
);
|
|
ASSERT(null_ok);
|
|
# endif
|
|
} else {
|
|
ASSERT(lock->owning_dcontext == dcontext);
|
|
if (dcontext->thread_owned_locks != NULL) {
|
|
DOLOG(6, LOG_THREADS, { dump_owned_locks(dcontext); });
|
|
/* LIFO order even though order in releasing doesn't matter */
|
|
ASSERT(dcontext->thread_owned_locks->last_lock == lock);
|
|
dcontext->thread_owned_locks->last_lock = lock->prev_owned_lock;
|
|
lock->prev_owned_lock = NULL;
|
|
}
|
|
}
|
|
}
|
|
lock->owner = INVALID_THREAD_ID;
|
|
lock->owning_dcontext = NULL;
|
|
}
|
|
# define DEADLOCK_AVOIDANCE_LOCK(lock, acquired, ownable) \
|
|
deadlock_avoidance_lock(lock, acquired, ownable)
|
|
# define DEADLOCK_AVOIDANCE_UNLOCK(lock, ownable) \
|
|
deadlock_avoidance_unlock(lock, ownable)
|
|
#else
|
|
# define DEADLOCK_AVOIDANCE_LOCK(lock, acquired, ownable) /* do nothing */
|
|
# define DEADLOCK_AVOIDANCE_UNLOCK(lock, ownable) /* do nothing */
|
|
#endif /* DEADLOCK_AVOIDANCE */
|
|
|
|
#ifdef UNIX
|
|
void
|
|
d_r_mutex_fork_reset(mutex_t *mutex)
|
|
{
|
|
/* i#239/PR 498752: need to free locks held by other threads at fork time.
|
|
* We can't call ASSIGN_INIT_LOCK_FREE as that clobbers any contention event
|
|
* (=> leak) and the debug-build lock lists (=> asserts like PR 504594).
|
|
* If the synch before fork succeeded, this is unecessary. If we encounter
|
|
* more deadlocks after fork because of synch failure, we can add more calls
|
|
* to reset locks on a case by case basis.
|
|
*/
|
|
mutex->lock_requests = LOCK_FREE_STATE;
|
|
# ifdef DEADLOCK_AVOIDANCE
|
|
mutex->owner = INVALID_THREAD_ID;
|
|
mutex->owning_dcontext = NULL;
|
|
# endif
|
|
}
|
|
#endif
|
|
|
|
static uint spinlock_count = 0; /* initialized in utils_init, but 0 is always safe */
|
|
DECLARE_FREQPROT_VAR(static uint random_seed, 1234); /* initialized in utils_init */
|
|
DEBUG_DECLARE(static uint initial_random_seed;)
|
|
|
|
void
|
|
utils_init(void)
|
|
{
|
|
/* XXX: We need to find a formula (or a better constant) based on real experiments
|
|
also see comment on spinlock_count_on_SMP in optionsx.h */
|
|
/* we want to make sure it is 0 on UP, the rest is speculation */
|
|
spinlock_count = (get_num_processors() - 1) * DYNAMO_OPTION(spinlock_count_on_SMP);
|
|
|
|
/* allow reproducing PRNG sequence
|
|
* (of course, thread scheduling may still affect requests) */
|
|
random_seed =
|
|
(DYNAMO_OPTION(prng_seed) == 0) ? os_random_seed() : DYNAMO_OPTION(prng_seed);
|
|
/* logged only at end, preserved so can be looked up in a dump */
|
|
DODEBUG(initial_random_seed = random_seed;);
|
|
|
|
/* sanity check since we cast back and forth */
|
|
ASSERT(sizeof(spin_mutex_t) == sizeof(mutex_t));
|
|
|
|
ASSERT(sizeof(uint64) == 8);
|
|
ASSERT(sizeof(uint32) == 4);
|
|
ASSERT(sizeof(uint) == 4);
|
|
ASSERT(sizeof(reg_t) == sizeof(void *));
|
|
|
|
#ifdef UNIX /* after options_init(), before we open logfile or call instrument_init() */
|
|
os_file_init();
|
|
#endif
|
|
|
|
set_exception_strings(NULL, NULL); /* use defaults */
|
|
}
|
|
|
|
/* NOTE since used by spinmutex_lock_no_yield, can make no system calls before
|
|
* the lock is grabbed (required by check_wait_at_safe_spot). */
|
|
bool
|
|
spinmutex_trylock(spin_mutex_t *spin_lock)
|
|
{
|
|
mutex_t *lock = &spin_lock->lock;
|
|
int mutexval;
|
|
mutexval = atomic_swap(&lock->lock_requests, LOCK_SET_STATE);
|
|
ASSERT(mutexval == LOCK_FREE_STATE || mutexval == LOCK_SET_STATE);
|
|
DEADLOCK_AVOIDANCE_LOCK(lock, mutexval == LOCK_FREE_STATE, LOCK_OWNABLE);
|
|
return (mutexval == LOCK_FREE_STATE);
|
|
}
|
|
|
|
void
|
|
spinmutex_lock(spin_mutex_t *spin_lock)
|
|
{
|
|
/* busy-wait until mutex is locked */
|
|
while (!spinmutex_trylock(spin_lock)) {
|
|
os_thread_yield();
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* special version of spinmutex_lock that makes no system calls (i.e. no yield)
|
|
* as required by check_wait_at_safe_spot */
|
|
void
|
|
spinmutex_lock_no_yield(spin_mutex_t *spin_lock)
|
|
{
|
|
/* busy-wait until mutex is locked */
|
|
while (!spinmutex_trylock(spin_lock)) {
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
mutex_t *lock = &spin_lock->lock;
|
|
/* Trylock inc'ed count_times_contended, but since we are prob. going
|
|
* to spin a lot, we'd rather attribute that to a separate counter
|
|
* count_times_spin_pause to keep the counts meaningful. */
|
|
lock->count_times_contended--;
|
|
lock->count_times_spin_pause++;
|
|
#endif
|
|
SPINLOCK_PAUSE();
|
|
}
|
|
return;
|
|
}
|
|
|
|
void
|
|
spinmutex_unlock(spin_mutex_t *spin_lock)
|
|
{
|
|
mutex_t *lock = &spin_lock->lock;
|
|
/* if this fails, it means you don't already own the lock. */
|
|
ASSERT(lock->lock_requests > LOCK_FREE_STATE && "lock not owned");
|
|
ASSERT(lock->lock_requests == LOCK_SET_STATE);
|
|
DEADLOCK_AVOIDANCE_UNLOCK(lock, LOCK_OWNABLE);
|
|
lock->lock_requests = LOCK_FREE_STATE;
|
|
/* NOTE - check_wait_at_safe_spot requires that no system calls be made
|
|
* after we release the lock */
|
|
return;
|
|
}
|
|
|
|
void
|
|
spinmutex_delete(spin_mutex_t *spin_lock)
|
|
{
|
|
ASSERT(!ksynch_var_initialized(&spin_lock->lock.contended_event));
|
|
d_r_mutex_delete(&spin_lock->lock);
|
|
}
|
|
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
static bool
|
|
mutex_ownable(mutex_t *lock)
|
|
{
|
|
bool ownable = LOCK_OWNABLE;
|
|
/* i#779: support DR locks used as app locks */
|
|
if (lock->app_lock) {
|
|
ASSERT(lock->rank == dr_client_mutex_rank);
|
|
ownable = LOCK_NOT_OWNABLE;
|
|
}
|
|
return ownable;
|
|
}
|
|
#endif
|
|
|
|
void
|
|
d_r_mutex_lock_app(mutex_t *lock, priv_mcontext_t *mc)
|
|
{
|
|
bool acquired;
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
bool ownable = mutex_ownable(lock);
|
|
#endif
|
|
|
|
/* we may want to first spin the lock for a while if we are on a multiprocessor
|
|
* machine
|
|
*/
|
|
/* option is external only so that we can set it to 0 on a uniprocessor */
|
|
if (spinlock_count) {
|
|
uint i;
|
|
/* in the common case we'll just get it */
|
|
if (d_r_mutex_trylock(lock))
|
|
return;
|
|
|
|
/* otherwise contended, we should spin for some time */
|
|
i = spinlock_count;
|
|
/* while spinning we are PAUSEing and reading without LOCKing the bus in the
|
|
* spin loop
|
|
*/
|
|
do {
|
|
/* hint we are spinning */
|
|
SPINLOCK_PAUSE();
|
|
|
|
/* We spin only while lock_requests == 0 which means that exactly one thread
|
|
holds the lock, while the current one (and possibly a few others) are
|
|
contending on who will grab it next. It doesn't make much sense to spin
|
|
when the lock->lock_requests > 0 (which means that at least one thread is
|
|
already blocked). And of course, we also break if it is LOCK_FREE_STATE.
|
|
*/
|
|
if (atomic_aligned_read_int(&lock->lock_requests) != LOCK_SET_STATE) {
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
lock->count_times_spin_only++;
|
|
#endif
|
|
break;
|
|
}
|
|
i--;
|
|
} while (i > 0);
|
|
}
|
|
|
|
/* we have strong intentions to grab this lock, increment requests */
|
|
acquired = atomic_inc_and_test(&lock->lock_requests);
|
|
DEADLOCK_AVOIDANCE_LOCK(lock, acquired, ownable);
|
|
|
|
if (!acquired) {
|
|
mutex_wait_contended_lock(lock, mc);
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
DEADLOCK_AVOIDANCE_LOCK(lock, true, ownable); /* now we got it */
|
|
/* this and previous owner are not included in lock_requests */
|
|
if (lock->max_contended_requests < (uint)lock->lock_requests)
|
|
lock->max_contended_requests = (uint)lock->lock_requests;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void
|
|
d_r_mutex_lock(mutex_t *lock)
|
|
{
|
|
d_r_mutex_lock_app(lock, NULL);
|
|
}
|
|
|
|
/* try once to grab the lock, return whether or not successful */
|
|
bool
|
|
d_r_mutex_trylock(mutex_t *lock)
|
|
{
|
|
bool acquired;
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
bool ownable = mutex_ownable(lock);
|
|
#endif
|
|
|
|
/* preserve old value in case not LOCK_FREE_STATE */
|
|
acquired =
|
|
atomic_compare_exchange(&lock->lock_requests, LOCK_FREE_STATE, LOCK_SET_STATE);
|
|
/* if old value was free, that means we just obtained lock
|
|
old value may be >=0 when several threads are trying to acquire lock,
|
|
so we should return false
|
|
*/
|
|
DEADLOCK_AVOIDANCE_LOCK(lock, acquired, ownable);
|
|
return acquired;
|
|
}
|
|
|
|
/* free the lock */
|
|
void
|
|
d_r_mutex_unlock(mutex_t *lock)
|
|
{
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
bool ownable = mutex_ownable(lock);
|
|
#endif
|
|
|
|
ASSERT(lock->lock_requests > LOCK_FREE_STATE && "lock not owned");
|
|
DEADLOCK_AVOIDANCE_UNLOCK(lock, ownable);
|
|
|
|
if (atomic_dec_and_test(&lock->lock_requests))
|
|
return;
|
|
/* if we were not the last one to hold the lock,
|
|
(i.e. final value is not LOCK_FREE_STATE)
|
|
we need to notify another waiting thread */
|
|
mutex_notify_released_lock(lock);
|
|
}
|
|
|
|
/* releases any associated kernel objects */
|
|
void
|
|
d_r_mutex_delete(mutex_t *lock)
|
|
{
|
|
LOG(GLOBAL, LOG_THREADS, 3, "mutex_delete lock " PFX "\n", lock);
|
|
/* When doing detach, application locks may be held on threads which have
|
|
* already been interrupted and will never execute lock code again. Just
|
|
* ignore the assert on the lock_requests field below in those cases.
|
|
*/
|
|
DEBUG_DECLARE(bool skip_lock_request_assert = false;)
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
LOG(THREAD_GET, LOG_THREADS, 3,
|
|
"mutex_delete " DUMP_LOCK_INFO_ARGS(0, lock, lock->prev_process_lock));
|
|
remove_process_lock(lock);
|
|
lock->deleted = true;
|
|
if (doing_detach) {
|
|
/* For possible re-attach we clear the acquired count. We leave
|
|
* deleted==true as it is not used much and we don't have a simple method for
|
|
* clearing it: we'd have to keep a special list of all locks used (appending
|
|
* as they're deleted) and then walk it from dynamo_exit_post_detach().
|
|
*/
|
|
lock->count_times_acquired = 0;
|
|
# ifdef DEBUG
|
|
skip_lock_request_assert = lock->app_lock;
|
|
# endif
|
|
}
|
|
#else
|
|
# ifdef DEBUG
|
|
/* We don't support !DEADLOCK_AVOIDANCE && DEBUG: we need to know whether to
|
|
* skip the assert lock_requests but don't know if this lock is an app lock
|
|
*/
|
|
# error DEBUG mode not supported without DEADLOCK_AVOIDANCE
|
|
# endif /* DEBUG */
|
|
#endif /* DEADLOCK_AVOIDANCE */
|
|
ASSERT(skip_lock_request_assert || lock->lock_requests == LOCK_FREE_STATE);
|
|
|
|
if (ksynch_var_initialized(&lock->contended_event)) {
|
|
mutex_free_contended_event(lock);
|
|
}
|
|
}
|
|
|
|
void
|
|
d_r_mutex_mark_as_app(mutex_t *lock)
|
|
{
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
lock->app_lock = true;
|
|
#endif
|
|
}
|
|
|
|
static inline void
|
|
own_recursive_lock(recursive_lock_t *lock)
|
|
{
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
ASSERT(!mutex_ownable(&lock->lock) || OWN_MUTEX(&lock->lock));
|
|
#endif
|
|
ASSERT(lock->owner == INVALID_THREAD_ID);
|
|
ASSERT(lock->count == 0);
|
|
lock->owner = d_r_get_thread_id();
|
|
ASSERT(lock->owner != INVALID_THREAD_ID);
|
|
lock->count = 1;
|
|
}
|
|
|
|
void
|
|
acquire_recursive_app_lock(recursive_lock_t *lock, priv_mcontext_t *mc)
|
|
{
|
|
/* we no longer use the pattern of implementing acquire_lock as a
|
|
busy try_lock
|
|
*/
|
|
|
|
if (ATOMIC_READ_THREAD_ID(&lock->owner) == d_r_get_thread_id()) {
|
|
lock->count++;
|
|
} else {
|
|
d_r_mutex_lock_app(&lock->lock, mc);
|
|
own_recursive_lock(lock);
|
|
}
|
|
}
|
|
|
|
/* XXX: rename recursive routines to parallel mutex_ routines */
|
|
void
|
|
acquire_recursive_lock(recursive_lock_t *lock)
|
|
{
|
|
acquire_recursive_app_lock(lock, NULL);
|
|
}
|
|
|
|
bool
|
|
try_recursive_lock(recursive_lock_t *lock)
|
|
{
|
|
if (ATOMIC_READ_THREAD_ID(&lock->owner) == d_r_get_thread_id()) {
|
|
lock->count++;
|
|
} else {
|
|
if (!d_r_mutex_trylock(&lock->lock))
|
|
return false;
|
|
own_recursive_lock(lock);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
release_recursive_lock(recursive_lock_t *lock)
|
|
{
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
ASSERT(!mutex_ownable(&lock->lock) || OWN_MUTEX(&lock->lock));
|
|
#endif
|
|
ASSERT(lock->owner == d_r_get_thread_id());
|
|
ASSERT(lock->count > 0);
|
|
lock->count--;
|
|
if (lock->count == 0) {
|
|
lock->owner = INVALID_THREAD_ID;
|
|
d_r_mutex_unlock(&lock->lock);
|
|
}
|
|
}
|
|
|
|
bool
|
|
self_owns_recursive_lock(recursive_lock_t *lock)
|
|
{
|
|
return (ATOMIC_READ_THREAD_ID(&lock->owner) == d_r_get_thread_id());
|
|
}
|
|
|
|
/* Read write locks */
|
|
/* A read write lock allows multiple readers or alternatively a single writer */
|
|
|
|
/* We're keeping here an older implementation under
|
|
INTERNAL_OPTION(spin_yield_rwlock) that spins on the contention
|
|
path. In the Attic we also have the initial naive implementation
|
|
wrapping mutex_t'es !INTERNAL_OPTION(fast_rwlock).
|
|
*/
|
|
|
|
/*
|
|
XXX: Since we are using multiple words to contain the state,
|
|
we still have to keep looping on contention events.
|
|
|
|
We need to switch to using a single variable for this but for now
|
|
let's first put all pieces of the kernel objects support together.
|
|
|
|
PLAN: All state should be in one 32bit word.
|
|
Then we need one atomic operation that decrements readers and tells us:
|
|
1) whether there was a writer (e.g. MSB set)
|
|
2) whether this was the last reader (e.g. 0 in all other bits)
|
|
Only when 1) & 2) are true (e.g. 0x80000000) we need to notify the writer.
|
|
Think about using XADD: atomic_add_exchange(state, -1)
|
|
*/
|
|
/* XXX: See /usr/src/linux-2.4/include/asm-i386/rwlock.h,
|
|
spinlock.h and /usr/src/linux-2.4/arch/i386/kernel/semaphore.c
|
|
for the Linux kernel implementation on x86.
|
|
*/
|
|
|
|
/* Currently we are using kernel objects to block on contention paths.
|
|
Writers are blocked from each other at the mutex_t, and are notified
|
|
by previous readers by an auto event. Readers, of course, can have
|
|
the lock simultaneously, but block on a previous writer - note also
|
|
on an auto event. Broadcasting to all readers is done by
|
|
explicitly waking up each by the previous one, while the writer
|
|
continues execution. There is no fairness to the readers that are
|
|
blocked vs any new readers that will grab the lock immediately, and
|
|
for that matter vs any new writers.
|
|
|
|
XXX: Keep in mind that a successful wait on the kernel events in read
|
|
locks should not be used as a guarantee that the current thread can
|
|
proceed with a granted request. We should rather keep looping to
|
|
verify that we are back on the fast path.
|
|
|
|
Due to the two reasons above we still have unbound loops in the
|
|
rwlock primitives. It also lets the Linux implementation just yield.
|
|
*/
|
|
|
|
void
|
|
d_r_read_lock(read_write_lock_t *rw)
|
|
{
|
|
/* wait for writer here if lock is held
|
|
* XXX: generalize DEADLOCK_AVOIDANCE to both detect
|
|
* order violations and gather contention stats for
|
|
* this mutex-less synch
|
|
*/
|
|
if (INTERNAL_OPTION(spin_yield_rwlock)) {
|
|
do {
|
|
while (mutex_testlock(&rw->lock)) {
|
|
/* contended read */
|
|
/* am I the writer?
|
|
* ASSUMPTION: reading field is atomic
|
|
* For linux d_r_get_thread_id() is expensive -- we
|
|
* should either address that through special handling
|
|
* of native and new thread cases, or switch this
|
|
* routine to pass in dcontext and use that.
|
|
* Update: linux d_r_get_thread_id() now calls get_tls_thread_id()
|
|
* and avoids the syscall (xref PR 473640).
|
|
* XXX: we could also reorganize this check so that it is done only once
|
|
* instead of in the loop body but it doesn't seem wortwhile
|
|
*/
|
|
/* We have the lock so we do not need a load-acquire. */
|
|
if (rw->writer == d_r_get_thread_id()) {
|
|
/* we would share the code below but we do not want
|
|
* the deadlock avoidance to consider this an acquire
|
|
*/
|
|
ATOMIC_INC(int, rw->num_readers);
|
|
return;
|
|
}
|
|
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, false, LOCK_NOT_OWNABLE);
|
|
/* XXX: last places where we yield instead of wait */
|
|
os_thread_yield();
|
|
}
|
|
ATOMIC_INC(int, rw->num_readers);
|
|
if (!mutex_testlock(&rw->lock))
|
|
break;
|
|
/* else, race with writer, must try again */
|
|
ATOMIC_DEC(int, rw->num_readers);
|
|
} while (true);
|
|
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, true, LOCK_NOT_OWNABLE);
|
|
return;
|
|
}
|
|
|
|
/* event based notification, yet still need to loop */
|
|
do {
|
|
while (mutex_testlock(&rw->lock)) {
|
|
/* contended read */
|
|
/* am I the writer?
|
|
* ASSUMPTION: reading field is atomic
|
|
* For linux d_r_get_thread_id() is expensive -- we
|
|
* should either address that through special handling
|
|
* of native and new thread cases, or switch this
|
|
* routine to pass in dcontext and use that.
|
|
* Update: linux d_r_get_thread_id() now calls get_tls_thread_id()
|
|
* and avoids the syscall (xref PR 473640).
|
|
*/
|
|
/* We have the lock so we do not need a load-acquire. */
|
|
if (rw->writer == d_r_get_thread_id()) {
|
|
/* we would share the code below but we do not want
|
|
* the deadlock avoidance to consider this an acquire
|
|
*/
|
|
/* we also have to do this check on the read_unlock path */
|
|
ATOMIC_INC(int, rw->num_readers);
|
|
return;
|
|
}
|
|
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, false, LOCK_NOT_OWNABLE);
|
|
|
|
ATOMIC_INC(int, rw->num_pending_readers);
|
|
/* if we get interrupted before we have incremented this counter?
|
|
Then no signal will be send our way, so we shouldn't be waiting then
|
|
*/
|
|
if (mutex_testlock(&rw->lock)) {
|
|
/* still holding up */
|
|
rwlock_wait_contended_reader(rw);
|
|
} else {
|
|
/* otherwise race with writer */
|
|
/* after the write lock is released pending readers
|
|
should no longer wait since no one will wake them up */
|
|
/* no need to pause */
|
|
}
|
|
/* Even if we didn't wait another reader may be waiting for notification */
|
|
if (!atomic_dec_becomes_zero(&rw->num_pending_readers)) {
|
|
/* If we were not the last pending reader,
|
|
we need to notify another waiting one so that
|
|
it can get out of the contention path.
|
|
*/
|
|
rwlock_notify_readers(rw);
|
|
/* Keep in mind that here we don't guarantee that after blocking
|
|
we have an automatic right to claim the lock.
|
|
*/
|
|
}
|
|
}
|
|
/* fast path */
|
|
ATOMIC_INC(int, rw->num_readers);
|
|
if (!mutex_testlock(&rw->lock))
|
|
break;
|
|
/* else, race with writer, must try again */
|
|
/* XXX: need to get num_readers and the mutex in one place,
|
|
or otherwise add a mutex grabbed by readers for the above
|
|
test.
|
|
*/
|
|
ATOMIC_DEC(int, rw->num_readers);
|
|
/* What if a writer thought that this reader has
|
|
already taken turn - and will then wait thinking this guy has
|
|
grabbed the read lock first? For now we'll have to wake up
|
|
the writer to retry even if it spuriously wakes up the next writer.
|
|
*/
|
|
// XXX: we need to do only when num_readers has become zero,
|
|
// but it is OK for now as this won't usually happen
|
|
rwlock_notify_writer(rw); /* --ok since writers still have to loop */
|
|
/* hint we are spinning */
|
|
SPINLOCK_PAUSE();
|
|
} while (true);
|
|
|
|
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, true, LOCK_NOT_OWNABLE);
|
|
}
|
|
|
|
void
|
|
d_r_write_lock(read_write_lock_t *rw)
|
|
{
|
|
/* we do not follow the pattern of having lock call trylock in
|
|
* a loop because that would be unfair to writers -- first guy
|
|
* in this implementation gets to write
|
|
*/
|
|
if (INTERNAL_OPTION(spin_yield_rwlock)) {
|
|
d_r_mutex_lock(&rw->lock);
|
|
/* We have the lock so we do not need a load-acquire. */
|
|
while (rw->num_readers > 0) {
|
|
/* contended write */
|
|
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, false, LOCK_NOT_OWNABLE);
|
|
/* XXX: last places where we yield instead of wait */
|
|
os_thread_yield();
|
|
}
|
|
rw->writer = d_r_get_thread_id();
|
|
return;
|
|
}
|
|
|
|
d_r_mutex_lock(&rw->lock);
|
|
/* We still do this in a loop, since the event signal doesn't guarantee
|
|
that num_readers is 0 when unblocked.
|
|
*/
|
|
while (rw->num_readers > 0) {
|
|
/* contended write */
|
|
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, false, LOCK_NOT_OWNABLE);
|
|
rwlock_wait_contended_writer(rw);
|
|
}
|
|
rw->writer = d_r_get_thread_id();
|
|
}
|
|
|
|
bool
|
|
d_r_write_trylock(read_write_lock_t *rw)
|
|
{
|
|
if (d_r_mutex_trylock(&rw->lock)) {
|
|
ASSERT_NOT_TESTED();
|
|
/* We have the lock so we do not need a load-acquire. */
|
|
if (rw->num_readers == 0) {
|
|
rw->writer = d_r_get_thread_id();
|
|
return true;
|
|
} else {
|
|
/* We need to duplicate the bottom of d_r_write_unlock() */
|
|
/* since if a new reader has appeared after we have acquired the lock
|
|
that one may already be waiting on the broadcast event */
|
|
d_r_mutex_unlock(&rw->lock);
|
|
/* check whether any reader is currently waiting */
|
|
if (atomic_aligned_read_int(&rw->num_pending_readers) > 0) {
|
|
/* after we've released the write lock, pending
|
|
* readers will no longer wait
|
|
*/
|
|
rwlock_notify_readers(rw);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
d_r_read_unlock(read_write_lock_t *rw)
|
|
{
|
|
if (INTERNAL_OPTION(spin_yield_rwlock)) {
|
|
ATOMIC_DEC(int, rw->num_readers);
|
|
DEADLOCK_AVOIDANCE_UNLOCK(&rw->lock, LOCK_NOT_OWNABLE);
|
|
return;
|
|
}
|
|
|
|
/* if we were the last reader to hold the lock, (i.e. final value is 0)
|
|
we may need to notify a waiting writer */
|
|
|
|
/* unfortunately even on the hot path (of a single reader) we have
|
|
to check if the writer is in fact waiting. Even though this is
|
|
not atomic we don't need to loop here - d_r_write_lock() will loop.
|
|
*/
|
|
if (atomic_dec_becomes_zero(&rw->num_readers)) {
|
|
/* if the writer is waiting it definitely needs to hold the mutex */
|
|
if (mutex_testlock(&rw->lock)) {
|
|
/* test that it was not this thread owning both write and read lock */
|
|
/* We have the lock so we do not need a load-acquire. */
|
|
if (rw->writer != d_r_get_thread_id()) {
|
|
/* we're assuming the writer has been forced to wait,
|
|
but since we can't tell whether it did indeed wait this
|
|
notify may leave signaled the event for the next turn
|
|
|
|
If the writer has grabbed the mutex and checked
|
|
when num_readers==0 and has gone assuming to be the
|
|
rwlock owner. In that case the above
|
|
rwlock_notify_writer will give the wrong signal to
|
|
the next writer.
|
|
--ok since writers still have to loop
|
|
*/
|
|
rwlock_notify_writer(rw);
|
|
}
|
|
}
|
|
}
|
|
|
|
DEADLOCK_AVOIDANCE_UNLOCK(&rw->lock, LOCK_NOT_OWNABLE);
|
|
}
|
|
|
|
void
|
|
d_r_write_unlock(read_write_lock_t *rw)
|
|
{
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
ASSERT(!mutex_ownable(&rw->lock) || rw->writer == rw->lock.owner);
|
|
#endif
|
|
rw->writer = INVALID_THREAD_ID;
|
|
if (INTERNAL_OPTION(spin_yield_rwlock)) {
|
|
d_r_mutex_unlock(&rw->lock);
|
|
return;
|
|
}
|
|
/* we need to signal all waiting readers (if any) that they can now go
|
|
ahead. No writer should be allowed to lock until all currently
|
|
waiting readers are unblocked.
|
|
*/
|
|
/* We first unlock so that any blocked readers can start making
|
|
progress as soon as they are notified. Further field
|
|
accesses however have to be assumed unprotected.
|
|
*/
|
|
d_r_mutex_unlock(&rw->lock);
|
|
/* check whether any reader is currently waiting */
|
|
if (atomic_aligned_read_int(&rw->num_pending_readers) > 0) {
|
|
/* after we've released the write lock, pending readers will no longer wait */
|
|
rwlock_notify_readers(rw);
|
|
}
|
|
}
|
|
|
|
bool
|
|
self_owns_write_lock(read_write_lock_t *rw)
|
|
{
|
|
return (ATOMIC_READ_THREAD_ID(&rw->writer) == d_r_get_thread_id());
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* HASHING */
|
|
|
|
ptr_uint_t
|
|
hash_value(ptr_uint_t val, hash_function_t func, ptr_uint_t mask, uint bits)
|
|
{
|
|
if (func == HASH_FUNCTION_NONE)
|
|
return val;
|
|
switch (func) {
|
|
case HASH_FUNCTION_MULTIPLY_PHI: {
|
|
/* case 8457: keep in sync w/ HASH_VALUE_FOR_TABLE() */
|
|
return ((val * HASH_PHI) >> (HASH_TAG_BITS - bits));
|
|
}
|
|
#ifdef INTERNAL
|
|
case HASH_FUNCTION_LOWER_BSWAP: {
|
|
IF_X64(ASSERT_NOT_IMPLEMENTED(false));
|
|
return (((val & 0xFFFF0000)) | ((val & 0x000000FF) << 8) |
|
|
((val & 0x0000FF00) >> 8));
|
|
}
|
|
case HASH_FUNCTION_BSWAP_XOR: {
|
|
IF_X64(ASSERT_NOT_IMPLEMENTED(false));
|
|
return (val ^
|
|
(((val & 0x000000FF) << 24) | ((val & 0x0000FF00) << 8) |
|
|
((val & 0x00FF0000) >> 8) | ((val & 0xFF000000) >> 24)));
|
|
}
|
|
case HASH_FUNCTION_SWAP_12TO15: {
|
|
IF_X64(ASSERT_NOT_IMPLEMENTED(false));
|
|
return (((val & 0xFFFF0FF0)) | ((val & 0x0000F000) >> 12) |
|
|
((val & 0x0000000F) << 12));
|
|
}
|
|
case HASH_FUNCTION_SWAP_12TO15_AND_NONE: {
|
|
IF_X64(ASSERT_NOT_IMPLEMENTED(false));
|
|
return (mask <= 0xFFF ? val
|
|
: (((val & 0xFFFF0FF0)) | ((val & 0x0000F000) >> 12) |
|
|
((val & 0x0000000F) << 12)));
|
|
}
|
|
case HASH_FUNCTION_SHIFT_XOR: {
|
|
IF_X64(ASSERT_NOT_IMPLEMENTED(false));
|
|
return val ^ (val >> 12) ^ (val << 12);
|
|
}
|
|
#endif
|
|
case HASH_FUNCTION_STRING:
|
|
case HASH_FUNCTION_STRING_NOCASE: {
|
|
const char *s = (const char *)val;
|
|
char c;
|
|
ptr_uint_t hash = 0;
|
|
uint i, shift;
|
|
uint max_shift = ALIGN_FORWARD(bits, 8);
|
|
/* Simple hash function that combines unbiased via xor and
|
|
* shifts to get input chars to cover the full range. We clamp
|
|
* the shift to avoid useful bits being truncated. An
|
|
* alternative is to combine blocks of 4 chars at a time but
|
|
* that's more complex.
|
|
*/
|
|
for (i = 0; s[i] != '\0'; i++) {
|
|
c = s[i];
|
|
if (func == HASH_FUNCTION_STRING_NOCASE)
|
|
c = (char)tolower(c);
|
|
shift = (i % 4) * 8;
|
|
hash ^= (c << MIN(shift, max_shift));
|
|
}
|
|
return hash;
|
|
}
|
|
default: {
|
|
ASSERT_NOT_REACHED();
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint
|
|
hashtable_num_bits(uint size)
|
|
{
|
|
uint bits = 0;
|
|
uint sz = size;
|
|
while (sz > 0) {
|
|
sz = sz >> 1;
|
|
bits++;
|
|
}
|
|
ASSERT(HASHTABLE_SIZE(bits) > size && HASHTABLE_SIZE(bits) <= size * 2);
|
|
return bits;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* BITMAP */
|
|
|
|
/* Since there is no ffs() on windows we use the one from
|
|
* /usr/src/linux-2.4/include/linux/bitops.h. TODO: An easier
|
|
* x86-specific way using BSF is
|
|
* /usr/src/linux-2.4/include/asm/bitops.h
|
|
*/
|
|
|
|
/* Returns the position of the first set bit - betwen 0 and 31 */
|
|
static inline uint
|
|
bitmap_find_first_set_bit(bitmap_element_t x)
|
|
{
|
|
int r = 0;
|
|
|
|
ASSERT(x);
|
|
if (!(x & 0xffff)) {
|
|
x >>= 16;
|
|
r += 16;
|
|
}
|
|
if (!(x & 0xff)) {
|
|
x >>= 8;
|
|
r += 8;
|
|
}
|
|
if (!(x & 0xf)) {
|
|
x >>= 4;
|
|
r += 4;
|
|
}
|
|
if (!(x & 3)) {
|
|
x >>= 2;
|
|
r += 2;
|
|
}
|
|
if (!(x & 1)) {
|
|
x >>= 1;
|
|
r += 1;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/* A block is marked free with a set bit.
|
|
Returns -1 if no block is found!
|
|
*/
|
|
static inline uint
|
|
bitmap_find_set_block(bitmap_t b, uint bitmap_size)
|
|
{
|
|
uint i = 0;
|
|
uint last_index = BITMAP_INDEX(bitmap_size);
|
|
|
|
while (b[i] == 0 && i < last_index)
|
|
i++;
|
|
if (i == last_index)
|
|
return BITMAP_NOT_FOUND;
|
|
return i * BITMAP_DENSITY + bitmap_find_first_set_bit(b[i]);
|
|
}
|
|
|
|
/* Looks for a sequence of free blocks
|
|
* Returns -1 if no such sequence is found!
|
|
|
|
* Considering the fact that the majority of our allocations will be
|
|
* for a single block this operation is not terribly efficient.
|
|
*/
|
|
static uint
|
|
bitmap_find_set_block_sequence(bitmap_t b, uint bitmap_size, uint requested)
|
|
{
|
|
uint last_bit = bitmap_size - requested + 1;
|
|
/* find quickly at least a single block */
|
|
uint first = bitmap_find_set_block(b, bitmap_size);
|
|
if (first == BITMAP_NOT_FOUND)
|
|
return BITMAP_NOT_FOUND;
|
|
|
|
do {
|
|
/* now check if there is room for the requested number of bits */
|
|
uint hole_size = 1;
|
|
while (hole_size < requested && bitmap_test(b, first + hole_size)) {
|
|
hole_size++;
|
|
}
|
|
if (hole_size == requested)
|
|
return first;
|
|
/* otherwise first + hole_size is not set, so we should skip that */
|
|
first += hole_size + 1;
|
|
while (first < last_bit && !bitmap_test(b, first))
|
|
first++;
|
|
} while (first < last_bit);
|
|
|
|
return BITMAP_NOT_FOUND;
|
|
}
|
|
|
|
void
|
|
bitmap_initialize_free(bitmap_t b, uint bitmap_size)
|
|
{
|
|
memset(b, 0xff, BITMAP_INDEX(bitmap_size) * sizeof(bitmap_element_t));
|
|
}
|
|
|
|
uint
|
|
bitmap_allocate_blocks(bitmap_t b, uint bitmap_size, uint request_blocks,
|
|
uint start_block)
|
|
{
|
|
uint i, res;
|
|
if (start_block != UINT_MAX) {
|
|
if (start_block + request_blocks > bitmap_size)
|
|
return BITMAP_NOT_FOUND;
|
|
uint hole_size = 0;
|
|
while (hole_size < request_blocks && bitmap_test(b, start_block + hole_size)) {
|
|
hole_size++;
|
|
}
|
|
if (hole_size == request_blocks)
|
|
res = start_block;
|
|
else
|
|
return BITMAP_NOT_FOUND;
|
|
} else if (request_blocks == 1) {
|
|
res = bitmap_find_set_block(b, bitmap_size);
|
|
} else {
|
|
res = bitmap_find_set_block_sequence(b, bitmap_size, request_blocks);
|
|
}
|
|
if (res == BITMAP_NOT_FOUND)
|
|
return BITMAP_NOT_FOUND;
|
|
i = res;
|
|
do {
|
|
bitmap_clear(b, i++);
|
|
} while (--request_blocks);
|
|
return res;
|
|
}
|
|
|
|
void
|
|
bitmap_free_blocks(bitmap_t b, uint bitmap_size, uint first_block, uint num_free)
|
|
{
|
|
ASSERT(first_block + num_free <= bitmap_size);
|
|
do {
|
|
ASSERT(!bitmap_test(b, first_block));
|
|
bitmap_set(b, first_block++);
|
|
} while (--num_free);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* used only for ASSERTs */
|
|
bool
|
|
bitmap_are_reserved_blocks(bitmap_t b, uint bitmap_size, uint first_block,
|
|
uint num_blocks)
|
|
{
|
|
ASSERT(first_block + num_blocks <= bitmap_size);
|
|
do {
|
|
if (bitmap_test(b, first_block))
|
|
return false;
|
|
first_block++;
|
|
} while (--num_blocks);
|
|
return true;
|
|
}
|
|
|
|
static inline uint
|
|
bitmap_count_set_bits(bitmap_element_t x)
|
|
{
|
|
int r = 0;
|
|
|
|
/* count set bits in each element */
|
|
while (x) {
|
|
r++;
|
|
x &= x - 1;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
bool
|
|
bitmap_check_consistency(bitmap_t b, uint bitmap_size, uint expect_free)
|
|
{
|
|
uint last_index = BITMAP_INDEX(bitmap_size);
|
|
uint i;
|
|
uint current = 0;
|
|
for (i = 0; i < last_index; i++) {
|
|
current += bitmap_count_set_bits(b[i]);
|
|
}
|
|
|
|
LOG(GLOBAL, LOG_HEAP, 3,
|
|
"bitmap_check_consistency(b=" PFX ", bitmap_size=%d)"
|
|
" expected=%d current=%d\n",
|
|
b, bitmap_size, expect_free, current);
|
|
return expect_free == current;
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
/****************************************************************************/
|
|
/* LOGGING */
|
|
|
|
file_t
|
|
get_thread_private_logfile(void)
|
|
{
|
|
#ifdef DEBUG
|
|
dcontext_t *dcontext = get_thread_private_dcontext();
|
|
if (dcontext == NULL)
|
|
dcontext = GLOBAL_DCONTEXT;
|
|
return THREAD;
|
|
#else
|
|
return INVALID_FILE;
|
|
#endif
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
DECLARE_FREQPROT_VAR(static bool do_once_do_file_write, false);
|
|
#endif
|
|
|
|
/* XXX: add buffering? */
|
|
ssize_t
|
|
do_file_write(file_t f, const char *fmt, va_list ap)
|
|
{
|
|
ssize_t size, written;
|
|
char logbuf[MAX_LOG_LENGTH];
|
|
|
|
if (f == INVALID_FILE)
|
|
return -1;
|
|
size = vsnprintf(logbuf, BUFFER_SIZE_ELEMENTS(logbuf), fmt, ap);
|
|
NULL_TERMINATE_BUFFER(logbuf); /* always NULL terminate */
|
|
DOCHECK(1, {
|
|
/* we have our own do-once to avoid infinite recursion w/ protect_data_section */
|
|
if (size < 0 || size >= BUFFER_SIZE_ELEMENTS(logbuf)) {
|
|
if (!do_once_do_file_write) {
|
|
do_once_do_file_write = true;
|
|
ASSERT_CURIOSITY(size >= 0 && size < sizeof(logbuf));
|
|
}
|
|
}
|
|
});
|
|
/* handle failure values */
|
|
if (size >= BUFFER_SIZE_ELEMENTS(logbuf) || size < 0)
|
|
size = strlen(logbuf);
|
|
written = os_write(f, logbuf, size);
|
|
if (written < 0)
|
|
return -1;
|
|
return written;
|
|
}
|
|
|
|
/* a little utiliy for printing a float that is formed by dividing 2 uints,
|
|
* gives back high and low parts for printing, also supports percentages
|
|
* XXX : we might need to handle signed numbers at some point (but not yet),
|
|
* also could be smarter about overflow conditions (i.e. for calculating
|
|
* bottom or top) but we never call with numbers that big, also truncates
|
|
* instead of rounding
|
|
* Usage : given a, b (uint[64]); d_int()==divide_uint64_print();
|
|
* uint c, d tmp; parameterized on precision p and width w
|
|
* note that %f is eqv. to %.6f, 3rd ex. is a percentage ex.
|
|
* "%.pf", a/(float)b => d_int(a, b, false, p, &c, &d); "%u.%.pu", c,d
|
|
* "%w.pf", a/(float)b => d_int(a, b, false, p, &c, &d); "%(w-p-1)u.%.pu", c,d
|
|
* "%.pf%%", 100*(a/(float)b) => d_int(a, b, true, p, &c, &d); "%u.%.pu%%", c,d
|
|
*/
|
|
void
|
|
divide_uint64_print(uint64 numerator, uint64 denominator, bool percentage, uint precision,
|
|
uint *top, uint *bottom)
|
|
{
|
|
uint i, precision_multiple, multiple = percentage ? 100 : 1;
|
|
#ifdef HOT_PATCHING_INTERFACE
|
|
/* case 6657: hotp_only does not have many of the DR stats, so
|
|
* numerator and/or denominator may be 0 */
|
|
ASSERT(denominator != 0 || DYNAMO_OPTION(hotp_only));
|
|
#else
|
|
ASSERT(denominator != 0);
|
|
#endif
|
|
ASSERT(top != NULL && bottom != NULL);
|
|
if (denominator == 0)
|
|
return;
|
|
ASSERT_TRUNCATE(*top, uint, ((multiple * numerator) / denominator));
|
|
*top = (uint)((multiple * numerator) / denominator);
|
|
for (i = 0, precision_multiple = 1; i < precision; i++)
|
|
precision_multiple *= 10;
|
|
ASSERT_TRUNCATE(*bottom, uint,
|
|
(((precision_multiple * multiple * numerator) / denominator) -
|
|
(precision_multiple * *top)));
|
|
/* FUNNY: if I forget the above ) I crash the preprocessor: cc1
|
|
* internal compiler error couldn't reproduce in a smaller set to
|
|
* file a bug against gcc version 3.3.3 (cygwin special)
|
|
*/
|
|
*bottom = (uint)(((precision_multiple * multiple * numerator) / denominator) -
|
|
(precision_multiple * *top));
|
|
}
|
|
|
|
#ifndef LINUX_KERNEL
|
|
/* When building with /QIfist casting rounds instead of truncating (i#763)
|
|
* so we use these routines from io.c.
|
|
*/
|
|
extern long
|
|
double2int_trunc(double d);
|
|
|
|
/* For printing a float.
|
|
* NOTE: You must preserve x87 floating point state to call this function, unless
|
|
* you can prove the compiler will never use x87 state for float operations.
|
|
* XXX: Truncates instead of rounding; also, negative with width looks funny;
|
|
* finally, width can be one off if negative
|
|
* Usage : given double/float a; uint c, d and char *s tmp; dp==double_print
|
|
* parameterized on precision p width w
|
|
* Note that %f is eqv. to %.6f.
|
|
* "%.pf", a => dp(a, p, &c, &d, &s) "%s%u.%.pu", s, c, d
|
|
* "%w.pf", a => dp(a, p, &c, &d, &s) "%s%(w-p-1)u.%.pu", s, c, d
|
|
*/
|
|
void
|
|
double_print(double val, uint precision, uint *top, uint *bottom, const char **sign)
|
|
{
|
|
uint i, precision_multiple;
|
|
ASSERT(top != NULL && bottom != NULL && sign != NULL);
|
|
if (val < 0.0) {
|
|
val = -val;
|
|
*sign = "-";
|
|
} else {
|
|
*sign = "";
|
|
}
|
|
for (i = 0, precision_multiple = 1; i < precision; i++)
|
|
precision_multiple *= 10;
|
|
/* when building with /QIfist casting rounds instead of truncating (i#763) */
|
|
*top = double2int_trunc(val);
|
|
*bottom = double2int_trunc((val - *top) * precision_multiple);
|
|
}
|
|
#endif
|
|
|
|
#ifdef WINDOWS
|
|
/* for pre_inject, injector, and core shared files, is just wrapper for syslog
|
|
* internal */
|
|
void
|
|
display_error(char *msg)
|
|
{
|
|
SYSLOG_INTERNAL_ERROR("%s", msg);
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
# ifdef WINDOWS
|
|
/* print_symbolic_address is in module.c */
|
|
# else
|
|
/* prints a symbolic name, or best guess of it into a caller provided buffer */
|
|
void
|
|
print_symbolic_address(app_pc tag, char *buf, int max_chars, bool exact_only)
|
|
{
|
|
buf[0] = '\0';
|
|
}
|
|
# endif
|
|
#endif /* DEBUG */
|
|
|
|
void
|
|
print_file(file_t f, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
do_file_write(f, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/* For repeated appending to a buffer. The "sofar" var should be set
|
|
* to 0 by the caller before the first call to print_to_buffer.
|
|
* Returns false if there was not room for the string plus a null,
|
|
* but still prints the maximum that will fit plus a null.
|
|
*/
|
|
/* XXX: This is duplicated in ir/decodlib.c's print_to_buffer.
|
|
* Could we move this into io.c to share it with decodelib?
|
|
*/
|
|
static bool
|
|
vprint_to_buffer(char *buf, size_t bufsz, size_t *sofar DR_PARAM_INOUT, const char *fmt,
|
|
va_list ap)
|
|
{
|
|
/* in io.c */
|
|
extern int d_r_vsnprintf(char *s, size_t max, const char *fmt, va_list ap);
|
|
ssize_t len;
|
|
bool ok;
|
|
/* we use d_r_vsnprintf for consistent return value and to handle floats */
|
|
len = d_r_vsnprintf(buf + *sofar, bufsz - *sofar, fmt, ap);
|
|
/* we support appending an empty string (len==0) */
|
|
ok = (len >= 0 && len < (ssize_t)(bufsz - *sofar));
|
|
/* If the written chars filled up the max size exactly, that max size is returned
|
|
* without a final null by d_r_vsnprintf(). Since we guarantee a null, we have
|
|
* to clobber a char, just like for -1 being returned when the written chars do
|
|
* not all fit.
|
|
*/
|
|
*sofar += (len == -1 || len == (ssize_t)(bufsz - *sofar) ? (bufsz - *sofar - 1)
|
|
: (len < 0 ? 0 : len));
|
|
/* be paranoid: though usually many calls in a row and could delay until end */
|
|
buf[bufsz - 1] = '\0';
|
|
return ok;
|
|
}
|
|
|
|
/* For repeated appending to a buffer. The "sofar" var should be set
|
|
* to 0 by the caller before the first call to print_to_buffer.
|
|
* Returns false if there was not room for the string plus a null,
|
|
* but still prints the maximum that will fit plus a null.
|
|
*/
|
|
bool
|
|
print_to_buffer(char *buf, size_t bufsz, size_t *sofar DR_PARAM_INOUT, const char *fmt,
|
|
...)
|
|
{
|
|
va_list ap;
|
|
bool ok;
|
|
va_start(ap, fmt);
|
|
ok = vprint_to_buffer(buf, bufsz, sofar, fmt, ap);
|
|
va_end(ap);
|
|
return ok;
|
|
}
|
|
|
|
/* N.B.: this routine is duplicated in instrument.c's dr_log!
|
|
* Maybe export this routine itself, or make another layer of
|
|
* calls that passes the va_list (-> less efficient)?
|
|
* For now I'm assuming this routine changes little.
|
|
*/
|
|
void
|
|
d_r_print_log(file_t logfile, uint mask, uint level, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
#ifdef DEBUG
|
|
/* XXX: now the LOG macro checks these, remove here? */
|
|
if (logfile == INVALID_FILE ||
|
|
(d_r_stats != NULL &&
|
|
((d_r_stats->logmask & mask) == 0 || d_r_stats->loglevel < level)))
|
|
return;
|
|
#else
|
|
return;
|
|
#endif
|
|
|
|
KSTART(logging);
|
|
va_start(ap, fmt);
|
|
do_file_write(logfile, fmt, ap);
|
|
va_end(ap);
|
|
KSTOP_NOT_PROPAGATED(logging);
|
|
}
|
|
|
|
#ifdef WINDOWS
|
|
static void
|
|
do_syslog(syslog_event_type_t priority, uint message_id, uint substitutions_num, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, substitutions_num);
|
|
os_syslog(priority, message_id, substitutions_num, ap);
|
|
va_end(ap);
|
|
}
|
|
#endif
|
|
|
|
/* notify present a notification message to one or more destinations,
|
|
* depending on the runtime parameters and the priority:
|
|
* -syslog_mask controls sending to the system log
|
|
* -stderr_mask controls sending to stderr
|
|
* -msgbox_mask controls sending to an interactive pop-up window, or
|
|
* a wait for a keypress on linux
|
|
*/
|
|
void
|
|
d_r_notify(syslog_event_type_t priority, bool internal, bool synch,
|
|
IF_WINDOWS_(uint message_id) uint substitution_num, const char *prefix,
|
|
const char *fmt, ...)
|
|
{
|
|
char msgbuf[MAX_LOG_LENGTH];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
/* XXX: the vsnprintf call is not needed in the most common case where
|
|
* we are going to just os_syslog, but it gets pretty ugly to do that
|
|
*/
|
|
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
|
|
NULL_TERMINATE_BUFFER(msgbuf); /* always NULL terminate */
|
|
/* not a good idea to assert here since we'll just die and lose original message,
|
|
* so we don't check size return value and just go ahead and truncate
|
|
*/
|
|
va_end(ap);
|
|
|
|
LOG(GLOBAL, LOG_ALL, 1, "%s: %s\n", prefix, msgbuf);
|
|
/* so can skip synchronizing when failure is in option parsing to avoid
|
|
* infinite recursion, still could be issue with exception, but separate
|
|
* recursive bailout will at least kill us then, XXX, note will use
|
|
* default masks below if original parse */
|
|
if (synch)
|
|
synchronize_dynamic_options(); /* TODO: dynamic THREAD mask */
|
|
LOG(THREAD_GET, LOG_ALL, 1, "%s: %s\n", prefix, msgbuf);
|
|
|
|
#ifdef WINDOWS
|
|
if (TESTANY(priority, dynamo_options.syslog_mask)) {
|
|
if (internal) {
|
|
if (TESTANY(priority, INTERNAL_OPTION(syslog_internal_mask))) {
|
|
do_syslog(priority, message_id, 3, get_application_name(),
|
|
get_application_pid(), msgbuf);
|
|
}
|
|
} else {
|
|
va_start(ap, fmt);
|
|
os_syslog(priority, message_id, substitution_num, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
#else
|
|
/* syslog not yet implemented on linux, XXX */
|
|
#endif
|
|
|
|
if (TESTANY(priority, dynamo_options.stderr_mask))
|
|
print_file(STDERR, "<%s>\n", msgbuf);
|
|
|
|
if (TESTANY(priority, dynamo_options.msgbox_mask)) {
|
|
#ifdef WINDOWS
|
|
/* XXX: could use os_countdown_msgbox (if ever implemented) here to
|
|
* do a timed out messagebox, could then also replace the os_timeout in
|
|
* vmareas.c
|
|
*/
|
|
debugbox(msgbuf);
|
|
#else
|
|
/* i#116/PR 394985: this won't work for apps that are
|
|
* themselves reading from stdin, but this is a simple way to
|
|
* pause and continue, allowing gdb to attach
|
|
*/
|
|
if (DYNAMO_OPTION(pause_via_loop)) {
|
|
while (DYNAMO_OPTION(pause_via_loop)) {
|
|
/* infinite loop */
|
|
os_thread_yield();
|
|
}
|
|
} else {
|
|
char keypress;
|
|
print_file(STDERR, "<press enter to continue>\n");
|
|
os_read(STDIN, &keypress, sizeof(keypress));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* REPORTING DYNAMORIO PROBLEMS
|
|
* Including assertions, curiosity asserts, API usage errors,
|
|
* deadlock timeouts, internal exceptions, and the app modifying our memory.
|
|
*
|
|
* The following constants are for the pieces of a buffer we will send
|
|
* to the event log, to diagnostics, and to stderr/msgbox/logfile.
|
|
* It's static to avoid adding 500+ bytes to the stack on a critical
|
|
* path, and so needs synchronization, but the risk of a problem with
|
|
* the lock is worth getting a clear message on the first exception.
|
|
*
|
|
* Here's a sample of a report. First four lines here are the
|
|
* passed-in custom string fmt, subsequent are the options and call
|
|
* stack, which are always appended:
|
|
* Platform exception at PC 0x15003075
|
|
* 0xc0000005 0x00000000 0x15003075 0x15003075 0x00000001 0x00000037
|
|
* Registers: eax 0x00000000 ebx 0x00000000 ecx 0x177c9040 edx 0x177c9040
|
|
* esi 0x00000b56 edi 0x0000015f esp 0x177e3eb0 eflags 0x00010246
|
|
* Base: 0x15000000
|
|
* internal version, custom build
|
|
* -loglevel 2 -msgbox_mask 12 -stderr_mask 12
|
|
* 0x00342ee8 0x150100f2
|
|
* 0x00342f64 0x15010576
|
|
* 0x00342f84 0x1503d77b
|
|
* 0x00342fb0 0x1503f12c
|
|
* 0x00342ff4 0x150470e9
|
|
* 0x77f82b95 0x565308ec
|
|
*/
|
|
/* The magic number 271 comes from MAXIMUM_PATH (on WINDOWS = 260) + 11 for PR 204171
|
|
* (in other words historical reasons). Xref PR 226547 we use a constant value here
|
|
* instead of MAXIMUM_PATH since it has different length on Linux and makes this buffer
|
|
* too long. */
|
|
#ifdef X64
|
|
# define REPORT_MSG_MAX (271 + 32 * 23) /* wider, + more regs */
|
|
#elif defined(ARM)
|
|
# define REPORT_MSG_MAX (271 + 17 * 8) /* more regs */
|
|
#else
|
|
# define REPORT_MSG_MAX (271)
|
|
#endif
|
|
#define REPORT_LEN_VERSION 96
|
|
/* example: "\ninternal version, build 94201\n"
|
|
* For custom builds, the build # is generated as follows
|
|
* (cut-and-paste from Makefile):
|
|
* # custom builds: 9XYYZ
|
|
* # X = developer, YY = tree, Z = diffnum
|
|
* # YY defaults to 1st 2 letters of CUR_TREE, unless CASENUM is defined,
|
|
* # in which case it is the last 2 letters of CASENUM (all % 10 of course)
|
|
*/
|
|
#define REPORT_LEN_OPTIONS 324
|
|
/* still not long enough for ALL non-default options but I'll wager money we'll never
|
|
* see this option string truncated, at least for non-internal builds
|
|
* (famous last words?) => yes! For clients this can get quite long.
|
|
* List options from staging mode could be problematic though.
|
|
*/
|
|
#define REPORT_NUM_STACK 15
|
|
#ifdef X64
|
|
# define REPORT_LEN_STACK_EACH (22 + 2 * 8)
|
|
#else
|
|
# define REPORT_LEN_STACK_EACH 22
|
|
#endif
|
|
/* just frame ptr, ret addr: "0x0342fc7c 0x77f8c6dd\n" == 22 chars per line */
|
|
#define REPORT_LEN_STACK (REPORT_LEN_STACK_EACH) * (REPORT_NUM_STACK)
|
|
/* We have to stay under MAX_LOG_LENGTH so we limit to ~10 basenames */
|
|
#define REPORT_LEN_PRIVLIBS (45 * 10)
|
|
/* Not persistent across code cache execution, so not protected */
|
|
DECLARE_NEVERPROT_VAR(
|
|
static char reportbuf[REPORT_MSG_MAX + REPORT_LEN_VERSION + REPORT_LEN_OPTIONS +
|
|
REPORT_LEN_STACK + REPORT_LEN_PRIVLIBS + 1],
|
|
{
|
|
0,
|
|
});
|
|
DECLARE_CXTSWPROT_VAR(static mutex_t report_buf_lock, INIT_LOCK_FREE(report_buf_lock));
|
|
/* Avoid deadlock w/ nested reports */
|
|
DECLARE_CXTSWPROT_VAR(static thread_id_t report_buf_lock_owner, 0);
|
|
|
|
#define ASSERT_ROOM(reportbuf, curbuf, maxlen) \
|
|
ASSERT(curbuf + maxlen < reportbuf + sizeof(reportbuf))
|
|
|
|
/* random number generator */
|
|
DECLARE_CXTSWPROT_VAR(static mutex_t prng_lock, INIT_LOCK_FREE(prng_lock));
|
|
|
|
#ifdef DEBUG
|
|
/* callers should play it safe - no memory allocations, no grabbing locks */
|
|
bool
|
|
under_internal_exception(void)
|
|
{
|
|
# ifdef DEADLOCK_AVOIDANCE
|
|
/* ASSUMPTION: reading owner field is atomic */
|
|
return (report_buf_lock.owner == d_r_get_thread_id());
|
|
# else
|
|
/* mutexes normally don't have an owner, stay safe no matter who owns */
|
|
return mutex_testlock(&report_buf_lock);
|
|
# endif /* DEADLOCK_AVOIDANCE */
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
/* Defaults, overridable by the client (i#1470) */
|
|
const char *exception_label_core = PRODUCT_NAME;
|
|
static const char *exception_report_url = BUG_REPORT_URL;
|
|
const char *exception_label_client = "Client";
|
|
|
|
/* We allow clients to display their version instead of DR's */
|
|
static char display_version[REPORT_LEN_VERSION];
|
|
|
|
/* HACK: to avoid duplicating the prefix of the event log message, we
|
|
* skip it for the SYSLOG, but not the other notifications
|
|
*/
|
|
static char exception_prefix[MAXIMUM_PATH];
|
|
|
|
static inline size_t
|
|
report_exception_skip_prefix(void)
|
|
{
|
|
return strlen(exception_prefix);
|
|
}
|
|
|
|
static char client_exception_prefix[MAXIMUM_PATH];
|
|
|
|
static inline size_t
|
|
report_client_exception_skip_prefix(void)
|
|
{
|
|
return strlen(client_exception_prefix);
|
|
}
|
|
|
|
void
|
|
set_exception_strings(const char *override_label, const char *override_url)
|
|
{
|
|
if (dynamo_initialized)
|
|
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
if (override_url != NULL)
|
|
exception_report_url = override_url;
|
|
if (override_label != NULL)
|
|
exception_label_core = override_label;
|
|
ASSERT(strlen(CRASH_NAME) == strlen(STACK_OVERFLOW_NAME));
|
|
snprintf(exception_prefix, BUFFER_SIZE_ELEMENTS(exception_prefix), "%s %s at PC " PFX,
|
|
exception_label_core, CRASH_NAME, 0);
|
|
NULL_TERMINATE_BUFFER(exception_prefix);
|
|
if (override_label != NULL)
|
|
exception_label_client = override_label;
|
|
snprintf(client_exception_prefix, BUFFER_SIZE_ELEMENTS(client_exception_prefix),
|
|
"%s %s at PC " PFX, exception_label_client, CRASH_NAME, 0);
|
|
NULL_TERMINATE_BUFFER(client_exception_prefix);
|
|
#ifdef WINDOWS
|
|
debugbox_setup_title();
|
|
#endif
|
|
if (dynamo_initialized)
|
|
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
}
|
|
|
|
void
|
|
set_display_version(const char *ver)
|
|
{
|
|
if (dynamo_initialized)
|
|
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
snprintf(display_version, BUFFER_SIZE_ELEMENTS(display_version), "%s", ver);
|
|
NULL_TERMINATE_BUFFER(display_version);
|
|
if (dynamo_initialized)
|
|
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
}
|
|
|
|
/* Fine to pass NULL for dcontext, will obtain it for you.
|
|
* If TESTANY(DUMPCORE_INTERNAL_EXCEPTION, dumpcore_flag), does a full SYSLOG;
|
|
* else, does a SYSLOG_INTERNAL_ERROR.
|
|
* Fine to pass NULL for report_ebp: will use current ebp for you.
|
|
*/
|
|
void
|
|
report_dynamorio_problem(dcontext_t *dcontext, uint dumpcore_flag, app_pc exception_addr,
|
|
app_pc report_ebp, const char *fmt, ...)
|
|
{
|
|
/* WARNING: this routine is called for fatal errors, and
|
|
* a fault in DR means that potentially anything could be
|
|
* inconsistent or corrupted! Do not grab locks or traverse
|
|
* data structures or read memory if you can avoid it!
|
|
*/
|
|
char *curbuf;
|
|
ptr_uint_t *pc;
|
|
uint num;
|
|
int len;
|
|
va_list ap;
|
|
|
|
/* synchronize dynamic options */
|
|
synchronize_dynamic_options();
|
|
|
|
ASSERT(sizeof(reportbuf) < MAX_LOG_LENGTH);
|
|
if (dcontext == NULL)
|
|
dcontext = get_thread_private_dcontext();
|
|
if (dcontext == NULL)
|
|
dcontext = GLOBAL_DCONTEXT;
|
|
|
|
if (report_buf_lock_owner == d_r_get_thread_id()) {
|
|
/* nested report: can't do much except bail on inner */
|
|
return;
|
|
}
|
|
d_r_mutex_lock(&report_buf_lock);
|
|
report_buf_lock_owner = d_r_get_thread_id();
|
|
/* we assume the caller does a DO_ONCE to prevent hanging on a
|
|
* fault in this routine, if called to report a fatal error.
|
|
*/
|
|
|
|
/* now build up the report */
|
|
curbuf = reportbuf;
|
|
|
|
ASSERT_ROOM(reportbuf, curbuf, REPORT_MSG_MAX);
|
|
va_start(ap, fmt);
|
|
len = vsnprintf(curbuf, REPORT_MSG_MAX, fmt, ap);
|
|
curbuf += (len == -1 ? REPORT_MSG_MAX : (len < 0 ? 0 : len));
|
|
va_end(ap);
|
|
|
|
if (display_version[0] != '\0') {
|
|
len = snprintf(curbuf, REPORT_LEN_VERSION, "\n%s\n", display_version);
|
|
curbuf += (len == -1 ? REPORT_LEN_VERSION : (len < 0 ? 0 : len));
|
|
} else {
|
|
/* don't use dynamorio_version_string, we don't need copyright notice */
|
|
ASSERT_ROOM(reportbuf, curbuf, REPORT_LEN_VERSION);
|
|
len = snprintf(curbuf, REPORT_LEN_VERSION, "\n%s, %s\n", VERSION_NUMBER_STRING,
|
|
BUILD_NUMBER_STRING);
|
|
curbuf += (len == -1 ? REPORT_LEN_VERSION : (len < 0 ? 0 : len));
|
|
}
|
|
|
|
ASSERT_ROOM(reportbuf, curbuf, REPORT_LEN_OPTIONS);
|
|
/* leave room for newline */
|
|
get_dynamo_options_string(&dynamo_options, curbuf, REPORT_LEN_OPTIONS - 1, true);
|
|
/* get_dynamo_options_string will null-terminate even if truncates */
|
|
curbuf += strlen(curbuf);
|
|
*(curbuf++) = '\n';
|
|
|
|
/* print just frame ptr and ret addr for top of call stack */
|
|
ASSERT_ROOM(reportbuf, curbuf, REPORT_LEN_STACK);
|
|
if (report_ebp == NULL) {
|
|
GET_FRAME_PTR(report_ebp);
|
|
}
|
|
for (num = 0, pc = (ptr_uint_t *)report_ebp; num < REPORT_NUM_STACK && pc != NULL &&
|
|
is_readable_without_exception_query_os_noblock((app_pc)pc, 2 * sizeof(reg_t));
|
|
num++, pc = (ptr_uint_t *)*pc) {
|
|
len = snprintf(curbuf, REPORT_LEN_STACK_EACH, PFX " " PFX "\n", pc, *(pc + 1));
|
|
curbuf += (len == -1 ? REPORT_LEN_STACK_EACH : (len < 0 ? 0 : len));
|
|
}
|
|
/* Only walk the module list if we think the data structs are safe */
|
|
if (!TESTANY(DUMPCORE_INTERNAL_EXCEPTION, dumpcore_flag)) {
|
|
size_t sofar = 0;
|
|
/* We decided it's better to include the paths even if it means we may
|
|
* not fit all the modules (i#968). We plan to add the modules to the
|
|
* forensics file to have complete info (i#972).
|
|
*/
|
|
privload_print_modules(true /*include path*/, false /*no lock*/, curbuf,
|
|
REPORT_LEN_PRIVLIBS, &sofar);
|
|
curbuf += sofar;
|
|
}
|
|
|
|
/* SYSLOG_INTERNAL and diagnostics expect no trailing newline */
|
|
if (*(curbuf - 1) == '\n') /* won't be if we truncated something */
|
|
curbuf--;
|
|
/* now we for sure have room for \0 */
|
|
*curbuf = '\0';
|
|
/* now done with reportbuf */
|
|
|
|
if (TESTANY(dumpcore_flag, DYNAMO_OPTION(dumpcore_mask)) &&
|
|
DYNAMO_OPTION(live_dump)) {
|
|
/* non-fatal coredumps attempted before printing further diagnostics */
|
|
os_dump_core(reportbuf);
|
|
}
|
|
|
|
/* we already synchronized the options at the top of this function and we
|
|
* might be stack critical so use _NO_OPTION_SYNCH */
|
|
if (TESTANY(DUMPCORE_INTERNAL_EXCEPTION, dumpcore_flag) ||
|
|
TESTANY(DUMPCORE_CLIENT_EXCEPTION, dumpcore_flag)) {
|
|
char saddr[IF_X64_ELSE(19, 11)];
|
|
snprintf(saddr, BUFFER_SIZE_ELEMENTS(saddr), PFX, exception_addr);
|
|
NULL_TERMINATE_BUFFER(saddr);
|
|
if (TESTANY(DUMPCORE_INTERNAL_EXCEPTION, dumpcore_flag)) {
|
|
SYSLOG_NO_OPTION_SYNCH(
|
|
SYSLOG_CRITICAL, EXCEPTION, 7 /*#args*/, get_application_name(),
|
|
get_application_pid(), exception_label_core,
|
|
TESTANY(DUMPCORE_STACK_OVERFLOW, dumpcore_flag) ? STACK_OVERFLOW_NAME
|
|
: CRASH_NAME,
|
|
saddr, exception_report_url,
|
|
/* skip the prefix since the event log string
|
|
* already has it */
|
|
reportbuf + report_exception_skip_prefix());
|
|
} else {
|
|
SYSLOG_NO_OPTION_SYNCH(
|
|
SYSLOG_CRITICAL, CLIENT_EXCEPTION, 7 /*#args*/, get_application_name(),
|
|
get_application_pid(), exception_label_client,
|
|
TESTANY(DUMPCORE_STACK_OVERFLOW, dumpcore_flag) ? STACK_OVERFLOW_NAME
|
|
: CRASH_NAME,
|
|
saddr, exception_report_url,
|
|
reportbuf + report_client_exception_skip_prefix());
|
|
}
|
|
} else if (TESTANY(DUMPCORE_ASSERTION, dumpcore_flag)) {
|
|
/* We need to report ASSERTS in DEBUG=1 INTERNAL=0 builds since we're still
|
|
* going to kill the process. Xref PR 232783. d_r_internal_error() already
|
|
* obfuscated the which file info. */
|
|
SYSLOG_NO_OPTION_SYNCH(SYSLOG_ERROR, INTERNAL_SYSLOG_ERROR, 3,
|
|
get_application_name(), get_application_pid(), reportbuf);
|
|
} else if (TESTANY(DUMPCORE_CURIOSITY, dumpcore_flag)) {
|
|
SYSLOG_INTERNAL_NO_OPTION_SYNCH(SYSLOG_WARNING, "%s", reportbuf);
|
|
} else {
|
|
SYSLOG_INTERNAL_NO_OPTION_SYNCH(SYSLOG_ERROR, "%s", reportbuf);
|
|
}
|
|
|
|
/* no forensics files for usage error */
|
|
if (dumpcore_flag != DUMPCORE_FATAL_USAGE_ERROR) {
|
|
/* NULL for the threat id
|
|
* We always assume BAD state, even for curiosity asserts, etc., since
|
|
* diagnostics grabs memory when ok and we can't have that at arbitrary points!
|
|
*/
|
|
report_diagnostics(reportbuf, NULL, NO_VIOLATION_BAD_INTERNAL_STATE);
|
|
}
|
|
|
|
/* Print out pretty call stack to logfile where we have plenty of room.
|
|
* This avoids grabbing a lock b/c print_symbolic_address() checks
|
|
* under_internal_exception(). However we cannot include module info b/c
|
|
* that grabs locks: hence the fancier callstack in the main report
|
|
* for client and app crashes but not DR crashes.
|
|
*/
|
|
DOLOG(1, LOG_ALL, {
|
|
if (TESTANY(DUMPCORE_INTERNAL_EXCEPTION, dumpcore_flag))
|
|
dump_callstack(exception_addr, report_ebp, THREAD, DUMP_NOT_XML);
|
|
else
|
|
dump_dr_callstack(THREAD);
|
|
});
|
|
|
|
report_buf_lock_owner = 0;
|
|
d_r_mutex_unlock(&report_buf_lock);
|
|
|
|
if (dumpcore_flag != DUMPCORE_CURIOSITY) {
|
|
/* print out stats, can't be done inside the report_buf_lock
|
|
* because of non-trivial lock rank order violation on the
|
|
* snapshot_lock */
|
|
DOLOG(1, LOG_ALL, {
|
|
dump_global_stats(false);
|
|
if (dcontext != GLOBAL_DCONTEXT)
|
|
dump_thread_stats(dcontext, false);
|
|
});
|
|
}
|
|
|
|
if (TESTANY(dumpcore_flag, DYNAMO_OPTION(dumpcore_mask)) &&
|
|
!DYNAMO_OPTION(live_dump)) {
|
|
/* fatal coredump goes last */
|
|
os_dump_core(reportbuf);
|
|
}
|
|
}
|
|
|
|
void
|
|
report_app_problem(dcontext_t *dcontext, uint appfault_flag, app_pc pc, app_pc report_ebp,
|
|
const char *fmt, ...)
|
|
{
|
|
char buf[MAX_LOG_LENGTH];
|
|
size_t sofar = 0;
|
|
va_list ap;
|
|
char excpt_addr[IF_X64_ELSE(20, 12)];
|
|
|
|
if (!TESTANY(appfault_flag, DYNAMO_OPTION(appfault_mask)))
|
|
return;
|
|
|
|
snprintf(excpt_addr, BUFFER_SIZE_ELEMENTS(excpt_addr), PFX, pc);
|
|
NULL_TERMINATE_BUFFER(excpt_addr);
|
|
|
|
va_start(ap, fmt);
|
|
vprint_to_buffer(buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, fmt, ap);
|
|
va_end(ap);
|
|
|
|
print_to_buffer(buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, "Callstack:\n");
|
|
if (report_ebp == NULL)
|
|
GET_FRAME_PTR(report_ebp);
|
|
/* We decided it's better to include the paths even if it means we may
|
|
* not fit all the modules (i#968). A forensics file can be requested
|
|
* to get full info.
|
|
*/
|
|
dump_callstack_to_buffer(buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, pc, report_ebp,
|
|
CALLSTACK_MODULE_INFO | CALLSTACK_MODULE_PATH);
|
|
|
|
SYSLOG(SYSLOG_WARNING, APP_EXCEPTION, 4, get_application_name(),
|
|
get_application_pid(), excpt_addr, buf);
|
|
|
|
report_diagnostics(buf, NULL, NO_VIOLATION_OK_INTERNAL_STATE);
|
|
|
|
if (TESTANY(DUMPCORE_APP_EXCEPTION, DYNAMO_OPTION(dumpcore_mask)))
|
|
os_dump_core("application fault");
|
|
}
|
|
|
|
bool
|
|
is_readable_without_exception_try(byte *pc, size_t size)
|
|
{
|
|
dcontext_t *dcontext = get_thread_private_dcontext();
|
|
|
|
/* note we need a dcontext for a TRY block */
|
|
if (dcontext == NULL) {
|
|
/* XXX: should rename the current
|
|
* is_readable_without_exception() to
|
|
* is_readable_without_exception_os_read(). On each platform
|
|
* we should pick the fastest implementation for the
|
|
* non-faulting common case as the default version of
|
|
* is_readable_without_exception(). Some callers may still call a
|
|
* specific version if the fast path is not as common.
|
|
*/
|
|
return is_readable_without_exception(pc, size);
|
|
}
|
|
|
|
TRY_EXCEPT(
|
|
dcontext,
|
|
{
|
|
byte *check_pc = (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE);
|
|
if (size > (size_t)((byte *)POINTER_MAX - pc)) {
|
|
ASSERT_NOT_TESTED();
|
|
size = (byte *)POINTER_MAX - pc;
|
|
}
|
|
do {
|
|
PROBE_READ_PC(check_pc);
|
|
/* note the minor perf benefit - we check the whole loop
|
|
* in a single TRY/EXCEPT, and no system calls xref
|
|
* is_readable_without_exception() [based on safe_read]
|
|
* and is_readable_without_exception_query_os() [based on
|
|
* query_virtual_memory].
|
|
*/
|
|
|
|
check_pc += PAGE_SIZE;
|
|
} while (check_pc != 0 /*overflow*/ && check_pc < pc + size);
|
|
/* TRY usage note: can't return here */
|
|
},
|
|
{ /* EXCEPT */
|
|
/* no state to preserve */
|
|
return false;
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
is_string_readable_without_exception(char *str, size_t *str_length /* OPTIONAL OUT */)
|
|
{
|
|
size_t length = 0;
|
|
dcontext_t *dcontext = get_thread_private_dcontext();
|
|
|
|
if (str == NULL)
|
|
return false;
|
|
|
|
if (dcontext != NULL) {
|
|
TRY_EXCEPT(
|
|
dcontext, /* try */
|
|
{
|
|
length = strlen(str);
|
|
if (str_length != NULL)
|
|
*str_length = length;
|
|
/* NOTE - can't return here (try usage restriction) */
|
|
},
|
|
/* except */ { return false; });
|
|
return true;
|
|
} else {
|
|
/* ok have to do this the hard way... */
|
|
char *cur_page = (char *)ALIGN_BACKWARD(str, PAGE_SIZE);
|
|
char *cur_str = str;
|
|
do {
|
|
if (!is_readable_without_exception((byte *)cur_str,
|
|
(cur_page + PAGE_SIZE) - cur_str)) {
|
|
return false;
|
|
}
|
|
while (cur_str < cur_page + PAGE_SIZE) {
|
|
if (*cur_str == '\0') {
|
|
if (str_length != NULL)
|
|
*str_length = length;
|
|
return true;
|
|
}
|
|
cur_str++;
|
|
length++;
|
|
}
|
|
cur_page += PAGE_SIZE;
|
|
ASSERT(cur_page == cur_str && ALIGNED(cur_page, PAGE_SIZE));
|
|
} while (true);
|
|
ASSERT_NOT_REACHED();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
safe_write_try_except(void *base, size_t size, const void *in_buf, size_t *bytes_written)
|
|
{
|
|
uint prot;
|
|
byte *region_base;
|
|
size_t region_size;
|
|
dcontext_t *dcontext = get_thread_private_dcontext();
|
|
bool res = false;
|
|
if (bytes_written != NULL)
|
|
*bytes_written = 0;
|
|
|
|
if (dcontext != NULL) {
|
|
TRY_EXCEPT(dcontext,
|
|
{
|
|
/* We abort on the 1st fault, just like safe_read */
|
|
memcpy(base, in_buf, size);
|
|
res = true;
|
|
},
|
|
{
|
|
/* EXCEPT */
|
|
/* nothing: res is already false */
|
|
});
|
|
} else {
|
|
/* this is subject to races, but should only happen at init/attach when
|
|
* there should only be one live thread.
|
|
*/
|
|
/* on x86 must be readable to be writable so start with that */
|
|
if (is_readable_without_exception(base, size) &&
|
|
IF_UNIX_ELSE(get_memory_info_from_os, get_memory_info)(base, ®ion_base,
|
|
®ion_size, &prot) &&
|
|
TESTANY(MEMPROT_WRITE, prot)) {
|
|
size_t bytes_checked = region_size - ((byte *)base - region_base);
|
|
while (bytes_checked < size) {
|
|
if (!IF_UNIX_ELSE(get_memory_info_from_os, get_memory_info)(
|
|
region_base + region_size, ®ion_base, ®ion_size, &prot) ||
|
|
!TESTANY(MEMPROT_WRITE, prot))
|
|
return false;
|
|
bytes_checked += region_size;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
/* ok, checks passed do the copy, XXX - because of races this isn't safe! */
|
|
memcpy(base, in_buf, size);
|
|
res = true;
|
|
}
|
|
|
|
if (res) {
|
|
if (bytes_written != NULL)
|
|
*bytes_written = size;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
const char *
|
|
memprot_string(uint prot)
|
|
{
|
|
switch (prot) {
|
|
case (MEMPROT_READ | MEMPROT_WRITE | MEMPROT_EXEC): return "rwx";
|
|
case (MEMPROT_READ | MEMPROT_WRITE): return "rw-";
|
|
case (MEMPROT_READ | MEMPROT_EXEC): return "r-x";
|
|
case (MEMPROT_READ): return "r--";
|
|
case (MEMPROT_WRITE | MEMPROT_EXEC): return "-wx";
|
|
case (MEMPROT_WRITE): return "-w-";
|
|
case (MEMPROT_EXEC): return "--x";
|
|
case (0): return "---";
|
|
}
|
|
return "<error>";
|
|
}
|
|
|
|
/* returns true if every byte in the region addr to addr+size is set to val */
|
|
bool
|
|
is_region_memset_to_char(byte *addr, size_t size, byte val)
|
|
{
|
|
/* XXX : we could make this much faster with arch specific implementation
|
|
* (for x86 repe scasd w/proper alignment handling) */
|
|
size_t i;
|
|
for (i = 0; i < size; i++) {
|
|
if (*addr++ != val)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* returns pointer to first char of string that matches either c1 or c2
|
|
* or NULL if can't find */
|
|
char *
|
|
double_strchr(char *string, char c1, char c2)
|
|
{
|
|
while (*string != '\0') {
|
|
if (*string == c1 || *string == c2) {
|
|
return string;
|
|
}
|
|
string++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#ifndef WINDOWS
|
|
/* returns pointer to last char of string that matches either c1 or c2
|
|
* or NULL if can't find */
|
|
const char *
|
|
double_strrchr(const char *string, char c1, char c2)
|
|
{
|
|
const char *ret = NULL;
|
|
while (*string != '\0') {
|
|
if (*string == c1 || *string == c2) {
|
|
ret = string;
|
|
}
|
|
string++;
|
|
}
|
|
return ret;
|
|
}
|
|
#else
|
|
/* in inject_shared.c, XXX : move both copies to a common location */
|
|
#endif
|
|
|
|
#ifdef WINDOWS
|
|
/* Just like wcslen, but if the string is >= MAX characters long returns MAX
|
|
* whithout interrogating past str+MAX. NOTE - this matches most library
|
|
* implementations, but does NOT work the same way as the strnlen etc.
|
|
* functions in the hotpatch2 module (they return MAX+1 for strings > MAX).
|
|
* The hotpatch2 module implementation is scheduled to be changed. XXX -
|
|
* eventually would be nice to share the various string routines used both by
|
|
* the core and the hotpatch2 module. */
|
|
size_t
|
|
our_wcsnlen(const wchar_t *str, size_t max)
|
|
{
|
|
const wchar_t *s = str;
|
|
size_t i = 0;
|
|
|
|
while (i < max && *s != L'\0') {
|
|
i++;
|
|
s++;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
strcasecmp_with_wildcards(const char *regexp, const char *consider)
|
|
{
|
|
char cr, cc;
|
|
while (true) {
|
|
if (*regexp == '\0') {
|
|
if (*consider == '\0')
|
|
return 0;
|
|
return -1;
|
|
} else if (*consider == '\0')
|
|
return 1;
|
|
cr = (char)tolower(*regexp);
|
|
cc = (char)tolower(*consider);
|
|
if (cr != '?' && cr != cc) {
|
|
if (cr < cc)
|
|
return -1;
|
|
else
|
|
return 1;
|
|
}
|
|
regexp++;
|
|
consider++;
|
|
}
|
|
}
|
|
|
|
bool
|
|
str_case_prefix(const char *str, const char *pfx)
|
|
{
|
|
while (true) {
|
|
if (*pfx == '\0')
|
|
return true;
|
|
if (*str == '\0')
|
|
return false;
|
|
if (tolower(*str) != tolower(*pfx))
|
|
return false;
|
|
str++;
|
|
pfx++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
check_filter_common(const char *filter, const char *short_name, bool wildcards)
|
|
{
|
|
const char *next, *prev;
|
|
/* XXX: can we shrink this? not using full paths here */
|
|
char consider[MAXIMUM_PATH];
|
|
bool done = false;
|
|
|
|
ASSERT(short_name != NULL && filter != NULL);
|
|
/* XXX: consider replacing most of this with
|
|
strtok_r(copy_filter, ";", &pos) */
|
|
prev = filter;
|
|
do {
|
|
next = strchr(prev, ';');
|
|
if (next == NULL) {
|
|
next = prev + strlen(prev);
|
|
if (next == prev)
|
|
break;
|
|
done = true;
|
|
}
|
|
strncpy(consider, prev, MIN(BUFFER_SIZE_ELEMENTS(consider), (next - prev)));
|
|
consider[next - prev] = '\0'; /* if max no null */
|
|
LOG(THREAD_GET, LOG_ALL, 3, "considering \"%s\" == \"%s\"\n", consider,
|
|
short_name);
|
|
if (wildcards && strcasecmp_with_wildcards(consider, short_name) == 0)
|
|
return true;
|
|
else if (strcasecmp(consider, short_name) == 0)
|
|
return true;
|
|
prev = next + 1;
|
|
} while (!done);
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
check_filter(const char *filter, const char *short_name)
|
|
{
|
|
return check_filter_common(filter, short_name, false /*no wildcards*/);
|
|
}
|
|
|
|
bool
|
|
check_filter_with_wildcards(const char *filter, const char *short_name)
|
|
{
|
|
return check_filter_common(filter, short_name, true /*allow wildcards*/);
|
|
}
|
|
|
|
static char logdir[MAXIMUM_PATH];
|
|
static bool logdir_initialized = false;
|
|
static char basedir[MAXIMUM_PATH];
|
|
static bool basedir_initialized = false;
|
|
/* below used in the create_log_dir function to avoid having it on the stack
|
|
* on what is a critical path for stack depth (diagnostics->create_log_dir->
|
|
* get_parameter */
|
|
static char old_basedir[MAXIMUM_PATH];
|
|
/* this lock is recursive because current implementation recurses to create the
|
|
* basedir when called to create the logdir before the basedir is created, is
|
|
* also useful in case we receive an exception in the create_log_dir function
|
|
* since it is called in the diagnostics path, we should relook this though
|
|
* as is probably not the best way to avoid the diagnostics problem XXX */
|
|
DECLARE_CXTSWPROT_VAR(static recursive_lock_t logdir_mutex,
|
|
INIT_RECURSIVE_LOCK(logdir_mutex));
|
|
|
|
/* enable creating a new base logdir (for a fork, e.g.) */
|
|
void
|
|
enable_new_log_dir(void)
|
|
{
|
|
logdir_initialized = false;
|
|
}
|
|
|
|
void
|
|
create_log_dir(int dir_type)
|
|
{
|
|
#ifdef UNIX
|
|
char *pre_execve = getenv(DYNAMORIO_VAR_EXECVE_LOGDIR);
|
|
DEBUG_DECLARE(bool sharing_logdir = false;)
|
|
#endif
|
|
/* synchronize */
|
|
acquire_recursive_lock(&logdir_mutex);
|
|
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
#ifdef UNIX
|
|
if (dir_type == PROCESS_DIR && pre_execve != NULL) {
|
|
/* if this app has a logdir option or config, that should trump sharing
|
|
* the pre-execve logdir. a logdir env var should not.
|
|
*/
|
|
bool is_env;
|
|
if (IS_STRING_OPTION_EMPTY(logdir) &&
|
|
(get_config_val_ex(DYNAMORIO_VAR_LOGDIR, NULL, &is_env) == NULL || is_env)) {
|
|
/* use same dir as pre-execve! */
|
|
DODEBUG(sharing_logdir = true;);
|
|
strncpy(logdir, pre_execve, BUFFER_SIZE_ELEMENTS(logdir));
|
|
NULL_TERMINATE_BUFFER(logdir); /* if max no null */
|
|
logdir_initialized = true;
|
|
}
|
|
/* important to remove it, don't want to propagate to forked children */
|
|
/* i#909: unsetenv is unsafe as it messes up auxv access, so we disable */
|
|
disable_env(DYNAMORIO_VAR_EXECVE_LOGDIR);
|
|
/* check that it's gone: we've had problems with unsetenv */
|
|
ASSERT(getenv(DYNAMORIO_VAR_EXECVE_LOGDIR) == NULL);
|
|
}
|
|
#endif
|
|
/* used to be an else: leaving indentation though */
|
|
if (dir_type == BASE_DIR) {
|
|
int retval;
|
|
ASSERT(sizeof(basedir) == sizeof(old_basedir));
|
|
strncpy(old_basedir, basedir, sizeof(old_basedir));
|
|
/* option takes precedence over config var */
|
|
if (IS_STRING_OPTION_EMPTY(logdir)) {
|
|
retval = d_r_get_parameter(PARAM_STR(DYNAMORIO_VAR_LOGDIR), basedir,
|
|
BUFFER_SIZE_ELEMENTS(basedir));
|
|
if (IS_GET_PARAMETER_FAILURE(retval))
|
|
basedir[0] = '\0';
|
|
} else {
|
|
string_option_read_lock();
|
|
strncpy(basedir, DYNAMO_OPTION(logdir), BUFFER_SIZE_ELEMENTS(basedir));
|
|
string_option_read_unlock();
|
|
}
|
|
basedir[sizeof(basedir) - 1] = '\0';
|
|
if (!basedir_initialized || strncmp(old_basedir, basedir, sizeof(basedir))) {
|
|
/* need to create basedir, is changed or not yet created */
|
|
basedir_initialized = true;
|
|
/* skip creating dir basedir if is empty */
|
|
if (basedir[0] == '\0') {
|
|
#ifndef STATIC_LIBRARY
|
|
SYSLOG(SYSLOG_WARNING, WARNING_EMPTY_OR_NONEXISTENT_LOGDIR_KEY, 2,
|
|
get_application_name(), get_application_pid());
|
|
#endif
|
|
} else {
|
|
if (!os_create_dir(basedir, CREATE_DIR_ALLOW_EXISTING)) {
|
|
/* try to create full path */
|
|
char swap;
|
|
char *end = double_strchr(basedir, DIRSEP, ALT_DIRSEP);
|
|
bool res;
|
|
#ifdef WINDOWS
|
|
/* skip the drive */
|
|
if (end != NULL && end > basedir && *(end - 1) == ':')
|
|
end = double_strchr(++end, DIRSEP, ALT_DIRSEP);
|
|
#endif
|
|
while (end) {
|
|
swap = *end;
|
|
*end = '\0';
|
|
res = os_create_dir(basedir, CREATE_DIR_ALLOW_EXISTING);
|
|
*end = swap;
|
|
end = double_strchr(++end, DIRSEP, ALT_DIRSEP);
|
|
}
|
|
res = os_create_dir(basedir, CREATE_DIR_ALLOW_EXISTING);
|
|
/* check for success */
|
|
if (!res) {
|
|
SYSLOG(SYSLOG_ERROR, ERROR_UNABLE_TO_CREATE_BASEDIR, 3,
|
|
get_application_name(), get_application_pid(), basedir);
|
|
/* everything should work out fine, individual log
|
|
* dirs will also fail to open and just won't be
|
|
* logged to */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* only create one logging directory (i.e. not dynamic) */
|
|
else if (dir_type == PROCESS_DIR && !logdir_initialized) {
|
|
char *base = basedir;
|
|
if (!basedir_initialized) {
|
|
create_log_dir(BASE_DIR);
|
|
}
|
|
ASSERT(basedir_initialized);
|
|
logdir_initialized = true;
|
|
/* skip creating if basedir is empty */
|
|
if (*base != '\0') {
|
|
if (!get_unique_logfile("", logdir, sizeof(logdir), /*open_directory=*/true,
|
|
/*output_directory=*/NULL, /*embed_timestamp=*/false,
|
|
NULL)) {
|
|
SYSLOG_INTERNAL_WARNING("Unable to create log directory %s", logdir);
|
|
}
|
|
}
|
|
}
|
|
|
|
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
release_recursive_lock(&logdir_mutex);
|
|
|
|
#ifdef DEBUG
|
|
if (d_r_stats != NULL) {
|
|
/* if null, we're trying to report an error (probably via a core dump),
|
|
* so who cares if we lose logdir name */
|
|
strncpy(d_r_stats->logdir, logdir, sizeof(d_r_stats->logdir));
|
|
d_r_stats->logdir[sizeof(d_r_stats->logdir) - 1] = '\0'; /* if max no null */
|
|
}
|
|
if (dir_type == PROCESS_DIR
|
|
# ifdef UNIX
|
|
&& !sharing_logdir
|
|
# endif
|
|
)
|
|
SYSLOG_INTERNAL_INFO("log dir=%s", logdir);
|
|
#endif /* DEBUG */
|
|
}
|
|
|
|
/* Copies the name of the specified directory into buffer, returns true if
|
|
* the specified buffer has been initialized (if it hasn't then no copying is
|
|
* done).
|
|
* Will not copy more than *buffer_length bytes and does not ensure null
|
|
* termination.
|
|
* buffer can be NULL
|
|
* buffer_length can be NULL, but only if buffer is NULL
|
|
* on return (if it is not NULL) *buffer_length will hold the length of the
|
|
* specified directory's name (including the terminating NULL, i.e. the number
|
|
* of chars written to the buffer assuming the buffer was not NULL and large
|
|
* enough)
|
|
*/
|
|
bool
|
|
get_log_dir(log_dir_t dir_type, char *buffer, uint *buffer_length)
|
|
{
|
|
bool target_initialized = false;
|
|
char *target_dir = NULL;
|
|
ASSERT(buffer == NULL || buffer_length != NULL);
|
|
acquire_recursive_lock(&logdir_mutex);
|
|
if (dir_type == BASE_DIR) {
|
|
target_dir = basedir;
|
|
target_initialized = basedir_initialized;
|
|
} else if (dir_type == PROCESS_DIR) {
|
|
target_dir = logdir;
|
|
target_initialized = logdir_initialized;
|
|
} else {
|
|
/* should never get here */
|
|
ASSERT(false);
|
|
}
|
|
if (buffer != NULL && target_initialized) {
|
|
strncpy(buffer, target_dir, *buffer_length);
|
|
}
|
|
if (buffer_length != NULL && target_initialized) {
|
|
ASSERT_TRUNCATE(*buffer_length, uint, strlen(target_dir) + 1);
|
|
*buffer_length = (uint)strlen(target_dir) + 1;
|
|
}
|
|
release_recursive_lock(&logdir_mutex);
|
|
return target_initialized;
|
|
}
|
|
|
|
/*#ifdef UNIX
|
|
* N.B.: if you create a log file, you'll probably want to create a new one
|
|
* upon a fork. Should we require a callback passed in to this routine?
|
|
* For clients we have a dynamorio_fork_init routine. For internal modules,
|
|
* for now make your own fork_init routine.
|
|
* For closing on fork, since could have many threads with their own files
|
|
* open, we use our fd_table to close.
|
|
*#endif
|
|
*/
|
|
file_t
|
|
open_log_file(const char *basename, char *finalname_with_path, uint maxlen)
|
|
{
|
|
file_t file;
|
|
char name[MAXIMUM_PATH];
|
|
uint name_size = BUFFER_SIZE_ELEMENTS(name);
|
|
/* all logfiles are auto-closed on fork; we then make new ones */
|
|
uint flags = OS_OPEN_WRITE | OS_OPEN_ALLOW_LARGE | OS_OPEN_CLOSE_ON_FORK;
|
|
name[0] = '\0';
|
|
|
|
if (DYNAMO_OPTION(log_to_stderr))
|
|
return STDERR;
|
|
|
|
if (!get_log_dir(PROCESS_DIR, name, &name_size)) {
|
|
create_log_dir(PROCESS_DIR);
|
|
if (!get_log_dir(PROCESS_DIR, name, &name_size)) {
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
}
|
|
NULL_TERMINATE_BUFFER(name);
|
|
/* skip if logdir empty */
|
|
if (name[0] == '\0')
|
|
return INVALID_FILE;
|
|
snprintf(&name[strlen(name)], BUFFER_SIZE_ELEMENTS(name) - strlen(name),
|
|
"%c%s.%d." TIDFMT ".html", DIRSEP, basename,
|
|
get_thread_num(d_r_get_thread_id()), d_r_get_thread_id());
|
|
NULL_TERMINATE_BUFFER(name);
|
|
#ifdef UNIX
|
|
if (post_execve) /* reuse same log file */
|
|
file = os_open_protected(name, flags | OS_OPEN_APPEND);
|
|
else
|
|
#endif
|
|
file = os_open_protected(name, flags | OS_OPEN_REQUIRE_NEW);
|
|
if (file == INVALID_FILE) {
|
|
SYSLOG_INTERNAL_WARNING_ONCE("Cannot create log file %s", name);
|
|
/* everything should work out fine, log statements will just fail to
|
|
* write since invalid handle */
|
|
}
|
|
/* full path is often too long, so just print final dir and file name */
|
|
#ifdef UNIX
|
|
if (!post_execve) {
|
|
#endif
|
|
/* Note that we won't receive a message for the global logfile
|
|
* since the caller won't have set it yet. However, we will get
|
|
* all thread log files logged here. */
|
|
LOG(GLOBAL, LOG_THREADS, 1, "created log file %d=%s\n", file,
|
|
double_strrchr(name, DIRSEP, ALT_DIRSEP) + 1);
|
|
#ifdef UNIX
|
|
}
|
|
#endif
|
|
if (finalname_with_path != NULL) {
|
|
strncpy(finalname_with_path, name, maxlen);
|
|
finalname_with_path[maxlen - 1] = '\0'; /* if max no null */
|
|
}
|
|
return file;
|
|
}
|
|
|
|
void
|
|
close_log_file(file_t f)
|
|
{
|
|
if (f == STDERR)
|
|
return;
|
|
os_close_protected(f);
|
|
}
|
|
|
|
/* Generalize further as needed
|
|
* Creates a unique file or directory of the form
|
|
* BASEDIR/[app_name].[pid].<unique num of up to 8 digits>[file_type]
|
|
* when embed_timestamp is false,
|
|
* BASEDIR/[app_name].[pid].[timestamp in hex].<unique num of up to 8 digits>[file_type]
|
|
* when embed_timestamp is true.
|
|
* If the filename_buffer is not NULL, the filename of the obtained file
|
|
* is copied there. For creating a directory the file argument is expected
|
|
* to be null. Return true if the requested file or directory was created
|
|
* and, in the case of a file, returns a handle to file in the file argument.
|
|
*/
|
|
bool
|
|
get_unique_logfile(const char *file_type, char *filename_buffer, uint maxlen,
|
|
bool open_directory, const char *output_directory,
|
|
bool embed_timestamp, file_t *file)
|
|
{
|
|
char buf[MAXIMUM_PATH];
|
|
uint size = BUFFER_SIZE_ELEMENTS(buf), counter = 0, base_offset;
|
|
bool success = false;
|
|
ASSERT((open_directory && file == NULL) || (!open_directory && file != NULL));
|
|
if (!open_directory)
|
|
*file = INVALID_FILE;
|
|
|
|
bool target_initialized = false;
|
|
if (output_directory == NULL) {
|
|
create_log_dir(BASE_DIR);
|
|
target_initialized = get_log_dir(BASE_DIR, buf, &size);
|
|
} else {
|
|
target_initialized = dr_directory_exists(output_directory);
|
|
if (target_initialized) {
|
|
strncpy(buf, output_directory, size);
|
|
}
|
|
}
|
|
if (target_initialized) {
|
|
NULL_TERMINATE_BUFFER(buf);
|
|
ASSERT_TRUNCATE(base_offset, uint, strlen(buf));
|
|
base_offset = (uint)strlen(buf);
|
|
buf[base_offset++] = DIRSEP;
|
|
size = BUFFER_SIZE_ELEMENTS(buf) - base_offset;
|
|
do {
|
|
if (embed_timestamp) {
|
|
snprintf(&(buf[base_offset]), size, "%s.%s.%012lx.%08d%s",
|
|
get_app_name_for_path(), get_application_pid(),
|
|
query_time_millis(), counter, file_type);
|
|
} else {
|
|
snprintf(&(buf[base_offset]), size, "%s.%s.%.8d%s",
|
|
get_app_name_for_path(), get_application_pid(), counter,
|
|
file_type);
|
|
}
|
|
NULL_TERMINATE_BUFFER(buf);
|
|
if (open_directory) {
|
|
success = os_create_dir(buf, CREATE_DIR_REQUIRE_NEW);
|
|
} else {
|
|
*file = os_open(buf, OS_OPEN_REQUIRE_NEW | OS_OPEN_WRITE);
|
|
success = (*file != INVALID_FILE);
|
|
}
|
|
} while (!success && counter++ < 99999999 && os_file_exists(buf, open_directory));
|
|
DOLOG(1, LOG_ALL, {
|
|
if (!success)
|
|
LOG(GLOBAL, LOG_ALL, 1, "Failed to create unique logfile %s\n", buf);
|
|
else
|
|
LOG(GLOBAL, LOG_ALL, 1, "Created unique logfile %s\n", buf);
|
|
});
|
|
}
|
|
|
|
/* copy the filename over if we have a valid buffer */
|
|
if (NULL != filename_buffer) {
|
|
strncpy(filename_buffer, buf, maxlen);
|
|
filename_buffer[maxlen - 1] = '\0'; /* NULL terminate */
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
const char *
|
|
get_app_name_for_path(void)
|
|
{
|
|
return get_short_name(get_application_name());
|
|
}
|
|
|
|
const char *
|
|
get_short_name(const char *exename)
|
|
{
|
|
const char *exe;
|
|
exe = double_strrchr(exename, DIRSEP, ALT_DIRSEP);
|
|
if (exe == NULL)
|
|
exe = exename;
|
|
else
|
|
exe++; /* skip (back)slash */
|
|
return exe;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
|
|
#ifdef DEBUG
|
|
# ifdef FRAGMENT_SIZES_STUDY /* to isolate sqrt dependence */
|
|
/* given an array of size size of integers, computes and prints the
|
|
* min, max, mean, and stddev
|
|
*/
|
|
void
|
|
print_statistics(int *data, int size)
|
|
{
|
|
int i;
|
|
int min, max;
|
|
double mean, stddev, sum;
|
|
uint top, bottom;
|
|
const char *sign;
|
|
/* our context switch does not save & restore floating point state,
|
|
* so we have to do it here!
|
|
*/
|
|
PRESERVE_FLOATING_POINT_STATE_START();
|
|
|
|
sum = 0.;
|
|
min = max = data[0];
|
|
for (i = 0; i < size; i++) {
|
|
if (data[i] < min)
|
|
min = data[i];
|
|
if (data[i] > max)
|
|
max = data[i];
|
|
sum += data[i];
|
|
}
|
|
mean = sum / (double)size;
|
|
|
|
stddev = 0.;
|
|
for (i = 0; i < size; i++) {
|
|
double diff = ((double)data[i]) - mean;
|
|
stddev += diff * diff;
|
|
}
|
|
stddev /= (double)size;
|
|
/* XXX i#46: We need a private sqrt impl. libc's sqrt can actually
|
|
* clobber errno, too!
|
|
*/
|
|
ASSERT(!DYNAMO_OPTION(early_inject) &&
|
|
"FRAGMENT_SIZES_STUDY incompatible with early injection");
|
|
stddev = sqrt(stddev);
|
|
|
|
LOG(GLOBAL, LOG_ALL, 0, "\t# = %9d\n", size);
|
|
LOG(GLOBAL, LOG_ALL, 0, "\tmin = %9d\n", min);
|
|
LOG(GLOBAL, LOG_ALL, 0, "\tmax = %9d\n", max);
|
|
double_print(mean, 1, &top, &bottom, &sign);
|
|
LOG(GLOBAL, LOG_ALL, 0, "\tmean = %s%7u.%.1u\n", sign, top, bottom);
|
|
double_print(stddev, 1, &top, &bottom, &sign);
|
|
LOG(GLOBAL, LOG_ALL, 0, "\tstddev = %s%7u.%.1u\n", sign, top, bottom);
|
|
|
|
PRESERVE_FLOATING_POINT_STATE_END();
|
|
}
|
|
# endif
|
|
|
|
/* XXX: these should be under ifdef STATS, not necessarily ifdef DEBUG */
|
|
void
|
|
stats_thread_init(dcontext_t *dcontext)
|
|
{
|
|
thread_local_statistics_t *new_thread_stats;
|
|
if (!INTERNAL_OPTION(thread_stats))
|
|
return; /* dcontext->thread_stats stays NULL */
|
|
|
|
new_thread_stats =
|
|
HEAP_TYPE_ALLOC(dcontext, thread_local_statistics_t, ACCT_STATS, UNPROTECTED);
|
|
LOG(THREAD, LOG_STATS, 2, "thread_stats=" PFX " size=%d\n", new_thread_stats,
|
|
sizeof(thread_local_statistics_t));
|
|
/* initialize any thread stats bookkeeping fields before assigning to dcontext */
|
|
memset(new_thread_stats, 0x0, sizeof(thread_local_statistics_t));
|
|
new_thread_stats->thread_id = d_r_get_thread_id();
|
|
ASSIGN_INIT_LOCK_FREE(new_thread_stats->thread_stats_lock, thread_stats_lock);
|
|
dcontext->thread_stats = new_thread_stats;
|
|
}
|
|
|
|
void
|
|
stats_thread_exit(dcontext_t *dcontext)
|
|
{
|
|
/* We need to free even in non-debug b/c unprot local heap is global. */
|
|
if (dcontext->thread_stats) {
|
|
thread_local_statistics_t *old_thread_stats = dcontext->thread_stats;
|
|
# ifdef DEBUG
|
|
DELETE_LOCK(old_thread_stats->thread_stats_lock);
|
|
# endif
|
|
dcontext->thread_stats = NULL; /* disable thread stats before freeing memory */
|
|
HEAP_TYPE_FREE(dcontext, old_thread_stats, thread_local_statistics_t, ACCT_STATS,
|
|
UNPROTECTED);
|
|
}
|
|
}
|
|
|
|
DISABLE_NULL_SANITIZER
|
|
void
|
|
dump_thread_stats(dcontext_t *dcontext, bool raw)
|
|
{
|
|
/* Note that this routine may be called by another thread, but
|
|
we want the LOGs and the STATs to be for the thread getting dumped
|
|
Make sure we use the passed dcontext everywhere here.
|
|
Avoid implicit use of get_thread_private_dcontext (e.g. THREAD_GET).
|
|
*/
|
|
/* Each use of THREAD causes cl to make two implicit local variables
|
|
* (without optimizations on) (culprit is the ? : syntax). Since the
|
|
* number of locals sums over scopes, this leads to stack usage
|
|
* of ~3kb for this function due to the many LOGs.
|
|
* Instead we use THREAD once here and use a local below, cutting stack
|
|
* usage to 12 bytes, ref bug 2203 */
|
|
file_t logfile = THREAD;
|
|
if (!THREAD_STATS_ON(dcontext))
|
|
return;
|
|
|
|
/* XXX: for now we'll have code duplication with dump_global_stats()
|
|
* with the only difference being THREAD vs GLOBAL, e.g. LOG(GLOBAL and GLOBAL_STAT
|
|
* Keep in sync or make a template statistics dump macro for both cases.
|
|
*/
|
|
LOG(logfile, LOG_STATS, 1,
|
|
"(Begin) Thread statistics @%d global, %d thread fragments ",
|
|
GLOBAL_STAT(num_fragments), THREAD_STAT(dcontext, num_fragments));
|
|
DOLOG(1, LOG_STATS, { d_r_print_timestamp(logfile); });
|
|
/* give up right away if thread stats lock already held, will dump next time
|
|
most likely thread interrupted while dumping state
|
|
*/
|
|
if (!d_r_mutex_trylock(&dcontext->thread_stats->thread_stats_lock)) {
|
|
LOG(logfile, LOG_STATS, 1, " WARNING: skipped! Another dump in progress.\n");
|
|
return;
|
|
}
|
|
LOG(logfile, LOG_STATS, 1, ":\n");
|
|
|
|
# define STATS_DEF(desc, stat) \
|
|
if (THREAD_STAT(dcontext, stat)) { \
|
|
if (raw) { \
|
|
LOG(logfile, LOG_STATS, 1, "\t%s\t= " SSZFMT "\n", #stat, \
|
|
THREAD_STAT(dcontext, stat)); \
|
|
} else { \
|
|
LOG(logfile, LOG_STATS, 1, \
|
|
"%50s %s:" IF_X64_ELSE("%18", "%9") SSZFC "\n", desc, "(thread)", \
|
|
THREAD_STAT(dcontext, stat)); \
|
|
} \
|
|
}
|
|
# include "statsx.h"
|
|
# undef STATS_DEF
|
|
|
|
LOG(logfile, LOG_STATS, 1, "(End) Thread statistics\n");
|
|
d_r_mutex_unlock(&dcontext->thread_stats->thread_stats_lock);
|
|
/* TODO: update thread statistics, using the thread stats delta when implemented */
|
|
|
|
# ifdef KSTATS
|
|
dump_thread_kstats(dcontext);
|
|
# endif
|
|
}
|
|
|
|
DISABLE_NULL_SANITIZER
|
|
void
|
|
dump_global_stats(bool raw)
|
|
{
|
|
DOLOG(1, LOG_MEMSTATS, {
|
|
if (!dynamo_exited_and_cleaned)
|
|
mem_stats_snapshot();
|
|
});
|
|
if (!dynamo_exited_and_cleaned)
|
|
print_vmm_heap_data(GLOBAL);
|
|
if (GLOBAL_STATS_ON()) {
|
|
LOG(GLOBAL, LOG_STATS, 1, "(Begin) All statistics @%d ",
|
|
GLOBAL_STAT(num_fragments));
|
|
DOLOG(1, LOG_STATS, { d_r_print_timestamp(GLOBAL); });
|
|
LOG(GLOBAL, LOG_STATS, 1, ":\n");
|
|
# define STATS_DEF(desc, stat) \
|
|
if (GLOBAL_STAT(stat)) { \
|
|
if (raw) { \
|
|
LOG(GLOBAL, LOG_STATS, 1, "\t%s\t= " SSZFMT "\n", #stat, \
|
|
GLOBAL_STAT(stat)); \
|
|
} else { \
|
|
LOG(GLOBAL, LOG_STATS, 1, "%50s :" IF_X64_ELSE("%18", "%9") SSZFC "\n", \
|
|
desc, GLOBAL_STAT(stat)); \
|
|
} \
|
|
}
|
|
# include "statsx.h"
|
|
# undef STATS_DEF
|
|
LOG(GLOBAL, LOG_STATS, 1, "(End) All statistics\n");
|
|
}
|
|
# ifdef HEAP_ACCOUNTING
|
|
DOLOG(1, LOG_HEAP | LOG_STATS, { print_heap_statistics(); });
|
|
# endif
|
|
DOLOG(1, LOG_CACHE, {
|
|
/* shared cache stats */
|
|
fcache_stats_exit();
|
|
});
|
|
# ifdef SHARING_STUDY
|
|
DOLOG(1, LOG_ALL, {
|
|
if (INTERNAL_OPTION(fragment_sharing_study) && !dynamo_exited)
|
|
print_shared_stats();
|
|
});
|
|
# endif
|
|
# ifdef DEADLOCK_AVOIDANCE
|
|
dump_process_locks();
|
|
# endif
|
|
}
|
|
|
|
uint
|
|
print_timestamp_to_buffer(char *buffer, size_t len)
|
|
{
|
|
uint min, sec, msec;
|
|
size_t print_len = MIN(len, PRINT_TIMESTAMP_MAX_LENGTH);
|
|
static uint64 initial_time = 0ULL; /* in milliseconds */
|
|
uint64 current_time;
|
|
|
|
if (initial_time == 0ULL)
|
|
initial_time = query_time_millis();
|
|
current_time = query_time_millis();
|
|
if (current_time == 0ULL) /* call failed */
|
|
return 0;
|
|
current_time -= initial_time; /* elapsed */
|
|
sec = (uint)(current_time / 1000);
|
|
msec = (uint)(current_time % 1000);
|
|
min = sec / 60;
|
|
sec = sec % 60;
|
|
return d_r_snprintf(buffer, print_len, "(%ld:%02ld.%03ld)", min, sec, msec);
|
|
}
|
|
|
|
/* prints elapsed time since program startup to the given logfile
|
|
* TODO: should also print absolute timestamp
|
|
* TODO: and relative time from thread start
|
|
*/
|
|
uint
|
|
d_r_print_timestamp(file_t logfile)
|
|
{
|
|
char buffer[PRINT_TIMESTAMP_MAX_LENGTH];
|
|
uint len = print_timestamp_to_buffer(buffer, PRINT_TIMESTAMP_MAX_LENGTH);
|
|
|
|
if (len > 0)
|
|
print_file(logfile, buffer);
|
|
return len;
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
void
|
|
dump_global_rstats_to_stderr(void)
|
|
{
|
|
if (GLOBAL_STATS_ON()) {
|
|
print_file(STDERR, "%s statistics:\n", PRODUCT_NAME);
|
|
#undef RSTATS_DEF
|
|
/* It doesn't make sense to print Current stats. We assume no rstat starts
|
|
* with "Cu".
|
|
*/
|
|
#define RSTATS_DEF(desc, stat) \
|
|
if (GLOBAL_STAT(stat) && (desc[0] != 'C' || desc[1] != 'u')) { \
|
|
print_file(STDERR, "%50s :" IF_X64_ELSE("%18", "%9") SSZFC "\n", desc, \
|
|
GLOBAL_STAT(stat)); \
|
|
}
|
|
#define RSTATS_ONLY
|
|
#include "statsx.h"
|
|
#undef RSTATS_ONLY
|
|
#undef RSTATS_DEF
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_buffer_as_ascii(file_t logfile, char *buffer, size_t len)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < len; i++) {
|
|
print_file(logfile, "%c", isprint_fast(buffer[i]) ? buffer[i] : '.');
|
|
}
|
|
}
|
|
|
|
void
|
|
dump_buffer_as_bytes(file_t logfile, void *buffer, size_t len, int flags)
|
|
{
|
|
bool octal = TESTANY(DUMP_OCTAL, flags);
|
|
bool raw = TESTANY(DUMP_RAW, flags);
|
|
bool usechars = !raw && !TESTANY(DUMP_NO_CHARS, flags);
|
|
bool replayable = usechars && !TESTANY(DUMP_NO_QUOTING, flags);
|
|
bool dword = TESTANY(DUMP_DWORD, flags);
|
|
bool prepend_address = TESTANY(DUMP_ADDRESS, flags);
|
|
bool append_ascii = TESTANY(DUMP_APPEND_ASCII, flags);
|
|
|
|
unsigned char *buf = (unsigned char *)buffer;
|
|
|
|
int per_line =
|
|
(flags & DUMP_PER_LINE) ? (flags & DUMP_PER_LINE) : DUMP_PER_LINE_DEFAULT;
|
|
size_t i;
|
|
int nonprint = 0;
|
|
size_t line_start = 0;
|
|
|
|
if (!raw)
|
|
print_file(logfile, "%s", "\"");
|
|
|
|
for (i = 0; i + (dword ? 4 : 1) <= len; i += (dword ? 4 : 1)) {
|
|
if (i > 0 && 0 == i % per_line) {
|
|
if (append_ascii) {
|
|
print_file(logfile, "%s", " ");
|
|
/* append current line as ASCII */
|
|
ASSERT(line_start == (i - per_line));
|
|
dump_buffer_as_ascii(logfile, (char *)buf + line_start, per_line);
|
|
line_start = i;
|
|
}
|
|
/* new line */
|
|
print_file(logfile, "%s", raw ? "\n" : "\"\n\"");
|
|
}
|
|
if (prepend_address && 0 == i % per_line) // prepend address on new line
|
|
print_file(logfile, PFX " ", buf + i);
|
|
|
|
if (replayable) {
|
|
if (isdigit_fast(buf[i]) && nonprint) {
|
|
print_file(logfile, "%s", "\"\""); // to make \01 into \0""1
|
|
}
|
|
if (buf[i] == '"') {
|
|
print_file(logfile, "%s", "\\\"");
|
|
continue;
|
|
}
|
|
if (buf[i] == '\\')
|
|
print_file(logfile, "%s", "\\");
|
|
}
|
|
|
|
if (usechars && isprint_fast(buf[i])) {
|
|
print_file(logfile, "%c", buf[i]);
|
|
nonprint = 0;
|
|
} else {
|
|
if (!raw) {
|
|
print_file(logfile, "%s", octal ? "\\" : "\\x");
|
|
}
|
|
if (dword)
|
|
print_file(logfile, "%08x", *((uint *)(buf + i)));
|
|
else
|
|
print_file(logfile, octal ? "%03o" : "%02x", buf[i]);
|
|
nonprint = 1;
|
|
if (raw) {
|
|
print_file(logfile, "%s", " ");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (append_ascii) {
|
|
/* append last line as ASCII */
|
|
/* pad to align columns */
|
|
size_t empty = ALIGN_FORWARD(buf + len, per_line);
|
|
uint size = (dword ? 4 : 1);
|
|
/* In general we expect dword requests to be dword aligned but
|
|
* we don't enforce it. Note that we don't print DWORDs that
|
|
* may extend beyond valid len, but we'll print as ASCII any
|
|
* bytes included in valid len even if not printed in hex.
|
|
*/
|
|
for (i = ALIGN_BACKWARD(buf + len, size); i < empty; i += size) {
|
|
if (dword) {
|
|
print_file(logfile, "%8c ", ' ');
|
|
} else {
|
|
print_file(logfile, octal ? "%3c " : "%2c ", ' ');
|
|
}
|
|
}
|
|
|
|
print_file(logfile, "%s", " ");
|
|
dump_buffer_as_ascii(logfile, (char *)buf + line_start, len - line_start);
|
|
}
|
|
|
|
if (!raw)
|
|
print_file(logfile, "%s", "\";\n");
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* xml escaping routines, presumes iso-8859-1 encoding */
|
|
|
|
bool
|
|
is_valid_xml_char(char c)
|
|
{
|
|
/* XXX - wld.exe xml parsing complains about any C0 control character other
|
|
* then \t \r and \n. However, in this encoding (to my understanding) all values
|
|
* should be valid and IE doesn't complain opening an xml file in this encoding
|
|
* with these characters. Not sure where the wld.exe problem lies, but since it is
|
|
* our primary consumer we work around here. */
|
|
if ((uchar)c < 0x20 && c != '\t' && c != '\n' && c != '\r') {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
is_valid_xml_string(const char *str)
|
|
{
|
|
while (*str != '\0') {
|
|
if (!is_valid_xml_char(*str))
|
|
return false;
|
|
str++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* NOTE - string should not include the <![CDATA[ ]]> markup as one thing
|
|
* this routine checks for is an inadvertant ending sequence ]]>. Caller is
|
|
* responsible for correct markup. */
|
|
static bool
|
|
is_valid_xml_cdata_string(const char *str)
|
|
{
|
|
/* check for end CDATA tag */
|
|
/* XXX - optimization,combine the two walks of the string into a
|
|
* single walk.*/
|
|
return (strstr(str, "]]>") == NULL && is_valid_xml_string(str));
|
|
}
|
|
|
|
#if 0 /* Not yet used */
|
|
static bool
|
|
is_valid_xml_body_string(const char *str)
|
|
{
|
|
/* check for & < > */
|
|
/* XXX - optimization, combine into a single walk of the string. */
|
|
return (strchr(str, '>') == NULL && strchr(str, '<') == NULL &&
|
|
strchr(str, '&') == NULL && is_valid_xml_string(str));
|
|
}
|
|
|
|
static bool
|
|
is_valid_xml_attribute_string(const char *str)
|
|
{
|
|
/* check for & < > ' " */
|
|
/* XXX - optimization, combine into a single walk of the string. */
|
|
return (strchr(str, '\'') == NULL && strchr(str, '\"') == NULL &&
|
|
is_valid_xml_body_string(str));
|
|
}
|
|
#endif
|
|
|
|
/* NOTE - string should not include the <![CDATA[ ]]> markup as one thing
|
|
* this routine checks for is an inadvertant ending sequence ]]> (in which
|
|
* case the first ] will be escaped). We escape using \%03d, note that since
|
|
* we don't escape \ , '\003' and "\003" will be indistinguishable (XXX),
|
|
* but given that these should really be normal ascii strings we'll live with
|
|
* that. */
|
|
void
|
|
print_xml_cdata(file_t f, const char *str)
|
|
{
|
|
if (is_valid_xml_cdata_string(str)) {
|
|
print_file(f, "%s", str);
|
|
} else {
|
|
while (*str != '\0') {
|
|
if (!is_valid_xml_char(*str) ||
|
|
(*str == ']' && *(str + 1) == ']' && *(str + 2) == '>')) {
|
|
print_file(f, "\\%03d", (int)*(uchar *)str);
|
|
} else {
|
|
/* XXX : could batch up printing normal chars for perf.
|
|
* but we usually expect to have valid strings anyways. */
|
|
print_file(f, "%c", *str);
|
|
}
|
|
str++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* TODO - NYI print_xml_body_string, print_xml_attribute_string */
|
|
|
|
void
|
|
print_version_and_app_info(file_t file)
|
|
{
|
|
print_file(file, "%s\n", dynamorio_version_string);
|
|
/* print qualified name (not d_r_stats->process_name) to get cmdline */
|
|
print_file(file, "Running: %s\n", get_application_name());
|
|
#ifdef WINDOWS
|
|
/* XXX: also get linux cmdline -- separate since wide on win32 */
|
|
print_file(file, "App cmdline: %S\n", get_application_cmdline());
|
|
#endif
|
|
print_file(file, PRODUCT_NAME " built with: %s\n", DYNAMORIO_DEFINES);
|
|
print_file(file, PRODUCT_NAME " built on: %s\n", dynamorio_buildmark);
|
|
#ifndef _WIN32_WCE
|
|
print_file(file, DYNAMORIO_VAR_OPTIONS ": %s\n", d_r_option_string);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
utils_exit(void)
|
|
{
|
|
LOG(GLOBAL, LOG_STATS, 1, "-prng_seed " PFX " for reproducing random sequence\n",
|
|
initial_random_seed);
|
|
if (doing_detach)
|
|
enable_new_log_dir(); /* For potential re-attach. */
|
|
|
|
DELETE_LOCK(report_buf_lock);
|
|
DELETE_RECURSIVE_LOCK(logdir_mutex);
|
|
DELETE_LOCK(prng_lock);
|
|
#ifdef DEADLOCK_AVOIDANCE
|
|
DELETE_LOCK(do_threshold_mutex);
|
|
#endif
|
|
|
|
spinlock_count = 0;
|
|
}
|
|
|
|
/* returns a pseudo random number in [0, max_offset) */
|
|
|
|
/* XXX: [minor security] while the first user may get more
|
|
* randomness from the lower bits of the seed, I am not sure the
|
|
* following users should strongly prefer higher or lower bits.
|
|
*/
|
|
size_t
|
|
get_random_offset(size_t max_offset)
|
|
{
|
|
/* These linear congruential constants taken from
|
|
* http://remus.rutgers.edu/~rhoads/Code/random.c
|
|
* XXX: Look up Knuth's recommendations in vol. 2
|
|
*/
|
|
enum { LCM_A = 279470273, LCM_Q = 15, LCM_R = 102913196 };
|
|
/* I prefer not risking any of the randomness in our seed to go
|
|
* through the fishy LCM and value generation - it doesn't buy us
|
|
* anything for the first instance which is all we currently use.
|
|
* Calculating value based on the previous seed also removes
|
|
* dependencies from the critical path and will be faster if we
|
|
* use this on a critical path..
|
|
*/
|
|
size_t value;
|
|
|
|
/* Avoids div-by-zero if offset is 0; see case 8602 for implications. */
|
|
if (max_offset == 0)
|
|
return 0;
|
|
|
|
/* process-shared random sequence */
|
|
d_r_mutex_lock(&prng_lock);
|
|
/* XXX: this is not getting the best randomness
|
|
* see srand() comments why taking higher order bits is usually better
|
|
* j=1+(int) (10.0*rand()/(RAND_MAX+1.0));
|
|
* but I want to do it without floating point
|
|
*/
|
|
value = random_seed % max_offset;
|
|
|
|
random_seed = LCM_A * (random_seed % LCM_Q) - LCM_R * (random_seed / LCM_Q);
|
|
d_r_mutex_unlock(&prng_lock);
|
|
LOG(GLOBAL, LOG_ALL, 2, "get_random_offset: value=%d (mod %d), new rs=%d\n", value,
|
|
max_offset, random_seed);
|
|
return value;
|
|
}
|
|
|
|
void
|
|
d_r_set_random_seed(uint seed)
|
|
{
|
|
random_seed = seed;
|
|
}
|
|
|
|
uint
|
|
d_r_get_random_seed(void)
|
|
{
|
|
return random_seed;
|
|
}
|
|
|
|
/* millis is the number of milliseconds since Jan 1, 1601 (this is
|
|
* the current UTC time).
|
|
*/
|
|
void
|
|
convert_millis_to_date(uint64 millis, dr_time_t *dr_time DR_PARAM_OUT)
|
|
{
|
|
uint64 time = millis;
|
|
uint days, year, month, q;
|
|
|
|
#define DAYS_IN_400_YEARS (400 * 365 + 97)
|
|
|
|
dr_time->milliseconds = (uint)(time % 1000);
|
|
time /= 1000;
|
|
dr_time->second = (uint)(time % 60);
|
|
time /= 60;
|
|
dr_time->minute = (uint)(time % 60);
|
|
time /= 60;
|
|
dr_time->hour = (uint)(time % 24);
|
|
time /= 24;
|
|
/* Time is now number of days since 1 Jan 1601 (Gregorian).
|
|
* We rebase to 1 Mar 1600 (Gregorian) as this day immediately
|
|
* follows the irregular element in each cycle: 12-month cycle,
|
|
* 4-year cycle, 100-year cycle, 400-year cycle.
|
|
* Noon, 1 Jan 1601 is the start of Julian Day 2305814.
|
|
* Noon, 1 Mar 1600 is the start of Julian Day 2305508.
|
|
*/
|
|
time += 2305814 - 2305508;
|
|
|
|
/* Reduce modulo 400 years, which gets us into the range of a uint. */
|
|
year = 1600 + (uint)(time / DAYS_IN_400_YEARS) * 400;
|
|
days = (uint)(time % DAYS_IN_400_YEARS);
|
|
|
|
/* The constants used in the rest of this function are difficult to
|
|
* get right, but the number of days in 400 years is small enough for
|
|
* us to test this code exhaustively, which we will, of course, do.
|
|
*/
|
|
|
|
/* Sunday is 0, Monday is 1, ... 1 Mar 1600 was Wednesday. */
|
|
dr_time->day_of_week = (days + 3) % 7;
|
|
|
|
/* Determine century: divide by average number of days in a century,
|
|
* 36524.25 = 146097 / 4, rounding up because the long century comes last.
|
|
*/
|
|
q = (days * 4 + 3) / 146097;
|
|
year += q * 100;
|
|
days -= q * 146097 / 4;
|
|
|
|
/* Determine year: divide by average number of days in a year,
|
|
* 365.25 = 1461 / 4, rounding up because the long year comes last.
|
|
* The average is 365.25 because we ignore the irregular year at the
|
|
* end of the century, which we can safely do because it is shorter.
|
|
*/
|
|
q = (days * 4 + 3) / 1461;
|
|
year += q;
|
|
days -= q * 1461 / 4;
|
|
|
|
/* Determine month: divide by (31 + 30 + 31 + 30 + 31) / 5, with carefully tuned
|
|
* rounding to get the consecutive 31-day months in the right place. This works
|
|
* because the number of days in a month follows a cycle, starting in March:
|
|
* 31, 30, 31, 30, 31; 31, 30, 31, 30, 31; 31, ... This is followed by Februrary,
|
|
* of course, but that is not a problem because it is shorter rather than longer
|
|
* than expected. The values "2" and "2" are easiest to determine experimentally.
|
|
*/
|
|
month = (days * 5 + 2) / 153;
|
|
days -= (month * 153 + 2) / 5;
|
|
|
|
/* Adjust for calendar year starting in January rather than March. */
|
|
dr_time->day = days + 1;
|
|
dr_time->month = month < 10 ? month + 3 : month - 9;
|
|
dr_time->year = month < 10 ? year : year + 1;
|
|
}
|
|
|
|
/* millis is the number of milliseconds since Jan 1, 1601 (this is
|
|
* the current UTC time).
|
|
*/
|
|
void
|
|
convert_date_to_millis(const dr_time_t *dr_time, uint64 *millis DR_PARAM_OUT)
|
|
{
|
|
/* Formula adapted from http://en.wikipedia.org/wiki/Julian_day.
|
|
* We rebase the input year from -4800 to +1600, and we rebase
|
|
* the output day from from 1 Mar -4800 (Gregorian) to 1 Jan 1601.
|
|
* Noon, 1 Mar 1600 (Gregorian) is the start of Julian Day 2305508.
|
|
* Noon, 1 Mar -4800 (Gregorian) is the start of Julian Day -32044.
|
|
* Noon, 1 Jan 1601 (Gregorian) is the start of Julian Day 2305814.
|
|
*/
|
|
uint a = dr_time->month < 3 ? 1 : 0;
|
|
uint y = dr_time->year - a - 1600;
|
|
uint m = dr_time->month + 12 * a - 3;
|
|
uint64 days = ((dr_time->day + (153 * m + 2) / 5 + y / 4 - y / 100 + y / 400) +
|
|
365 * (uint64)y - 32045 + 2305508 + 32044 - 2305814);
|
|
*millis =
|
|
((((days * 24 + dr_time->hour) * 60 + dr_time->minute) * 60 + dr_time->second) *
|
|
1000 +
|
|
dr_time->milliseconds);
|
|
}
|
|
|
|
static const uint crctab[] = {
|
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
|
|
0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd,
|
|
0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
|
|
0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
|
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
|
|
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
|
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
|
|
0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
|
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
|
|
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
|
|
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb,
|
|
0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
|
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
|
|
0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce,
|
|
0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
|
|
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
|
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409,
|
|
0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
|
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
|
|
0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
|
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268,
|
|
0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0,
|
|
0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
|
|
0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
|
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
|
|
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703,
|
|
0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
|
|
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
|
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae,
|
|
0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
|
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
|
|
0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
|
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d,
|
|
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
|
|
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
|
|
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
|
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
|
};
|
|
|
|
/* This function implements the Ethernet AUTODIN II CRC32 algorithm. */
|
|
uint
|
|
d_r_crc32(const char *buf, const uint len)
|
|
{
|
|
uint i;
|
|
uint crc = 0xFFFFFFFF;
|
|
|
|
for (i = 0; i < len; i++)
|
|
crc = (crc >> 8) ^ crctab[(crc ^ buf[i]) & 0xFF];
|
|
|
|
return crc;
|
|
}
|
|
|
|
/* MD5 is used for persistent and process-shared caches, process_control, and
|
|
* ASLR persistent sharing. The definition of MD5 below has a public
|
|
* license; source: http://stuff.mit.edu/afs/sipb/user/kenta/lj/clive/clive-0.4.5/
|
|
*/
|
|
/*----------------------------------------------------------------------------*/
|
|
/* This code implements the MD5 message-digest algorithm.
|
|
* The algorithm is due to Ron Rivest. This code was
|
|
* written by Colin Plumb in 1993, no copyright is claimed.
|
|
* This code is in the public domain; do with it what you wish.
|
|
*
|
|
* Equivalent code is available from RSA Data Security, Inc.
|
|
* This code has been tested against that, and is equivalent,
|
|
* except that you don't need to include two pages of legalese
|
|
* with every copy.
|
|
*
|
|
* To compute the message digest of a chunk of bytes, declare an
|
|
* MD5Context structure, pass it to d_r_md5_init, call d_r_md5_update as
|
|
* needed on buffers full of bytes, and then call d_r_md5_final, which
|
|
* will fill a supplied 16-byte array with the digest.
|
|
*/
|
|
static void
|
|
MD5Transform(uint32 state[4], const unsigned char block[MD5_BLOCK_LENGTH]);
|
|
|
|
#define PUT_64BIT_LE(cp, value) \
|
|
do { \
|
|
(cp)[7] = (unsigned char)((value) >> 56); \
|
|
(cp)[6] = (unsigned char)((value) >> 48); \
|
|
(cp)[5] = (unsigned char)((value) >> 40); \
|
|
(cp)[4] = (unsigned char)((value) >> 32); \
|
|
(cp)[3] = (unsigned char)((value) >> 24); \
|
|
(cp)[2] = (unsigned char)((value) >> 16); \
|
|
(cp)[1] = (unsigned char)((value) >> 8); \
|
|
(cp)[0] = (unsigned char)(value); \
|
|
} while (0)
|
|
|
|
#define PUT_32BIT_LE(cp, value) \
|
|
do { \
|
|
(cp)[3] = (unsigned char)((value) >> 24); \
|
|
(cp)[2] = (unsigned char)((value) >> 16); \
|
|
(cp)[1] = (unsigned char)((value) >> 8); \
|
|
(cp)[0] = (unsigned char)(value); \
|
|
} while (0)
|
|
|
|
static unsigned char PADDING[MD5_BLOCK_LENGTH] = {
|
|
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
|
|
/*
|
|
* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
|
|
* initialization constants.
|
|
*/
|
|
void
|
|
d_r_md5_init(struct MD5Context *ctx)
|
|
{
|
|
ctx->count = 0;
|
|
ctx->state[0] = 0x67452301;
|
|
ctx->state[1] = 0xefcdab89;
|
|
ctx->state[2] = 0x98badcfe;
|
|
ctx->state[3] = 0x10325476;
|
|
}
|
|
|
|
/*
|
|
* Update context to reflect the concatenation of another buffer full
|
|
* of bytes.
|
|
*/
|
|
void
|
|
d_r_md5_update(struct MD5Context *ctx, const unsigned char *input, size_t len)
|
|
{
|
|
size_t have, need;
|
|
|
|
/* Check how many bytes we already have and how many more we need. */
|
|
have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1));
|
|
need = MD5_BLOCK_LENGTH - have;
|
|
|
|
/* Update bitcount */
|
|
ctx->count += (uint64)len << 3;
|
|
|
|
if (len >= need) {
|
|
if (have != 0) {
|
|
memcpy(ctx->buffer + have, input, need);
|
|
MD5Transform(ctx->state, ctx->buffer);
|
|
input += need;
|
|
len -= need;
|
|
have = 0;
|
|
}
|
|
|
|
/* Process data in MD5_BLOCK_LENGTH-byte chunks. */
|
|
while (len >= MD5_BLOCK_LENGTH) {
|
|
MD5Transform(ctx->state, input);
|
|
input += MD5_BLOCK_LENGTH;
|
|
len -= MD5_BLOCK_LENGTH;
|
|
}
|
|
}
|
|
|
|
/* Handle any remaining bytes of data. */
|
|
if (len != 0)
|
|
memcpy(ctx->buffer + have, input, len);
|
|
}
|
|
|
|
/*
|
|
* Pad pad to 64-byte boundary with the bit pattern
|
|
* 1 0* (64-bit count of bits processed, MSB-first)
|
|
*/
|
|
static void
|
|
MD5Pad(struct MD5Context *ctx)
|
|
{
|
|
unsigned char count[8];
|
|
size_t padlen;
|
|
|
|
/* Convert count to 8 bytes in little endian order. */
|
|
PUT_64BIT_LE(count, ctx->count);
|
|
|
|
/* Pad out to 56 mod 64. */
|
|
padlen = (size_t)(MD5_BLOCK_LENGTH - ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)));
|
|
if (padlen < 1 + 8)
|
|
padlen += MD5_BLOCK_LENGTH;
|
|
d_r_md5_update(ctx, PADDING, padlen - 8); /* padlen - 8 <= 64 */
|
|
d_r_md5_update(ctx, count, 8);
|
|
}
|
|
|
|
/*
|
|
* Final wrapup--call MD5Pad, fill in digest and zero out ctx.
|
|
*/
|
|
void
|
|
d_r_md5_final(unsigned char digest[MD5_RAW_BYTES], struct MD5Context *ctx)
|
|
{
|
|
int i;
|
|
|
|
MD5Pad(ctx);
|
|
if (digest != NULL) {
|
|
for (i = 0; i < 4; i++)
|
|
PUT_32BIT_LE(digest + i * 4, ctx->state[i]);
|
|
}
|
|
memset(ctx, 0, sizeof(*ctx)); /* in case it's sensitive */
|
|
}
|
|
|
|
/* The four core functions - F1 is optimized somewhat */
|
|
|
|
/* #define F1(x, y, z) (x & y | ~x & z) */
|
|
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
|
#define F2(x, y, z) F1(z, x, y)
|
|
#define F3(x, y, z) (x ^ y ^ z)
|
|
#define F4(x, y, z) (y ^ (x | ~z))
|
|
|
|
/* This is the central step in the MD5 algorithm. */
|
|
#define MD5STEP(f, w, x, y, z, data, s) \
|
|
(w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
|
|
|
|
/*
|
|
* The core of the MD5 algorithm, this alters an existing MD5 hash to
|
|
* reflect the addition of 16 longwords of new data. d_r_md5_update blocks
|
|
* the data and converts bytes into longwords for this routine.
|
|
*/
|
|
static void
|
|
MD5Transform(uint32 state[4], const unsigned char block[MD5_BLOCK_LENGTH])
|
|
{
|
|
uint32 a, b, c, d, in[MD5_BLOCK_LENGTH / 4];
|
|
|
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
memcpy(in, block, sizeof(in));
|
|
#else
|
|
for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) {
|
|
in[a] =
|
|
(uint32)((uint32)(block[a * 4 + 0]) | (uint32)(block[a * 4 + 1]) << 8 |
|
|
(uint32)(block[a * 4 + 2]) << 16 | (uint32)(block[a * 4 + 3]) << 24);
|
|
}
|
|
#endif
|
|
|
|
a = state[0];
|
|
b = state[1];
|
|
c = state[2];
|
|
d = state[3];
|
|
|
|
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
|
|
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
|
|
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
|
|
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
|
|
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
|
|
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
|
|
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
|
|
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
|
|
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
|
|
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
|
|
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
|
|
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
|
|
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
|
|
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
|
|
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
|
|
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
|
|
|
|
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
|
|
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
|
|
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
|
|
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
|
|
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
|
|
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
|
|
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
|
|
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
|
|
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
|
|
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
|
|
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
|
|
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
|
|
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
|
|
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
|
|
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
|
|
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
|
|
|
|
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
|
|
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
|
|
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
|
|
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
|
|
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
|
|
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
|
|
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
|
|
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
|
|
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
|
|
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
|
|
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
|
|
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
|
|
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
|
|
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
|
|
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
|
|
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
|
|
|
|
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
|
|
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
|
|
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
|
|
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
|
|
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
|
|
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
|
|
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
|
|
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
|
|
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
|
|
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
|
|
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
|
|
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
|
|
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
|
|
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
|
|
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
|
|
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
|
|
|
|
state[0] += a;
|
|
state[1] += b;
|
|
state[2] += c;
|
|
state[3] += d;
|
|
}
|
|
#undef F1
|
|
#undef F2
|
|
#undef F3
|
|
#undef F4
|
|
#undef MD5STEP
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
bool
|
|
module_digests_equal(const module_digest_t *calculated_digest,
|
|
const module_digest_t *matching_digest, bool check_short,
|
|
bool check_full)
|
|
{
|
|
bool match = true;
|
|
if (check_short) {
|
|
match = match &&
|
|
md5_digests_equal(calculated_digest->short_MD5, matching_digest->short_MD5);
|
|
}
|
|
if (check_full) {
|
|
match = match &&
|
|
md5_digests_equal(calculated_digest->full_MD5, matching_digest->full_MD5);
|
|
}
|
|
return match;
|
|
}
|
|
|
|
/* Reads the full file, returns it in a buffer and sets buf_len to the size
|
|
* of the buffer allocated on the specified heap, if successful. Returns NULL
|
|
* and sets buf_len to 0 on failure. Defined when fixing case 8187.
|
|
*/
|
|
char *
|
|
read_entire_file(const char *file, size_t *buf_len /* OUT */ HEAPACCT(which_heap_t heap))
|
|
{
|
|
ssize_t bytes_read;
|
|
file_t fd = INVALID_FILE;
|
|
char *buf = NULL;
|
|
uint64 buf_len64 = 0;
|
|
|
|
/* No point in reading the file if the length can't be returned - the caller
|
|
* won't be able to free the buffer without the size.
|
|
*/
|
|
if (file == NULL || buf_len == NULL)
|
|
return NULL;
|
|
|
|
*buf_len = 0;
|
|
|
|
fd = os_open((char *)file, OS_OPEN_READ);
|
|
if (fd == INVALID_FILE)
|
|
return NULL;
|
|
|
|
if (!os_get_file_size(file, &buf_len64)) {
|
|
os_close(fd);
|
|
return NULL;
|
|
}
|
|
ASSERT_TRUNCATE(*buf_len, uint, buf_len64);
|
|
|
|
/* Though only 1 byte is needed for the \0 at the end of the buffer,
|
|
* 4 may be allocated to work around case 8048. XXX: remove
|
|
* alignment after case is resolved.
|
|
*/
|
|
*buf_len = (uint)ALIGN_FORWARD((buf_len64 + 1), 4);
|
|
buf = (char *)heap_alloc(GLOBAL_DCONTEXT, *buf_len HEAPACCT(heap));
|
|
bytes_read = os_read(fd, buf, *buf_len);
|
|
if (bytes_read <= 0) {
|
|
heap_free(GLOBAL_DCONTEXT, buf, *buf_len HEAPACCT(heap));
|
|
os_close(fd);
|
|
return NULL;
|
|
}
|
|
ASSERT(CHECK_TRUNCATE_TYPE_uint(bytes_read));
|
|
ASSERT((uint)bytes_read != *buf_len && "buffer too small");
|
|
ASSERT((uint)bytes_read < *buf_len); /* use MIN below just to be safe */
|
|
buf[MIN((uint)bytes_read, *buf_len - 1)] = 0;
|
|
|
|
os_close(fd);
|
|
return buf;
|
|
}
|
|
|
|
/* returns false if we are too low on disk to create a file of desired size */
|
|
bool
|
|
check_low_disk_threshold(file_t f, uint64 new_file_size)
|
|
{
|
|
/* XXX: we only use UserAvailable to find the minimum expressed
|
|
* as absolute bytes to leave available. In addition we could
|
|
* also have percentage limits, where minimum available should be
|
|
* based on TotalQuotaBytes, and maximum cache size on
|
|
* TotalVolumeBytes.
|
|
*/
|
|
uint64 user_available_bytes;
|
|
/* XXX: does this work for compressed volumes? */
|
|
bool ok = os_get_disk_free_space(f, &user_available_bytes, NULL, NULL);
|
|
if (ok) {
|
|
LOG(THREAD_GET, LOG_SYSCALLS | LOG_THREADS, 2,
|
|
"available disk space quota %dMB\n", user_available_bytes / 1024 / 1024);
|
|
|
|
/* note that the actual needed bytes are in fact aligned to a
|
|
* cluster, so this is somewhat imprecise */
|
|
ok = (user_available_bytes > new_file_size) &&
|
|
(user_available_bytes - new_file_size) > DYNAMO_OPTION(min_free_disk);
|
|
if (!ok) {
|
|
/* XXX: notify the customer that they are low on disk space? */
|
|
SYSLOG_INTERNAL_WARNING_ONCE(
|
|
"reached minimal free disk space limit,"
|
|
" available " UINT64_FORMAT_STRING "MB, limit %dMB, "
|
|
"asking for " UINT64_FORMAT_STRING "KB",
|
|
user_available_bytes / 1024 / 1024,
|
|
DYNAMO_OPTION(min_free_disk) / 1024 / 1024, new_file_size / 1024);
|
|
/* ONCE, even though later we may succeed and start failing again */
|
|
}
|
|
} else {
|
|
/* impersonated thread may not have rights to even query */
|
|
/* or we have an invalid path */
|
|
/* do nothing */
|
|
LOG(THREAD_GET, LOG_SYSCALLS | LOG_THREADS, 2,
|
|
"unable to retrieve available disk space\n");
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
#ifdef PROCESS_CONTROL /* currently used for only for this; need not be so */
|
|
/* Note: Reading the full file in one shot will cause committed memory and
|
|
* wss to shoot up. Even if this memory is freed, only wss will come down.
|
|
* While this may be ok for fcache mode, it is probably not for hotp_only mode,
|
|
* and definitely not for thin_client mode. The main use of thin_client today
|
|
* is process_control, which means that MD5 will be computed in thin_client
|
|
* mode.
|
|
*
|
|
* The options for this memory issue are to mmap the memory and unmap it after
|
|
* use rather than use the heap or to use a moderate sized buffer and read the
|
|
* file in chunks, which is wat I have chosen. If startup performance becomes
|
|
* a problem because of this play with bigger pages or just use mmap and munmap.
|
|
*
|
|
* Measurements on my laptop on a total of 5076 executables showed,
|
|
* Average size : 233 kb
|
|
* Standard deviation : 684 kb
|
|
* Median size : 68 kb!
|
|
* 80% percentile : 205 kb
|
|
* 90% percentile : 469 kb
|
|
* So a buffer of 16 kb seems reasonable, even though the data is not based
|
|
* on frequency of usage.
|
|
*
|
|
* XXX: use nt_map_view_of_section with SEC_MAPPED && !SEC_IMAGE.
|
|
*/
|
|
# define MD5_FILE_READ_BUF_SIZE (4 * PAGE_SIZE)
|
|
|
|
/* Reads 'file', computes MD5 hash for it, which is returned in hash_buf
|
|
* when successful and true is returned. On failure false is returned and
|
|
* hash_buf contents invalid.
|
|
* Note: Reads in file in 16k chunks to avoid private/committed memory
|
|
* increase.
|
|
*/
|
|
bool
|
|
get_md5_for_file(const char *file, char *hash_buf /* OUT */)
|
|
{
|
|
ssize_t bytes_read;
|
|
int i;
|
|
file_t fd;
|
|
char *file_buf;
|
|
struct MD5Context md5_cxt;
|
|
unsigned char md5_buf[MD5_STRING_LENGTH / 2];
|
|
|
|
if (file == NULL || hash_buf == NULL)
|
|
return false;
|
|
|
|
fd = os_open((char *)file, OS_OPEN_READ);
|
|
if (fd == INVALID_FILE)
|
|
return false;
|
|
|
|
d_r_md5_init(&md5_cxt);
|
|
file_buf =
|
|
(char *)heap_alloc(GLOBAL_DCONTEXT, MD5_FILE_READ_BUF_SIZE HEAPACCT(ACCT_OTHER));
|
|
while ((bytes_read = os_read(fd, file_buf, MD5_FILE_READ_BUF_SIZE)) > 0) {
|
|
ASSERT(CHECK_TRUNCATE_TYPE_uint(bytes_read));
|
|
d_r_md5_update(&md5_cxt, (byte *)file_buf, (uint)bytes_read);
|
|
}
|
|
d_r_md5_final(md5_buf, &md5_cxt);
|
|
|
|
/* Convert 16-byte signature into 32-byte string, which is how MD5 is
|
|
* usually printed/used. n is 3, 2 for chars & 1 for the '\0';
|
|
*/
|
|
for (i = 0; i < BUFFER_SIZE_ELEMENTS(md5_buf); i++)
|
|
snprintf(hash_buf + (i * 2), 3, "%02X", md5_buf[i]);
|
|
|
|
/* Just be safe & terminate the buffer; assuming it has 33 chars! */
|
|
hash_buf[MD5_STRING_LENGTH] = '\0';
|
|
|
|
heap_free(GLOBAL_DCONTEXT, file_buf, MD5_FILE_READ_BUF_SIZE HEAPACCT(ACCT_OTHER));
|
|
os_close(fd);
|
|
return true;
|
|
}
|
|
#endif /* PROCESS_CONTROL */
|
|
|
|
/* Computes and caches MD5 for the application if it isn't there; returns it. */
|
|
/* Note: this function isn't under PROCESS_CONTROL even though that is what
|
|
* uses this because md5 for the executable may be needed in future.
|
|
* Also, this avoids having to define 2 types of process start events.
|
|
* Further, if we decide to remove process control in future leaving the
|
|
* md5 in the start event will prevent needless backward compatibility
|
|
* problems caused by removing md5 from it.
|
|
*/
|
|
const char *
|
|
get_application_md5(void)
|
|
{
|
|
/* Though exe_md5 is used in the process start event, it is currently set
|
|
* only by process_control. BTW, 1 for the terminating '\0'.
|
|
*/
|
|
static char exe_md5[MD5_STRING_LENGTH + 1] = { 0 };
|
|
|
|
#ifdef PROCESS_CONTROL
|
|
/* MD5 is computed only if process control is turned on, otherwise the cost
|
|
* isn't paid (roughly 10ms for a 350kb exe), i.e. "" is returned.
|
|
*/
|
|
if (exe_md5[0] == '\0') {
|
|
if (IS_PROCESS_CONTROL_ON()) {
|
|
DEBUG_DECLARE(bool res;)
|
|
# ifdef WINDOWS
|
|
/* XXX - inefficient, we use stack buffer here to convert wchar to char,
|
|
* and later we'll use another stack buffer to convert char to wchar to
|
|
* open the file. That said this is only done once (either at startup or
|
|
* for process_control nudge [app_stack]) so as long as the stack doesn't
|
|
* overflow it doesn't really matter. */
|
|
char exe_name[MAXIMUM_PATH];
|
|
snprintf(exe_name, BUFFER_SIZE_ELEMENTS(exe_name), "%ls",
|
|
get_own_unqualified_name());
|
|
NULL_TERMINATE_BUFFER(exe_name);
|
|
# else
|
|
/* XXX - will need to strip out qualifications if we add those to Linux */
|
|
const char *exe_name = get_application_name();
|
|
# endif
|
|
|
|
/* Change protection to make .data writable; this is a nop at init
|
|
* time - which is the most common case. For the nudge case we
|
|
* will pay full price of data protection if MD5 hasn't been
|
|
* computed yet; this is rare, so it is ok.
|
|
*/
|
|
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
DEBUG_DECLARE(res =) get_md5_for_file(exe_name, exe_md5);
|
|
ASSERT(res && strlen(exe_md5) == MD5_STRING_LENGTH);
|
|
NULL_TERMINATE_BUFFER(exe_md5); /* just be safe */
|
|
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT); /* restore protection */
|
|
}
|
|
} else {
|
|
/* If it isn't null then it must be a full MD5 and process control
|
|
* should be turned on.
|
|
*/
|
|
ASSERT(strlen(exe_md5) == MD5_STRING_LENGTH);
|
|
ASSERT(IS_PROCESS_CONTROL_ON());
|
|
}
|
|
#else
|
|
/* If there is no process control, for now, app MD5 isn't computed. */
|
|
ASSERT(exe_md5[0] == '\0');
|
|
#endif
|
|
return exe_md5;
|
|
}
|
|
|
|
/* Producing a single MD5 digest for a given readable memory region.
|
|
*
|
|
* An empty region is not expected, though legal, since produces a
|
|
* constant value.
|
|
*/
|
|
void
|
|
get_md5_for_region(const byte *region_start, uint len,
|
|
unsigned char digest[MD5_RAW_BYTES] /* OUT */)
|
|
{
|
|
struct MD5Context md5_cxt;
|
|
d_r_md5_init(&md5_cxt);
|
|
ASSERT(region_start != NULL);
|
|
ASSERT_CURIOSITY(len != 0);
|
|
|
|
if (region_start != NULL && len != 0)
|
|
d_r_md5_update(&md5_cxt, region_start, len);
|
|
d_r_md5_final(digest, &md5_cxt);
|
|
ASSERT_NOT_TESTED();
|
|
}
|
|
|
|
bool
|
|
md5_digests_equal(const byte digest1[MD5_RAW_BYTES], const byte digest2[MD5_RAW_BYTES])
|
|
{
|
|
return (memcmp(digest1, digest2, MD5_RAW_BYTES) == 0);
|
|
}
|
|
|
|
/* calculates intersection of two regions defined as open ended intervals
|
|
* [region1_start, region1_start + region1_len) \intersect
|
|
* [region2_start, region2_start + region2_len)
|
|
*
|
|
* intersection_len is set to 0 if the regions do not overlap
|
|
* otherwise returns the intersecting region
|
|
* [intersection_start, intersection_start + intersection_len)
|
|
*/
|
|
void
|
|
region_intersection(app_pc *intersection_start /* OUT */,
|
|
size_t *intersection_len /* OUT */, const app_pc region1_start,
|
|
size_t region1_len, const app_pc region2_start, size_t region2_len)
|
|
{
|
|
/* intersection */
|
|
app_pc intersection_end =
|
|
MIN(region1_start + region1_len, region2_start + region2_len);
|
|
ASSERT(intersection_start != NULL);
|
|
ASSERT(intersection_len != NULL);
|
|
*intersection_start = MAX(region1_start, region2_start);
|
|
|
|
/* set length as long as result is a proper intersecting region,
|
|
* max(0, intersection_end - intersection_start) if signed
|
|
*/
|
|
*intersection_len = (intersection_end > *intersection_start)
|
|
? (intersection_end - *intersection_start)
|
|
: 0;
|
|
}
|
|
/***************************************************************************/
|
|
#ifdef CALL_PROFILE
|
|
|
|
typedef struct _profile_callers_t {
|
|
app_pc caller[MAX_CALL_PROFILE_DEPTH];
|
|
uint count;
|
|
struct _profile_callers_t *next;
|
|
} profile_callers_t;
|
|
|
|
/* debug-only so neverprot */
|
|
DECLARE_NEVERPROT_VAR(static profile_callers_t *profcalls, NULL);
|
|
DECLARE_CXTSWPROT_VAR(static mutex_t profile_callers_lock,
|
|
INIT_LOCK_FREE(profile_callers_lock));
|
|
|
|
/* Usage:
|
|
* Simply place a profile_callers() call in the routine you wish to profile.
|
|
* You MUST build without optimizations to enable call stack walking.
|
|
* Results go to a separate log file and are dumped only at exit.
|
|
* XXX: combine w/ a generalized mutex_collect_callstack()?
|
|
*/
|
|
void
|
|
profile_callers(void)
|
|
{
|
|
profile_callers_t *entry;
|
|
uint *pc;
|
|
uint num = 0;
|
|
app_pc our_ebp = 0;
|
|
app_pc caller[MAX_CALL_PROFILE_DEPTH];
|
|
app_pc saferead[2];
|
|
if (DYNAMO_OPTION(prof_caller) == 0 || dynamo_exited_and_cleaned /*no heap*/)
|
|
return;
|
|
ASSERT(DYNAMO_OPTION(prof_caller) <= MAX_CALL_PROFILE_DEPTH);
|
|
GET_FRAME_PTR(our_ebp);
|
|
memset(caller, 0, sizeof(caller));
|
|
pc = (uint *)our_ebp;
|
|
/* XXX: mutex_collect_callstack() assumes caller addresses are in
|
|
* DR and thus are safe to read, but checks for dstack, etc.
|
|
* Should combine the two into a general routine.
|
|
*/
|
|
while (pc != NULL && d_r_safe_read((byte *)pc, sizeof(saferead), saferead)) {
|
|
caller[num] = saferead[1];
|
|
num++;
|
|
/* yes I've seen weird recursive cases before */
|
|
if (pc == (uint *)saferead[0] || num >= DYNAMO_OPTION(prof_caller))
|
|
break;
|
|
pc = (uint *)saferead[0];
|
|
}
|
|
/* Assumption: there aren't many unique callstacks being profiled, so a
|
|
* linear search is sufficient!
|
|
* XXX: make this more performant if necessary
|
|
*/
|
|
for (entry = profcalls; entry != NULL; entry = entry->next) {
|
|
bool match = true;
|
|
for (num = 0; num < DYNAMO_OPTION(prof_caller); num++) {
|
|
if (entry->caller[num] != caller[num]) {
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match) {
|
|
entry->count++;
|
|
break;
|
|
}
|
|
}
|
|
if (entry == NULL) {
|
|
entry = global_heap_alloc(sizeof(profile_callers_t) HEAPACCT(ACCT_OTHER));
|
|
memcpy(entry->caller, caller, sizeof(caller));
|
|
entry->count = 1;
|
|
d_r_mutex_lock(&profile_callers_lock);
|
|
entry->next = profcalls;
|
|
profcalls = entry;
|
|
d_r_mutex_unlock(&profile_callers_lock);
|
|
}
|
|
}
|
|
|
|
void
|
|
profile_callers_exit(void)
|
|
{
|
|
profile_callers_t *entry, *next;
|
|
file_t file;
|
|
if (DYNAMO_OPTION(prof_caller) > 0) {
|
|
d_r_mutex_lock(&profile_callers_lock);
|
|
file = open_log_file("callprof", NULL, 0);
|
|
for (entry = profcalls; entry != NULL; entry = next) {
|
|
uint num;
|
|
next = entry->next;
|
|
for (num = 0; num < DYNAMO_OPTION(prof_caller); num++) {
|
|
print_file(file, PFX " ", entry->caller[num]);
|
|
}
|
|
print_file(file, "%d\n", entry->count);
|
|
global_heap_free(entry, sizeof(profile_callers_t) HEAPACCT(ACCT_OTHER));
|
|
}
|
|
close_log_file(file);
|
|
profcalls = NULL;
|
|
d_r_mutex_unlock(&profile_callers_lock);
|
|
}
|
|
DELETE_LOCK(profile_callers_lock);
|
|
}
|
|
|
|
#endif /* CALL_PROFILE */
|
|
|
|
#ifdef STANDALONE_UNIT_TEST
|
|
|
|
# ifdef printf
|
|
# undef printf
|
|
# endif
|
|
# define printf(...) print_file(STDERR, __VA_ARGS__)
|
|
|
|
static void
|
|
test_date_conversion_millis(uint64 millis)
|
|
{
|
|
dr_time_t dr_time;
|
|
uint64 res;
|
|
convert_millis_to_date(millis, &dr_time);
|
|
convert_date_to_millis(&dr_time, &res);
|
|
if (res != millis ||
|
|
dr_time.day_of_week != (millis / (24 * 60 * 60 * 1000) + 1) % 7 ||
|
|
dr_time.month < 1 || dr_time.month > 12 || dr_time.day < 1 || dr_time.day > 31 ||
|
|
dr_time.hour > 23 || dr_time.minute > 59 || dr_time.second > 59 ||
|
|
dr_time.milliseconds > 999) {
|
|
printf("FAIL : test_date_conversion_millis\n");
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
test_date_conversion_day(dr_time_t *dr_time)
|
|
{
|
|
uint64 millis;
|
|
dr_time_t res;
|
|
convert_date_to_millis(dr_time, &millis);
|
|
convert_millis_to_date(millis, &res);
|
|
if (res.year != dr_time->year || res.month != dr_time->month ||
|
|
res.day != dr_time->day || res.hour != dr_time->hour ||
|
|
res.minute != dr_time->minute || res.second != dr_time->second ||
|
|
res.milliseconds != dr_time->milliseconds) {
|
|
printf("FAIL : test_date_conversion_day\n");
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
/* Tests for double_print(), divide_uint64_print(), and date routines. */
|
|
void
|
|
unit_test_utils(void)
|
|
{
|
|
char buf[128];
|
|
uint c, d;
|
|
const char *s;
|
|
int t;
|
|
dr_time_t dr_time;
|
|
|
|
# define DO_TEST(a, b, p, percent, fmt, result) \
|
|
divide_uint64_print(a, b, percent, p, &c, &d); \
|
|
snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), fmt, c, d); \
|
|
NULL_TERMINATE_BUFFER(buf); \
|
|
if (strcmp(buf, result) == 0) { \
|
|
printf("PASS\n"); \
|
|
} else { \
|
|
printf("FAIL : \"%s\" doesn't match \"%s\"\n", buf, result); \
|
|
exit(-1); \
|
|
}
|
|
|
|
DO_TEST(1, 20, 3, false, "%u.%.3u", "0.050");
|
|
DO_TEST(2, 5, 2, false, "%3u.%.2u", " 0.40");
|
|
DO_TEST(100, 7, 4, false, "%u.%.4u", "14.2857");
|
|
DO_TEST(475, 1000, 2, true, "%u.%.2u%%", "47.50%");
|
|
|
|
# undef DO_TEST
|
|
# define DO_TEST(a, p, fmt, result) \
|
|
double_print(a, p, &c, &d, &s); \
|
|
snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), fmt, s, c, d); \
|
|
NULL_TERMINATE_BUFFER(buf); \
|
|
if (strcmp(buf, result) == 0) { \
|
|
printf("PASS\n"); \
|
|
} else { \
|
|
printf("FAIL : \"%s\" doesn't match \"%s\"\n", buf, result); \
|
|
exit(-1); \
|
|
}
|
|
|
|
DO_TEST(-2.06, 3, "%s%u.%.3u", "-2.060");
|
|
DO_TEST(2.06, 4, "%s%u.%.4u", "2.0600");
|
|
DO_TEST(.0563, 2, "%s%u.%.2u", "0.05");
|
|
DO_TEST(-.0563, 2, "%s%u.%.2u", "-0.05");
|
|
DO_TEST(23.0456, 5, "%s%4u.%.5u", " 23.04560");
|
|
DO_TEST(-23.0456, 5, "%s%4u.%.5u", "- 23.04560");
|
|
|
|
# undef DO_TEST
|
|
|
|
EXPECT(BOOLS_MATCH(1, 1), true);
|
|
EXPECT(BOOLS_MATCH(1, 0), false);
|
|
EXPECT(BOOLS_MATCH(0, 1), false);
|
|
EXPECT(BOOLS_MATCH(0, 0), true);
|
|
EXPECT(BOOLS_MATCH(1, 2), true);
|
|
EXPECT(BOOLS_MATCH(2, 1), true);
|
|
EXPECT(BOOLS_MATCH(1, -1), true);
|
|
|
|
/* Test each millisecond in first and last 100 seconds. */
|
|
for (t = 0; t < 100000; t++) {
|
|
test_date_conversion_millis(t);
|
|
test_date_conversion_millis(-t - 1);
|
|
}
|
|
/* Test each second in first and last day and a bit. */
|
|
for (t = 0; t < 100000; t++) {
|
|
test_date_conversion_millis(t * 1000);
|
|
test_date_conversion_millis(-(t * 1000) - 1);
|
|
}
|
|
/* Test each day from 1601 to 2148. */
|
|
for (t = 0; t < 200000; t++)
|
|
test_date_conversion_millis((uint64)t * 24 * 60 * 60 * 1000);
|
|
/* Test the first of each month from 1601 to 99999. */
|
|
dr_time.day_of_week = 0; /* not checked */
|
|
dr_time.day = 1;
|
|
dr_time.hour = 0;
|
|
dr_time.minute = 0;
|
|
dr_time.second = 0;
|
|
dr_time.milliseconds = 0;
|
|
for (t = 0; t < (99999 - 1601) * 12; t++) {
|
|
dr_time.year = 1601 + t / 12;
|
|
dr_time.month = 1 + t % 12;
|
|
test_date_conversion_day(&dr_time);
|
|
}
|
|
}
|
|
|
|
# undef printf
|
|
|
|
#endif /* STANDALONE_UNIT_TEST */
|
|
|
|
char *
|
|
dr_strdup(const char *str HEAPACCT(which_heap_t which))
|
|
{
|
|
char *dup;
|
|
size_t str_len;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
str_len = strlen(str) + 1; /* Extra 1 char for the '\0' at the end. */
|
|
dup = (char *)heap_alloc(GLOBAL_DCONTEXT, str_len HEAPACCT(which));
|
|
strncpy(dup, str, str_len);
|
|
dup[str_len - 1] = '\0'; /* Being on the safe side. */
|
|
return dup;
|
|
}
|
|
|
|
#ifdef WINDOWS
|
|
/* Allocates a new char *(NOT a new wchar_t*) from a wchar_t* */
|
|
char *
|
|
dr_wstrdup(const wchar_t *str HEAPACCT(which_heap_t which))
|
|
{
|
|
char *dup;
|
|
ssize_t encode_len;
|
|
size_t str_len;
|
|
int res;
|
|
if (str == NULL)
|
|
return NULL;
|
|
/* XXX: should have a max length and truncate?
|
|
* I'm assuming we're using not directly on external inputs.
|
|
* If we do put in a max length, should do the same for dr_strdup.
|
|
*/
|
|
encode_len = utf16_to_utf8_size(str, 0 /*no max*/, NULL);
|
|
if (encode_len < 0)
|
|
str_len = 1;
|
|
else
|
|
str_len = encode_len + 1; /* Extra 1 char for the '\0' at the end. */
|
|
dup = (char *)heap_alloc(GLOBAL_DCONTEXT, str_len HEAPACCT(which));
|
|
if (encode_len >= 0) {
|
|
res = snprintf(dup, str_len, "%S", str);
|
|
if (res < 0 || (size_t)res < str_len - 1) {
|
|
ASSERT_NOT_REACHED();
|
|
if (res < 0)
|
|
dup[0] = '\0';
|
|
/* apparently for some versions of ntdll!_snprintf, if %S
|
|
* conversion hits a non-ASCII char it will write a NULL and
|
|
* snprintf will return -1 (that's the libc behavior) or the
|
|
* number of chars to that point. we don't want strlen to return
|
|
* fewer chars than we allocated so we fill it in (i#347).
|
|
*/
|
|
/* Xref i#347, though we shouldn't get here b/c utf16_to_utf8_size uses
|
|
* the same code. We fall back on filling with '?'.
|
|
*/
|
|
memset(dup + strlen(dup), '?', str_len - 1 - strlen(dup));
|
|
}
|
|
}
|
|
dup[str_len - 1] = '\0'; /* Being on the safe side. */
|
|
/* Ensure when we free we'll pass the same size (i#347) */
|
|
ASSERT(strlen(dup) == str_len - 1);
|
|
return dup;
|
|
}
|
|
#endif
|
|
|
|
/* Frees a char *string (NOT a wchar_t*) allocated via dr_strdup or
|
|
* dr_wstrdup that has not been modified since being copied!
|
|
*/
|
|
void
|
|
dr_strfree(const char *str HEAPACCT(which_heap_t which))
|
|
{
|
|
size_t str_len;
|
|
ASSERT_CURIOSITY(str != NULL);
|
|
if (str == NULL)
|
|
return;
|
|
str_len = strlen(str) + 1; /* Extra 1 char for the '\0' at the end. */
|
|
heap_free(GLOBAL_DCONTEXT, (void *)str, str_len HEAPACCT(which));
|
|
}
|
|
|
|
/* Merges two unsorted arrays, treating their elements as type void*
|
|
* (but will work on other types of same size as void*)
|
|
* as an intersection if intersect is true or as a union (removing
|
|
* duplicates) otherwise. Allocates a new array on dcontext's heap
|
|
* for the result and returns it and its size; if the new size is 0,
|
|
* does NOT allocate anything and points the new array at NULL.
|
|
*/
|
|
void
|
|
array_merge(dcontext_t *dcontext, bool intersect /* else union */, void **src1,
|
|
uint src1_num, void **src2, uint src2_num,
|
|
/*OUT*/ void ***dst, /*OUT*/ uint *dst_num HEAPACCT(which_heap_t which))
|
|
{
|
|
/* Two passes: one to find number of unique entries, and second to
|
|
* fill them in.
|
|
* XXX: if this routine is ever on a performance-critical path then
|
|
* we should switch to a temp hashtable and avoid this quadratic cost.
|
|
*/
|
|
uint num;
|
|
void **vec = NULL;
|
|
uint i, j;
|
|
DEBUG_DECLARE(uint res;)
|
|
ASSERT(dst != NULL && dst_num != NULL);
|
|
ASSERT(src1 != NULL || src1_num == 0);
|
|
ASSERT(src2 != NULL || src2_num == 0);
|
|
if (src1 == NULL || src2 == NULL || dst == NULL) /* paranoid */
|
|
return; /* XXX: return a bool? */
|
|
if (src1_num == 0 && src2_num == 0) {
|
|
*dst = NULL;
|
|
*dst_num = 0;
|
|
return;
|
|
}
|
|
num = intersect ? 0 : src1_num;
|
|
for (i = 0; i < src2_num; i++) {
|
|
for (j = 0; j < src1_num; j++) {
|
|
if (src2[i] == src1[j]) {
|
|
if (intersect)
|
|
num++;
|
|
break;
|
|
}
|
|
}
|
|
if (!intersect && j == src1_num)
|
|
num++;
|
|
}
|
|
if (num > 0) {
|
|
vec = HEAP_ARRAY_ALLOC(dcontext, void *, num, which, PROTECTED);
|
|
if (!intersect)
|
|
memcpy(vec, src1, sizeof(void *) * src1_num);
|
|
DODEBUG(res = num;);
|
|
num = intersect ? 0 : src1_num;
|
|
for (i = 0; i < src2_num; i++) {
|
|
for (j = 0; j < src1_num; j++) {
|
|
if (src2[i] == src1[j]) {
|
|
if (intersect)
|
|
vec[num++] = src2[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!intersect && j == src1_num)
|
|
vec[num++] = src2[i];
|
|
}
|
|
ASSERT(num == res);
|
|
} else {
|
|
ASSERT(intersect);
|
|
ASSERT(vec == NULL);
|
|
}
|
|
*dst = vec;
|
|
*dst_num = num;
|
|
}
|
|
|
|
bool
|
|
stats_get_snapshot(dr_stats_t *drstats)
|
|
{
|
|
if (!GLOBAL_STATS_ON())
|
|
return false;
|
|
CLIENT_ASSERT(drstats != NULL, "Expected non-null value for parameter drstats.");
|
|
drstats->basic_block_count = GLOBAL_STAT(num_bbs);
|
|
if (drstats->size <= offsetof(dr_stats_t, peak_num_threads)) {
|
|
return true;
|
|
}
|
|
drstats->peak_num_threads = GLOBAL_STAT(peak_num_threads);
|
|
drstats->num_threads_created = GLOBAL_STAT(num_threads_created);
|
|
if (drstats->size <= offsetof(dr_stats_t, synchs_not_at_safe_spot))
|
|
return true;
|
|
drstats->synchs_not_at_safe_spot = GLOBAL_STAT(synchs_not_at_safe_spot);
|
|
if (drstats->size <= offsetof(dr_stats_t, peak_vmm_blocks_unreach_heap))
|
|
return true;
|
|
/* These fields were added all at once. */
|
|
drstats->peak_vmm_blocks_unreach_heap = GLOBAL_STAT(peak_vmm_blocks_unreach_heap);
|
|
drstats->peak_vmm_blocks_unreach_stack = GLOBAL_STAT(peak_vmm_blocks_unreach_stack);
|
|
drstats->peak_vmm_blocks_unreach_special_heap =
|
|
GLOBAL_STAT(peak_vmm_blocks_unreach_special_heap);
|
|
drstats->peak_vmm_blocks_unreach_special_mmap =
|
|
GLOBAL_STAT(peak_vmm_blocks_unreach_special_mmap);
|
|
drstats->peak_vmm_blocks_reach_heap = GLOBAL_STAT(peak_vmm_blocks_reach_heap);
|
|
drstats->peak_vmm_blocks_reach_cache = GLOBAL_STAT(peak_vmm_blocks_reach_cache);
|
|
drstats->peak_vmm_blocks_reach_special_heap =
|
|
GLOBAL_STAT(peak_vmm_blocks_reach_special_heap);
|
|
drstats->peak_vmm_blocks_reach_special_mmap =
|
|
GLOBAL_STAT(peak_vmm_blocks_reach_special_mmap);
|
|
if (drstats->size <= offsetof(dr_stats_t, num_native_signals))
|
|
return true;
|
|
#ifdef UNIX
|
|
drstats->num_native_signals = GLOBAL_STAT(num_native_signals);
|
|
#else
|
|
drstats->num_native_signals = 0;
|
|
#endif
|
|
if (drstats->size <= offsetof(dr_stats_t, num_cache_exits))
|
|
return true;
|
|
drstats->num_cache_exits = GLOBAL_STAT(num_exits);
|
|
return true;
|
|
}
|