| /* ********************************************************** |
| * Copyright (c) 2013-2014 Google, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of Google, 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 GOOGLE, 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. |
| */ |
| |
| /* DynamoRio eXtension utilities */ |
| |
| #include "dr_api.h" |
| #include "drx.h" |
| #include "hashtable.h" |
| #include "../ext_utils.h" |
| |
| /* We use drmgr but only internally. A user of drx will end up loading in |
| * the drmgr library, but it won't affect the user's code. |
| */ |
| #include "drmgr.h" |
| |
| #ifdef UNIX |
| # ifdef LINUX |
| # include "../../core/unix/include/syscall.h" |
| # else |
| # include <sys/syscall.h> |
| # endif |
| # include <signal.h> /* SIGKILL */ |
| #endif |
| |
| #ifdef DEBUG |
| # define ASSERT(x, msg) DR_ASSERT_MSG(x, msg) |
| # define IF_DEBUG(x) x |
| #else |
| # define ASSERT(x, msg) /* nothing */ |
| # define IF_DEBUG(x) /* nothing */ |
| #endif /* DEBUG */ |
| |
| #define MINSERT instrlist_meta_preinsert |
| #define TESTALL(mask, var) (((mask) & (var)) == (mask)) |
| #define TESTANY(mask, var) (((mask) & (var)) != 0) |
| #define TEST TESTANY |
| #define ALIGNED(x, alignment) ((((ptr_uint_t)x) & ((alignment)-1)) == 0) |
| #define ALIGN_FORWARD(x, alignment) \ |
| ((((ptr_uint_t)x) + ((alignment)-1)) & (~((alignment)-1))) |
| #define ALIGN_BACKWARD(x, alignment) \ |
| (((ptr_uint_t)x) & (~((ptr_uint_t)(alignment)-1))) |
| |
| /* Reserved note range values */ |
| enum { |
| DRX_NOTE_AFLAGS_RESTORE_BEGIN, |
| DRX_NOTE_AFLAGS_RESTORE_SAHF, |
| DRX_NOTE_AFLAGS_RESTORE_END, |
| DRX_NOTE_COUNT, |
| }; |
| static ptr_uint_t note_base; |
| #define NOTE_VAL(enum_val) ((void *)(ptr_int_t)(note_base + (enum_val))) |
| |
| static bool soft_kills_enabled; |
| |
| static void soft_kills_exit(void); |
| |
| /* For debugging */ |
| static uint verbose = 0; |
| |
| #undef NOTIFY |
| #define NOTIFY(n, ...) do { \ |
| if (verbose >= (n)) { \ |
| dr_fprintf(STDERR, __VA_ARGS__); \ |
| } \ |
| } while (0) |
| |
| /*************************************************************************** |
| * INIT |
| */ |
| |
| static int drx_init_count; |
| |
| DR_EXPORT |
| bool |
| drx_init(void) |
| { |
| int count = dr_atomic_add32_return_sum(&drx_init_count, 1); |
| if (count > 1) |
| return true; |
| |
| drmgr_init(); |
| note_base = drmgr_reserve_note_range(DRX_NOTE_COUNT); |
| ASSERT(note_base != DRMGR_NOTE_NONE, "failed to reserve note range"); |
| |
| return true; |
| } |
| |
| DR_EXPORT |
| void |
| drx_exit() |
| { |
| int count = dr_atomic_add32_return_sum(&drx_init_count, -1); |
| if (count != 0) |
| return; |
| |
| if (soft_kills_enabled) |
| soft_kills_exit(); |
| |
| drmgr_exit(); |
| } |
| |
| |
| /*************************************************************************** |
| * INSTRUCTION NOTE FIELD |
| */ |
| |
| /* For historical reasons we have this routine exported by drx. |
| * We just forward to drmgr. |
| */ |
| DR_EXPORT |
| ptr_uint_t |
| drx_reserve_note_range(size_t size) |
| { |
| return drmgr_reserve_note_range(size); |
| } |
| |
| /*************************************************************************** |
| * ANALYSIS |
| */ |
| |
| DR_EXPORT |
| bool |
| drx_aflags_are_dead(instr_t *where) |
| { |
| instr_t *instr; |
| uint flags; |
| for (instr = where; instr != NULL; instr = instr_get_next(instr)) { |
| /* we treat syscall/interrupt as aflags read */ |
| if (instr_is_syscall(instr) || instr_is_interrupt(instr)) |
| return false; |
| flags = instr_get_arith_flags(instr, DR_QUERY_DEFAULT); |
| if (TESTANY(EFLAGS_READ_6, flags)) |
| return false; |
| if (TESTALL(EFLAGS_WRITE_6, flags)) |
| return true; |
| if (instr_is_cti(instr)) { |
| if (instr_is_app(instr) && |
| (instr_is_ubr(instr) || instr_is_call_direct(instr))) { |
| instr_t *next = instr_get_next(instr); |
| opnd_t tgt = instr_get_target(instr); |
| /* continue on elision */ |
| if (next != NULL && instr_is_app(next) && |
| opnd_is_pc(tgt) && |
| opnd_get_pc(tgt) == instr_get_app_pc(next)) |
| continue; |
| } |
| /* unknown target, assume aflags is live */ |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| |
| /*************************************************************************** |
| * INSTRUMENTATION |
| */ |
| |
| /* insert a label instruction with note */ |
| static void |
| ilist_insert_note_label(void *drcontext, instrlist_t *ilist, instr_t *where, |
| void *note) |
| { |
| instr_t *instr = INSTR_CREATE_label(drcontext); |
| instr_set_note(instr, note); |
| MINSERT(ilist, where, instr); |
| } |
| |
| /* insert arithmetic flags saving code with more control |
| * - skip %eax save if !save_eax |
| * - save %eax to reg if reg is not DR_REG_NULL, |
| * - save %eax to slot otherwise |
| */ |
| static void |
| drx_save_arith_flags(void *drcontext, instrlist_t *ilist, instr_t *where, |
| bool save_eax, bool save_oflag, |
| dr_spill_slot_t slot, reg_id_t reg) |
| { |
| instr_t *instr; |
| |
| /* save %eax if necessary */ |
| if (save_eax) { |
| if (reg != DR_REG_NULL) { |
| ASSERT(reg >= DR_REG_START_GPR && reg <= DR_REG_STOP_GPR && |
| reg != DR_REG_XAX, "wrong dead reg"); |
| MINSERT(ilist, where, |
| INSTR_CREATE_mov_st(drcontext, |
| opnd_create_reg(reg), |
| opnd_create_reg(DR_REG_XAX))); |
| } else { |
| ASSERT(slot >= SPILL_SLOT_1 && slot <= SPILL_SLOT_MAX, |
| "wrong spill slot"); |
| dr_save_reg(drcontext, ilist, where, DR_REG_XAX, slot); |
| } |
| } |
| /* lahf */ |
| instr = INSTR_CREATE_lahf(drcontext); |
| MINSERT(ilist, where, instr); |
| if (save_oflag) { |
| /* seto %al */ |
| instr = INSTR_CREATE_setcc(drcontext, OP_seto, opnd_create_reg(DR_REG_AL)); |
| MINSERT(ilist, where, instr); |
| } |
| } |
| |
| /* insert arithmetic flags restore code with more control |
| * - skip %eax restore if !restore_eax |
| * - restore %eax from reg if reg is not DR_REG_NULL |
| * - restore %eax from slot otherwise |
| * |
| * Routine merge_prev_drx_aflags_switch looks for labels inserted by |
| * drx_restore_arith_flags, so changes to this routine may affect |
| * merge_prev_drx_aflags_switch. |
| */ |
| static void |
| drx_restore_arith_flags(void *drcontext, instrlist_t *ilist, instr_t *where, |
| bool restore_eax, bool restore_oflag, |
| dr_spill_slot_t slot, reg_id_t reg) |
| { |
| instr_t *instr; |
| ilist_insert_note_label(drcontext, ilist, where, |
| NOTE_VAL(DRX_NOTE_AFLAGS_RESTORE_BEGIN)); |
| if (restore_oflag) { |
| /* add 0x7f, %al */ |
| instr = INSTR_CREATE_add(drcontext, opnd_create_reg(DR_REG_AL), |
| OPND_CREATE_INT8(0x7f)); |
| MINSERT(ilist, where, instr); |
| } |
| /* sahf */ |
| instr = INSTR_CREATE_sahf(drcontext); |
| instr_set_note(instr, NOTE_VAL(DRX_NOTE_AFLAGS_RESTORE_SAHF)); |
| MINSERT(ilist, where, instr); |
| /* restore eax if necessary */ |
| if (restore_eax) { |
| if (reg != DR_REG_NULL) { |
| ASSERT(reg >= DR_REG_START_GPR && reg <= DR_REG_STOP_GPR && |
| reg != DR_REG_XAX, "wrong dead reg"); |
| MINSERT(ilist, where, |
| INSTR_CREATE_mov_st(drcontext, |
| opnd_create_reg(DR_REG_XAX), |
| opnd_create_reg(reg))); |
| } else { |
| ASSERT(slot >= SPILL_SLOT_1 && slot <= SPILL_SLOT_MAX, |
| "wrong spill slot"); |
| dr_restore_reg(drcontext, ilist, where, DR_REG_XAX, slot); |
| } |
| } |
| ilist_insert_note_label(drcontext, ilist, where, |
| NOTE_VAL(DRX_NOTE_AFLAGS_RESTORE_END)); |
| } |
| |
| /* Check if current instrumentation can be merged into previous aflags |
| * save/restore inserted by drx_restore_arith_flags. |
| * Returns NULL if cannot merge. Otherwise, returns the right insertion point, |
| * i.e., DRX_NOTE_AFLAGS_RESTORE_BEGIN label instr. |
| * |
| * This routine looks for labels inserted by drx_restore_arith_flags, |
| * so changes to drx_restore_arith_flags may affect this routine. |
| */ |
| static instr_t * |
| merge_prev_drx_aflags_switch(instr_t *where) |
| { |
| instr_t *instr; |
| #ifdef DEBUG |
| bool has_sahf = false; |
| #endif |
| |
| if (where == NULL) |
| return NULL; |
| instr = instr_get_prev(where); |
| if (instr == NULL) |
| return NULL; |
| if (!instr_is_label(instr)) |
| return NULL; |
| /* Check if prev instr is DRX_NOTE_AFLAGS_RESTORE_END. |
| * We bail even there is only a label instr in between, which |
| * might be a target of internal cti. |
| */ |
| if (instr_get_note(instr) != NOTE_VAL(DRX_NOTE_AFLAGS_RESTORE_END)) |
| return NULL; |
| |
| /* find DRX_NOTE_AFLAGS_RESTORE_BEGIN */ |
| for (instr = instr_get_prev(instr); |
| instr != NULL; |
| instr = instr_get_prev(instr)) { |
| if (instr_is_app(instr)) { |
| /* we do not expect any app instr */ |
| ASSERT(false, "drx aflags restore is corrupted"); |
| return NULL; |
| } |
| if (instr_is_label(instr)) { |
| if (instr_get_note(instr) == NOTE_VAL(DRX_NOTE_AFLAGS_RESTORE_BEGIN)) { |
| ASSERT(has_sahf, "missing sahf"); |
| return instr; |
| } |
| /* we do not expect any other label instr */ |
| ASSERT(false, "drx aflags restore is corrupted"); |
| return NULL; |
| #ifdef DEBUG |
| } else { |
| if (instr_get_note(instr) == NOTE_VAL(DRX_NOTE_AFLAGS_RESTORE_SAHF)) |
| has_sahf = true; |
| #endif |
| } |
| } |
| return NULL; |
| } |
| |
| static bool |
| counter_crosses_cache_line(byte *addr, size_t size) |
| { |
| size_t cache_line_size = proc_get_cache_line_size(); |
| if (ALIGN_BACKWARD(addr, cache_line_size) == |
| ALIGN_BACKWARD(addr+size-1, cache_line_size)) |
| return false; |
| return true; |
| } |
| |
| DR_EXPORT |
| bool |
| drx_insert_counter_update(void *drcontext, instrlist_t *ilist, instr_t *where, |
| dr_spill_slot_t slot, void *addr, int value, |
| uint flags) |
| { |
| instr_t *instr; |
| bool save_aflags = !drx_aflags_are_dead(where); |
| bool is_64 = TEST(DRX_COUNTER_64BIT, flags); |
| |
| if (drcontext == NULL) { |
| ASSERT(false, "drcontext cannot be NULL"); |
| return false; |
| } |
| if (!(slot >= SPILL_SLOT_1 && slot <= SPILL_SLOT_MAX)) { |
| ASSERT(false, "wrong spill slot"); |
| return false; |
| } |
| |
| /* check whether we can add lock */ |
| if (TEST(DRX_COUNTER_LOCK, flags)) { |
| if (IF_NOT_X64(is_64 ||) /* 64-bit counter in 32-bit mode */ |
| counter_crosses_cache_line((byte *)addr, is_64 ? 8 : 4)) |
| return false; |
| } |
| |
| /* if save_aflags, check if we can merge with the prev aflags save */ |
| if (save_aflags) { |
| instr = merge_prev_drx_aflags_switch(where); |
| if (instr != NULL) { |
| save_aflags = false; |
| where = instr; |
| } |
| } |
| |
| /* save aflags if necessary */ |
| if (save_aflags) { |
| drx_save_arith_flags(drcontext, ilist, where, |
| true /* save eax */, true /* save oflag */, |
| slot, DR_REG_NULL); |
| } |
| /* update counter */ |
| instr = INSTR_CREATE_add(drcontext, |
| OPND_CREATE_ABSMEM |
| (addr, IF_X64_ELSE((is_64 ? OPSZ_8 : OPSZ_4), OPSZ_4)), |
| OPND_CREATE_INT32(value)); |
| if (TEST(DRX_COUNTER_LOCK, flags)) |
| instr = LOCK(instr); |
| MINSERT(ilist, where, instr); |
| |
| #ifndef X64 |
| if (is_64) { |
| MINSERT(ilist, where, |
| INSTR_CREATE_adc(drcontext, |
| OPND_CREATE_ABSMEM |
| ((void *)((ptr_int_t)addr + 4), OPSZ_4), |
| OPND_CREATE_INT32(0))); |
| } |
| #endif /* !X64 */ |
| /* restore aflags if necessary */ |
| if (save_aflags) { |
| drx_restore_arith_flags(drcontext, ilist, where, |
| true /* restore eax */, true /* restore oflag */, |
| slot, DR_REG_NULL); |
| } |
| return true; |
| } |
| |
| /*************************************************************************** |
| * SOFT KILLS |
| */ |
| |
| /* Track callbacks in a simple list protected by a lock */ |
| typedef struct _cb_entry_t { |
| /* XXX: the bool return value is complex to support in some situations. We |
| * ignore the return value and always skip the app's termination of the |
| * child process for jobs containing multiple pids and for |
| * JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. If we wanted to not skip those we'd |
| * have to emulate the kill via NtTerminateProcess, which doesn't seem worth |
| * it when our two use cases (DrMem and drcov) don't need that kind of |
| * control. |
| */ |
| bool (*cb)(process_id_t, int); |
| struct _cb_entry_t *next; |
| } cb_entry_t; |
| |
| static cb_entry_t *cb_list; |
| static void *cb_lock; |
| |
| static bool |
| soft_kills_invoke_cbs(process_id_t pid, int exit_code) |
| { |
| cb_entry_t *e; |
| bool skip = false; |
| NOTIFY(1, "--drx-- parent %d soft killing pid %d code %d\n", dr_get_process_id(), |
| pid, exit_code); |
| dr_mutex_lock(cb_lock); |
| for (e = cb_list; e != NULL; e = e->next) { |
| /* If anyone wants to skip, we skip */ |
| skip = e->cb(pid, exit_code) || skip; |
| } |
| dr_mutex_unlock(cb_lock); |
| return skip; |
| } |
| |
| #ifdef WINDOWS |
| |
| /* The system calls we need to watch for soft kills. |
| * These are are in ntoskrnl so we get away without drsyscall. |
| */ |
| enum { |
| SYS_NUM_PARAMS_TerminateProcess = 2, |
| SYS_NUM_PARAMS_TerminateJobObject = 2, |
| SYS_NUM_PARAMS_SetInformationJobObject = 4, |
| SYS_NUM_PARAMS_Close = 1, |
| SYS_NUM_PARAMS_DuplicateObject = 7, |
| }; |
| |
| enum { |
| SYS_WOW64_IDX_TerminateProcess = 0, |
| SYS_WOW64_IDX_TerminateJobObject = 0, |
| SYS_WOW64_IDX_SetInformationJobObject = 7, |
| SYS_WOW64_IDX_Close = 0, |
| SYS_WOW64_IDX_DuplicateObject = 0, |
| }; |
| |
| static int sysnum_TerminateProcess; |
| static int sysnum_TerminateJobObject; |
| static int sysnum_SetInformationJobObject; |
| static int sysnum_Close; |
| static int sysnum_DuplicateObject; |
| |
| /* Table of job handles for which the app set JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE */ |
| #define JOB_TABLE_HASH_BITS 6 |
| static hashtable_t job_table; |
| |
| /* Entry in job_table. If it is present in the table, it should only be |
| * accessed while holding the table lock. |
| */ |
| typedef struct _job_info_t { |
| /* So far just a reference count. We don't need store a duplicated handle |
| * b/c we always have a valid app handle for this job. |
| */ |
| uint ref_count; |
| } job_info_t; |
| |
| /* We need CLS as we track data across syscalls, where TLS is not sufficient */ |
| static int cls_idx_soft; |
| |
| typedef struct _cls_soft_t { |
| /* For NtSetInformationJobObject */ |
| DWORD job_limit_flags_orig; |
| DWORD *job_limit_flags_loc; |
| /* For NtDuplicateObject */ |
| bool dup_proc_src_us; |
| bool dup_proc_dst_us; |
| ULONG dup_options; |
| HANDLE dup_src; |
| HANDLE *dup_dst; |
| job_info_t *dup_jinfo; |
| /* If we add data for more syscalls, we could use a union to save space */ |
| } cls_soft_t; |
| |
| /* XXX: should we have some kind of shared wininc/ dir for these common defines? |
| * I don't really want to include core/win32/ntdll.h here. |
| */ |
| |
| typedef LONG NTSTATUS; |
| #define NT_SUCCESS(status) (((NTSTATUS)(status)) >= 0) |
| |
| /* Since we invoke only in a client/privlib context, we can statically link |
| * with ntdll to call these syscall wrappers: |
| */ |
| #define GET_NTDLL(NtFunction, signature) NTSYSAPI NTSTATUS NTAPI NtFunction signature |
| |
| GET_NTDLL(NtQueryInformationJobObject, (IN HANDLE JobHandle, |
| IN JOBOBJECTINFOCLASS JobInformationClass, |
| OUT PVOID JobInformation, |
| IN ULONG JobInformationLength, |
| OUT PULONG ReturnLength OPTIONAL)); |
| |
| #define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) |
| |
| #define NT_CURRENT_PROCESS ((HANDLE)(ptr_int_t)-1) |
| |
| typedef LONG KPRIORITY; |
| |
| typedef enum _PROCESSINFOCLASS { |
| ProcessBasicInformation, |
| } PROCESSINFOCLASS; |
| |
| typedef struct _PROCESS_BASIC_INFORMATION { |
| NTSTATUS ExitStatus; |
| void * PebBaseAddress; |
| ULONG_PTR AffinityMask; |
| KPRIORITY BasePriority; |
| ULONG_PTR UniqueProcessId; |
| ULONG_PTR InheritedFromUniqueProcessId; |
| } PROCESS_BASIC_INFORMATION; |
| typedef PROCESS_BASIC_INFORMATION *PPROCESS_BASIC_INFORMATION; |
| |
| GET_NTDLL(NtQueryInformationProcess, (IN HANDLE ProcessHandle, |
| IN PROCESSINFOCLASS ProcessInformationClass, |
| OUT PVOID ProcessInformation, |
| IN ULONG ProcessInformationLength, |
| OUT PULONG ReturnLength OPTIONAL)); |
| GET_NTDLL(NtTerminateProcess, (IN HANDLE ProcessHandle, |
| IN NTSTATUS ExitStatus)); |
| |
| static ssize_t |
| num_job_object_pids(HANDLE job) |
| { |
| /* i#1401: despite what Nebbett says and MSDN hints at, on Win7 at least |
| * JobObjectBasicProcessIdList returning STATUS_BUFFER_OVERFLOW does NOT |
| * fill in any data at all. We thus have to query through a different |
| * mechanism. |
| */ |
| JOBOBJECT_BASIC_ACCOUNTING_INFORMATION info; |
| NTSTATUS res; |
| DWORD len; |
| res = NtQueryInformationJobObject(job, JobObjectBasicAccountingInformation, |
| &info, sizeof(info), &len); |
| NOTIFY(1, "--drx-- job 0x%x => %d pids len=%d res=0x%08x\n", |
| job, info.ActiveProcesses, len, res); |
| if (NT_SUCCESS(res)) |
| return info.ActiveProcesses; |
| else |
| return -1; |
| } |
| |
| static bool |
| get_job_object_pids(HANDLE job, JOBOBJECT_BASIC_PROCESS_ID_LIST *list, size_t list_sz) |
| { |
| NTSTATUS res; |
| res = NtQueryInformationJobObject(job, JobObjectBasicProcessIdList, |
| list, (ULONG) list_sz, NULL); |
| return NT_SUCCESS(res); |
| } |
| |
| /* XXX: should DR provide a routine to query this? */ |
| static bool |
| get_app_exit_code(int *exit_code) |
| { |
| ULONG got; |
| PROCESS_BASIC_INFORMATION info; |
| NTSTATUS res; |
| memset(&info, 0, sizeof(PROCESS_BASIC_INFORMATION)); |
| res = NtQueryInformationProcess(NT_CURRENT_PROCESS, ProcessBasicInformation, |
| &info, sizeof(PROCESS_BASIC_INFORMATION), &got); |
| if (!NT_SUCCESS(res) || got != sizeof(PROCESS_BASIC_INFORMATION)) |
| return false; |
| *exit_code = (int) info.ExitStatus; |
| return true; |
| } |
| |
| static void |
| soft_kills_context_init(void *drcontext, bool new_depth) |
| { |
| cls_soft_t *cls; |
| if (new_depth) { |
| cls = (cls_soft_t *) dr_thread_alloc(drcontext, sizeof(*cls)); |
| drmgr_set_cls_field(drcontext, cls_idx_soft, cls); |
| } else { |
| cls = (cls_soft_t *) drmgr_get_cls_field(drcontext, cls_idx_soft); |
| } |
| memset(cls, 0, sizeof(*cls)); |
| } |
| |
| static void |
| soft_kills_context_exit(void *drcontext, bool thread_exit) |
| { |
| if (thread_exit) { |
| cls_soft_t *cls = (cls_soft_t *) drmgr_get_cls_field(drcontext, cls_idx_soft); |
| dr_thread_free(drcontext, cls, sizeof(*cls)); |
| } |
| /* else, nothing to do: we leave the struct for re-use on next callback */ |
| } |
| |
| static int |
| soft_kills_get_sysnum(const char *name, int num_params, int wow64_idx) |
| { |
| static module_handle_t ntdll; |
| app_pc wrapper; |
| int sysnum; |
| |
| if (ntdll == NULL) { |
| module_data_t *data = dr_lookup_module_by_name("ntdll.dll"); |
| if (data == NULL) |
| return -1; |
| ntdll = data->handle; |
| dr_free_module_data(data); |
| } |
| wrapper = (app_pc) dr_get_proc_address(ntdll, name); |
| if (wrapper == NULL) |
| return -1; |
| sysnum = drmgr_decode_sysnum_from_wrapper(wrapper); |
| if (sysnum == -1) |
| return -1; |
| /* Ensure that DR intercepts these if we go native. |
| * XXX: better to only do this if client plans to use native execution |
| * to reduce the hook count and shrink chance of hook conflicts? |
| */ |
| if (!dr_syscall_intercept_natively(name, sysnum, num_params, wow64_idx)) |
| return -1; |
| return sysnum; |
| } |
| |
| static void |
| soft_kills_handle_job_termination(void *drcontext, HANDLE job, int exit_code) |
| { |
| ssize_t num_jobs = num_job_object_pids(job); |
| NOTIFY(1, "--drx-- for job 0x%x got %d jobs\n", job, num_jobs); |
| if (num_jobs > 0) { |
| JOBOBJECT_BASIC_PROCESS_ID_LIST *list; |
| size_t sz = sizeof(*list) + (num_jobs- 1)*sizeof(list->ProcessIdList[0]); |
| byte *buf = dr_thread_alloc(drcontext, sz); |
| list = (JOBOBJECT_BASIC_PROCESS_ID_LIST *) buf; |
| if (get_job_object_pids(job, list, sz)) { |
| uint i; |
| NOTIFY(1, "--drx-- for job 0x%x got %d jobs in list\n", job, |
| list->NumberOfProcessIdsInList); |
| for (i = 0; i < list->NumberOfProcessIdsInList; i++) { |
| process_id_t pid = list->ProcessIdList[i]; |
| if (!soft_kills_invoke_cbs(pid, exit_code)) { |
| /* Client is not terminating and requests not to skip the action. |
| * But since we have multiple pids, we go with a local decision |
| * here and emulate the kill. |
| */ |
| HANDLE phandle = dr_convert_pid_to_handle(pid); |
| if (phandle != INVALID_HANDLE_VALUE) |
| NtTerminateProcess(phandle, exit_code); |
| /* else, child stays alive: not much we can do */ |
| } |
| } |
| } |
| dr_thread_free(drcontext, buf, sz); |
| } /* else query failed: I'd issue a warning log msg if not inside drx library */ |
| } |
| |
| static void |
| soft_kills_free_job_info(void *ptr) |
| { |
| job_info_t *jinfo = (job_info_t *) ptr; |
| if (jinfo->ref_count == 0) |
| dr_global_free(jinfo, sizeof(*jinfo)); |
| } |
| |
| /* Called when the app closes a job handle "job". |
| * Caller must hold job_table lock. |
| * If "remove" is true, removes from the hashtable and de-allocates "jinfo", |
| * if refcount is 0. |
| */ |
| static void |
| soft_kills_handle_close(void *drcontext, job_info_t *jinfo, HANDLE job, int exit_code, |
| bool remove) |
| { |
| ASSERT(jinfo->ref_count > 0, "invalid ref count"); |
| jinfo->ref_count--; |
| if (jinfo->ref_count == 0) { |
| NOTIFY(1, "--drx-- closing kill-on-close handle 0x%x in pid %d\n", |
| job, dr_get_process_id()); |
| /* XXX: It's possible for us to miss a handle being closed from another |
| * process. In such a case, our ref count won't reach 0 and we'll |
| * fail to kill the child at all. |
| * If that handle value is re-used as a job object (else our job queryies |
| * will get STATUS_OBJECT_TYPE_MISMATCH) with no kill-on-close, we could |
| * incorrectly kill a job when the app is just closing its handle, but |
| * this would only happen when a job is being controlled from multiple |
| * processes. We'll have to live with the risk. We could watch |
| * NtCreateJobObject but it doesn't seem worth it. |
| */ |
| soft_kills_handle_job_termination(drcontext, job, exit_code); |
| } |
| if (remove) |
| hashtable_remove(&job_table, (void *)job); |
| } |
| |
| static bool |
| soft_kills_filter_syscall(void *drcontext, int sysnum) |
| { |
| return (sysnum == sysnum_TerminateProcess || |
| sysnum == sysnum_TerminateJobObject || |
| sysnum == sysnum_SetInformationJobObject || |
| sysnum == sysnum_Close || |
| sysnum == sysnum_DuplicateObject); |
| } |
| |
| static bool |
| soft_kills_pre_SetInformationJobObject(void *drcontext, cls_soft_t *cls) |
| { |
| HANDLE job = (HANDLE) dr_syscall_get_param(drcontext, 0); |
| JOBOBJECTINFOCLASS class = (JOBOBJECTINFOCLASS) |
| dr_syscall_get_param(drcontext, 1); |
| ULONG sz = (ULONG) dr_syscall_get_param(drcontext, 3); |
| /* MSDN claims that JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE requires an |
| * extended info struct, which we trust, though it seems odd as it's |
| * a flag in the basic struct. |
| */ |
| JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; |
| if (class == JobObjectExtendedLimitInformation && |
| sz >= sizeof(info) && |
| dr_safe_read((byte *)dr_syscall_get_param(drcontext, 2), |
| sizeof(info), &info, NULL)) { |
| if (TEST(JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, |
| info.BasicLimitInformation.LimitFlags)) { |
| /* Remove the kill-on-close flag from the syscall arg. |
| * We restore in post-syscall in case app uses the memory |
| * for something else. There is of course a race where another |
| * thread could use it and get the wrong value: -soft_kills isn't |
| * perfect. |
| */ |
| JOBOBJECT_EXTENDED_LIMIT_INFORMATION *ptr = |
| (JOBOBJECT_EXTENDED_LIMIT_INFORMATION *) |
| dr_syscall_get_param(drcontext, 2); |
| ULONG new_flags = info.BasicLimitInformation.LimitFlags & |
| (~JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE); |
| bool isnew; |
| job_info_t *jinfo; |
| cls->job_limit_flags_orig = info.BasicLimitInformation.LimitFlags; |
| cls->job_limit_flags_loc = &ptr->BasicLimitInformation.LimitFlags; |
| ASSERT(sizeof(cls->job_limit_flags_orig) == |
| sizeof(ptr->BasicLimitInformation.LimitFlags), "size mismatch"); |
| if (!dr_safe_write(cls->job_limit_flags_loc, |
| sizeof(ptr->BasicLimitInformation.LimitFlags), |
| &new_flags, NULL)) { |
| /* XXX: Any way we can send a WARNING on our failure to write? */ |
| NOTIFY(1, "--drx-- FAILED to remove kill-on-close from job 0x%x " |
| "in pid %d\n", job, dr_get_process_id()); |
| } else { |
| NOTIFY(1, "--drx-- removed kill-on-close from job 0x%x in pid %d\n", |
| job, dr_get_process_id()); |
| } |
| /* Track the handle so we can notify the client on close or exit */ |
| hashtable_lock(&job_table); |
| /* See if already there (in case app called Set 2x) */ |
| if (hashtable_lookup(&job_table, (void *)job) == NULL) { |
| jinfo = (job_info_t *) dr_global_alloc(sizeof(*jinfo)); |
| jinfo->ref_count = 1; |
| isnew = hashtable_add(&job_table, (void *)job, (void *)jinfo); |
| ASSERT(isnew, "missed an NtClose"); |
| } |
| hashtable_unlock(&job_table); |
| } |
| } |
| return true; |
| } |
| |
| /* We must do two things on NtDuplicateObject: |
| * 1) Update our job table: adding a new entry for the duplicate, |
| * and removing the source handle if it is closed. |
| * 2) Process a handle being closed but a new one not being |
| * created (in this process): corner case that triggers a kill. |
| */ |
| static bool |
| soft_kills_pre_DuplicateObject(void *drcontext, cls_soft_t *cls) |
| { |
| HANDLE proc_src = (HANDLE) dr_syscall_get_param(drcontext, 0); |
| process_id_t id_src = dr_convert_handle_to_pid(proc_src); |
| cls->dup_proc_src_us = (id_src == dr_get_process_id()); |
| cls->dup_jinfo = NULL; |
| if (cls->dup_proc_src_us) { |
| /* NtDuplicateObject seems more likely than NtClose to fail, so we |
| * shift as much handling as possible post-syscall. |
| */ |
| HANDLE proc_dst = (HANDLE) dr_syscall_get_param(drcontext, 2); |
| process_id_t id_dst = dr_convert_handle_to_pid(proc_dst); |
| cls->dup_proc_dst_us = (id_dst == dr_get_process_id()); |
| cls->dup_src = (HANDLE) dr_syscall_get_param(drcontext, 1); |
| cls->dup_dst = (HANDLE *) dr_syscall_get_param(drcontext, 3); |
| cls->dup_options = (ULONG) dr_syscall_get_param(drcontext, 6); |
| hashtable_lock(&job_table); |
| /* We have to save jinfo b/c dup_src will be gone */ |
| cls->dup_jinfo = (job_info_t *) |
| hashtable_lookup(&job_table, (void *)cls->dup_src); |
| if (cls->dup_jinfo != NULL) { |
| if (TEST(DUPLICATE_CLOSE_SOURCE, cls->dup_options)) { |
| /* "This occurs regardless of any error status returned" |
| * according to MSDN DuplicateHandle, and Nebbett. |
| * Thus, we act on this here, which avoids any handle value |
| * reuse race, and we don't have to undo in post. |
| * If this weren't true, we'd have to reinstate in the table |
| * on failure, and we'd have to duplicate the handle |
| * (dr_dup_file_handle would do it -- at least w/ current impl) |
| * to call soft_kills_handle_close() in post. |
| */ |
| if (!cls->dup_proc_dst_us) { |
| NOTIFY(1, "--drx-- job 0x%x closed in pid %d w/ dst outside proc\n", |
| cls->dup_src, dr_get_process_id()); |
| /* The exit code is set to 0 by the kernel for this case */ |
| soft_kills_handle_close(drcontext, cls->dup_jinfo, cls->dup_src, 0, |
| true/*remove*/); |
| } else { |
| hashtable_remove(&job_table, (void *)cls->dup_src); |
| /* Adjust refcount after removing to avoid freeing prematurely. |
| * The refcount may be sitting at 0, but no other thread should |
| * be able to affect it as there is no hashtable entry. |
| */ |
| ASSERT(cls->dup_jinfo->ref_count > 0, "invalid ref count"); |
| cls->dup_jinfo->ref_count--; |
| } |
| } |
| } |
| hashtable_unlock(&job_table); |
| } |
| return true; |
| } |
| |
| static void |
| soft_kills_post_DuplicateObject(void *drcontext) |
| { |
| cls_soft_t *cls = (cls_soft_t *) drmgr_get_cls_field(drcontext, cls_idx_soft); |
| HANDLE dup_dst; |
| if (cls->dup_jinfo == NULL) |
| return; |
| if (!NT_SUCCESS(dr_syscall_get_result(drcontext))) |
| return; |
| ASSERT(cls->dup_proc_src_us, "shouldn't get here"); |
| if (!cls->dup_proc_dst_us) |
| return; /* already handled in pre */ |
| /* At this point we have a successful intra-process duplication. If |
| * DUPLICATE_CLOSE_SOURCE, we already removed from the table in pre. |
| */ |
| hashtable_lock(&job_table); |
| if (cls->dup_dst != NULL && |
| dr_safe_read(cls->dup_dst, sizeof(dup_dst), &dup_dst, NULL)) { |
| NOTIFY(1, "--drx-- job 0x%x duplicated as 0x%x in pid %d\n", |
| cls->dup_src, dup_dst, dr_get_process_id()); |
| cls->dup_jinfo->ref_count++; |
| hashtable_add(&job_table, (void *)dup_dst, (void *)cls->dup_jinfo); |
| } |
| hashtable_unlock(&job_table); |
| } |
| |
| /* Returns whether to execute the system call */ |
| static bool |
| soft_kills_pre_syscall(void *drcontext, int sysnum) |
| { |
| cls_soft_t *cls = (cls_soft_t *) drmgr_get_cls_field(drcontext, cls_idx_soft); |
| /* Xref DrMem i#544, DrMem i#1297, and DRi#1231: give child |
| * processes a chance for clean exit for dumping of data or other |
| * actions. |
| * |
| * XXX: a child under DR but not a supporting client will be left |
| * alive: but that's a risk we can live with. |
| */ |
| if (sysnum == sysnum_TerminateProcess) { |
| HANDLE proc = (HANDLE) dr_syscall_get_param(drcontext, 0); |
| process_id_t pid = dr_convert_handle_to_pid(proc); |
| if (pid != INVALID_PROCESS_ID && pid != dr_get_process_id()) { |
| int exit_code = (int) dr_syscall_get_param(drcontext, 1); |
| NOTIFY(1, "--drx-- NtTerminateProcess in pid %d\n", dr_get_process_id()); |
| if (soft_kills_invoke_cbs(pid, exit_code)) { |
| dr_syscall_set_result(drcontext, 0/*success*/); |
| return false; /* skip syscall */ |
| } else |
| return true; /* execute syscall */ |
| } |
| } |
| else if (sysnum == sysnum_TerminateJobObject) { |
| /* There are several ways a process in a job can be killed: |
| * |
| * 1) NtTerminateJobObject |
| * 2) The last handle is closed + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE is set |
| * 3) JOB_OBJECT_LIMIT_ACTIVE_PROCESS is hit |
| * 4) Time limit and JOB_OBJECT_TERMINATE_AT_END_OF_JOB is hit |
| * |
| * XXX: we only handle #1 and #2. |
| */ |
| HANDLE job = (HANDLE) dr_syscall_get_param(drcontext, 0); |
| NTSTATUS exit_code = (NTSTATUS) dr_syscall_get_param(drcontext, 1); |
| NOTIFY(1, "--drx-- NtTerminateJobObject job 0x%x in pid %d\n", |
| job, dr_get_process_id()); |
| soft_kills_handle_job_termination(drcontext, job, exit_code); |
| /* We always skip this syscall. If individual processes were requested |
| * to not be skipped, we emulated via NtTerminateProcess in |
| * soft_kills_handle_job_termination(). |
| */ |
| dr_syscall_set_result(drcontext, 0/*success*/); |
| return false; /* skip syscall */ |
| } |
| else if (sysnum == sysnum_SetInformationJobObject) { |
| return soft_kills_pre_SetInformationJobObject(drcontext, cls); |
| } |
| else if (sysnum == sysnum_Close) { |
| /* If a job object, act on it, and remove from our table */ |
| HANDLE handle = (HANDLE) dr_syscall_get_param(drcontext, 0); |
| job_info_t *jinfo; |
| hashtable_lock(&job_table); |
| jinfo = (job_info_t *) hashtable_lookup(&job_table, (void *)handle); |
| if (jinfo != NULL) { |
| NOTIFY(1, "--drx-- explicit close of job 0x%x in pid %d\n", |
| handle, dr_get_process_id()); |
| /* The exit code is set to 0 by the kernel for this case */ |
| soft_kills_handle_close(drcontext, jinfo, handle, 0, true/*remove*/); |
| } |
| hashtable_unlock(&job_table); |
| } |
| else if (sysnum == sysnum_DuplicateObject) { |
| return soft_kills_pre_DuplicateObject(drcontext, cls); |
| } |
| return true; |
| } |
| |
| static void |
| soft_kills_post_syscall(void *drcontext, int sysnum) |
| { |
| if (sysnum == sysnum_SetInformationJobObject) { |
| cls_soft_t *cls = (cls_soft_t *) drmgr_get_cls_field(drcontext, cls_idx_soft); |
| if (cls->job_limit_flags_loc != NULL) { |
| /* Restore the app's memory */ |
| if (!dr_safe_write(cls->job_limit_flags_loc, |
| sizeof(cls->job_limit_flags_orig), |
| &cls->job_limit_flags_orig, NULL)) { |
| /* If we weren't in drx lib I'd log a warning */ |
| } |
| cls->job_limit_flags_loc = NULL; |
| } |
| } |
| else if (sysnum == sysnum_DuplicateObject) { |
| soft_kills_post_DuplicateObject(drcontext); |
| } |
| } |
| #else /* WINDOWS */ |
| |
| static bool |
| soft_kills_filter_syscall(void *drcontext, int sysnum) |
| { |
| return (sysnum == SYS_kill); |
| } |
| |
| /* Returns whether to execute the system call */ |
| static bool |
| soft_kills_pre_syscall(void *drcontext, int sysnum) |
| { |
| if (sysnum == SYS_kill) { |
| process_id_t pid = (process_id_t) dr_syscall_get_param(drcontext, 0); |
| int sig = (int) dr_syscall_get_param(drcontext, 1); |
| if (sig == SIGKILL && pid != INVALID_PROCESS_ID && pid != dr_get_process_id()) { |
| /* Pass exit code << 8 for use with dr_exit_process() */ |
| int exit_code = sig << 8; |
| if (soft_kills_invoke_cbs(pid, exit_code)) { |
| /* set result to 0 (success) and use_high and use_errno to false */ |
| dr_syscall_result_info_t info = { sizeof(info), }; |
| info.succeeded = true; |
| dr_syscall_set_result_ex(drcontext, &info); |
| return false; /* skip syscall */ |
| } else |
| return true; /* execute syscall */ |
| } |
| } |
| return true; |
| } |
| |
| static void |
| soft_kills_post_syscall(void *drcontext, int sysnum) |
| { |
| /* nothing yet */ |
| } |
| |
| #endif /* UNIX */ |
| |
| static bool |
| soft_kills_init(void) |
| { |
| #ifdef WINDOWS |
| IF_DEBUG(bool ok;) |
| #endif |
| /* XXX: would be nice to fail if it's not still process init, |
| * but we don't have an easy way to check. |
| */ |
| soft_kills_enabled = true; |
| |
| NOTIFY(1, "--drx-- init pid %d %s\n", dr_get_process_id(), dr_get_application_name()); |
| |
| cb_lock = dr_mutex_create(); |
| |
| #ifdef WINDOWS |
| hashtable_init_ex(&job_table, JOB_TABLE_HASH_BITS, HASH_INTPTR, false/*!strdup*/, |
| false/*!synch*/, soft_kills_free_job_info, NULL, NULL); |
| |
| sysnum_TerminateProcess = |
| soft_kills_get_sysnum("NtTerminateProcess", |
| SYS_NUM_PARAMS_TerminateProcess, |
| SYS_WOW64_IDX_TerminateProcess); |
| if (sysnum_TerminateProcess == -1) |
| return false; |
| sysnum_TerminateJobObject = |
| soft_kills_get_sysnum("NtTerminateJobObject", |
| SYS_NUM_PARAMS_TerminateJobObject, |
| SYS_WOW64_IDX_TerminateJobObject); |
| if (sysnum_TerminateJobObject == -1) |
| return false; |
| sysnum_SetInformationJobObject = |
| soft_kills_get_sysnum("NtSetInformationJobObject", |
| SYS_NUM_PARAMS_SetInformationJobObject, |
| SYS_WOW64_IDX_SetInformationJobObject); |
| if (sysnum_SetInformationJobObject == -1) |
| return false; |
| sysnum_Close = soft_kills_get_sysnum("NtClose", |
| SYS_NUM_PARAMS_Close, SYS_WOW64_IDX_Close); |
| if (sysnum_Close == -1) |
| return false; |
| sysnum_DuplicateObject = |
| soft_kills_get_sysnum("NtDuplicateObject", |
| SYS_NUM_PARAMS_DuplicateObject, |
| SYS_WOW64_IDX_DuplicateObject); |
| if (sysnum_DuplicateObject == -1) |
| return false; |
| |
| cls_idx_soft = drmgr_register_cls_field(soft_kills_context_init, |
| soft_kills_context_exit); |
| if (cls_idx_soft == -1) |
| return false; |
| |
| /* Ensure that DR intercepts these when we're native */ |
| IF_DEBUG(ok = ) |
| dr_syscall_intercept_natively("NtTerminateProcess", |
| sysnum_TerminateProcess, |
| SYS_NUM_PARAMS_TerminateProcess, |
| SYS_WOW64_IDX_TerminateProcess); |
| ASSERT(ok, "failure to watch syscall while native"); |
| IF_DEBUG(ok = ) |
| dr_syscall_intercept_natively("NtTerminateJobObject", |
| sysnum_TerminateJobObject, |
| SYS_NUM_PARAMS_TerminateJobObject, |
| SYS_WOW64_IDX_TerminateJobObject); |
| ASSERT(ok, "failure to watch syscall while native"); |
| IF_DEBUG(ok = ) |
| dr_syscall_intercept_natively("NtSetInformationJobObject", |
| sysnum_SetInformationJobObject, |
| SYS_NUM_PARAMS_SetInformationJobObject, |
| SYS_WOW64_IDX_SetInformationJobObject); |
| ASSERT(ok, "failure to watch syscall while native"); |
| IF_DEBUG(ok = ) |
| dr_syscall_intercept_natively("NtClose", |
| sysnum_Close, |
| SYS_NUM_PARAMS_Close, |
| SYS_WOW64_IDX_Close); |
| ASSERT(ok, "failure to watch syscall while native"); |
| IF_DEBUG(ok = ) |
| dr_syscall_intercept_natively("NtDuplicateObject", |
| sysnum_DuplicateObject, |
| SYS_NUM_PARAMS_DuplicateObject, |
| SYS_WOW64_IDX_DuplicateObject); |
| ASSERT(ok, "failure to watch syscall while native"); |
| #endif |
| |
| if (!drmgr_register_pre_syscall_event(soft_kills_pre_syscall) || |
| !drmgr_register_post_syscall_event(soft_kills_post_syscall)) |
| return false; |
| dr_register_filter_syscall_event(soft_kills_filter_syscall); |
| |
| return true; |
| } |
| |
| static void |
| soft_kills_exit(void) |
| { |
| cb_entry_t *e; |
| #ifdef WINDOWS |
| /* Any open job handles will be closed, triggering |
| * JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
| */ |
| uint i; |
| /* The exit code used is the exit code for this process */ |
| int exit_code; |
| if (!get_app_exit_code(&exit_code)) |
| exit_code = 0; |
| hashtable_lock(&job_table); |
| for (i = 0; i < HASHTABLE_SIZE(job_table.table_bits); i++) { |
| hash_entry_t *he; |
| for (he = job_table.table[i]; he != NULL; he = he->next) { |
| HANDLE job = (HANDLE) he->key; |
| job_info_t *jinfo = (job_info_t *) he->payload; |
| NOTIFY(1, "--drx-- implicit close of job 0x%x in pid %d\n", |
| job, dr_get_process_id()); |
| soft_kills_handle_close(dr_get_current_drcontext(), jinfo, job, exit_code, |
| false/*do not remove*/); |
| } |
| } |
| hashtable_unlock(&job_table); |
| |
| hashtable_delete(&job_table); |
| |
| drmgr_unregister_cls_field(soft_kills_context_init, soft_kills_context_exit, |
| cls_idx_soft); |
| #endif |
| |
| dr_mutex_lock(cb_lock); |
| while (cb_list != NULL) { |
| e = cb_list; |
| cb_list = e->next; |
| dr_global_free(e, sizeof(*e)); |
| } |
| dr_mutex_unlock(cb_lock); |
| |
| dr_mutex_destroy(cb_lock); |
| } |
| |
| bool |
| drx_register_soft_kills(bool (*event_cb)(process_id_t pid, int exit_code)) |
| { |
| /* We split our init from drx_init() to avoid extra work when nobody |
| * requests this feature. |
| */ |
| static int soft_kills_init_count; |
| cb_entry_t *e; |
| int count = dr_atomic_add32_return_sum(&soft_kills_init_count, 1); |
| if (count == 1) { |
| soft_kills_init(); |
| } |
| |
| e = dr_global_alloc(sizeof(*e)); |
| e->cb = event_cb; |
| |
| dr_mutex_lock(cb_lock); |
| e->next = cb_list; |
| cb_list = e; |
| dr_mutex_unlock(cb_lock); |
| return true; |
| } |
| |
| /*************************************************************************** |
| * LOGGING |
| */ |
| #ifdef WINDOWS |
| # define DIRSEP '\\' |
| #else |
| # define DIRSEP '/' |
| #endif |
| |
| file_t |
| drx_open_unique_file(const char *dir, const char *prefix, const char *suffix, |
| uint extra_flags, char *result OUT, size_t result_len) |
| { |
| char buf[MAXIMUM_PATH]; |
| file_t f; |
| int i; |
| ssize_t len; |
| for (i = 0; i < 10000; i++) { |
| len = dr_snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), |
| "%s%c%s.%04d.%s", dir, DIRSEP, prefix, i, suffix); |
| if (len < 0) |
| return false; |
| NULL_TERMINATE_BUFFER(buf); |
| f = dr_open_file(buf, DR_FILE_WRITE_REQUIRE_NEW | extra_flags); |
| if (f != INVALID_FILE) { |
| if (result != NULL) |
| dr_snprintf(result, result_len, "%s", buf); |
| return f; |
| } |
| } |
| return INVALID_FILE; |
| } |
| |
| file_t |
| drx_open_unique_appid_file(const char *dir, ptr_int_t id, |
| const char *prefix, const char *suffix, |
| uint extra_flags, char *result OUT, size_t result_len) |
| { |
| int len; |
| char appid[MAXIMUM_PATH]; |
| const char *app_name = dr_get_application_name(); |
| if (app_name == NULL) |
| app_name = "<unknown-app>"; |
| len = dr_snprintf(appid, BUFFER_SIZE_ELEMENTS(appid), |
| "%s.%s.%05d", prefix, app_name, id); |
| if (len < 0 || (size_t)len >= BUFFER_SIZE_ELEMENTS(appid)) |
| return INVALID_FILE; |
| NULL_TERMINATE_BUFFER(appid); |
| |
| return drx_open_unique_file(dir, appid, suffix, extra_flags, result, result_len); |
| } |