* Copyright (c) 2010-2022 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.
*/
* utils.c - miscellaneous utilities
*/
#include "globals.h"
#include "configure_defines.h"
#include "utils.h"
#include "module_shared.h"
#include <math.h>
#ifdef PROCESS_CONTROL
# include "moduledb.h"
#endif
#ifdef UNIX
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <errno.h>
#else
# include <errno.h>
# include "events.h"
#endif
#ifdef SHARING_STUDY
# include "fragment.h"
#endif
#ifdef DEBUG
# include "fcache.h"
# include "synch.h"
#endif
#include <stdarg.h>
#include <stddef.h>
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
* performs some cleanup and then calls os_terminate
*/
static void
soft_terminate()
{
#ifdef SIDELINE
if (dynamo_options.sideline) {
sideline_stop();
sideline_exit();
}
#endif
DOSTATS({
if (d_r_stats != NULL)
GLOBAL_STAT(exited) = true;
});
os_terminate(NULL, TERMINATE_PROCESS);
}
#if defined(INTERNAL) || defined(DEBUG)
* 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) {
SYSLOG_INTERNAL_WARNING("Ignoring assert %s %s", assert_stmt, expr);
}
return ignore;
}
* report_dynamorio_problem prevent DO_ONCE itself
*/
DECLARE_FREQPROT_VAR(static bool do_once_internal_error, false);
void
d_r_internal_error(const char *file, int line, const char *expr)
{
* xref PR 303817 */
* we'll reconstruct file and line # here
*/
if (!IS_STRING_OPTION_EMPTY(ignore_assert_list)) {
char assert_stmt[MAXIMUM_PATH];
* infinte loop if any asserts fail. Not very safe to set and
* unset a static bool either since we'll be noisy.
*/
* 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;
}
if (do_once_internal_error)
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
void
external_error(const char *file, int line, const char *msg)
{
DO_ONCE({
* 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();
}
#ifdef DEADLOCK_AVOIDANCE
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;
};
DECLARE_CXTSWPROT_VAR(mutex_t outermost_lock, INIT_LOCK_FREE(outermost_lock));
DECLARE_CXTSWPROT_VAR(mutex_t innermost_lock, INIT_LOCK_FREE(innermost_lock));
* .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));
# 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
# 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)
{
* 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++) {
* only references to callstack */
LOG(GLOBAL, LOG_THREADS, 1, " " PFX "\n", lock->callstack[i]);
}
# endif
}
# endif
void
dump_owned_locks(dcontext_t *dcontext)
{
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;
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;
cur_lock = dcontext->thread_owned_locks->last_lock;
return (cur_lock == lock && cur_lock->prev_owned_lock == &outermost_lock);
}
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;
cur_lock = dcontext->thread_owned_locks->last_lock;
return (cur_lock == lock1 && cur_lock->prev_owned_lock == lock2 &&
lock2->prev_owned_lock == &outermost_lock);
}
* (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;
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)));
}
void
dump_process_locks()
{
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");
d_r_mutex_lock(&innermost_lock);
cur_lock = &innermost_lock;
do {
depth++;
LOG(GLOBAL, LOG_STATS,
(cur_lock->count_times_contended ? 1U : 2U),
DUMP_LOCK_INFO_ARGS(depth, cur_lock, cur_lock->next_process_lock));
DOLOG((cur_lock->count_times_contended ? 2U : 3U),
LOG_THREADS, {
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()
{
mutex_t *cur_lock;
uint forgotten = 0;
uint ignored = 0;
* 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;
* 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);
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)
IF_UNIX(|| cur_lock->rank == LOCK_RANK(detached_sigact_lock)))) {
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));
new_thread_locks->last_lock = &outermost_lock;
dcontext->thread_owned_locks = new_thread_locks;
}
void
locks_thread_exit(dcontext_t *dcontext)
{
if (dcontext->thread_owned_locks) {
thread_locks_t *old_thread_locks = dcontext->thread_owned_locks;
CHECK: is this true for detaching */
ASSERT(dcontext->thread_owned_locks->last_lock == &thread_initexit_lock ||
dcontext->thread_owned_locks->last_lock == &outermost_lock
|| dcontext->thread_owned_locks->last_lock->rank == dr_client_mutex_rank);
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)
{
d_r_mutex_lock(&innermost_lock);
if (lock->prev_process_lock != NULL) {
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'");
d_r_mutex_lock(&innermost_lock);
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;
d_r_mutex_unlock(&innermost_lock);
}
# ifdef MUTEX_CALLSTACK
static void
mutex_collect_callstack(mutex_t *lock)
{
uint max_depth = INTERNAL_OPTION(mutex_callstack);
uint depth = 0;
uint skip = 2;
byte *fp;
dcontext_t *dcontext = get_thread_private_dcontext();
GET_FRAME_PTR(fp);
while (depth < max_depth && (is_on_initstack(fp) || is_on_dstack(dcontext, fp)) &&
* 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
enum { LOCK_NOT_OWNABLE, LOCK_OWNABLE };
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++;
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();
}
if (lock->prev_process_lock == NULL && lock != &innermost_lock) {
add_process_lock(lock);
}
* 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) {
* 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 && !both_client) {
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);
if (TEST(DUMPCORE_DEADLOCK, DYNAMO_OPTION(dumpcore_mask)))
os_dump_core("rank order violation");
}
ASSERT((dcontext->thread_owned_locks->last_lock->rank < lock->rank ||
first_client
|| 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 {
* 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++;
}
}
void
deadlock_avoidance_unlock(mutex_t *lock, bool ownable)
{
if (INTERNAL_OPTION(simulate_contention)) {
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
* 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); });
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)
# define DEADLOCK_AVOIDANCE_UNLOCK(lock, ownable)
#endif
#ifdef UNIX
void
d_r_mutex_fork_reset(mutex_t *mutex)
{
* 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;
DECLARE_FREQPROT_VAR(static uint random_seed, 1234);
DEBUG_DECLARE(static uint initial_random_seed;)
void
utils_init()
{
also see comment on spinlock_count_on_SMP in optionsx.h */
spinlock_count = (get_num_processors() - 1) * DYNAMO_OPTION(spinlock_count_on_SMP);
* (of course, thread scheduling may still affect requests) */
random_seed =
(DYNAMO_OPTION(prng_seed) == 0) ? os_random_seed() : DYNAMO_OPTION(prng_seed);
DODEBUG(initial_random_seed = random_seed;);
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
os_file_init();
#endif
set_exception_strings(NULL, NULL);
}
* 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)
{
while (!spinmutex_trylock(spin_lock)) {
os_thread_yield();
}
return;
}
* as required by check_wait_at_safe_spot */
void
spinmutex_lock_no_yield(spin_mutex_t *spin_lock)
{
while (!spinmutex_trylock(spin_lock)) {
#ifdef DEADLOCK_AVOIDANCE
mutex_t *lock = &spin_lock->lock;
* 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;
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;
* 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;
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
* machine
*/
if (spinlock_count) {
uint i;
if (d_r_mutex_trylock(lock))
return;
i = spinlock_count;
* spin loop
*/
do {
SPINLOCK_PAUSE();
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);
}
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);
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);
}
bool
d_r_mutex_trylock(mutex_t *lock)
{
bool acquired;
#ifdef DEADLOCK_AVOIDANCE
bool ownable = mutex_ownable(lock);
#endif
acquired =
atomic_compare_exchange(&lock->lock_requests, LOCK_FREE_STATE, LOCK_SET_STATE);
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;
}
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;
(i.e. final value is not LOCK_FREE_STATE)
we need to notify another waiting thread */
mutex_notify_released_lock(lock);
}
void
d_r_mutex_delete(mutex_t *lock)
{
LOG(GLOBAL, LOG_THREADS, 3, "mutex_delete lock " PFX "\n", lock);
* 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) {
* 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
* 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
#endif
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)
{
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);
}
}
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());
}
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).
*/
FIXME: 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)
*/
spinlock.h and /usr/src/linux-2.4/arch/i386/kernel/semaphore.c
for the Linux kernel implementation on x86.
*/
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.
FIXME: 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)
{
* FIXME: 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)) {
* 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).
* FIXME: 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
*/
if (rw->writer == d_r_get_thread_id()) {
* the deadlock avoidance to consider this an acquire
*/
ATOMIC_INC(int, rw->num_readers);
return;
}
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, false, LOCK_NOT_OWNABLE);
os_thread_yield();
}
ATOMIC_INC(int, rw->num_readers);
if (!mutex_testlock(&rw->lock))
break;
ATOMIC_DEC(int, rw->num_readers);
} while (true);
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, true, LOCK_NOT_OWNABLE);
return;
}
do {
while (mutex_testlock(&rw->lock)) {
* 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).
*/
if (rw->writer == d_r_get_thread_id()) {
* the deadlock avoidance to consider this an acquire
*/
ATOMIC_INC(int, rw->num_readers);
return;
}
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, false, LOCK_NOT_OWNABLE);
ATOMIC_INC(int, rw->num_pending_readers);
Then no signal will be send our way, so we shouldn't be waiting then
*/
if (mutex_testlock(&rw->lock)) {
rwlock_wait_contended_reader(rw);
} else {
should no longer wait since no one will wake them up */
}
if (!atomic_dec_becomes_zero(&rw->num_pending_readers)) {
we need to notify another waiting one so that
it can get out of the contention path.
*/
rwlock_notify_readers(rw);
we have an automatic right to claim the lock.
*/
}
}
ATOMIC_INC(int, rw->num_readers);
if (!mutex_testlock(&rw->lock))
break;
or otherwise add a mutex grabbed by readers for the above
test.
*/
ATOMIC_DEC(int, rw->num_readers);
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.
*/
rwlock_notify_writer(rw);
SPINLOCK_PAUSE();
} while (true);
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, true, LOCK_NOT_OWNABLE);
}
void
d_r_write_lock(read_write_lock_t *rw)
{
* 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);
while (rw->num_readers > 0) {
DEADLOCK_AVOIDANCE_LOCK(&rw->lock, false, LOCK_NOT_OWNABLE);
os_thread_yield();
}
rw->writer = d_r_get_thread_id();
return;
}
d_r_mutex_lock(&rw->lock);
that num_readers is 0 when unblocked.
*/
while (rw->num_readers > 0) {
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();
if (rw->num_readers == 0) {
rw->writer = d_r_get_thread_id();
return true;
} else {
that one may already be waiting on the broadcast event */
d_r_mutex_unlock(&rw->lock);
if (atomic_aligned_read_int(&rw->num_pending_readers) > 0) {
* 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;
}
we may need to notify a waiting writer */
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 (mutex_testlock(&rw->lock)) {
if (rw->writer != d_r_get_thread_id()) {
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;
}
ahead. No writer should be allowed to lock until all currently
waiting readers are unblocked.
*/
progress as soon as they are notified. Further field
accesses however have to be assumed unprotected.
*/
d_r_mutex_unlock(&rw->lock);
if (atomic_aligned_read_int(&rw->num_pending_readers) > 0) {
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());
}
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: {
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);
* 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;
}
* /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
*/
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;
}
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]);
}
* 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;
uint first = bitmap_find_set_block(b, bitmap_size);
if (first == BITMAP_NOT_FOUND)
return BITMAP_NOT_FOUND;
do {
uint hole_size = 1;
while (hole_size < requested && bitmap_test(b, first + hole_size)) {
hole_size++;
}
if (hole_size == requested)
return first;
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
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;
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
file_t
get_thread_private_logfile()
{
#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
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);
DOCHECK(1, {
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));
}
}
});
if (size >= BUFFER_SIZE_ELEMENTS(logbuf) || size < 0)
size = strlen(logbuf);
written = os_write(f, logbuf, size);
if (written < 0)
return -1;
return written;
}
* gives back high and low parts for printing, also supports percentages
* FIXME : 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
* 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)));
* 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));
}
* so we use these routines from io.c.
*/
extern long
double2int_trunc(double d);
* 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;
*top = double2int_trunc(val);
*bottom = double2int_trunc((val - *top) * precision_multiple);
}
#ifdef WINDOWS
* internal */
void
display_error(char *msg)
{
SYSLOG_INTERNAL_ERROR("%s", msg);
}
#endif
#ifdef DEBUG
# ifdef WINDOWS
# else
void
print_symbolic_address(app_pc tag, char *buf, int max_chars, bool exact_only)
{
buf[0] = '\0';
}
# endif
#endif
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);
}
* 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.
*/
* 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 INOUT, const char *fmt,
va_list ap)
{
extern int d_r_vsnprintf(char *s, size_t max, const char *fmt, va_list ap);
ssize_t len;
bool ok;
len = d_r_vsnprintf(buf + *sofar, bufsz - *sofar, fmt, ap);
ok = (len >= 0 && len < (ssize_t)(bufsz - *sofar));
* 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));
buf[bufsz - 1] = '\0';
return ok;
}
* 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 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;
}
* 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
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
* 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);
* 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);
* 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);
* infinite recursion, still could be issue with exception, but separate
* recursive bailout will at least kill us then, FIXME, note will use
* default masks below if original parse */
if (synch)
synchronize_dynamic_options();
LOG(THREAD_GET, LOG_ALL, 1, "%s: %s\n", prefix, msgbuf);
#ifdef WINDOWS
if (TEST(priority, dynamo_options.syslog_mask)) {
if (internal) {
if (TEST(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
#endif
if (TEST(priority, dynamo_options.stderr_mask))
print_file(STDERR, "<%s>\n", msgbuf);
if (TEST(priority, dynamo_options.msgbox_mask)) {
#ifdef WINDOWS
* do a timed out messagebox, could then also replace the os_timeout in
* vmareas.c
*/
debugbox(msgbuf);
#else
* 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)) {
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
*/
* (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 + 17 * 8 + 8 * 23 + 2)
#elif defined(ARM)
# define REPORT_MSG_MAX (271 + 17 * 8)
#else
# define REPORT_MSG_MAX (271)
#endif
#define REPORT_LEN_VERSION 96
* 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
* 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
#define REPORT_LEN_STACK (REPORT_LEN_STACK_EACH) * (REPORT_NUM_STACK)
#define REPORT_LEN_PRIVLIBS (45 * 10)
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));
DECLARE_CXTSWPROT_VAR(static thread_id_t report_buf_lock_owner, 0);
#define ASSERT_ROOM(reportbuf, curbuf, maxlen) \
ASSERT(curbuf + maxlen < reportbuf + sizeof(reportbuf))
DECLARE_CXTSWPROT_VAR(static mutex_t prng_lock, INIT_LOCK_FREE(prng_lock));
#ifdef DEBUG
bool
under_internal_exception()
{
# ifdef DEADLOCK_AVOIDANCE
return (report_buf_lock.owner == d_r_get_thread_id());
# else
return mutex_testlock(&report_buf_lock);
# endif
}
#endif
const char *exception_label_core = PRODUCT_NAME;
static const char *exception_report_url = BUG_REPORT_URL;
const char *exception_label_client = "Client";
static char display_version[REPORT_LEN_VERSION];
* 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);
}
* If TEST(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, ...)
{
* 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();
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()) {
return;
}
d_r_mutex_lock(&report_buf_lock);
report_buf_lock_owner = d_r_get_thread_id();
* fault in this routine, if called to report a fatal error.
*/
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 {
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);
get_dynamo_options_string(&dynamo_options, curbuf, REPORT_LEN_OPTIONS - 1, true);
curbuf += strlen(curbuf);
*(curbuf++) = '\n';
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));
}
if (!TEST(DUMPCORE_INTERNAL_EXCEPTION, dumpcore_flag)) {
size_t sofar = 0;
* 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 , false , curbuf,
REPORT_LEN_PRIVLIBS, &sofar);
curbuf += sofar;
}
if (*(curbuf - 1) == '\n')
curbuf--;
*curbuf = '\0';
if (TEST(dumpcore_flag, DYNAMO_OPTION(dumpcore_mask)) && DYNAMO_OPTION(live_dump)) {
os_dump_core(reportbuf);
}
* might be stack critical so use _NO_OPTION_SYNCH */
if (TEST(DUMPCORE_INTERNAL_EXCEPTION, dumpcore_flag) ||
TEST(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 (TEST(DUMPCORE_INTERNAL_EXCEPTION, dumpcore_flag)) {
SYSLOG_NO_OPTION_SYNCH(
SYSLOG_CRITICAL, EXCEPTION, 7 , get_application_name(),
get_application_pid(), exception_label_core,
TEST(DUMPCORE_STACK_OVERFLOW, dumpcore_flag) ? STACK_OVERFLOW_NAME
: CRASH_NAME,
saddr, exception_report_url,
* already has it */
reportbuf + report_exception_skip_prefix());
} else {
SYSLOG_NO_OPTION_SYNCH(
SYSLOG_CRITICAL, CLIENT_EXCEPTION, 7 , get_application_name(),
get_application_pid(), exception_label_client,
TEST(DUMPCORE_STACK_OVERFLOW, dumpcore_flag) ? STACK_OVERFLOW_NAME
: CRASH_NAME,
saddr, exception_report_url,
reportbuf + report_client_exception_skip_prefix());
}
} else if (TEST(DUMPCORE_ASSERTION, dumpcore_flag)) {
* 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 (TEST(DUMPCORE_CURIOSITY, dumpcore_flag)) {
SYSLOG_INTERNAL_NO_OPTION_SYNCH(SYSLOG_WARNING, "%s", reportbuf);
} else {
SYSLOG_INTERNAL_NO_OPTION_SYNCH(SYSLOG_ERROR, "%s", reportbuf);
}
if (dumpcore_flag != DUMPCORE_FATAL_USAGE_ERROR) {
* 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);
}
* 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 (TEST(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) {
* 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 (TEST(dumpcore_flag, DYNAMO_OPTION(dumpcore_mask)) && !DYNAMO_OPTION(live_dump)) {
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 (!TEST(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);
* 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 (TEST(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();
if (dcontext == NULL) {
* 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);
* 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 && check_pc < pc + size);
},
{
return false;
});
return true;
}
bool
is_string_readable_without_exception(char *str, size_t *str_length )
{
size_t length = 0;
dcontext_t *dcontext = get_thread_private_dcontext();
if (str == NULL)
return false;
if (dcontext != NULL) {
TRY_EXCEPT(
dcontext,
{
length = strlen(str);
if (str_length != NULL)
*str_length = length;
},
{ return false; });
return true;
} else {
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,
{
memcpy(base, in_buf, size);
res = true;
},
{
});
} else {
* there should only be one live thread.
*/
if (is_readable_without_exception(base, size) &&
IF_UNIX_ELSE(get_memory_info_from_os, get_memory_info)(base, ®ion_base,
®ion_size, &prot) &&
TEST(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) ||
!TEST(MEMPROT_WRITE, prot))
return false;
bytes_checked += region_size;
}
} else {
return false;
}
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>";
}
bool
is_region_memset_to_char(byte *addr, size_t size, byte val)
{
* (for x86 repe scasd w/proper alignment handling) */
size_t i;
for (i = 0; i < size; i++) {
if (*addr++ != val)
return false;
}
return true;
}
* 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
* 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
#endif
#ifdef WINDOWS
* 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. FIXME -
* 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;
ASSERT((sbyte)*regexp != EOF && (sbyte)*consider != EOF);
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;
char consider[MAXIMUM_PATH];
bool done = false;
ASSERT(short_name != NULL && filter != NULL);
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';
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 );
}
bool
check_filter_with_wildcards(const char *filter, const char *short_name)
{
return check_filter_common(filter, short_name, true );
}
static char logdir[MAXIMUM_PATH];
static bool logdir_initialized = false;
static char basedir[MAXIMUM_PATH];
static bool basedir_initialized = false;
* on what is a critical path for stack depth (diagnostics->create_log_dir->
* get_parameter */
static char old_basedir[MAXIMUM_PATH];
* 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 FIXME */
DECLARE_CXTSWPROT_VAR(static recursive_lock_t logdir_mutex,
INIT_RECURSIVE_LOCK(logdir_mutex));
void
enable_new_log_dir()
{
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
acquire_recursive_lock(&logdir_mutex);
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
#ifdef UNIX
if (dir_type == PROCESS_DIR && pre_execve != NULL) {
* 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)) {
DODEBUG(sharing_logdir = true;);
strncpy(logdir, pre_execve, BUFFER_SIZE_ELEMENTS(logdir));
NULL_TERMINATE_BUFFER(logdir);
logdir_initialized = true;
}
disable_env(DYNAMORIO_VAR_EXECVE_LOGDIR);
ASSERT(getenv(DYNAMORIO_VAR_EXECVE_LOGDIR) == NULL);
}
#endif
if (dir_type == BASE_DIR) {
int retval;
ASSERT(sizeof(basedir) == sizeof(old_basedir));
strncpy(old_basedir, basedir, sizeof(old_basedir));
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))) {
basedir_initialized = true;
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)) {
char swap;
char *end = double_strchr(basedir, DIRSEP, ALT_DIRSEP);
bool res;
#ifdef WINDOWS
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);
if (!res) {
SYSLOG(SYSLOG_ERROR, ERROR_UNABLE_TO_CREATE_BASEDIR, 3,
get_application_name(), get_application_pid(), basedir);
* dirs will also fail to open and just won't be
* logged to */
}
}
}
}
}
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;
if (*base != '\0') {
if (!get_unique_logfile("", logdir, sizeof(logdir), true, 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) {
* 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 (dir_type == PROCESS_DIR
# ifdef UNIX
&& !sharing_logdir
# endif
)
SYSLOG_INTERNAL_INFO("log dir=%s", logdir);
#endif
}
* 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 {
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;
}
* 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);
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);
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)
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);
* write since invalid handle */
}
#ifdef UNIX
if (!post_execve) {
#endif
* 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';
}
return file;
}
void
close_log_file(file_t f)
{
if (f == STDERR)
return;
os_close_protected(f);
}
* Creates a unique file or directory of the form
* BASEDIR/[app_name].[pid].<unique num of up to 8 digits>[file_type]
* 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, 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;
create_log_dir(BASE_DIR);
if (get_log_dir(BASE_DIR, buf, &size)) {
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 {
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);
});
}
if (NULL != filename_buffer) {
strncpy(filename_buffer, buf, maxlen);
filename_buffer[maxlen - 1] = '\0';
}
return success;
}
const char *
get_app_name_for_path()
{
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++;
return exe;
}
#ifdef DEBUG
# ifdef FRAGMENT_SIZES_STUDY
* 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;
* 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;
* 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
void
stats_thread_init(dcontext_t *dcontext)
{
thread_local_statistics_t *new_thread_stats;
if (!INTERNAL_OPTION(thread_stats))
return;
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));
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)
{
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;
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)
{
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).
*/
* (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;
* 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); });
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);
# 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, {
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;
uint64 current_time;
if (initial_time == 0ULL)
initial_time = query_time_millis();
current_time = query_time_millis();
if (current_time == 0ULL)
return 0;
current_time -= initial_time;
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);
}
* 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
void
dump_global_rstats_to_stderr(void)
{
if (GLOBAL_STATS_ON()) {
print_file(STDERR, "%s statistics:\n", PRODUCT_NAME);
#undef RSTATS_DEF
* 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 = TEST(DUMP_OCTAL, flags);
bool raw = TEST(DUMP_RAW, flags);
bool usechars = !raw && !TEST(DUMP_NO_CHARS, flags);
bool replayable = usechars && !TEST(DUMP_NO_QUOTING, flags);
bool dword = TEST(DUMP_DWORD, flags);
bool prepend_address = TEST(DUMP_ADDRESS, flags);
bool append_ascii = TEST(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", " ");
ASSERT(line_start == (i - per_line));
dump_buffer_as_ascii(logfile, (char *)buf + line_start, per_line);
line_start = i;
}
print_file(logfile, "%s", raw ? "\n" : "\"\n\"");
}
if (prepend_address && 0 == i % per_line)
print_file(logfile, PFX " ", buf + i);
if (replayable) {
if (isdigit_fast(buf[i]) && nonprint) {
print_file(logfile, "%s", "\"\"");
}
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) {
size_t empty = ALIGN_FORWARD(buf + len, per_line);
uint size = (dword ? 4 : 1);
* 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");
}
bool
is_valid_xml_char(char c)
{
* 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;
}
* 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)
{
* single walk.*/
return (strstr(str, "]]>") == NULL && is_valid_xml_string(str));
}
#if 0
static bool
is_valid_xml_body_string(const char *str)
{
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)
{
return (strchr(str, '\'') == NULL && strchr(str, '\"') == NULL &&
is_valid_xml_body_string(str));
}
#endif
* 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 (FIXME),
* 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 {
* but we usually expect to have valid strings anyways. */
print_file(f, "%c", *str);
}
str++;
}
}
}
void
print_version_and_app_info(file_t file)
{
print_file(file, "%s\n", dynamorio_version_string);
print_file(file, "Running: %s\n", get_application_name());
#ifdef WINDOWS
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()
{
LOG(GLOBAL, LOG_STATS, 1, "-prng_seed " PFX " for reproducing random sequence\n",
initial_random_seed);
if (doing_detach)
enable_new_log_dir();
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;
}
* 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)
{
* http://remus.rutgers.edu/~rhoads/Code/random.c
* FIXME: Look up Knuth's recommendations in vol. 2
*/
enum { LCM_A = 279470273, LCM_Q = 15, LCM_R = 102913196 };
* 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;
if (max_offset == 0)
return 0;
d_r_mutex_lock(&prng_lock);
* 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;
}
* the current UTC time).
*/
void
convert_millis_to_date(uint64 millis, dr_time_t *dr_time 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;
* 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;
year = 1600 + (uint)(time / DAYS_IN_400_YEARS) * 400;
days = (uint)(time % DAYS_IN_400_YEARS);
* 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.
*/
dr_time->day_of_week = (days + 3) % 7;
* 36524.25 = 146097 / 4, rounding up because the long century comes last.
*/
q = (days * 4 + 3) / 146097;
year += q * 100;
days -= q * 146097 / 4;
* 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;
* 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;
dr_time->day = days + 1;
dr_time->month = month < 10 ? month + 3 : month - 9;
dr_time->year = month < 10 ? year : year + 1;
}
* the current UTC time).
*/
void
convert_date_to_millis(const dr_time_t *dr_time, uint64 *millis OUT)
{
* 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
};
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;
}
* 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/
*/
* 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;
have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1));
need = MD5_BLOCK_LENGTH - have;
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;
}
while (len >= MD5_BLOCK_LENGTH) {
MD5Transform(ctx->state, input);
input += MD5_BLOCK_LENGTH;
len -= MD5_BLOCK_LENGTH;
}
}
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;
PUT_64BIT_LE(count, ctx->count);
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);
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));
}
#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))
#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 == 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;
}
* 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 HEAPACCT(which_heap_t heap))
{
ssize_t bytes_read;
file_t fd = INVALID_FILE;
char *buf = NULL;
uint64 buf_len64 = 0;
* 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);
* 4 may be allocated to work around case 8048. FIXME: 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);
buf[MIN((uint)bytes_read, *buf_len - 1)] = 0;
os_close(fd);
return buf;
}
bool
check_low_disk_threshold(file_t f, uint64 new_file_size)
{
* 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;
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);
* 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) {
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);
}
} else {
LOG(THREAD_GET, LOG_SYSCALLS | LOG_THREADS, 2,
"unable to retrieve available disk space\n");
}
return ok;
}
#ifdef PROCESS_CONTROL
* 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.
*
* FIXME: use nt_map_view_of_section with SEC_MAPPED && !SEC_IMAGE.
*/
# define MD5_FILE_READ_BUF_SIZE (4 * PAGE_SIZE)
* 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 )
{
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);
* 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]);
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
* 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)
{
* only by process_control. BTW, 1 for the terminating '\0'.
*/
static char exe_md5[MD5_STRING_LENGTH + 1] = { 0 };
#ifdef PROCESS_CONTROL
* 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
* 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
const char *exe_name = get_application_name();
# endif
* 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);
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
}
} else {
* should be turned on.
*/
ASSERT(strlen(exe_md5) == MD5_STRING_LENGTH);
ASSERT(IS_PROCESS_CONTROL_ON());
}
#else
ASSERT(exe_md5[0] == '\0');
#endif
return exe_md5;
}
*
* 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] )
{
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);
}
* [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 ,
size_t *intersection_len , const app_pc region1_start,
size_t region1_len, const app_pc region2_start, size_t region2_len)
{
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);
* 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;
DECLARE_NEVERPROT_VAR(static profile_callers_t *profcalls, NULL);
DECLARE_CXTSWPROT_VAR(static mutex_t profile_callers_lock,
INIT_LOCK_FREE(profile_callers_lock));
* 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.
* FIXME: combine w/ a generalized mutex_collect_callstack()?
*/
void
profile_callers()
{
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 )
return;
ASSERT(DYNAMO_OPTION(prof_caller) <= MAX_CALL_PROFILE_DEPTH);
GET_FRAME_PTR(our_ebp);
memset(caller, 0, sizeof(caller));
pc = (uint *)our_ebp;
* 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++;
if (pc == (uint *)saferead[0] || num >= DYNAMO_OPTION(prof_caller))
break;
pc = (uint *)saferead[0];
}
* linear search is sufficient!
* FIXME: 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()
{
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
#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);
}
}
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);
for (t = 0; t < 100000; t++) {
test_date_conversion_millis(t);
test_date_conversion_millis(-t - 1);
}
for (t = 0; t < 100000; t++) {
test_date_conversion_millis(t * 1000);
test_date_conversion_millis(-(t * 1000) - 1);
}
for (t = 0; t < 200000; t++)
test_date_conversion_millis((uint64)t * 24 * 60 * 60 * 1000);
dr_time.day_of_week = 0;
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
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;
dup = (char *)heap_alloc(GLOBAL_DCONTEXT, str_len HEAPACCT(which));
strncpy(dup, str, str_len);
dup[str_len - 1] = '\0';
return dup;
}
#ifdef WINDOWS
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;
* 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 , NULL);
if (encode_len < 0)
str_len = 1;
else
str_len = encode_len + 1;
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';
* 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).
*/
* the same code. We fall back on filling with '?'.
*/
memset(dup + strlen(dup), '?', str_len - 1 - strlen(dup));
}
}
dup[str_len - 1] = '\0';
ASSERT(strlen(dup) == str_len - 1);
return dup;
}
#endif
* 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;
heap_free(GLOBAL_DCONTEXT, (void *)str, str_len HEAPACCT(which));
}
* (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 , void **src1,
uint src1_num, void **src2, uint src2_num,
void ***dst, uint *dst_num HEAPACCT(which_heap_t which))
{
* fill them in.
* FIXME: 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)
return;
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;
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;
}