* Copyright (c) 2010-2022 Google, Inc. 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.
*/
* dynamo.c -- initialization and cleanup routines for DynamoRIO
*/
#include "globals.h"
#include "configure_defines.h"
#include "link.h"
#include "fragment.h"
#include "fcache.h"
#include "emit.h"
#include "dispatch.h"
#include "utils.h"
#include "monitor.h"
#include "vmareas.h"
#ifdef SIDELINE
# include "sideline.h"
#endif
#ifdef PAPI
# include "perfctr.h"
#endif
#include "instrument.h"
#include "hotpatch.h"
#include "moduledb.h"
#include "module_shared.h"
#include "synch.h"
#include "native_exec.h"
#include "jit_opt.h"
#ifdef ANNOTATIONS
# include "annotations.h"
#endif
#ifdef WINDOWS
*/
# include "ntdll.h"
# include "nudge.h"
#endif
#ifdef RCT_IND_BRANCH
# include "rct.h"
#endif
#include "perscache.h"
#ifdef VMX86_SERVER
# include "vmkuw.h"
#endif
#ifndef STANDALONE_UNIT_TEST
# ifdef __AVX512F__
# error "DynamoRIO core should run without AVX-512 instructions to remain \
portable and to avoid frequency scaling."
# endif
#endif
bool dynamo_initialized = false;
static bool dynamo_options_initialized = false;
bool dynamo_heap_initialized = false;
bool dynamo_started = false;
bool automatic_startup = false;
bool control_all_threads = false;
* injection, and we do see early threads in place which is the point of
* this flag: so we always set it.
*/
bool dynamo_control_via_attach = IF_WINDOWS_ELSE(true, false);
#ifdef WINDOWS
bool dr_early_injected = false;
int dr_early_injected_location = INJECT_LOCATION_Invalid;
bool dr_earliest_injected = false;
static void *dr_earliest_inject_args;
* injecting initially (!dr_injected_secondary_thread), or by retaking
* over (dr_late_injected_primary_thread). Used only for debugging
* purposes, yet can't rely on !dr_injected_secondary_thread very
* early in the process
*/
bool dr_injected_primary_thread = false;
bool dr_injected_secondary_thread = false;
bool dr_late_injected_primary_thread = false;
#endif
bool dr_api_entry = false;
bool dr_api_exit = false;
#ifdef RETURN_AFTER_CALL
bool dr_preinjected = false;
#endif
#ifdef UNIX
static bool dynamo_exiting = false;
#endif
bool dynamo_exited = false;
bool dynamo_exited_all_other_threads = false;
bool dynamo_exited_and_cleaned = false;
#ifdef DEBUG
bool dynamo_exited_log_and_stats = false;
#endif
* its placement in .nspdata. If we use it for more we should protect it.
*/
DECLARE_NEVERPROT_VAR(bool dynamo_all_threads_synched, false);
bool dynamo_resetting = false;
bool standalone_library = false;
static int standalone_init_count;
#ifdef UNIX
bool post_execve = false;
#endif
byte *d_r_initstack;
event_t dr_app_started;
event_t dr_attach_finished;
#ifdef WINDOWS
# define EXCEPTION_STACK_SIZE (2 * PAGE_SIZE)
DECLARE_NEVERPROT_VAR(byte *exception_stack, NULL);
#endif
* protection issues -- we need to write to these vars in bootstrapping
* spots where we cannot unprotect first
*/
START_DATA_SECTION(NEVER_PROTECTED_SECTION, "w");
mutex_t initstack_mutex IF_AARCH64(__attribute__((aligned(8))))
VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = INIT_SPINLOCK_FREE(initstack_mutex);
byte *initstack_app_xsp VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = 0;
volatile int exiting_thread_count VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = 0;
volatile int uninit_thread_count VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = 0;
* segment is still protected (right now the only ones are selfmod stats)
*/
static dr_statistics_t nonshared_stats VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = {
{ 0 },
};
* datasec_writable variables.
*/
static mutex_t
datasec_lock[DATASEC_NUM] VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = { { 0 } };
END_DATA_SECTION()
* This would be a simple array, but we need each in its own protected
* section, as this could be exploited.
*/
const uint datasec_writable_neverprot = 1;
uint datasec_writable_rareprot = 1;
DECLARE_FREQPROT_VAR(uint datasec_writable_freqprot, 1);
DECLARE_CXTSWPROT_VAR(uint datasec_writable_cxtswprot, 1);
static app_pc datasec_start[DATASEC_NUM];
static app_pc datasec_end[DATASEC_NUM];
const uint DATASEC_SELFPROT[] = {
0,
SELFPROT_DATA_RARE,
SELFPROT_DATA_FREQ,
SELFPROT_DATA_CXTSW,
};
const char *const DATASEC_NAMES[] = {
NEVER_PROTECTED_SECTION,
RARELY_PROTECTED_SECTION,
FREQ_PROTECTED_SECTION,
CXTSW_PROTECTED_SECTION,
};
typedef struct _protect_info_t {
* and exceptions!
*/
mutex_t lock;
int num_threads_unprot;
int num_threads_suspended;
} protect_info_t;
static protect_info_t *protect_info;
static void
data_section_init(void);
static void
data_section_exit(void);
#ifdef DEBUG
# include <time.h>
* are we trying to hardcode the options for a release build?
*/
# ifdef UNIX
# include <sys/ipc.h>
# include <sys/types.h>
# include <unistd.h>
# endif
static uint starttime;
file_t main_logfile = INVALID_FILE;
#endif
dr_statistics_t *d_r_stats = NULL;
DECLARE_FREQPROT_VAR(static int num_known_threads, 0);
#ifdef UNIX
DECLARE_FREQPROT_VAR(int num_execve_threads, 0);
#endif
DECLARE_FREQPROT_VAR(static uint threads_ever_count, 0);
thread_record_t **all_threads;
* FIXME : is almost completely redundant in usage with thread_initexit_lock
* maybe replace this lock with thread_initexit_lock? */
DECLARE_CXTSWPROT_VAR(mutex_t all_threads_lock, INIT_LOCK_FREE(all_threads_lock));
* due to its use for flushing, this lock cannot be held while couldbelinking!
*/
DECLARE_CXTSWPROT_VAR(mutex_t thread_initexit_lock, INIT_LOCK_FREE(thread_initexit_lock));
DECLARE_CXTSWPROT_VAR(static recursive_lock_t thread_in_DR_exclusion,
INIT_RECURSIVE_LOCK(thread_in_DR_exclusion));
static thread_synch_state_t
exit_synch_state(void);
static void
synch_with_threads_at_exit(thread_synch_state_t synch_res, bool pre_exit);
static void
delete_dynamo_context(dcontext_t *dcontext, bool free_stack);
#ifdef DEBUG
static const char *
main_logfile_name(void)
{
return get_app_name_for_path();
}
static const char *
thread_logfile_name(void)
{
return "log";
}
#endif
static void
statistics_pre_init(void)
{
* really only logmask and loglevel are meaningful, so be careful!
* statistics_init and create_log_directory are the only routines that
* use stats before it's set up for real, currently
*/
* locations for stats (namely shared memory for the old MIT gui). */
d_r_stats = &nonshared_stats;
d_r_stats->process_id = get_process_id();
strncpy(d_r_stats->process_name, get_application_name(), MAXIMUM_PATH);
d_r_stats->process_name[MAXIMUM_PATH - 1] = '\0';
ASSERT(strlen(d_r_stats->process_name) > 0);
d_r_stats->num_stats = 0;
}
static void
statistics_init(void)
{
ASSERT(d_r_stats == &nonshared_stats);
ASSERT(d_r_stats->num_stats == 0);
#ifndef DEBUG
if (!DYNAMO_OPTION(global_rstats)) {
return;
}
#endif
d_r_stats->num_stats = 0
#ifdef DEBUG
# define STATS_DEF(desc, name) +1
#else
# define RSTATS_DEF(desc, name) +1
#endif
#include "statsx.h"
#undef STATS_DEF
#undef RSTATS_DEF
;
* to view our stats: they don't have to chase pointers, and we could put
* this in shared memory easily. However, we do waste some memory, but
* not much in release build.
*/
#ifdef DEBUG
# define STATS_DEF(desc, statname) \
strncpy(d_r_stats->statname##_pair.name, desc, \
BUFFER_SIZE_ELEMENTS(d_r_stats->statname##_pair.name)); \
NULL_TERMINATE_BUFFER(d_r_stats->statname##_pair.name);
#else
# define RSTATS_DEF(desc, statname) \
strncpy(d_r_stats->statname##_pair.name, desc, \
BUFFER_SIZE_ELEMENTS(d_r_stats->statname##_pair.name)); \
NULL_TERMINATE_BUFFER(d_r_stats->statname##_pair.name);
#endif
#include "statsx.h"
#undef STATS_DEF
#undef RSTATS_DEF
}
static void
statistics_exit(void)
{
if (doing_detach)
memset(d_r_stats, 0, sizeof(*d_r_stats));
d_r_stats = NULL;
}
dr_statistics_t *
get_dr_stats(void)
{
return d_r_stats;
}
* threads are created and before any other API calls are made;
* returns zero on success, non-zero on failure
*/
DYNAMORIO_EXPORT int
dynamorio_app_init(void)
{
dynamorio_app_init_part_one_options();
return dynamorio_app_init_part_two_finalize();
}
void
dynamorio_app_init_part_one_options(void)
{
if (dynamo_initialized || dynamo_options_initialized) {
if (standalone_library) {
REPORT_FATAL_ERROR_AND_EXIT(STANDALONE_ALREADY, 2, get_application_name(),
get_application_pid());
}
} else {
#ifdef UNIX
os_page_size_init((const char **)our_environ, is_our_environ_followed_by_auxv());
#endif
#ifdef WINDOWS
syscalls_init();
#endif
DODEBUG(starttime = query_time_seconds(););
#ifdef UNIX
if (getenv(DYNAMORIO_VAR_EXECVE) != NULL) {
post_execve = true;
# ifdef VMX86_SERVER
* clear it now to ensure we don't leak it and eventually run out of
* slots. We could alternatively call os_tls_exit() prior to
* execve, since syscalls use thread-private fcache_enter, but
* complex to recover from execve failure, so instead we pass which
* TLS index we had.
*/
os_tls_pre_init(atoi(getenv(DYNAMORIO_VAR_EXECVE)));
# endif
disable_env(DYNAMORIO_VAR_EXECVE);
ASSERT(getenv(DYNAMORIO_VAR_EXECVE) == NULL);
} else
post_execve = false;
#endif
* initialized to 0 automatically)
*/
#ifdef DEBUG
# ifndef INTERNAL
nonshared_stats.logmask = LOG_ALL_RELEASE;
# else
nonshared_stats.logmask = LOG_ALL;
# endif
statistics_pre_init();
#endif
d_r_config_init();
options_init();
#ifdef WINDOWS
syscalls_init_options_read();
* but before init_syscall_trampolines */
#endif
utils_init();
data_section_init();
#ifdef DEBUG
* fill it with perfctr stats!
*/
if (d_r_stats->loglevel > 0) {
main_logfile = open_log_file(main_logfile_name(), NULL, 0);
LOG(GLOBAL, LOG_TOP, 1, "global log file fd=%d\n", main_logfile);
} else {
* if the loglevel is later raised, too bad! it all goes to stderr!
* N.B.: when checking for no logdir, we check for empty string or
* first char '<'!
*/
strncpy(d_r_stats->logdir, "<none (loglevel was 0 on startup)>",
MAXIMUM_PATH - 1);
d_r_stats->logdir[MAXIMUM_PATH - 1] = '\0';
main_logfile = INVALID_FILE;
}
# ifdef PAPI
hardware_perfctr_init();
# endif
DOLOG(1, LOG_TOP, { print_version_and_app_info(GLOBAL); });
if (INTERNAL_OPTION(nullcalls)) {
return;
}
LOG(GLOBAL, LOG_TOP, 1, PRODUCT_NAME "'s stack size: %d Kb\n",
DYNAMORIO_STACK_SIZE / 1024);
#endif
#ifndef DEBUG
statistics_pre_init();
#endif
statistics_init();
dynamo_options_initialized = true;
}
}
int
dynamorio_app_init_part_two_finalize(void)
{
if (!dynamo_options_initialized) {
return FAILURE;
} else if (dynamo_initialized) {
if (standalone_library) {
REPORT_FATAL_ERROR_AND_EXIT(STANDALONE_ALREADY, 2, get_application_name(),
get_application_pid());
}
} else if (INTERNAL_OPTION(nullcalls)) {
print_file(main_logfile, "** nullcalls is set, NOT taking over execution **\n\n");
return SUCCESS;
} else {
#ifdef VMX86_SERVER
vmk_init_lib();
#endif
vmm_heap_init();
* since the client library load would hit our own hooks (xref hotpatch
* cases about that) -- though -private_loader removes that issue.
*/
instrument_load_client_libs();
d_r_heap_init();
dynamo_heap_initialized = true;
* process_control_int() because the former initializes event logging
* and the latter can kill the process if a violation occurs.
*/
SYSLOG(SYSLOG_INFORMATION, INFO_PROCESS_START_CLIENT, 2, get_application_name(),
get_application_pid());
#ifdef PROCESS_CONTROL
if (IS_PROCESS_CONTROL_ON())
process_control_init();
#endif
#ifdef WINDOWS
* we do our address space scans.
*/
if (dr_earliest_injected)
earliest_inject_cleanup(dr_earliest_inject_args);
#endif
dynamo_vm_areas_init();
d_r_decode_init();
proc_init();
modules_init();
d_r_os_init();
config_heap_init();
* N.B.: we never de-allocate d_r_initstack (see comments in app_exit)
*/
d_r_initstack = (byte *)stack_alloc(DYNAMORIO_STACK_SIZE, NULL);
LOG(GLOBAL, LOG_SYNCH, 2, "d_r_initstack is " PFX "-" PFX "\n",
d_r_initstack - DYNAMORIO_STACK_SIZE, d_r_initstack);
#ifdef WINDOWS
* dstack is exhausted
*/
exception_stack = (byte *)stack_alloc(EXCEPTION_STACK_SIZE, NULL);
#endif
#ifdef WINDOWS
if (!INTERNAL_OPTION(noasynch)) {
* exceptions in client init routines (PR 200207), but we don't want
* syscall hooks so client init can scan syscalls.
* Xref PR 216934 where this was originally down below 1st thread init,
* before we had GLOBAL_DCONTEXT.
*/
callback_interception_init_start();
}
#endif
* code, such as the private PEB on Windows.
*/
loader_init_prologue();
d_r_arch_init();
synch_init();
#ifdef KSTATS
kstat_init();
#endif
d_r_monitor_init();
fcache_init();
d_r_link_init();
fragment_init();
moduledb_init();
perscache_init();
native_exec_init();
if (!DYNAMO_OPTION(thin_client)) {
#ifdef HOT_PATCHING_INTERFACE
if (DYNAMO_OPTION(hot_patching))
hotp_init();
#endif
}
#ifdef INTERNAL
{
char initial_options[MAX_OPTIONS_STRING];
get_dynamo_options_string(&dynamo_options, initial_options,
sizeof(initial_options), true);
SYSLOG_INTERNAL_INFO("Initial options = %s", initial_options);
DOLOG(1, LOG_TOP, {
get_pcache_dynamo_options_string(&dynamo_options, initial_options,
sizeof(initial_options),
OP_PCACHE_LOCAL);
LOG(GLOBAL, LOG_TOP, 1, "Initial pcache-affecting options = %s\n",
initial_options);
});
}
#endif
LOG(GLOBAL, LOG_TOP, 1, "\n");
* look for spawned processes; however, if we plan to promote from
* thin_client to hotp_only mode (highly likely), this would be needed.
* For now, leave it in there unless thin_client footprint becomes an
* issue.
*/
int size = HASHTABLE_SIZE(ALL_THREADS_HASH_BITS) * sizeof(thread_record_t *);
all_threads =
(thread_record_t **)global_heap_alloc(size HEAPACCT(ACCT_THREAD_MGT));
memset(all_threads, 0, size);
if (!INTERNAL_OPTION(nop_initial_bblock) IF_WINDOWS(
|| !check_sole_thread()))
bb_lock_start = true;
#ifdef SIDELINE
if (dynamo_options.sideline)
sideline_init();
#endif
* to continue to avoid safe_read_tls_magic() in is_thread_tls_initialized().
* So we clear it on (re-)init in dynamorio_take_over_threads().
* From now until then, we avoid races where another thread invokes a
* safe_read during native signal delivery but we remove DR's handler before
* it reaches there and it is delivered to the app's handler instead, kind
* of like i#3535, by re-using the i#3535 mechanism of pointing at the only
* thread who could possibly have a dcontext.
* XXX: Should we rename this s/detacher_/singleton_/ or something?
*/
detacher_tid = IF_UNIX_ELSE(get_sys_thread_id(), INVALID_THREAD_ID);
* (in a race with injected threads, sometimes it is not the primary thread)
*/
* require changing start/stop API
*/
dynamo_thread_init(NULL, NULL, NULL, false);
loader_init_epilogue(get_thread_private_dcontext());
* two things: 1) a dcontext and 2) a SIGSEGV handler, for TRY/EXCEPT
* inside vm_areas_init() for PR 361594's probes and for d_r_safe_read().
* This means vm_areas_thread_init() runs before vm_areas_init().
*/
if (!DYNAMO_OPTION(thin_client)) {
vm_areas_init();
#ifdef RCT_IND_BRANCH
rct_init();
#endif
} else {
* internal ones, but can be app ones too. */
dynamo_vm_areas_lock();
find_dynamo_library_vm_areas();
dynamo_vm_areas_unlock();
}
#ifdef ANNOTATIONS
annotation_init();
#endif
jitopt_init();
dr_attach_finished = create_broadcast_event();
* that before initializing clients.
*/
dr_app_started = create_broadcast_event();
* dynamo_thread_init so the client can use a dcontext (PR 216936).
* Note that we *load* the client library before installing our hooks,
* but call the client's init routine afterward so that we correctly
* report crashes (PR 200207).
* Note: DllMain in client libraries can crash and we still won't
* report; better document that client libraries shouldn't have
* DllMain.
*/
instrument_init();
* delay the loading until we've initialized the clients.
*/
vm_area_delay_load_coarse_units();
#ifdef WINDOWS
if (!INTERNAL_OPTION(noasynch))
callback_interception_init_finish();
#endif
if (SELF_PROTECT_ON_CXT_SWITCH) {
protect_info = (protect_info_t *)global_unprotected_heap_alloc(
sizeof(protect_info_t) HEAPACCT(ACCT_OTHER));
ASSIGN_INIT_LOCK_FREE(protect_info->lock, protect_info);
protect_info->num_threads_unprot = 0;
protect_info->num_threads_suspended = 0;
if (INTERNAL_OPTION(single_privileged_thread)) {
ASSERT_NOT_IMPLEMENTED(false);
* the lock, and we are the initial thread in dynamo!
*/
d_r_mutex_lock(&thread_initexit_lock);
}
* FIXME: waste of protection change since will nop-unprotect!
*/
if (TEST(SELFPROT_DATA_CXTSW, DYNAMO_OPTION(protect_mask)))
datasec_writable_cxtswprot = 0;
if (TEST(SELFPROT_DATA_FREQ, DYNAMO_OPTION(protect_mask)))
datasec_writable_freqprot = 0;
}
ENTERING_DR();
#ifdef WINDOWS
if (DYNAMO_OPTION(early_inject)) {
* ENTERING_DR() */
early_inject_init();
}
#endif
}
dynamo_initialized = true;
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
if (INTERNAL_OPTION(unsafe_crash_process)) {
SYSLOG_INTERNAL_ERROR("Crashing the process deliberately!");
*((int *)PTR_UINT_MINUS_1) = 0;
}
if (INTERNAL_OPTION(unsafe_hang_process)) {
event_t never_signaled = create_event();
SYSLOG_INTERNAL_ERROR("Hanging the process deliberately!");
wait_for_event(never_signaled, 0);
destroy_event(never_signaled);
}
return SUCCESS;
}
#ifdef UNIX
void
dynamorio_fork_init(dcontext_t *dcontext)
{
* log files, which we want a separate directory for
*/
thread_record_t **threads;
int i, num_threads;
# ifdef DEBUG
char parent_logdir[MAXIMUM_PATH];
# endif
* create log dirs (xref i#189/PR 452168)
*/
os_fork_init(dcontext);
* even if parent did an execve, env var should be reset by now
*/
post_execve = (getenv(DYNAMORIO_VAR_EXECVE) != NULL);
ASSERT(!post_execve);
# ifdef DEBUG
* d_r_stats->logdir is static, so current copy is fine, don't need
* frozen copy
*/
strncpy(parent_logdir, d_r_stats->logdir, MAXIMUM_PATH - 1);
d_r_stats->logdir[MAXIMUM_PATH - 1] = '\0';
# endif
if (get_log_dir(PROCESS_DIR, NULL, NULL)) {
enable_new_log_dir();
create_log_dir(PROCESS_DIR);
}
# ifdef DEBUG
if (d_r_stats->loglevel > 0) {
main_logfile = open_log_file(main_logfile_name(), NULL, 0);
print_file(main_logfile, "%s\n", dynamorio_version_string);
print_file(main_logfile, "New log file for child %d forked by parent %d\n",
d_r_get_thread_id(), get_parent_id());
print_file(main_logfile, "Parent's log dir: %s\n", parent_logdir);
}
d_r_stats->process_id = get_process_id();
if (d_r_stats->loglevel > 0) {
LOG(GLOBAL, LOG_TOP, 1, "Running: %s\n", d_r_stats->process_name);
# ifndef _WIN32_WCE
LOG(GLOBAL, LOG_TOP, 1, "DYNAMORIO_OPTIONS: %s\n", d_r_option_string);
# endif
}
# endif
vmm_heap_fork_init(dcontext);
* other threads (fork -> we're alone in address space), so clear
* out entire thread table, then add child
*/
d_r_mutex_lock(&thread_initexit_lock);
get_list_of_threads_ex(&threads, &num_threads, true );
for (i = 0; i < num_threads; i++) {
if (threads[i] == dcontext->thread_record)
remove_thread(threads[i]->id);
else
dynamo_other_thread_exit(threads[i]);
}
d_r_mutex_unlock(&thread_initexit_lock);
global_heap_free(threads,
num_threads * sizeof(thread_record_t *) HEAPACCT(ACCT_THREAD_MGT));
add_thread(get_process_id(), d_r_get_thread_id(), true ,
dcontext);
GLOBAL_STAT(num_threads) = 1;
# ifdef DEBUG
if (d_r_stats->loglevel > 0) {
dcontext->logfile = open_log_file(thread_logfile_name(), NULL, 0);
print_file(dcontext->logfile, "%s\n", dynamorio_version_string);
print_file(dcontext->logfile, "New log file for child %d forked by parent %d\n",
d_r_get_thread_id(), get_parent_id());
LOG(THREAD, LOG_TOP | LOG_THREADS, 1, "THREAD %d (dcontext " PFX ")\n\n",
d_r_get_thread_id(), dcontext);
}
# endif
num_threads = 1;
* on a fork -- probably everyone who makes a log file on init.
*/
fragment_fork_init(dcontext);
signal_fork_init(dcontext);
if (CLIENTS_EXIST()) {
instrument_fork_init(dcontext);
}
}
#endif
* application (as opposed to a client library that works with
* DynamoRIO in executing a target application). This makes DynamoRIO
* useful as an IA-32 disassembly library, etc.
*/
dcontext_t *
standalone_init(void)
{
dcontext_t *dcontext;
int count = atomic_add_exchange_int(&standalone_init_count, 1);
if (count > 1 || dynamo_initialized)
return GLOBAL_DCONTEXT;
standalone_library = true;
d_r_stats = &nonshared_stats;
IF_X64(dynamo_options.reachable_heap = false;)
dynamo_options.vm_base_near_app = false;
#if defined(INTERNAL) && defined(DEADLOCK_AVOIDANCE)
dynamo_options.deadlock_avoidance = false;
#endif
#ifdef UNIX
os_page_size_init((const char **)our_environ, is_our_environ_followed_by_auxv());
#endif
#ifdef WINDOWS
if (!syscalls_init())
return NULL;
#endif
d_r_config_init();
options_init();
vmm_heap_init();
d_r_heap_init();
dynamo_heap_initialized = true;
dynamo_vm_areas_init();
d_r_decode_init();
proc_init();
d_r_os_init();
config_heap_init();
#ifdef STANDALONE_UNIT_TEST
os_tls_init();
dcontext = create_new_dynamo_context(true , NULL, NULL);
set_thread_private_dcontext(dcontext);
ASSERT(get_thread_private_dcontext() == dcontext);
heap_thread_init(dcontext);
# ifdef DEBUG
nonshared_stats.logmask = LOG_ALL;
options_init();
if (d_r_stats->loglevel > 0) {
char initial_options[MAX_OPTIONS_STRING];
main_logfile = open_log_file(main_logfile_name(), NULL, 0);
print_file(main_logfile, "%s\n", dynamorio_version_string);
print_file(main_logfile, "Log file for standalone unit test\n");
get_dynamo_options_string(&dynamo_options, initial_options,
sizeof(initial_options), true);
SYSLOG_INTERNAL_INFO("Initial options = %s", initial_options);
print_file(main_logfile, "\n");
}
# endif
#else
* every thread, we just use global dcontext everywhere (i#548)
*/
dcontext = GLOBAL_DCONTEXT;
#endif
* file right now. the only loss if that we can't synch options: but that
* should be less important for standalone. We disabling synching.
*/
dynamo_options.dynamic_options = false;
dynamo_initialized = true;
return dcontext;
}
void
standalone_exit(void)
{
int count = atomic_add_exchange_int(&standalone_init_count, -1);
if (count != 0)
return;
doing_detach = true;
#ifdef STANDALONE_UNIT_TEST
dcontext_t *dcontext = get_thread_private_dcontext();
set_thread_private_dcontext(NULL);
heap_thread_exit(dcontext);
delete_dynamo_context(dcontext, true);
* the TLS magic read on Linux.
*/
#endif
config_heap_exit();
os_fast_exit();
os_slow_exit();
#if !defined(STANDALONE_UNIT_TEST) || !defined(AARCH64)
dynamo_vm_areas_exit();
#endif
#ifndef STANDALONE_UNIT_TEST
* and leave it alone.
*/
d_r_heap_exit();
vmm_heap_exit();
#endif
options_exit();
d_r_config_exit();
doing_detach = false;
standalone_library = false;
dynamo_initialized = false;
dynamo_options_initialized = false;
dynamo_heap_initialized = false;
options_detach();
}
* already cleaned up by the time we reach dynamo_shared_exit() for both
* debug and detach paths.
*/
void
dynamo_process_exit_with_thread_info(void)
{
perscache_fast_exit();
}
int
dynamo_shared_exit(thread_record_t *toexit
_IF_WINDOWS(bool detach_stacked_callbacks))
{
DEBUG_DECLARE(uint endtime);
dynamo_exited = true;
DODEBUG(endtime = query_time_seconds(););
LOG(GLOBAL, LOG_STATS, 1, "\n#### Statistics for entire process:\n");
LOG(GLOBAL, LOG_STATS, 1, "Total running time: %d seconds\n", endtime - starttime);
#ifdef PAPI
hardware_perfctr_exit();
#endif
#ifdef DEBUG
# if defined(INTERNAL) && defined(X86)
print_optimization_stats();
# endif
DOLOG(1, LOG_STATS, { dump_global_stats(false); });
#endif
if (SELF_PROTECT_ON_CXT_SWITCH) {
DELETE_LOCK(protect_info->lock);
global_unprotected_heap_free(protect_info,
sizeof(protect_info_t) HEAPACCT(ACCT_OTHER));
}
DELETE_RECURSIVE_LOCK(thread_in_DR_exclusion);
DOSTATS({
LOG(GLOBAL, LOG_TOP | LOG_THREADS, 1,
"fcache_stats_exit: before fragment cleanup\n");
DOLOG(1, LOG_CACHE, fcache_stats_exit(););
});
#ifdef RCT_IND_BRANCH
if (!DYNAMO_OPTION(thin_client))
rct_exit();
#endif
fragment_exit();
#ifdef ANNOTATIONS
annotation_exit();
#endif
jitopt_exit();
* components. Must be after fragment_exit() so that the client gets all the
* fragment_deleted() callbacks (xref PR 228156). FIXME - might be issues with the
* client trying to use api routines that depend on fragment state.
*/
instrument_exit_event();
if (d_r_get_num_threads() > 1)
synch_with_threads_at_exit(exit_synch_state(), false );
dynamo_exited_all_other_threads = true;
fragment_exit_post_sideline();
* If it is set earlier after the first synch-all, some client thread may
* have memory leak due to dynamo_thread_exit_pre_client being skipped in
* dynamo_thread_exit_common called from exiting client threads.
*/
dynamo_exited_and_cleaned = true;
destroy_event(dr_app_started);
destroy_event(dr_attach_finished);
loader_make_exit_calls(get_thread_private_dcontext());
if (get_thread_private_dcontext() != NULL)
loader_thread_exit(get_thread_private_dcontext());
* thread exit calls in loader_thread_exit().
*/
instrument_exit();
loader_exit();
if (toexit != NULL) {
* Restoring the teb fields or segment registers can only be done
* on the current thread, which must be toexit.
*/
#ifdef WINDOWS
* the kstats stack as below. To avoid a kstats assert on this new path we
* repeat it here but it seems like we shouldn't need it.
*/
KSTOP_REWIND_DC(get_thread_private_dcontext(), thread_measured);
KSTART_DC(get_thread_private_dcontext(), thread_measured);
#endif
ASSERT(toexit->id == d_r_get_thread_id());
dynamo_thread_exit();
}
if (IF_WINDOWS_ELSE(!detach_stacked_callbacks, true)) {
if (thread_lookup(d_r_get_thread_id()) == NULL) {
LOG(GLOBAL, LOG_TOP | LOG_THREADS, 1,
"Current thread never under DynamoRIO control, not exiting it\n");
} else {
* been at one time
*/
dynamo_thread_exit();
}
}
d_r_mutex_lock(&all_threads_lock);
global_heap_free(all_threads,
HASHTABLE_SIZE(ALL_THREADS_HASH_BITS) *
sizeof(thread_record_t *) HEAPACCT(ACCT_THREAD_MGT));
all_threads = NULL;
d_r_mutex_unlock(&all_threads_lock);
#ifdef WINDOWS
if (!INTERNAL_OPTION(noasynch) && INTERNAL_OPTION(private_loader) && !doing_detach)
callback_interception_unintercept();
* that fragment_exit->frees fragments->instrument_fragment_deleted->
* hide_tag_from_fragment->is_intercepted_app_pc won't crash. Xref PR 228156.
*/
if (!INTERNAL_OPTION(noasynch)) {
callback_interception_exit();
}
#endif
d_r_link_exit();
fcache_exit();
d_r_monitor_exit();
synch_exit();
d_r_arch_exit(IF_WINDOWS(detach_stacked_callbacks));
#ifdef CALL_PROFILE
* create a log file
*/
profile_callers_exit();
#endif
os_fast_exit();
os_slow_exit();
native_exec_exit();
vm_areas_exit();
perscache_slow_exit();
modules_exit();
* after vm_areas & perscache exits */
moduledb_exit();
#ifdef HOT_PATCHING_INTERFACE
if (DYNAMO_OPTION(hot_patching))
hotp_exit();
#endif
#ifdef WINDOWS
stack_free(exception_stack, EXCEPTION_STACK_SIZE);
exception_stack = NULL;
#endif
config_heap_exit();
d_r_heap_exit();
vmm_heap_exit();
diagnost_exit();
data_section_exit();
* any options that are needed for other exits, so do it prior to
* checking locks in debug build. We have a separate options_detach()
* which resets options for re-attach.
*/
options_exit();
utils_exit();
d_r_config_exit();
#ifdef KSTATS
kstat_exit();
#endif
DELETE_LOCK(all_threads_lock);
DELETE_LOCK(thread_initexit_lock);
DOLOG(1, LOG_STATS, {
* are inc-ed and dec-ed actually come down to 0
*/
dump_global_stats(false);
});
if (INTERNAL_OPTION(rstats_to_stderr))
dump_global_rstats_to_stderr();
statistics_exit();
#ifdef DEBUG
# ifdef DEADLOCK_AVOIDANCE
ASSERT(locks_not_closed() == 0);
# endif
dynamo_exited_log_and_stats = true;
if (main_logfile != STDERR) {
* right now */
file_t file_temp = main_logfile;
main_logfile = INVALID_FILE;
close_log_file(file_temp);
}
#else
# ifdef DEADLOCK_AVOIDANCE
ASSERT(locks_not_closed() == 0);
# endif
#endif
dynamo_initialized = false;
dynamo_started = false;
return SUCCESS;
}
NOINLINE int
dynamorio_app_exit(void)
{
return dynamo_process_exit();
}
* also sets dynamo_exited to true.
* does not resume the threads but does release the thread_initexit_lock.
*/
static void
synch_with_threads_at_exit(thread_synch_state_t synch_res, bool pre_exit)
{
int num_threads;
thread_record_t **threads;
DEBUG_DECLARE(bool ok;)
* problems) ignore it. XXX: retry instead?
*/
uint flags = THREAD_SYNCH_SUSPEND_FAILURE_IGNORE;
if (pre_exit) {
flags |= THREAD_SYNCH_SKIP_CLIENT_THREAD;
}
LOG(GLOBAL, LOG_TOP | LOG_THREADS, 1,
"\nsynch_with_threads_at_exit: cleaning up %d un-terminated threads\n",
d_r_get_num_threads());
#ifdef WINDOWS
wait_for_outstanding_nudges();
#endif
* doesn't make a difference here which we use (since the process is about
* to die).
* On Linux, however, we do not have dependencies on OS thread
* properties like we do on Windows (TEB, etc.), and our suspended
* threads use their sigstacks and ostd data structs, making cleanup
* while still catching other leaks more difficult: thus it's
* simpler to terminate and then clean up. FIXME: by terminating
* we'll raise SIGCHLD that may not have been raised natively if the
* whole group went down in a single SYS_exit_group. Instead we
* could have the suspended thread move from the sigstack-reliant
* loop to a stack-free loop (xref i#95).
*/
IF_UNIX(dynamo_exiting = true;)
DEBUG_DECLARE(ok =)
synch_with_all_threads(synch_res, &threads, &num_threads,
* only care about threads carrying fcache
* state can ignore us
*/
THREAD_SYNCH_NO_LOCKS_NO_XFER, flags);
ASSERT(ok);
ASSERT(threads == NULL && num_threads == 0);
* thread_initexit_lock for us! */
* while we hold the thread_initexit_lock so any new threads that
* are waiting on it won't get in our way (see thread_init()) */
dynamo_exited = true;
end_synch_with_all_threads(threads, num_threads, false );
}
static thread_synch_state_t
exit_synch_state(void)
{
thread_synch_state_t synch_res = IF_WINDOWS_ELSE(THREAD_SYNCH_SUSPENDED_AND_CLEANED,
THREAD_SYNCH_TERMINATED_AND_CLEANED);
#if defined(DR_APP_EXPORTS) && defined(UNIX)
if (dr_api_exit) {
* after dr_app_cleanup(). Note that today we don't fully support that
* anyway: the app should use dr_app_stop_and_cleanup() whose detach
* code won't come here.
*/
synch_res = THREAD_SYNCH_SUSPENDED_AND_CLEANED;
}
#endif
return synch_res;
}
#ifdef DEBUG
static int
dynamo_process_exit_cleanup(void)
{
if (!dynamo_exited && !INTERNAL_OPTION(nullcalls)) {
APP_EXPORT_ASSERT(dynamo_initialized, "Improper DynamoRIO initialization");
* allocated using a separate mmap and so is not part of some
* large unit that is de-allocated), as it is used in special
* circumstances to call us...FIXME: is this memory leak ok?
* is there a better solution besides assuming the app stack?
*/
# ifdef SIDELINE
if (dynamo_options.sideline) {
sideline_exit();
}
# endif
dynamo_process_exit_with_thread_info();
if (INTERNAL_OPTION(single_privileged_thread)) {
d_r_mutex_unlock(&thread_initexit_lock);
}
* all have gone through dynamo_thread_exit, so clean them up now
* so we can get stats about them
*
* we don't check control_all_threads b/c we're just killing
* the threads we know about here
*/
synch_with_threads_at_exit(exit_synch_state(), true );
* dynamorio_exited is set and we've killed all the theads we know
* about, assumption is that no other threads will be running in
* dynamorio code from here on out (esp. when we get into shared exit)
* that will do anything that could be dangerous (could possibly be
* a thread in the APC interception code prior to reaching thread_init
* but it will only global log and do thread_lookup which should be
* safe throughout) */
* we do some thread cleanup early for the final thread so we can delay
* the rest (PR 536058). This is a little risky in that we
* clean up dcontext->fragment_field, which is used for lots of
* things like couldbelinking (and thus we have to disable some API
* routines in the thread exit event: i#1989).
*/
dynamo_thread_exit_pre_client(get_thread_private_dcontext(), d_r_get_thread_id());
# ifdef WINDOWS
* of something going wrong if new thread is just hitting its init APC
*/
* before we unload the client lib (and thus we miss client
* exit crashes): xref PR 200207.
*/
if (!INTERNAL_OPTION(noasynch) && !INTERNAL_OPTION(private_loader)) {
callback_interception_unintercept();
}
# else
unhook_vsyscall();
# endif
return dynamo_shared_exit(NULL
_IF_WINDOWS(false ));
}
return SUCCESS;
}
#endif
int
dynamo_nullcalls_exit(void)
{
* simply to get perfctr numbers in a log file
*/
ASSERT(INTERNAL_OPTION(nullcalls));
#ifdef PAPI
hardware_perfctr_exit();
#endif
#ifdef DEBUG
if (main_logfile != STDERR) {
close_log_file(main_logfile);
main_logfile = INVALID_FILE;
}
#endif
dynamo_exited = true;
return SUCCESS;
}
int
dynamo_process_exit(void)
{
#ifndef DEBUG
bool each_thread;
#endif
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
synchronize_dynamic_options();
SYSLOG(SYSLOG_INFORMATION, INFO_PROCESS_STOP, 2, get_application_name(),
get_application_pid());
#ifdef DEBUG
if (!dynamo_exited) {
if (INTERNAL_OPTION(nullcalls)) {
* the only place we can print them out and exit
*/
dynamo_nullcalls_exit();
} else {
* interface is used, we are about to be gone from the process
* address space, so we clean up now
*/
LOG(GLOBAL, LOG_TOP, 1,
"\ndynamo_process_exit from thread " TIDFMT " -- cleaning up dynamo\n",
d_r_get_thread_id());
dynamo_process_exit_cleanup();
}
}
return SUCCESS;
#else
if (dynamo_exited)
return SUCCESS;
* we didn't create any IPC objects or anything that might be persistent
* beyond our death, we're not holding any systemwide locks, etc.
*/
* Note that windows prof_pcs duplicates the thread walk in d_r_os_exit()
* FIXME: should combine that thread walk with this one
*/
each_thread = TRACEDUMP_ENABLED();
# ifdef UNIX
each_thread = each_thread || INTERNAL_OPTION(profile_pcs);
# endif
# ifdef KSTATS
each_thread = each_thread || DYNAMO_OPTION(kstats);
# endif
each_thread = each_thread ||
* racy crashes (PR 470957) by not calling instrument_thread_exit()
*/
(!INTERNAL_OPTION(nullcalls) && dr_thread_exit_hook_exists() &&
!DYNAMO_OPTION(skip_thread_exit_at_exit));
if (DYNAMO_OPTION(synch_at_exit)
|| (!DYNAMO_OPTION(multi_thread_exit) && dr_exit_hook_exists()) ||
(!DYNAMO_OPTION(skip_thread_exit_at_exit) && dr_thread_exit_hook_exists())) {
* can have racy crashes at exit time (xref PR 470957).
*/
synch_with_threads_at_exit(exit_synch_state(), true );
} else
dynamo_exited = true;
if (each_thread) {
thread_record_t **threads;
int num, i;
d_r_mutex_lock(&thread_initexit_lock);
get_list_of_threads(&threads, &num);
for (i = 0; i < num; i++) {
if (IS_CLIENT_THREAD(threads[i]->dcontext))
continue;
if (TRACEDUMP_ENABLED() || true) {
* dr_fragment_deleted() callbacks.
*/
fragment_thread_exit(threads[i]->dcontext);
}
# ifdef UNIX
if (INTERNAL_OPTION(profile_pcs))
pcprofile_thread_exit(threads[i]->dcontext);
# endif
# ifdef KSTATS
if (DYNAMO_OPTION(kstats))
kstat_thread_exit(threads[i]->dcontext);
# endif
if (!INTERNAL_OPTION(nullcalls) && !DYNAMO_OPTION(skip_thread_exit_at_exit)) {
instrument_thread_exit_event(threads[i]->dcontext);
if (threads[i]->id != d_r_get_thread_id())
loader_thread_exit(threads[i]->dcontext);
}
}
global_heap_free(threads,
num * sizeof(thread_record_t *) HEAPACCT(ACCT_THREAD_MGT));
d_r_mutex_unlock(&thread_initexit_lock);
}
dynamo_process_exit_with_thread_info();
* interface we need to call fragment_exit to get all the fragment deleted events. */
if (TRACEDUMP_ENABLED() || dr_fragment_deleted_hook_exists())
fragment_exit();
if (!INTERNAL_OPTION(nullcalls)) {
# ifdef WINDOWS
* LdrUnloadDll isn't hooked if using the app loader.
*/
if (!INTERNAL_OPTION(noasynch) && !INTERNAL_OPTION(private_loader)) {
callback_interception_unintercept();
}
# endif
# ifdef UNIX
if (dr_modload_hook_exists())
unhook_vsyscall();
# endif
* fragment_deleted() callbacks (xref PR 228156). FIXME - might be issues
* with the client trying to use api routines that depend on fragment state.
*/
instrument_exit_event();
if (d_r_get_num_threads() > 1)
synch_with_threads_at_exit(exit_synch_state(), false );
dynamo_exited_all_other_threads = true;
* destructors, etc.
*/
if (!INTERNAL_OPTION(nullcalls) && !DYNAMO_OPTION(skip_thread_exit_at_exit))
loader_thread_exit(get_thread_private_dcontext());
* thread exit calls in loader_thread_exit().
*/
instrument_exit();
loader_exit();
# ifdef WINDOWS
if (!INTERNAL_OPTION(noasynch) && INTERNAL_OPTION(private_loader))
callback_interception_unintercept();
# endif
}
fragment_exit_post_sideline();
# ifdef CALL_PROFILE
profile_callers_exit();
# endif
# ifdef KSTATS
if (DYNAMO_OPTION(kstats))
kstat_exit();
# endif
os_fast_exit();
if (INTERNAL_OPTION(rstats_to_stderr))
dump_global_rstats_to_stderr();
return SUCCESS;
#endif
}
void
dynamo_exit_post_detach(void)
{
do_once_generation++;
dynamo_initialized = false;
dynamo_options_initialized = false;
dynamo_heap_initialized = false;
automatic_startup = false;
control_all_threads = false;
dr_api_entry = false;
dr_api_exit = false;
#ifdef UNIX
dynamo_exiting = false;
#endif
dynamo_exited = false;
dynamo_exited_all_other_threads = false;
dynamo_exited_and_cleaned = false;
#ifdef DEBUG
dynamo_exited_log_and_stats = false;
#endif
dynamo_resetting = false;
#ifdef UNIX
post_execve = false;
#endif
vm_areas_post_exit();
heap_post_exit();
}
dcontext_t *
create_new_dynamo_context(bool initial, byte *dstack_in, priv_mcontext_t *mc)
{
dcontext_t *dcontext;
size_t alloc = sizeof(dcontext_t) + proc_get_cache_line_size();
void *alloc_start =
(void *)((TEST(SELFPROT_GLOBAL, dynamo_options.protect_mask) &&
!TEST(SELFPROT_DCONTEXT, dynamo_options.protect_mask))
?
mem */
global_unprotected_heap_alloc(alloc HEAPACCT(ACCT_OTHER))
: global_heap_alloc(alloc HEAPACCT(ACCT_OTHER)));
dcontext = (dcontext_t *)proc_bump_to_end_of_cache_line((ptr_uint_t)alloc_start);
ASSERT(proc_is_cache_aligned(dcontext));
#ifdef X86
ASSERT(ALIGNED(get_mcontext(dcontext)->simd, ZMM_REG_SIZE));
ASSERT(sizeof(priv_mcontext_t) ==
IF_X64_ELSE(18, 10) * sizeof(reg_t) + PRE_XMM_PADDING +
MCXT_TOTAL_SIMD_SLOTS_SIZE + MCXT_TOTAL_OPMASK_SLOTS_SIZE);
#elif defined(ARM)
#endif
* Make sure to update create_callback_dcontext to shared
* fields across callback dcontexts for the same thread.
*/
memset(dcontext, 0x0, sizeof(dcontext_t));
dcontext->allocated_start = alloc_start;
if (initial) {
* stack. If mc passed, use its xsp; else use cur xsp (initial thread
* is on the app stack here: xref i#1105), for lower bound for dstack.
*/
byte *app_xsp;
if (mc == NULL)
GET_STACK_PTR(app_xsp);
else
app_xsp = (byte *)mc->xsp;
if (dstack_in == NULL) {
dcontext->dstack = (byte *)stack_alloc(DYNAMORIO_STACK_SIZE, app_xsp);
} else
dcontext->dstack = dstack_in;
#ifdef WINDOWS
DOCHECK(1, {
if (dcontext->dstack < app_xsp)
SYSLOG_INTERNAL_WARNING_ONCE("dstack is below app xsp");
});
#endif
} else {
ASSERT(dstack_in == NULL);
}
if (TEST(SELFPROT_DCONTEXT, dynamo_options.protect_mask)) {
dcontext->upcontext.separate_upcontext = global_unprotected_heap_alloc(
sizeof(unprotected_context_t) HEAPACCT(ACCT_OTHER));
LOG(GLOBAL, LOG_TOP, 2, "new dcontext=" PFX ", dcontext->upcontext=" PFX "\n",
dcontext, dcontext->upcontext.separate_upcontext);
dcontext->upcontext_ptr = dcontext->upcontext.separate_upcontext;
} else
dcontext->upcontext_ptr = &(dcontext->upcontext.upcontext);
#ifdef HOT_PATCHING_INTERFACE
DODEBUG(memset(&dcontext->hotp_excpt_state, -1, sizeof(dr_jmp_buf_t)););
#endif
ASSERT(dcontext->try_except.try_except_state == NULL);
DODEBUG({ dcontext->logfile = INVALID_FILE; });
dcontext->owning_thread = d_r_get_thread_id();
#ifdef UNIX
dcontext->owning_process = get_process_id();
#endif
* among all dcontext instances of a thread, so the caller must
* set those fields
*/
* which is executed for each dr_app_start() and each
* callback start
*/
return dcontext;
}
static void
delete_dynamo_context(dcontext_t *dcontext, bool free_stack)
{
if (free_stack) {
ASSERT(dcontext->dstack != NULL);
ASSERT(!is_currently_on_dstack(dcontext));
LOG(GLOBAL, LOG_THREADS, 1, "Freeing DR stack " PFX "\n", dcontext->dstack);
stack_free(dcontext->dstack, DYNAMORIO_STACK_SIZE);
}
ASSERT(dcontext->try_except.try_except_state == NULL);
if (TEST(SELFPROT_DCONTEXT, dynamo_options.protect_mask)) {
global_unprotected_heap_free(dcontext->upcontext.separate_upcontext,
sizeof(unprotected_context_t) HEAPACCT(ACCT_OTHER));
}
if (TEST(SELFPROT_GLOBAL, dynamo_options.protect_mask) &&
!TEST(SELFPROT_DCONTEXT, dynamo_options.protect_mask)) {
global_unprotected_heap_free(dcontext->allocated_start,
sizeof(dcontext_t) +
proc_get_cache_line_size() HEAPACCT(ACCT_OTHER));
} else {
global_heap_free(dcontext->allocated_start,
sizeof(dcontext_t) +
proc_get_cache_line_size() HEAPACCT(ACCT_OTHER));
}
}
* but for every callback, etc. that gets a fresh execution
* environment!
*/
void
initialize_dynamo_context(dcontext_t *dcontext)
{
* (fields kept across callbacks, like dstack, module-private fields, next &
* prev, etc.)
*/
memset(dcontext->upcontext_ptr, 0, sizeof(unprotected_context_t));
dcontext->initialized = true;
dcontext->whereami = DR_WHERE_APP;
dcontext->next_tag = NULL;
dcontext->native_exec_postsyscall = NULL;
memset(dcontext->native_retstack, 0, sizeof(dcontext->native_retstack));
dcontext->native_retstack_cur = 0;
dcontext->isa_mode = DEFAULT_ISA_MODE;
#ifdef ARM
dcontext->encode_state[0] = 0;
dcontext->encode_state[1] = 0;
dcontext->decode_state[0] = 0;
dcontext->decode_state[1] = 0;
#endif
dcontext->sys_num = 0;
#ifdef WINDOWS
dcontext->app_errno = 0;
# ifdef DEBUG
dcontext->is_client_thread_exiting = false;
# endif
dcontext->sys_param_base = NULL;
dcontext->aslr_context.sys_aslr_clobbered = 0;
dcontext->aslr_context.randomized_section_handle = INVALID_HANDLE_VALUE;
dcontext->aslr_context.original_image_section_handle = INVALID_HANDLE_VALUE;
dcontext->aslr_context.original_section_base = ASLR_INVALID_SECTION_BASE;
# ifdef DEBUG
dcontext->aslr_context.last_app_section_handle = INVALID_HANDLE_VALUE;
# endif
dcontext->ignore_enterexit = false;
#else
dcontext->sys_param0 = 0;
dcontext->sys_param1 = 0;
dcontext->sys_param2 = 0;
#endif
#ifdef UNIX
dcontext->signals_pending = 0;
#endif
* or in create_callback_dcontext because they must be initialized differently
* in those two cases
*/
set_last_exit(dcontext, (linkstub_t *)get_starting_linkstub());
#ifdef PROFILE_RDTSC
dcontext->start_time = (uint64)0;
dcontext->prev_fragment = NULL;
dcontext->cache_frag_count = (uint64)0;
{
int i;
for (i = 0; i < 10; i++) {
dcontext->cache_time[i] = (uint64)0;
dcontext->cache_count[i] = (uint64)0;
}
}
#endif
#ifdef DEBUG
dcontext->in_opnd_disassemble = false;
#endif
#ifdef WINDOWS
* asynch_target to determine where the next app pc to execute is
* stored. Init it to 0 to indicate that this context's most recent
* syscall was not executed from handle_system_call().
*/
dcontext->asynch_target = NULL;
* created; we shouldn't zero them here, they may have valid data
*/
dcontext->valid = true;
#endif
#ifdef HOT_PATCHING_INTERFACE
dcontext->nudge_thread = false;
#endif
#ifdef CHECK_RETURNS_SSE2
* go ahead and use eax, it's dead (about to return)
*/
# ifdef UNIX
asm("movl $0, %eax");
asm("pinsrw $7,%eax,%xmm7");
# else
# error NYI
# endif
#endif
* read when last_exit indicates a coarse exit, which sets the fields.
*/
dcontext->go_native = false;
}
#ifdef WINDOWS
dcontext_t *
create_callback_dcontext(dcontext_t *old_dcontext)
{
dcontext_t *new_dcontext = create_new_dynamo_context(false, NULL, NULL);
new_dcontext->valid = false;
new_dcontext->owning_thread = old_dcontext->owning_thread;
# ifdef UNIX
new_dcontext->owning_process = old_dcontext->owning_process;
# endif
new_dcontext->thread_record = old_dcontext->thread_record;
ASSERT(old_dcontext->dstack != NULL);
new_dcontext->dstack = old_dcontext->dstack;
new_dcontext->isa_mode = old_dcontext->isa_mode;
new_dcontext->link_field = old_dcontext->link_field;
new_dcontext->monitor_field = old_dcontext->monitor_field;
new_dcontext->fcache_field = old_dcontext->fcache_field;
new_dcontext->fragment_field = old_dcontext->fragment_field;
new_dcontext->heap_field = old_dcontext->heap_field;
new_dcontext->vm_areas_field = old_dcontext->vm_areas_field;
new_dcontext->os_field = old_dcontext->os_field;
new_dcontext->synch_field = old_dcontext->synch_field;
* from within a callback.
*/
new_dcontext->win32_start_addr = old_dcontext->win32_start_addr;
new_dcontext->app_fls_data = old_dcontext->app_fls_data;
new_dcontext->priv_fls_data = old_dcontext->priv_fls_data;
new_dcontext->app_nt_rpc = old_dcontext->app_nt_rpc;
new_dcontext->priv_nt_rpc = old_dcontext->priv_nt_rpc;
new_dcontext->app_nls_cache = old_dcontext->app_nls_cache;
new_dcontext->priv_nls_cache = old_dcontext->priv_nls_cache;
new_dcontext->app_static_tls = old_dcontext->app_static_tls;
new_dcontext->priv_static_tls = old_dcontext->priv_static_tls;
new_dcontext->app_stack_limit = old_dcontext->app_stack_limit;
new_dcontext->app_stack_base = old_dcontext->app_stack_base;
new_dcontext->teb_base = old_dcontext->teb_base;
# ifdef UNIX
new_dcontext->signal_field = old_dcontext->signal_field;
new_dcontext->pcprofile_field = old_dcontext->pcprofile_field;
# endif
new_dcontext->private_code = old_dcontext->private_code;
new_dcontext->client_data = old_dcontext->client_data;
# ifdef DEBUG
new_dcontext->logfile = old_dcontext->logfile;
new_dcontext->thread_stats = old_dcontext->thread_stats;
# endif
# ifdef DEADLOCK_AVOIDANCE
new_dcontext->thread_owned_locks = old_dcontext->thread_owned_locks;
# endif
# ifdef KSTATS
new_dcontext->thread_kstats = old_dcontext->thread_kstats;
# endif
*
* FIXME: Yes need to share when swapping at NtCallbackReturn, but
* want to keep old so when return from cb will do post-syscall for
* syscall that triggered cb in the first place!
* Plus, new cb calls initialize_dynamo_context(), which clears this field
* anyway! This all works now b/c we don't have alertable syscalls
* that we do post-syscall processing on.
*/
new_dcontext->upcontext_ptr->at_syscall = old_dcontext->upcontext_ptr->at_syscall;
# ifdef HOT_PATCHING_INTERFACE
* be only because a hot patch made a system call with a callback. This is
* a bug because hot patches can't do system calls, let alone one with
* callbacks.
*/
DOCHECK(1, {
dr_jmp_buf_t empty;
memset(&empty, -1, sizeof(dr_jmp_buf_t));
ASSERT(memcmp(&old_dcontext->hotp_excpt_state, &empty, sizeof(dr_jmp_buf_t)) ==
0);
});
new_dcontext->nudge_thread = old_dcontext->nudge_thread;
# endif
ASSERT(old_dcontext->try_except.try_except_state == NULL);
new_dcontext->local_state = old_dcontext->local_state;
# ifdef WINDOWS
new_dcontext->aslr_context.last_child_padded =
old_dcontext->aslr_context.last_child_padded;
# endif
LOG(new_dcontext->logfile, LOG_TOP, 2, "made new dcontext " PFX " (old=" PFX ")\n",
new_dcontext, old_dcontext);
return new_dcontext;
}
#endif
bool
is_thread_initialized(void)
{
#if defined(UNIX) && defined(HAVE_TLS)
* get_thread_private_dcontext() when we only really need the
* check for this call here, so we explicitly check.
*/
if (get_tls_thread_id() != get_sys_thread_id())
return false;
#endif
return (get_thread_private_dcontext() != NULL);
}
bool
is_thread_known(thread_id_t tid)
{
return (thread_lookup(tid) != NULL);
}
#ifdef UNIX
* exited, but we can't easily clean up it for real immediately
*/
void
mark_thread_execve(thread_record_t *tr, bool execve)
{
ASSERT((execve && !tr->execve) || (!execve && tr->execve));
tr->execve = execve;
d_r_mutex_lock(&all_threads_lock);
if (execve) {
* more than one
*/
ASSERT(num_execve_threads == 0);
num_execve_threads++;
} else {
ASSERT(num_execve_threads > 0);
num_execve_threads--;
}
d_r_mutex_unlock(&all_threads_lock);
}
#endif
int
d_r_get_num_threads(void)
{
return num_known_threads IF_UNIX(-num_execve_threads);
}
bool
is_last_app_thread(void)
{
return (d_r_get_num_threads() == get_num_client_threads() + 1);
}
* NOT LIMITED to those currently under DR control!
* It returns an array of thread_record_t* and the length of the array
* The caller must free the array using global_heap_free
* The caller must hold the thread_initexit_lock to ensure that threads
* are not created or destroyed before the caller is done with the list
* The caller CANNOT be could_be_linking, else a deadlock with flushing
* can occur (unless the caller is the one flushing)
*/
static void
get_list_of_threads_common(thread_record_t ***list,
int *num _IF_UNIX(bool include_execve))
{
int i, cur = 0, max_num;
thread_record_t *tr;
thread_record_t **mylist;
* couldbelinking -- else a deadlock w/ flush!
* FIXME: this assert should be on any acquisition of thread_initexit_lock!
*/
ASSERT(is_self_flushing() || !is_self_couldbelinking());
ASSERT(all_threads != NULL);
ASSERT_OWN_MUTEX(true, &thread_initexit_lock);
d_r_mutex_lock(&all_threads_lock);
max_num = IF_UNIX_ELSE((include_execve || dynamo_exiting) ? num_known_threads
: d_r_get_num_threads(),
d_r_get_num_threads());
mylist = (thread_record_t **)global_heap_alloc(
max_num * sizeof(thread_record_t *) HEAPACCT(ACCT_THREAD_MGT));
for (i = 0; i < HASHTABLE_SIZE(ALL_THREADS_HASH_BITS); i++) {
for (tr = all_threads[i]; tr != NULL; tr = tr->next) {
* no race b/c vfork suspends the parent. xref i#237/PR 498284.
*/
if (IF_UNIX_ELSE(!tr->execve || include_execve || dynamo_exiting, true)) {
mylist[cur] = tr;
cur++;
}
}
}
ASSERT(cur > 0);
IF_WINDOWS(ASSERT(cur == max_num));
if (cur < max_num) {
mylist = (thread_record_t **)global_heap_realloc(
mylist, max_num, cur, sizeof(thread_record_t *) HEAPACCT(ACCT_THREAD_MGT));
}
*num = cur;
*list = mylist;
d_r_mutex_unlock(&all_threads_lock);
}
void
get_list_of_threads(thread_record_t ***list, int *num)
{
get_list_of_threads_common(list, num _IF_UNIX(false));
}
#ifdef UNIX
void
get_list_of_threads_ex(thread_record_t ***list, int *num, bool include_execve)
{
get_list_of_threads_common(list, num, include_execve);
}
#endif
* avoid races
*/
thread_record_t *
thread_lookup(thread_id_t tid)
{
thread_record_t *tr;
uint hindex;
* FIXME: no way to tell who has initexit_lock
*/
ASSERT(mutex_testlock(&thread_initexit_lock) || tid == d_r_get_thread_id());
hindex = HASH_FUNC_BITS(tid, ALL_THREADS_HASH_BITS);
d_r_mutex_lock(&all_threads_lock);
if (all_threads == NULL) {
tr = NULL;
} else {
tr = all_threads[hindex];
}
while (tr != NULL) {
if (tr->id == tid) {
d_r_mutex_unlock(&all_threads_lock);
return tr;
}
tr = tr->next;
}
d_r_mutex_unlock(&all_threads_lock);
return NULL;
}
* avoid races
*/
uint
get_thread_num(thread_id_t tid)
{
thread_record_t *tr = thread_lookup(tid);
if (tr != NULL)
return tr->num;
else
return 0;
}
void
add_thread(IF_WINDOWS_ELSE_NP(HANDLE hthread, process_id_t pid), thread_id_t tid,
bool under_dynamo_control, dcontext_t *dcontext)
{
thread_record_t *tr;
uint hindex;
ASSERT(all_threads != NULL);
tr = (thread_record_t *)global_heap_alloc(sizeof(thread_record_t)
HEAPACCT(ACCT_THREAD_MGT));
#ifdef WINDOWS
* Note that instead asking explicitly for THREAD_ALL_ACCESS or just for
* THREAD_TERMINATE|THREAD_SUSPEND_RESUME|THREAD_GET_CONTEXT|THREAD_SET_CONTEXT
* does not seem able to acquire more rights than simply duplicating the
* app handle gives.
*/
LOG(GLOBAL, LOG_THREADS, 1, "Thread %d app handle rights: " PFX "\n", tid,
nt_get_handle_access_rights(hthread));
duplicate_handle(NT_CURRENT_PROCESS, hthread, NT_CURRENT_PROCESS, &tr->handle, 0, 0,
DUPLICATE_SAME_ACCESS | DUPLICATE_SAME_ATTRIBUTES);
* (for synchronizing), and SET_CONTEXT (+ synchronizing requirements, for
* detach). All access includes this and quite a bit more. */
# if 0
* injected detach threads, have to ifdef out even the ASSERT_CURIOSITY
* (even a syslog internal warning is prob. to noisy for QA) */
ASSERT_CURIOSITY(TESTALL(THREAD_ALL_ACCESS, nt_get_handle_access_rights(tr->handle)));
# endif
LOG(GLOBAL, LOG_THREADS, 1, "Thread %d our handle rights: " PFX "\n", tid,
nt_get_handle_access_rights(tr->handle));
tr->retakeover = false;
#else
tr->pid = pid;
tr->execve = false;
#endif
tr->id = tid;
ASSERT(tid != INVALID_THREAD_ID);
tr->under_dynamo_control = under_dynamo_control;
tr->dcontext = dcontext;
if (dcontext != NULL)
dcontext->thread_record = tr;
d_r_mutex_lock(&all_threads_lock);
tr->num = threads_ever_count++;
hindex = HASH_FUNC_BITS(tr->id, ALL_THREADS_HASH_BITS);
tr->next = all_threads[hindex];
all_threads[hindex] = tr;
RSTATS_ADD_PEAK(num_threads, 1);
RSTATS_INC(num_threads_created);
num_known_threads++;
d_r_mutex_unlock(&all_threads_lock);
}
bool
remove_thread(IF_WINDOWS_(HANDLE hthread) thread_id_t tid)
{
thread_record_t *tr = NULL, *prevtr;
uint hindex = HASH_FUNC_BITS(tid, ALL_THREADS_HASH_BITS);
ASSERT(all_threads != NULL);
d_r_mutex_lock(&all_threads_lock);
for (tr = all_threads[hindex], prevtr = NULL; tr; prevtr = tr, tr = tr->next) {
if (tr->id == tid) {
if (prevtr)
prevtr->next = tr->next;
else
all_threads[hindex] = tr->next;
RSTATS_DEC(num_threads);
#ifdef UNIX
if (tr->execve) {
ASSERT(num_execve_threads > 0);
num_execve_threads--;
}
#endif
num_known_threads--;
#ifdef WINDOWS
close_handle(tr->handle);
#endif
global_heap_free(tr, sizeof(thread_record_t) HEAPACCT(ACCT_THREAD_MGT));
break;
}
}
d_r_mutex_unlock(&all_threads_lock);
return (tr != NULL);
}
DECLARE_FREQPROT_VAR(static bool reset_at_nth_thread_triggered, false);
* if dstack_in is NULL, then a dstack is allocated; else dstack_in is used
* as the thread's dstack
* mc can be NULL for the initial thread
* returns -1 if current thread has already been initialized
*/
* increased uninit_thread_count.
*/
int
dynamo_thread_init(byte *dstack_in, priv_mcontext_t *mc, void *os_data,
bool client_thread)
{
dcontext_t *dcontext;
bool reset_at_nth_thread_pending = false;
bool under_dynamo_control = true;
APP_EXPORT_ASSERT(dynamo_initialized || dynamo_exited || d_r_get_num_threads() == 0 ||
client_thread,
PRODUCT_NAME " not initialized");
if (INTERNAL_OPTION(nullcalls)) {
ASSERT(uninit_thread_count == 0);
return SUCCESS;
}
* for win32, in new_thread_setup for linux, in main init for 1st thread
*/
#if defined(WINDOWS) && defined(DR_APP_EXPORTS)
* take over all threads on dr_app_start(). Stack and pc checks aren't
* simple b/c it can be in ntdll waiting on a lock.
*/
if (dr_api_entry)
os_take_over_mark_thread(d_r_get_thread_id());
#endif
if (dynamo_initialized && !bb_lock_start)
pre_second_thread();
d_r_mutex_lock(&thread_initexit_lock);
* reach here on Windows despite init_apc_go_native (i#2600).
*/
ASSERT_BUG_NUM(2611, !doing_detach);
* clean up, initializing this thread then would be dangerous, better to
* wait here for the app to die.
*/
* debug build, or app_start app_exit interface */
while (dynamo_exited) {
* message */
DODEBUG_ONCE(LOG(GLOBAL, LOG_THREADS, 1,
"Thread %d reached initialization point while dynamo exiting, "
"waiting for app to exit\n",
d_r_get_thread_id()););
d_r_mutex_unlock(&thread_initexit_lock);
os_thread_yield();
* point */
d_r_mutex_lock(&thread_initexit_lock);
}
if (is_thread_initialized()) {
d_r_mutex_unlock(&thread_initexit_lock);
#if defined(WINDOWS) && defined(DR_APP_EXPORTS)
if (dr_api_entry)
os_take_over_unmark_thread(d_r_get_thread_id());
#endif
return -1;
}
os_tls_init();
dcontext = create_new_dynamo_context(true , dstack_in, mc);
initialize_dynamo_context(dcontext);
set_thread_private_dcontext(dcontext);
ASSERT(get_thread_private_dcontext() == dcontext);
dcontext->local_state = get_local_state();
if (mc != NULL)
*get_mcontext(dcontext) = *mc;
* the core should still get control of the thread at hook points to track
* what the application is doing & at patched points to execute hot patches.
* It is the same for thin_client except that there are fewer hooks, only to
* follow children.
*/
if (RUNNING_WITHOUT_CODE_CACHE())
under_dynamo_control = false;
* otherwise we'd like to do this only after we'd fully initialized the thread, but we
* hold the thread_initexit_lock, so nobody should be listing us -- thread_lookup
* on other than self, or a thread list, should only be done while the initexit_lock
* is held. CHECK: is this always correct? thread_lookup does have an assert
* to try and enforce but cannot tell who has the lock.
*/
add_thread(IF_WINDOWS_ELSE(NT_CURRENT_THREAD, get_process_id()), d_r_get_thread_id(),
under_dynamo_control, dcontext);
#ifdef UNIX
if (dstack_in != NULL) {
ASSERT(uninit_thread_count > 0);
ATOMIC_DEC(int, uninit_thread_count);
}
#endif
#if defined(WINDOWS) && defined(DR_APP_EXPORTS)
if (dr_api_entry)
os_take_over_unmark_thread(d_r_get_thread_id());
#endif
LOG(GLOBAL, LOG_TOP | LOG_THREADS, 1,
"\ndynamo_thread_init: %d thread(s) now, dcontext=" PFX ", #=%d, id=" TIDFMT
", pid=" PIDFMT "\n\n",
GLOBAL_STAT(num_threads), dcontext, get_thread_num(d_r_get_thread_id()),
d_r_get_thread_id(), get_process_id());
DOLOG(1, LOG_STATS, { dump_global_stats(false); });
#ifdef DEBUG
if (d_r_stats->loglevel > 0) {
dcontext->logfile = open_log_file(thread_logfile_name(), NULL, 0);
print_file(dcontext->logfile, "%s\n", dynamorio_version_string);
} else {
dcontext->logfile = INVALID_FILE;
}
DOLOG(1, LOG_TOP | LOG_THREADS, {
LOG(THREAD, LOG_TOP | LOG_THREADS, 1, PRODUCT_NAME " built with: %s\n",
DYNAMORIO_DEFINES);
LOG(THREAD, LOG_TOP | LOG_THREADS, 1, PRODUCT_NAME " built on: %s\n",
dynamorio_buildmark);
});
LOG(THREAD, LOG_TOP | LOG_THREADS, 1, "%sTHREAD %d (dcontext " PFX ")\n\n",
client_thread ? "CLIENT " : "", d_r_get_thread_id(), dcontext);
LOG(THREAD, LOG_TOP | LOG_THREADS, 1,
"DR stack is " PFX "-" PFX " (passed in " PFX ")\n",
dcontext->dstack - DYNAMORIO_STACK_SIZE, dcontext->dstack, dstack_in);
#endif
#ifdef DEADLOCK_AVOIDANCE
locks_thread_init(dcontext);
#endif
heap_thread_init(dcontext);
DOSTATS({ stats_thread_init(dcontext); });
#ifdef KSTATS
kstat_thread_init(dcontext);
#endif
os_thread_init(dcontext, os_data);
arch_thread_init(dcontext);
synch_thread_init(dcontext);
if (!DYNAMO_OPTION(thin_client))
vm_areas_thread_init(dcontext);
monitor_thread_init(dcontext);
fcache_thread_init(dcontext);
link_thread_init(dcontext);
fragment_thread_init(dcontext);
os_thread_init_finalize(dcontext, os_data);
* iterating over threads, B) mutex for add_thread, and C) mutex for synch_field
* to be set up.
* So we release it to shrink the time spent w/ this big lock, in particular
* to avoid holding it while running private lib thread init code (i#875).
*/
d_r_mutex_unlock(&thread_initexit_lock);
instrument_client_thread_init(dcontext, client_thread);
loader_thread_init(dcontext);
if (!DYNAMO_OPTION(thin_client)) {
* Note that we are calling this prior to instrument_init()
* now (PR 216936), which is required to initialize
* the client dcontext field prior to instrument_init().
*/
instrument_thread_init(dcontext, client_thread, mc != NULL);
#ifdef SIDELINE
if (dynamo_options.sideline) {
sideline_start();
}
#endif
}
* call fcache_reset_all_caches_proactively while holding it due to
* rank order of reset_pending_lock which we must also hold -- so we
* set a local bool reset_at_nth_thread_pending
*/
if (DYNAMO_OPTION(reset_at_nth_thread) != 0 && !reset_at_nth_thread_triggered &&
(uint)d_r_get_num_threads() == DYNAMO_OPTION(reset_at_nth_thread)) {
d_r_mutex_lock(&reset_pending_lock);
if (!reset_at_nth_thread_triggered) {
reset_at_nth_thread_triggered = true;
reset_at_nth_thread_pending = true;
}
d_r_mutex_unlock(&reset_pending_lock);
}
DOLOG(1, LOG_STATS, { dump_thread_stats(dcontext, false); });
if (reset_at_nth_thread_pending) {
d_r_mutex_lock(&reset_pending_lock);
fcache_reset_all_caches_proactively(RESET_ALL);
}
return SUCCESS;
}
* fragment_thread_exit(). Since this is called outside of dynamo_thread_exit()
* on process exit we assume fine to skip enter_threadexit().
*/
void
dynamo_thread_exit_pre_client(dcontext_t *dcontext, thread_id_t id)
{
* fragment exit, but real fcache exit needs to be after fragment exit
*/
#ifdef DEBUG
fcache_thread_exit_stats(dcontext);
#endif
* monitor_thread_exit remains later b/c of monitor_remove_fragment calls
*/
trace_abort_and_delete(dcontext);
fragment_thread_exit(dcontext);
IF_WINDOWS(loader_pre_client_thread_exit(dcontext));
instrument_thread_exit_event(dcontext);
}
* be true and the calling thread should hold the thread_initexit_lock
*/
static int
dynamo_thread_exit_common(dcontext_t *dcontext, thread_id_t id,
IF_WINDOWS_(bool detach_stacked_callbacks) bool other_thread)
{
dcontext_t *dcontext_tmp;
#ifdef WINDOWS
dcontext_t *dcontext_next;
int num_dcontext;
#endif
bool on_dstack = !other_thread && is_currently_on_dstack(dcontext);
local_state_t *local_state = dcontext->local_state;
if (INTERNAL_OPTION(nullcalls) || dcontext == NULL)
return SUCCESS;
enter_threadexit(dcontext);
if (!other_thread)
d_r_mutex_lock(&thread_initexit_lock);
ASSERT_OWN_MUTEX(true, &thread_initexit_lock);
#ifdef WINDOWS
* after we're made nolinking
*/
os_thread_stack_exit(dcontext);
if (dcontext->free_app_stack) {
byte *base;
ASSERT(dcontext->nudge_target == generic_nudge_target);
if (get_stack_bounds(dcontext, &base, NULL)) {
NTSTATUS res;
ASSERT(base != NULL);
res = nt_free_virtual_memory(base);
ASSERT(NT_SUCCESS(res));
} else {
ASSERT_NOT_REACHED();
}
}
#endif
#ifdef SIDELINE
* is still running! put it to sleep for duration of this routine!
*/
if (!DYNAMO_OPTION(thin_client)) {
if (dynamo_options.sideline) {
sideline_stop();
}
}
#endif
LOG(GLOBAL, LOG_TOP | LOG_THREADS, 1,
"\ndynamo_thread_exit (thread #%d id=" TIDFMT "): %d thread(s) now\n\n",
get_thread_num(id), id, GLOBAL_STAT(num_threads) - 1);
DOLOG(1, LOG_STATS, { dump_global_stats(false); });
LOG(THREAD, LOG_STATS | LOG_THREADS, 1, "\n## Statistics for this thread:\n");
#ifdef PROFILE_RDTSC
if (dynamo_options.profile_times) {
int i;
ASSERT(dcontext);
LOG(THREAD, LOG_STATS | LOG_THREADS, 1, "\nTop ten cache times:\n");
for (i = 0; i < 10; i++) {
if (dcontext->cache_time[i] > (uint64)0) {
uint top_part, bottom_part;
divide_int64_print(dcontext->cache_time[i], kilo_hertz, false, 3,
&top_part, &bottom_part);
LOG(THREAD, LOG_STATS | LOG_THREADS, 1,
"\t#%2d = %6u.%.3u ms, %9d hits\n", i + 1, top_part, bottom_part,
(int)dcontext->cache_count[i]);
}
}
LOG(THREAD, LOG_STATS | LOG_THREADS, 1, "\n");
}
#endif
* we do some thread cleanup early for the final thread so we can delay
* the rest (PR 536058)
*/
if (!dynamo_exited_and_cleaned)
dynamo_thread_exit_pre_client(dcontext, id);
if (!DYNAMO_OPTION(thin_client))
instrument_thread_exit(dcontext);
* This must be called after dynamo_thread_exit_pre_client where
* we called event callbacks.
*/
if (!other_thread) {
dynamo_thread_not_under_dynamo(dcontext);
#ifdef WINDOWS
* context switches. os_loader_exit() will call this, but it has no
* dcontext, so it won't swap internal TEB fields.
*/
swap_peb_pointer(dcontext, false );
#endif
}
* TRY_EXCEPT when calling the priv lib entry routine
*/
if (!dynamo_exited ||
(other_thread &&
(IF_WINDOWS_ELSE(!doing_detach, true) ||
dcontext->owning_thread != d_r_get_thread_id())))
loader_thread_exit(dcontext);
* alarm signals received during cleanup (we'll suppress if tls
* dc==NULL which seems the right thing to do: not worth our
* effort to pass to another thread if thread-group-shared alarm,
* and if thread-private then thread would have exited soon
* anyway). see PR 596127.
*/
* but we only do this at dynamorio_app_exit so who cares
*/
* get_thread_private_dcontext for app/dr state checks.
*/
if (id == d_r_get_thread_id())
set_thread_private_dcontext(NULL);
fcache_thread_exit(dcontext);
link_thread_exit(dcontext);
monitor_thread_exit(dcontext);
if (!DYNAMO_OPTION(thin_client))
vm_areas_thread_exit(dcontext);
synch_thread_exit(dcontext);
arch_thread_exit(dcontext _IF_WINDOWS(detach_stacked_callbacks));
os_thread_exit(dcontext, other_thread);
DOLOG(1, LOG_STATS, { dump_thread_stats(dcontext, false); });
#ifdef KSTATS
kstat_thread_exit(dcontext);
#endif
DOSTATS({ stats_thread_exit(dcontext); });
heap_thread_exit(dcontext);
#ifdef DEADLOCK_AVOIDANCE
locks_thread_exit(dcontext);
#endif
#ifdef DEBUG
if (dcontext->logfile != INVALID_FILE && dcontext->logfile != STDERR) {
os_flush(dcontext->logfile);
close_log_file(dcontext->logfile);
}
#endif
remove_thread(IF_WINDOWS_(NT_CURRENT_THREAD) id);
dcontext_tmp = dcontext;
#ifdef WINDOWS
num_dcontext = 0;
while (dcontext_tmp) {
num_dcontext++;
dcontext_next = dcontext_tmp->prev_unused;
delete_dynamo_context(dcontext_tmp,
dcontext_tmp == dcontext
&& !on_dstack );
dcontext_tmp = dcontext_next;
}
LOG(GLOBAL, LOG_STATS | LOG_THREADS, 1, "\tdynamo contexts used: %d\n", num_dcontext);
#else
delete_dynamo_context(dcontext_tmp, !on_dstack );
#endif
os_tls_exit(local_state, other_thread);
#ifdef SIDELINE
if (dynamo_options.sideline && d_r_get_num_threads() > 0) {
sideline_start();
}
#endif
if (!other_thread) {
d_r_mutex_unlock(&thread_initexit_lock);
* thread list, and a terminate targeting us could kill us in the middle
* of this call -- but this can't come before the unlock b/c the lock's
* in the data segment! (see case 3121)
* (note we do not re-protect for process exit, see !dynamo_exited check
* in exiting_dynamorio)
*/
if (!on_dstack) {
EXITING_DR();
* probably via dynamo_thread_stack_free_and_exit(), as the stack free
* must be done before the exit
*/
}
}
return SUCCESS;
}
NOINLINE int
dynamo_thread_exit(void)
{
dcontext_t *dcontext = get_thread_private_dcontext();
return dynamo_thread_exit_common(dcontext, d_r_get_thread_id(),
IF_WINDOWS_(false) false);
}
int
dynamo_other_thread_exit(thread_record_t *tr _IF_WINDOWS(bool detach_stacked_callbacks))
{
* under num_exits_dir_syscall, but for now rewinding all the way
*/
KSTOP_REWIND_DC(tr->dcontext, thread_measured);
KSTART_DC(tr->dcontext, thread_measured);
return dynamo_thread_exit_common(tr->dcontext, tr->id,
IF_WINDOWS_(detach_stacked_callbacks) true);
}
* The final steps are to free the stack and perform the exit hook.
*/
void
dynamo_thread_stack_free_and_exit(byte *stack)
{
if (stack != NULL) {
stack_free(stack, DYNAMORIO_STACK_SIZE);
* (fixes case 6967)
*/
EXITING_DR();
}
}
#ifdef DR_APP_EXPORTS
DR_APP_API int
dr_app_setup(void)
{
* more than one thread running, or we have to suspend all the threads.
* We should share the suspend-and-takeover loop (and for dr_app_setup_and_start
* share the takeover portion) from dr_app_start().
*/
int res;
dcontext_t *dcontext;
* We'll re-protect at the end of dynamorio_app_init().
*/
if (DATASEC_WRITABLE(DATASEC_RARELY_PROT) == 0)
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
dr_api_entry = true;
dynamo_control_via_attach = true;
res = dynamorio_app_init();
* races: i#2335): we delay until dr_app_start(). Plus the vsyscall hook is
* not set up until we find out the syscall method. Thus we're already
* "os_process_not_under_dynamorio".
* We can't as easily avoid initializing the thread TLS and then dropping
* it, however, as parts of init assume we have TLS.
*/
dcontext = get_thread_private_dcontext();
dynamo_thread_not_under_dynamo(dcontext);
return res;
}
DR_APP_API int
dr_app_cleanup(void)
{
thread_record_t *tr;
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
dr_api_exit = true;
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
* and must be balanced! On Linux, they track the shared itimer refcount,
* so a mismatch will lead to a refleak or negative refcount.
* dynamorio_app_exit() will call dynamo_thread_not_under_dynamo(), so we
* must ensure that we are under DR before calling it. Therefore, we
* require that the caller call dr_app_stop() before calling
* dr_app_cleanup(). However, we cannot make a usage assertion to that
* effect without addressing the FIXME comments in
* dynamo_thread_not_under_dynamo() about updating tr->under_dynamo_control.
*/
tr = thread_lookup(d_r_get_thread_id());
if (tr != NULL && tr->dcontext != NULL) {
os_process_under_dynamorio_initiate(tr->dcontext);
os_process_under_dynamorio_complete(tr->dcontext);
dynamo_thread_under_dynamo(tr->dcontext);
}
return dynamorio_app_exit();
}
void
dr_app_start_helper(priv_mcontext_t *mc)
{
apicheck(dynamo_initialized, PRODUCT_NAME " not initialized");
LOG(GLOBAL, LOG_TOP, 1, "dr_app_start in thread " TIDFMT "\n", d_r_get_thread_id());
LOG(THREAD_GET, LOG_TOP, 1, "dr_app_start\n");
if (!INTERNAL_OPTION(nullcalls)) {
* See dr_app_start in x86.asm.
*/
mc->xsp += DYNAMO_START_XSP_ADJUST;
dynamo_start(mc);
}
}
* under dynamo control.
* NOINLINE because dr_app_stop is a stopping point.
*/
DR_APP_API NOINLINE void
dr_app_stop(void)
{
}
DR_APP_API NOINLINE void
dr_app_stop_and_cleanup(void)
{
dr_app_stop_and_cleanup_with_stats(NULL);
}
DR_APP_API NOINLINE void
dr_app_stop_and_cleanup_with_stats(dr_stats_t *drstats)
{
* is not. We should try and have dr_app_cleanup() take this detach path
* here (and then we can simplify exit_synch_state()) but it's more complicated
* and we need to resolve the unbounded dr_app_stop() time.
*/
if (dynamo_initialized && !dynamo_exited && !doing_detach) {
# ifdef WINDOWS
swap_peb_pointer(get_thread_private_dcontext(), true );
# endif
detach_on_permanent_stack(true , true , drstats);
}
}
DR_APP_API int
dr_app_setup_and_start(void)
{
int r = dr_app_setup();
if (r == SUCCESS)
dr_app_start();
return r;
}
#endif
*/
void
dynamo_thread_under_dynamo(dcontext_t *dcontext)
{
LOG(THREAD, LOG_ASYNCH, 2, "thread %d under DR control\n", dcontext->owning_thread);
ASSERT(dcontext != NULL);
* see comments in not routine below
*/
os_thread_under_dynamo(dcontext);
#ifdef SIDELINE
if (dynamo_options.sideline) {
sideline_start();
}
#endif
dcontext->currently_stopped = false;
dcontext->go_native = false;
}
* This must be called by the owner of dcontext and not another
* non-executing thread.
*/
void
dynamo_thread_not_under_dynamo(dcontext_t *dcontext)
{
ASSERT_MESSAGE(CHKLVL_ASSERTS + 1 , "can only act on executing thread",
dcontext == get_thread_private_dcontext());
if (dcontext == NULL)
return;
LOG(THREAD, LOG_ASYNCH, 2, "thread %d not under DR control\n",
dcontext->owning_thread);
dcontext->currently_stopped = true;
os_thread_not_under_dynamo(dcontext);
#ifdef SIDELINE
if (dynamo_options.sideline) {
sideline_stop();
}
#endif
#ifdef DEBUG
os_flush(dcontext->logfile);
#endif
}
*/
void
dynamorio_take_over_threads(dcontext_t *dcontext)
{
* while we're checking one may be spawning additional threads.
*/
bool found_threads;
uint attempts = 0;
uint max_takeover_attempts = DYNAMO_OPTION(takeover_attempts);
os_process_under_dynamorio_initiate(dcontext);
* as handling signals.
*/
dynamo_thread_under_dynamo(dcontext);
signal_event(dr_app_started);
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
dynamo_started = true;
detacher_tid = INVALID_THREAD_ID;
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
* satisfy the parts of the init process that assume there are no races.
*/
do {
found_threads = os_take_over_all_unknown_threads(dcontext);
attempts++;
if (found_threads && !bb_lock_start)
bb_lock_start = true;
if (DYNAMO_OPTION(sleep_between_takeovers))
os_thread_sleep(1);
} while (found_threads && attempts < max_takeover_attempts);
os_process_under_dynamorio_complete(dcontext);
instrument_post_attach_event();
signal_event(dr_attach_finished);
if (found_threads) {
REPORT_FATAL_ERROR_AND_EXIT(FAILED_TO_TAKE_OVER_THREADS, 2,
get_application_name(), get_application_pid());
}
char buf[16];
int num_threads = d_r_get_num_threads();
if (num_threads > 1) {
snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), "%d", num_threads);
NULL_TERMINATE_BUFFER(buf);
SYSLOG(SYSLOG_INFORMATION, INFO_ATTACHED, 3, buf, get_application_name(),
get_application_pid());
}
}
void
dynamorio_app_take_over_helper(priv_mcontext_t *mc)
{
static bool have_taken_over = false;
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
APP_EXPORT_ASSERT(dynamo_initialized, PRODUCT_NAME " not initialized");
#ifdef RETURN_AFTER_CALL
* an injected thread turning on .C protection before the main thread
* sets this. */
dr_preinjected = true;
#endif
LOG(GLOBAL, LOG_TOP, 1, "taking over via preinject in %s\n", __FUNCTION__);
if (!INTERNAL_OPTION(nullcalls) && !have_taken_over) {
have_taken_over = true;
LOG(GLOBAL, LOG_TOP, 1, "dynamorio_app_take_over\n");
automatic_startup = true;
if (DYNAMO_OPTION(inject_primary))
take_over_primary_thread();
* unless we were auto-injected (preinject library calls this routine)
*/
control_all_threads = automatic_startup;
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
if (IF_WINDOWS_ELSE(!dr_earliest_injected && !dr_early_injected, true)) {
* See dynamorio_app_take_over in x86.asm.
*/
mc->xsp += DYNAMO_START_XSP_ADJUST;
}
* for our hooks.
* This is where apps hooked using appinit key are let go native.
* Even though control is going to native app code, we want
* automatic_startup and control_all_threads set.
*/
if (!RUNNING_WITHOUT_CODE_CACHE())
dynamo_start(mc);
} else
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
}
#ifdef WINDOWS
extern app_pc parent_early_inject_address;
void
dynamorio_app_take_over(void);
DYNAMORIO_EXPORT void
dynamorio_app_init_and_early_takeover(uint inject_location, void *restore_code)
{
int res;
ASSERT(!dynamo_initialized && !dynamo_exited);
* a single routine that also handles any early injection cleanup needed. */
ASSERT_NOT_IMPLEMENTED(inject_location != INJECT_LOCATION_KiUserApc);
ASSERT_CURIOSITY(INJECT_LOCATION_IS_LDR(inject_location));
* the address of LdrpLoadDll so we use the parent's value which is passed
* to us at the start of restore_code. FIXME - if we start using multiple
* inject locations we'll probably have to ensure we always pass this.
*/
if (INJECT_LOCATION_IS_LDR(inject_location)) {
parent_early_inject_address = *(app_pc *)restore_code;
}
dr_early_injected = true;
dr_early_injected_location = inject_location;
res = dynamorio_app_init();
ASSERT(res == SUCCESS);
ASSERT(dynamo_initialized && !dynamo_exited);
LOG(GLOBAL, LOG_TOP, 1, "taking over via early injection in %s\n", __FUNCTION__);
* first... could instead duplicate its tail here if we wrap this
* routine in asm or eqv. pass the continuation state in as args. */
ASSERT(inject_location != INJECT_LOCATION_KiUserApc);
dynamorio_app_take_over();
}
*/
void
dynamorio_earliest_init_takeover_C(byte *arg_ptr, priv_mcontext_t *mc)
{
int res;
bool earliest_inject;
earliest_inject = earliest_inject_init(arg_ptr);
if (earliest_inject) {
dr_earliest_injected = true;
dr_earliest_inject_args = arg_ptr;
} else
dr_early_injected = true;
res = dynamorio_app_init();
ASSERT(res == SUCCESS);
ASSERT(dynamo_initialized && !dynamo_exited);
LOG(GLOBAL, LOG_TOP, 1, "taking over via earliest injection in %s\n", __FUNCTION__);
* confusing the exec areas scan
*/
dynamorio_app_take_over_helper(mc);
}
#endif
* SELF-PROTECTION
*/
* because there's a window between us resuming the other threads and
* returning to our caller where another thread could clobber our return
* address or something.
*/
static void
dynamorio_protect(void)
{
ASSERT(SELF_PROTECT_ON_CXT_SWITCH);
LOG(GLOBAL, LOG_DISPATCH, 4, "dynamorio_protect thread=" TIDFMT "\n",
d_r_get_thread_id());
d_r_mutex_lock(&protect_info->lock);
ASSERT(protect_info->num_threads_unprot > 0);
if (protect_info->num_threads_unprot <= 0) {
* for case 7631/8030). However, this precludes an extra exit+enter
* pair from working properly (though an extra enter+exit will continue
* to work), though such a pair would have crashed if another thread
* had entered in the interim anyway.
*/
protect_info->num_threads_unprot = 0;
d_r_mutex_unlock(&protect_info->lock);
return;
}
protect_info->num_threads_unprot--;
if (protect_info->num_threads_unprot > 0) {
LOG(GLOBAL, LOG_DISPATCH, 4, "dynamorio_protect: not last thread => nop\n");
d_r_mutex_unlock(&protect_info->lock);
return;
}
SELF_PROTECT_GLOBAL(READONLY);
if (INTERNAL_OPTION(single_privileged_thread)) {
* _after_ protect data segment, but lock is in data segment!
*/
if (protect_info->num_threads_suspended > 0) {
thread_record_t *tr;
int i, num = 0;
* no threads can be added or removed so who cares if we
* access the data structure simultaneously with another
* reader of it
*/
for (i = 0; i < HASHTABLE_SIZE(ALL_THREADS_HASH_BITS); i++) {
for (tr = all_threads[i]; tr; tr = tr->next) {
if (tr->under_dynamo_control) {
os_thread_resume(all_threads[i]);
num++;
}
}
}
ASSERT(num == protect_info->num_threads_suspended);
protect_info->num_threads_suspended = 0;
}
d_r_mutex_unlock(&thread_initexit_lock);
}
* right places. if we were to leave this here we'd want to combine
* .fspdata and .cspdata for more efficient prot changes.
*/
SELF_PROTECT_DATASEC(DATASEC_FREQ_PROT);
SELF_PROTECT_DATASEC(DATASEC_CXTSW_PROT);
d_r_mutex_unlock(&protect_info->lock);
}
static void
dynamorio_unprotect(void)
{
ASSERT(SELF_PROTECT_ON_CXT_SWITCH);
d_r_mutex_lock(
&protect_info->lock);
protect_info->num_threads_unprot++;
if (protect_info->num_threads_unprot == 1) {
SELF_UNPROTECT_DATASEC(DATASEC_CXTSW_PROT);
* right places. if we were to leave this here we'd want to combine
* .fspdata and .cspdata for more efficient prot changes.
*/
SELF_UNPROTECT_DATASEC(DATASEC_FREQ_PROT);
if (INTERNAL_OPTION(single_privileged_thread)) {
* but need to guarantee no new threads while we're suspending them,
* and can't do that without setting a lock => need data segment!
*/
d_r_mutex_lock(&thread_initexit_lock);
if (d_r_get_num_threads() > 1) {
thread_record_t *tr;
int i;
ASSERT(protect_info->num_threads_suspended == 0);
* no threads can be added or removed so who cares if we
* access the data structure simultaneously with another
* reader of it
*/
for (i = 0; i < HASHTABLE_SIZE(ALL_THREADS_HASH_BITS); i++) {
for (tr = all_threads[i]; tr; tr = tr->next) {
if (tr->under_dynamo_control) {
DEBUG_DECLARE(bool ok =)
os_thread_suspend(all_threads[i]);
ASSERT(ok);
protect_info->num_threads_suspended++;
}
}
}
}
}
SELF_PROTECT_GLOBAL(WRITABLE);
}
* it was protected lazily
*/
d_r_mutex_unlock(&protect_info->lock);
LOG(GLOBAL, LOG_DISPATCH, 4, "dynamorio_unprotect thread=" TIDFMT "\n",
d_r_get_thread_id());
}
#ifdef DEBUG
const char *
get_data_section_name(app_pc pc)
{
uint i;
for (i = 0; i < DATASEC_NUM; i++) {
if (pc >= datasec_start[i] && pc < datasec_end[i])
return DATASEC_NAMES[i];
}
return NULL;
}
bool
check_should_be_protected(uint sec)
{
* another thread could be in an unprot window. We use some
* heuristics to try and identify bugs where a section is left
* unprot, but it's not easy.
*/
if (
* load a helper library and end up in d_r_dispatch() for
* syscall_while_native before DR is initialized.
*/
!dynamo_initialized ||
# ifdef WINDOWS
* thread synch, so don't count anything after that
*/
doing_detach ||
# endif
!TEST(DATASEC_SELFPROT[sec], DYNAMO_OPTION(protect_mask)) ||
DATASEC_PROTECTED(sec))
return true;
STATS_INC(datasec_not_prot);
* exit, and it's not worth grabbing thread_initexit_lock here..
*/
if (threads_ever_count == 1
# ifdef DR_APP_EXPORTS
* perfect protection
*/
&& !dr_api_entry
# endif
)
return false;
* Just return true and hope developer looks at datasec_not_prot stats.
* We do have an ASSERT_CURIOSITY on the stat in data_section_exit().
*/
return true;
}
# ifdef WINDOWS
bool
data_sections_enclose_region(app_pc start, app_pc end)
{
* we subtract each piece we find.
* It used to be that on 32-bit .data|.fspdata|.cspdata|.nspdata formed
* the only writable region, with .pdata between .data and .fspdata on 64.
* But building with VS2012, I'm seeing the sections in other orders (i#1075).
* And with x64 reachability we moved the interception buffer in .data,
* and marking it +rx results in sub-section calls to here.
*/
int i;
bool found_start = false, found_end = false;
ssize_t sz = end - start;
for (i = 0; i < DATASEC_NUM; i++) {
if (datasec_start[i] <= end && datasec_end[i] >= start) {
byte *overlap_start = MAX(datasec_start[i], start);
byte *overlap_end = MIN(datasec_end[i], end);
sz -= overlap_end - overlap_start;
}
}
return sz == 0;
}
# endif
#endif
static void
get_data_section_bounds(uint sec)
{
* sections, requiring specifying the order of sections (case 3789)!
* Should use an ld script to ensure that .nspdata is last, or find a unique
* attribute to force separation (perhaps mark as rwx, then
* remove the x at init time?) ld 2.15 puts it at the end, but
* ld 2.13 puts .got and .dynamic after it! For now we simply
* don't protect subsequent guys.
* On win32 there are no other rw sections, fortunately.
*/
ASSERT(sec >= 0 && sec < DATASEC_NUM);
ASSERT(IF_WINDOWS(IF_DEBUG(true ||))
TEST(DATASEC_SELFPROT[sec], dynamo_options.protect_mask));
d_r_mutex_lock(&datasec_lock[sec]);
ASSERT(datasec_start[sec] == NULL);
get_named_section_bounds(get_dynamorio_dll_start(), DATASEC_NAMES[sec],
&datasec_start[sec], &datasec_end[sec]);
d_r_mutex_unlock(&datasec_lock[sec]);
ASSERT(ALIGNED(datasec_start[sec], PAGE_SIZE));
ASSERT(ALIGNED(datasec_end[sec], PAGE_SIZE));
ASSERT(datasec_start[sec] < datasec_end[sec]);
#ifdef WINDOWS
if (IF_DEBUG(true ||) TEST(DATASEC_SELFPROT[sec], dynamo_options.protect_mask))
merge_writecopy_pages(datasec_start[sec], datasec_end[sec]);
#endif
}
#ifdef UNIX
* (such as when wrapping a function to get its local-scope statics in that section),
* but the VAR_IN_SECTION does the real work for us, just so long as we have one
* .section decl somewhere.
*/
DECLARE_DATA_SECTION(RARELY_PROTECTED_SECTION, "w")
DECLARE_DATA_SECTION(FREQ_PROTECTED_SECTION, "w")
DECLARE_DATA_SECTION(NEVER_PROTECTED_SECTION, "w")
END_DATA_SECTION_DECLARATIONS()
#endif
static void
data_section_init(void)
{
uint i;
for (i = 0; i < DATASEC_NUM; i++) {
if (datasec_start[i] != NULL) {
* We still retain our slightly later normal init position so we can
* log, etc. in normal runs.
*/
return;
}
ASSIGN_INIT_LOCK_FREE(datasec_lock[i], datasec_selfprot_lock);
if (IF_WINDOWS(IF_DEBUG(true ||))
TEST(DATASEC_SELFPROT[i], dynamo_options.protect_mask)) {
get_data_section_bounds(i);
}
}
DOCHECK(1, {
uint j;
for (i = 0; i < DATASEC_NUM; i++) {
for (j = i + 1; j < DATASEC_NUM; j++) {
ASSERT(datasec_start[i] >= datasec_end[j] ||
datasec_start[j] >= datasec_end[i]);
}
}
});
}
static void
data_section_exit(void)
{
uint i;
DOSTATS({
* A failure to re-protect should result in a ton of d_r_dispatch
* entrances w/ .data unprot, so should show up here.
* However, an app with threads that are initializing in DR and thus
* unprotected .data while other threads are running new code (such as
* on attach) can easily rack up hundreds of unprot cache entrances.
*/
ASSERT_CURIOSITY(GLOBAL_STAT(datasec_not_prot) < 5000);
});
for (i = 0; i < DATASEC_NUM; i++)
DELETE_LOCK(datasec_lock[i]);
}
#define DATASEC_WRITABLE_MOD(which, op) \
((which) == DATASEC_RARELY_PROT \
? (datasec_writable_rareprot op) \
: ((which) == DATASEC_CXTSW_PROT \
? (datasec_writable_cxtswprot op) \
: ((which) == DATASEC_FREQ_PROT \
? (datasec_writable_freqprot op) \
: (ASSERT_NOT_REACHED(), datasec_writable_neverprot))))
* that has a DO_ONCE, to avoid deadlock!
*/
void
protect_data_section(uint sec, bool writable)
{
ASSERT(sec >= 0 && sec < DATASEC_NUM);
ASSERT(TEST(DATASEC_SELFPROT[sec], dynamo_options.protect_mask));
* (data_section_init() has no dependences).
*/
if (datasec_start[sec] == NULL) {
ASSERT(!dynamo_initialized);
data_section_init();
}
d_r_mutex_lock(&datasec_lock[sec]);
ASSERT(datasec_start[sec] != NULL);
* thus, if making it writable, do that first, otherwise do it last.
* w/ ntdll this is not a problem.
*/
* calls simultaneously. The datasec_lock makes each individual call atomic,
* and if all calls are properly nested, our use of counters should result in
* the proper protection only after the final protect call and not in the
* middle of some other thread's writes to the data section.
*/
if (writable) {
* only protecting when the final thread leaves DR
*/
ASSERT_CURIOSITY(DATASEC_WRITABLE(sec) <= 2);
if (DATASEC_WRITABLE(sec) == 0) {
make_writable(datasec_start[sec], datasec_end[sec] - datasec_start[sec]);
STATS_INC(datasec_prot_changes);
} else
STATS_INC(datasec_prot_wasted_calls);
(void)DATASEC_WRITABLE_MOD(sec, ++);
}
LOG(TEST(DATASEC_SELFPROT[sec], SELFPROT_ON_CXT_SWITCH) ? THREAD_GET : GLOBAL,
LOG_VMAREAS, TEST(DATASEC_SELFPROT[sec], SELFPROT_ON_CXT_SWITCH) ? 3U : 2U,
"protect_data_section: thread " TIDFMT " %s (recur %d, stat %d) %s %s %d\n",
d_r_get_thread_id(), DATASEC_WRITABLE(sec) == 1 ? "changing" : "nop",
DATASEC_WRITABLE(sec), GLOBAL_STAT(datasec_not_prot), DATASEC_NAMES[sec],
writable ? "rw" : "r", DATASEC_WRITABLE(sec));
if (!writable) {
ASSERT(DATASEC_WRITABLE(sec) > 0);
(void)DATASEC_WRITABLE_MOD(sec, --);
if (DATASEC_WRITABLE(sec) == 0) {
make_unwritable(datasec_start[sec], datasec_end[sec] - datasec_start[sec]);
STATS_INC(datasec_prot_changes);
} else
STATS_INC(datasec_prot_wasted_calls);
}
d_r_mutex_unlock(&datasec_lock[sec]);
}
void
entering_dynamorio(void)
{
if (SELF_PROTECT_ON_CXT_SWITCH)
dynamorio_unprotect();
ASSERT(HOOK_ENABLED);
LOG(GLOBAL, LOG_DISPATCH, 3, "entering_dynamorio thread=" TIDFMT "\n",
d_r_get_thread_id());
STATS_INC(num_entering_DR);
if (INTERNAL_OPTION(single_thread_in_DR)) {
acquire_recursive_lock(&thread_in_DR_exclusion);
LOG(GLOBAL, LOG_DISPATCH, 3, "entering_dynamorio thread=" TIDFMT " count=%d\n",
d_r_get_thread_id(), thread_in_DR_exclusion.count);
}
}
void
exiting_dynamorio(void)
{
ASSERT(HOOK_ENABLED);
LOG(GLOBAL, LOG_DISPATCH, 3, "exiting_dynamorio thread=" TIDFMT "\n",
d_r_get_thread_id());
STATS_INC(num_exiting_DR);
if (INTERNAL_OPTION(single_thread_in_DR)) {
LOG(GLOBAL, LOG_DISPATCH, 3, "exiting_dynamorio thread=" TIDFMT " count=%d\n",
d_r_get_thread_id(), thread_in_DR_exclusion.count - 1);
release_recursive_lock(&thread_in_DR_exclusion);
}
if (SELF_PROTECT_ON_CXT_SWITCH && !dynamo_exited)
dynamorio_protect();
}
bool
is_on_initstack(byte *esp)
{
return (esp <= d_r_initstack && esp > d_r_initstack - DYNAMORIO_STACK_SIZE);
}
bool
is_on_dstack(dcontext_t *dcontext, byte *esp)
{
return (esp <= dcontext->dstack && esp > dcontext->dstack - DYNAMORIO_STACK_SIZE);
}
bool
is_currently_on_dstack(dcontext_t *dcontext)
{
byte *cur_esp;
GET_STACK_PTR(cur_esp);
return is_on_dstack(dcontext, cur_esp);
}
void
pre_second_thread(void)
{
* While normally we'll call this in the primary thread while not holding
* the lock, it's possible on Windows for an externally injected thread
* (or for a thread sneakily created by some native_exec code w/o going
* through ntdll wrappers) to appear. We solve the problem of the main
* thread currently holding bb_building_lock and us turning its
* unlock into an error by the bb_lock_would_have bool in
* SHARED_BB_UNLOCK().
*/
if (!bb_lock_start) {
d_r_mutex_lock(&bb_building_lock);
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
bb_lock_start = true;
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
d_r_mutex_unlock(&bb_building_lock);
}
}