blob: b741d5d7f797ce75d8149359f1de035c36103292 [file] [edit]
/* **********************************************************
* Copyright (c) 2012-2024 Google, Inc. All rights reserved.
* **********************************************************/
/* Dr. Memory: the memory debugger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License, and no later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* windows kernel handle leak checks */
#include "handlecheck.h"
#include "drmemory.h"
#include "callstack.h"
#include "syscall.h"
#include "drsyscall.h"
#include "../wininc/ndk_extypes.h"
#include "../wininc/ndk_psfuncs.h" /* for PROCESSINFOCLASS */
#ifndef WINDOWS
# error WINDOWS-only
#endif
#ifdef STATISTICS
static uint num_handle_add;
static uint num_handle_remove;
#endif /* STATISTICS */
/* handle table payload */
typedef struct _handle_callstack_info_t {
app_loc_t loc;
packed_callstack_t *pcs;
} handle_callstack_info_t;
typedef struct _open_close_pair_t {
handle_callstack_info_t open; /* handle open info */
handle_callstack_info_t close; /* handle close info */
} open_close_pair_t;
static handle_callstack_info_t other_proc_hci;
#define HANDLE_VERBOSE_1 1
#define HANDLE_VERBOSE_2 2
#define HANDLE_VERBOSE_3 3
/* We use one lock (handle_stack_table lock) for synchronizing all table operations
* and payload access.
*/
/* Hashtable for handle open/close callstack */
#define HSTACK_TABLE_HASH_BITS 8
static hashtable_t handle_stack_table;
#ifdef STATISTICS
uint handle_stack_count;
#endif
/* Hashtable for handle open/close pair */
#define OPEN_CLOSE_TABLE_BITS 8
static hashtable_t open_close_table;
#ifdef STATISTICS
uint open_close_count;
#endif
/* Table of handle entries: [handle, hci]
* there are multiple handle namespaces: kernel object, gdi object, user object,
* and they are disjoint, so we have different hashtables for each type.
*/
#define HANDLE_TABLE_HASH_BITS 6
static hashtable_t kernel_handle_table;
static hashtable_t gdi_handle_table;
static hashtable_t user_handle_table;
/* handle enumeration data structures and routines */
GET_NTDLL(NtQuerySystemInformation, (DR_PARAM_IN SYSTEM_INFORMATION_CLASS info_class,
DR_PARAM_OUT PVOID info,
DR_PARAM_IN ULONG info_size,
DR_PARAM_OUT PULONG bytes_received));
GET_NTDLL(NtQueryInformationProcess, (DR_PARAM_IN HANDLE ProcessHandle,
DR_PARAM_IN PROCESSINFOCLASS ProcessInformationClass,
DR_PARAM_OUT PVOID ProcessInformation,
DR_PARAM_IN ULONG ProcessInformationLength,
DR_PARAM_OUT PULONG ReturnLength OPTIONAL));
#define SYSTEM_HANDLE_INFORMATION_SIZE_INIT 0x10000
#define SYSTEM_HANDLE_INFORMATION_LIST_SIZE(x) \
(sizeof(SYSTEM_HANDLE_INFORMATION) + sizeof(SYSTEM_HANDLE_ENTRY) * ((x) - 1))
static void
free_system_handle_list(SYSTEM_HANDLE_INFORMATION *list, uint size)
{
global_free(list, size, HEAPSTAT_MISC);
}
static void
free_process_handle_list(SYSTEM_HANDLE_INFORMATION *list)
{
free_system_handle_list(list, SYSTEM_HANDLE_INFORMATION_LIST_SIZE(list->Count));
}
#ifdef DEBUG
static void
print_handle_list(SYSTEM_HANDLE_INFORMATION *list)
{
int i;
SYSTEM_HANDLE_ENTRY *handle = &list->Handle[0];
LOG(HANDLE_VERBOSE_1, "Total number of handles: %d\n", list->Count);
for (i = 0; i < list->Count; i++) {
LOG(HANDLE_VERBOSE_1,
"handle["PFX"]: pid="PFX", value="PFX", type="PFX", obj="PFX"\n",
i, handle[i].OwnerPid, handle[i].HandleValue,
handle[i].ObjectType, handle[i].ObjectPointer);
}
}
#endif
/* the caller must free the list by calling free_system_handle_list */
static SYSTEM_HANDLE_INFORMATION *
get_system_handle_list(DR_PARAM_OUT size_t *size_out)
{
NTSTATUS res;
SYSTEM_HANDLE_INFORMATION *list;
size_t size = SYSTEM_HANDLE_INFORMATION_SIZE_INIT;
if (size_out == NULL) {
ASSERT(false, "size_out must not be NULL");
return NULL;
}
do {
list = (SYSTEM_HANDLE_INFORMATION *) global_alloc(size, HEAPSTAT_MISC);
ASSERT(list != NULL, "failed to alloc memory for handle list");
res = NtQuerySystemInformation(SystemHandleInformation, list, size, NULL);
if (res == STATUS_INFO_LENGTH_MISMATCH) {
global_free(list, size, HEAPSTAT_MISC);
list = NULL;
size *= 2;
}
} while (res == STATUS_INFO_LENGTH_MISMATCH);
if (!NT_SUCCESS(res) && list == NULL) {
ASSERT(false, "fail to get system handle information");
list = NULL;
*size_out = 0;
} else {
*size_out = size;
}
DOLOG(HANDLE_VERBOSE_3, { print_handle_list(list); });
return list;
}
static ULONG
get_process_handle_count(void)
{
NTSTATUS res;
ULONG got, count = 0;
res = NtQueryInformationProcess(NT_CURRENT_PROCESS, ProcessHandleCount,
&count, sizeof(count), &got);
LOG(HANDLE_VERBOSE_2, "Process handle count: "PFX"\n",(ptr_uint_t)count);
if (!NT_SUCCESS(res))
return 0;
ASSERT(got == sizeof(ULONG), "fail to get handle count");
return count;
}
/* Returns NULL if fail to find the list.
* The caller must free the list by calling free_process_handle_list.
*/
static SYSTEM_HANDLE_INFORMATION *
get_process_handle_list(void)
{
SYSTEM_HANDLE_INFORMATION *sys_list, *our_list = NULL;
SYSTEM_HANDLE_ENTRY *h_our, *h_sys, *h_start;
size_t sys_list_size, our_list_size;
uint pid = dr_get_process_id();
uint num_matched_pid;
ULONG proc_h_cnt, i;
sys_list = get_system_handle_list(&sys_list_size);
if (sys_list == NULL) {
ASSERT(false, "fail to get system handle list");
return NULL;
}
/* i#1389: NtQuerySystemInformation SystemHandleInformation returns
* a truncated process id from the real pid, i.e., higher 16-bit of
* the real pid is cleared. So it is possible that there are several
* processes having the same pid in the list. We use the handle count
* obtained by get_process_handle_count to find the most likely
* process handle list.
* Assuming a process' handles are stored contiguously in the list, and
* two processes having the same pid are not stored next to each other.
* We count the number of handles of each matched pid chunk for comparison.
*/
proc_h_cnt = get_process_handle_count();
if (proc_h_cnt == 0) {
free_system_handle_list(sys_list, sys_list_size);
return NULL;
}
do {
SYSTEM_HANDLE_ENTRY *h_sys_end = &sys_list->Handle[0] + sys_list->Count;
ULONG count;
num_matched_pid = 0;
h_start = NULL;
/* iterate over the whole handle list */
for (h_sys = &sys_list->Handle[0]; h_sys < h_sys_end; h_sys++) {
/* if pid is matched, count the handles in that chunk */
LOG(4, "%s: comparing pid=%d to %d\n", __FUNCTION__, pid, h_sys->OwnerPid);
if (h_sys->OwnerPid == pid) {
num_matched_pid++;
for (h_start = h_sys, count = 0;
h_sys < h_sys_end && h_sys->OwnerPid == pid;
h_sys++)
count++;
if (count == proc_h_cnt) {
/* found the most likely process handle list */
break;
}
}
}
if (h_start != NULL) {
/* We saw matched pid but not matched count.
* If there is only one matched pid in the list, we will use it.
* Otherwise, we use our own kernel handle table instead.
*/
if (num_matched_pid == 1) {
/* use the only matched one */
proc_h_cnt = count;
} else {
/* fail to find one, use our own table */
h_start = NULL;
}
break;
} else {
if (pid < 0x10000) {
/* fail for unknown reason */
/* XXX i#1511: not an assert b/c it happens: we need to understand why */
WARN("WARNING: failed to find the process in the handle list");
break;
}
/* i#1389: we truncate the real pid to match the pid value returned
* from NtQuerySystemInformation SystemHandleInformation and try again.
*/
pid = pid & 0xffff;
}
} while (true);
if (h_start == NULL) {
LOG(HANDLE_VERBOSE_1, "fail to find the process handle list");
free_system_handle_list(sys_list, sys_list_size);
return NULL;
}
our_list_size = SYSTEM_HANDLE_INFORMATION_LIST_SIZE(proc_h_cnt);
our_list = (SYSTEM_HANDLE_INFORMATION *)
global_alloc(our_list_size, HEAPSTAT_MISC);
our_list->Count = proc_h_cnt;
h_sys = h_start;
h_our = &our_list->Handle[0];
for (i = 0; i < proc_h_cnt; h_sys++, h_our++, i++) {
*h_our = *h_sys;
ASSERT(h_sys->OwnerPid == pid, "wrong handle list");
ASSERT(h_our == &our_list->Handle[0] ||
h_our->HandleValue > (h_our - 1)->HandleValue,
"handle is not stored in the sorted order");
}
free_system_handle_list(sys_list, sys_list_size);
ASSERT(our_list != NULL, "fail to get process handle list");
DOLOG(HANDLE_VERBOSE_3, { print_handle_list(our_list); });
return our_list;
}
#ifdef DEBUG
static void
open_close_pair_print(open_close_pair_t *pair)
{
LOG(HANDLE_VERBOSE_2, "Handle open/close pair:\n");
LOG(HANDLE_VERBOSE_2, "Handle open stack:\n");
DOLOG(HANDLE_VERBOSE_2, {
packed_callstack_log(pair->open.pcs, INVALID_FILE);
});
LOG(HANDLE_VERBOSE_2, "Handle close stack:\n");
DOLOG(HANDLE_VERBOSE_2, {
packed_callstack_log(pair->close.pcs, INVALID_FILE);
});
}
#endif
static void
open_close_pair_free(void *p)
{
open_close_pair_t *pair = (open_close_pair_t *)p;
/* open_close_pair_free should only be called at exit,
* so we do not check if we hold lock here
*/
DODEBUG({ open_close_pair_print(pair); });
packed_callstack_free(pair->open.pcs);
packed_callstack_free(pair->close.pcs);
global_free(pair, sizeof(*pair), HEAPSTAT_CALLSTACK);
}
#ifdef DEBUG
static bool
handle_table_lock_self_owns(void)
{
return hashtable_lock_self_owns(&handle_stack_table);
}
#endif /* DEBUG */
/* Add open/close pair into table.
* The caller must hold the handle table lock.
* Called from handlecheck_delete_handle_post_syscall if the handle
* is closed successfully.
*/
static void
open_close_pair_add(handle_callstack_info_t *hci/* callstack of creation */,
drsys_sysnum_t sysnum,
dr_mcontext_t *mc/* context of close */)
{
open_close_pair_t *pair;
IF_DEBUG(bool res;)
if (!options.filter_handle_leaks)
return;
ASSERT(handle_table_lock_self_owns(), "caller must hold the handle table lock");
pair = (open_close_pair_t *)hashtable_lookup(&open_close_table, hci->pcs);
/* we only store one close pcs if there are multiple */
if (pair != NULL)
return;
pair = global_alloc(sizeof(*pair), HEAPSTAT_CALLSTACK);
/* pair->open is copied instead of cloned from hci,
* so we need call packed_callstack_add_ref
*/
pair->open = *hci;
packed_callstack_add_ref(hci->pcs);
/* create pair->close.pcs and add it into handle_stack_table */
syscall_to_loc(&pair->close.loc, sysnum, NULL);
packed_callstack_record(&pair->close.pcs, mc, &pair->close.loc,
options.callstack_max_frames);
pair->close.pcs = packed_callstack_add_to_table(&handle_stack_table,
pair->close.pcs
_IF_STATS(&handle_stack_count));
/* add to open_close_table */
IF_DEBUG(res =)
hashtable_add(&open_close_table, (void *)hci->pcs, (void *)pair);
ASSERT(res, "failed to add to open_close_table");
}
static void
handle_table_lock(void)
{
/* we use handle_stack_table lock for synchronizing all table operations */
hashtable_lock(&handle_stack_table);
}
static void
handle_table_unlock(void)
{
/* we use handle_stack_table lock for synchronizing all table operations */
hashtable_unlock(&handle_stack_table);
}
void
handle_callstack_free(void *p)
{
packed_callstack_destroy((packed_callstack_t *)p);
}
static handle_callstack_info_t *
handle_callstack_info_clone(handle_callstack_info_t *src)
{
handle_callstack_info_t *dst;
dst = global_alloc(sizeof(*src), HEAPSTAT_CALLSTACK);
*dst = *src;
packed_callstack_add_ref(dst->pcs);
return dst;
}
/* the caller must hold the handle table lock */
static handle_callstack_info_t *
handle_callstack_info_alloc(drsys_sysnum_t sysnum, app_pc pc, dr_mcontext_t *mc)
{
handle_callstack_info_t *hci;
ASSERT(handle_table_lock_self_owns(), "caller must hold the handle table lock");
hci = global_alloc(sizeof(*hci), HEAPSTAT_CALLSTACK);
/* assuming pc will never be NULL */
if (pc == NULL)
syscall_to_loc(&hci->loc, sysnum, NULL);
else
pc_to_loc(&hci->loc, pc);
packed_callstack_record(&hci->pcs, mc, &hci->loc, options.callstack_max_frames);
hci->pcs = packed_callstack_add_to_table(&handle_stack_table, hci->pcs
_IF_STATS(&handle_stack_count));
return hci;
}
static void
handle_callstack_info_free(handle_callstack_info_t *hci)
{
uint count;
count = packed_callstack_free(hci->pcs);
LOG(4, "%s: freed pcs "PFX" => refcount %d\n", __FUNCTION__, hci->pcs, count);
global_free(hci, sizeof(*hci), HEAPSTAT_CALLSTACK);
}
/* the caller must hold the handle table lock */
static void
handlecheck_handle_add(hashtable_t *table, HANDLE handle,
handle_callstack_info_t *hci)
{
void *res;
ASSERT(handle_table_lock_self_owns(), "caller must hold the handle table lock");
STATS_INC(num_handle_add);
/* We replace the old callstack with new callstack if we see
* duplicated handle in the table, b/c we might have missed a
* close, and it's best to take the latest creation of that
* handle value.
*/
res = hashtable_add_replace(table, (void *)handle, (void *)hci);
if (res != NULL) {
DOLOG(HANDLE_VERBOSE_2, {
handle_callstack_info_t *old;
LOG(HANDLE_VERBOSE_2,
"WARNING: duplicated handle "PFX"\n", handle);
LOG(HANDLE_VERBOSE_2, " old callstack:\n");
old = (handle_callstack_info_t *)res;
packed_callstack_log(old->pcs, INVALID_FILE);
LOG(HANDLE_VERBOSE_2, " new callstack:\n");
packed_callstack_log(hci->pcs, INVALID_FILE);
});
handle_callstack_info_free(res);
LOG(HANDLE_VERBOSE_1,
"WARNING: conflict on adding handle "PFX" back\n", handle);
}
}
/* the caller must hold the handle table lock */
static bool
handlecheck_handle_remove(hashtable_t *table, HANDLE handle,
handle_callstack_info_t **hci DR_PARAM_OUT)
{
bool res;
ASSERT(handle_table_lock_self_owns(), "caller must hold the handle table lock");
STATS_INC(num_handle_remove);
if (hci != NULL) {
handle_callstack_info_t *info;
info = hashtable_lookup(table, (void *)handle);
/* hashtable_remove calls handle_callstack_info_free to free info,
* so we make a copy here and return it to the caller
*/
if (info != NULL)
*hci = handle_callstack_info_clone(info);
else
*hci = NULL;
}
res = hashtable_remove(table, (void *)handle);
return res;
}
#define HANDLECHECK_PRE_MSG_SIZE 0x100
void
handlecheck_report_leak_on_syscall(dr_mcontext_t *mc, drsys_arg_t *arg,
HANDLE proc_handle)
{
handle_callstack_info_t *hci;
char msg[HANDLECHECK_PRE_MSG_SIZE];
const char *name;
app_loc_t loc;
/* Some system call like NtDuplicateObject may leak the handle by passing
* NULL to the out handle argument, so we assume that the leak on syscall
* is only caused by the arg PHANDLE being NULL.
*/
ASSERT(arg->value == (ptr_uint_t)NULL, "syscall arg value is not NULL");
if (drsys_syscall_name(arg->syscall, &name) != DRMF_SUCCESS)
name = "<unknown>";
/* We do not have the leaked handle value, so we report leak without
* value. We could passing our own ptr to get the value, which may have
* transparency problems.
*/
/* i#1380: DuplicateHandle may leak the handle in another process by setting
* the target process handle to other than the current process. We report the
* leak regardless of which process the handle belongs to.
*/
dr_snprintf(msg, BUFFER_SIZE_ELEMENTS(msg),
"Syscall %s leaks handle with NULL handle pointer in %s process "
PFX".", name,
is_current_process(proc_handle) ? "its own" : "another",
proc_handle);
syscall_to_loc(&loc, arg->sysnum, NULL);
report_handle_leak(arg->drcontext, mc, msg, &loc, NULL /* pcs */,
NULL /* aux_pcs */, false /* potential */);
}
/* the caller must hold the handle table lock */
static void
handlecheck_check_open_handle(const char *name,
HANDLE handle,
handle_callstack_info_t *hci)
{
open_close_pair_t *pair = NULL;
bool potential = false;
uint count;
char msg[HANDLECHECK_PRE_MSG_SIZE];
ASSERT(handle_table_lock_self_owns(), "caller must hold the handle table lock");
ASSERT(hci != NULL, "handle callstack info must not be NULL");
count = packed_callstack_refcount(hci->pcs) - 1 /* hashtable refcount */;
/* i#1373: use heuristics for better handle leak reports */
if (options.filter_handle_leaks) {
pair = hashtable_lookup(&open_close_table, (void *)hci->pcs);
if (pair != NULL) {
/* Heuristic 1: for each left-open-handle, we check if there is
* any handle being opened with the same callstack and being closed
* somewhere. If we see such cases, it means that all handles opened
* at that site should probably be closed.
*/
count--; /* pair table refcount */
if (count <= 2) {
/* Report it as a potential error if there are only one or two live
* handles from the same call site, as they could be left open on
* purpose. Also, we currently want to avoid noise and false positives
* and focus on significant leaks.
*/
potential = true;
}
} else if (count >= options.handle_leak_threshold) {
/* Heuristic 2: if too many handles created from the same callstack
* left open, it should be paid attention to, so report it.
*/
} else {
/* no heuristic is applied, report it as potential error */
potential = true;
}
}
dr_snprintf(msg, BUFFER_SIZE_ELEMENTS(msg),
"%s handle "PFX" and %d similar handle(s) were opened"
" but not closed:", name, handle, count-1/*exclude self*/);
report_handle_leak(dr_get_current_drcontext(), NULL, msg, &hci->loc, hci->pcs,
(pair == NULL) ? NULL : pair->close.pcs,
potential);
}
/* caller must hold the handle table lock */
static void
handlecheck_iterate_handle_table(hashtable_t *table, const char *name)
{
uint i;
hash_entry_t *entry;
ASSERT(handle_table_lock_self_owns(), "caller must hold the handle table lock");
for (i = 0; i < HASHTABLE_SIZE(table->table_bits); i++) {
for (entry = table->table[i]; entry != NULL; entry = entry->next) {
handlecheck_check_open_handle(name,
(HANDLE)entry->key,
(handle_callstack_info_t *)entry->payload);
}
}
}
/* caller must hold the handle table lock */
static bool
handlecheck_enumerate_handles(void)
{
SYSTEM_HANDLE_INFORMATION *list;
SYSTEM_HANDLE_ENTRY *entry;
uint i;
ASSERT(handle_table_lock_self_owns(), "caller must hold the handle table lock");
/* i#1380: there could be handles closed by other process, i.e., some
* handle in the table might be closed already, so we have to query the
* existing handle list from system.
*/
LOG(HANDLE_VERBOSE_3, "get process handle list\n");
list = get_process_handle_list();
if (list == NULL)
return false;
/* iterate the list and report handle leaks */
entry = &list->Handle[0];
for (i = 0; i < list->Count; i++, entry++) {
void *res;
res = hashtable_lookup(&kernel_handle_table, (void *)entry->HandleValue);
if (res == NULL) {
/* There might be handles not in the table, for example,
* handles created by DR or handles created before we attach.
*/
ASSERT(hashtable_lookup(&user_handle_table,
(void *)entry->HandleValue) == NULL,
"kernel handle in user handle table");
ASSERT(hashtable_lookup(&gdi_handle_table,
(void *)entry->HandleValue) == NULL,
"kernel handle in gdi handle table");
continue;
}
handlecheck_check_open_handle("KERNEL", (HANDLE)entry->HandleValue,
(handle_callstack_info_t *)res);
}
free_process_handle_list(list);
return true;
}
static void
handlecheck_iterate_handles(void)
{
void *drcontext = dr_get_current_drcontext();
handle_table_lock();
/* kernel handles */
LOG(HANDLE_VERBOSE_3, "enumerating kernel handles");
if (!handlecheck_enumerate_handles()) {
/* i#1389: kernel handle enumeration may fail, in which case
* we iterate kernel handle table instead
*/
LOG(HANDLE_VERBOSE_3, "iterating kernel handles");
handlecheck_iterate_handle_table(&kernel_handle_table, "KERNEL");
}
/* user handles */
LOG(HANDLE_VERBOSE_3, "iterating user handles");
handlecheck_iterate_handle_table(&user_handle_table, "USER");
/* gdi handles */
LOG(HANDLE_VERBOSE_3, "iterating gdi handles");
handlecheck_iterate_handle_table(&gdi_handle_table, "GDI");
handle_table_unlock();
}
static inline hashtable_t *
handlecheck_get_handle_table(int type
_IF_DEBUG(void *handle)
_IF_DEBUG(const char *msg))
{
hashtable_t *table;
switch (type) {
case HANDLE_TYPE_KERNEL:
LOG(HANDLE_VERBOSE_2, "kernel handle "PFX" is %s\n", handle, msg);
table = &kernel_handle_table;
break;
case HANDLE_TYPE_GDI:
LOG(HANDLE_VERBOSE_2, "gdi handle "PFX" is %s\n", handle, msg);
table = &gdi_handle_table;
break;
case HANDLE_TYPE_USER:
LOG(HANDLE_VERBOSE_2, "user handle "PFX" is %s\n", handle, msg);
table = &user_handle_table;
break;
default:
table = &kernel_handle_table; /* for release build */
ASSERT(false, "wrong handle type for creation");
}
return table;
}
void
handlecheck_init(void)
{
ASSERT(options.check_handle_leaks, "incorrectly called");
hashtable_init_ex(&kernel_handle_table, HANDLE_TABLE_HASH_BITS, HASH_INTPTR,
false/*!str_dup*/, false/*!synch*/,
handle_callstack_info_free, NULL, NULL);
hashtable_init_ex(&gdi_handle_table, HANDLE_TABLE_HASH_BITS, HASH_INTPTR,
false/*!str_dup*/, false/*!synch*/,
handle_callstack_info_free, NULL, NULL);
hashtable_init_ex(&user_handle_table, HANDLE_TABLE_HASH_BITS, HASH_INTPTR,
false/*!str_dup*/, false/*!synch*/,
handle_callstack_info_free, NULL, NULL);
hashtable_init_ex(&handle_stack_table, HSTACK_TABLE_HASH_BITS, HASH_CUSTOM,
false /*!str_dup*/, false /*!synch*/,
handle_callstack_free,
(uint (*)(void*)) packed_callstack_hash,
(bool (*)(void*, void *)) packed_callstack_cmp);
hashtable_init_ex(&open_close_table, OPEN_CLOSE_TABLE_BITS, HASH_CUSTOM,
false /*!str_dup*/, false /*!synch*/,
open_close_pair_free,
(uint (*)(void*)) packed_callstack_hash,
(bool (*)(void*, void *)) packed_callstack_cmp);
}
void
handlecheck_exit(void)
{
ASSERT(options.check_handle_leaks, "incorrectly called");
handlecheck_iterate_handles();
hashtable_delete_with_stats(&kernel_handle_table, "Kernel handle table");
hashtable_delete_with_stats(&gdi_handle_table, "GDI handle table");
hashtable_delete_with_stats(&user_handle_table, "USER handle table");
hashtable_delete_with_stats(&open_close_table, "Alloc/free pair table");
hashtable_delete_with_stats(&handle_stack_table, "Handle stack table");
}
void
handlecheck_create_handle(void *drcontext,
HANDLE proc_handle, HANDLE handle, int type,
drsys_sysnum_t sysnum, app_pc pc, dr_mcontext_t *mc)
{
handle_callstack_info_t *hci;
hashtable_t *table;
/* i#1380: ignore the handle created in another process */
if (proc_handle != NT_CURRENT_PROCESS && !is_current_process(proc_handle)) {
LOG(HANDLE_VERBOSE_2, "Create handle "PFX" in another process "PFX"\n",
handle, proc_handle);
return;
}
if (handle == INVALID_HANDLE_VALUE || handle == (HANDLE) NULL) {
ASSERT(false, "syscall succeeds but returns invalid handle value");
return;
}
table = handlecheck_get_handle_table(type
_IF_DEBUG((void *)handle)
_IF_DEBUG("opened"));
ASSERT(table != NULL, "fail to get handle table");
handle_table_lock();
hci = handle_callstack_info_alloc(sysnum, pc, mc);
DOLOG(HANDLE_VERBOSE_3, { packed_callstack_log(hci->pcs, INVALID_FILE); });
handlecheck_handle_add(table, handle, hci);
handle_table_unlock();
}
void *
handlecheck_delete_handle(void *drcontext,
HANDLE proc_handle, HANDLE handle, int type,
drsys_sysnum_t sysnum, app_pc pc, dr_mcontext_t *mc)
{
hashtable_t *table;
handle_callstack_info_t *hci;
if (handle == INVALID_HANDLE_VALUE) {
LOG(HANDLE_VERBOSE_1, "WARNING: deleting an invalid handle\n");
return NULL;
}
/* i#1380: ignore the handle closed in another process */
if (proc_handle != NT_CURRENT_PROCESS && !is_current_process(proc_handle)) {
LOG(HANDLE_VERBOSE_2, "Close handle "PFX" in a different process "PFX"\n",
handle, proc_handle);
return (void *)&other_proc_hci;
}
table = handlecheck_get_handle_table(type
_IF_DEBUG((void *)handle)
_IF_DEBUG("deleted"));
ASSERT(table != NULL, "fail to get handle table");
DOLOG(HANDLE_VERBOSE_3, { report_callstack(drcontext, mc); });
handle_table_lock();
if (!handlecheck_handle_remove(table, handle, &hci)) {
LOG(HANDLE_VERBOSE_1,
"WARNING: fail to remove handle "PFX" at:\n", handle);
DOLOG(HANDLE_VERBOSE_2, { report_callstack(drcontext, mc); });
}
handle_table_unlock();
return (void *)hci;
}
void
handlecheck_delete_handle_post_syscall(void *drcontext, HANDLE handle,
drsys_sysnum_t sysnum, dr_mcontext_t *mc,
int type, void *handle_info, bool success)
{
handle_callstack_info_t *hci;
hashtable_t *table;
hci = (handle_callstack_info_t *)handle_info;
/* i#1380: ignore handle deleted in other process */
if (hci == &other_proc_hci)
return;
if (hci == NULL) {
if (success) {
LOG(HANDLE_VERBOSE_2,
"WARNING: delete handle succeeded unexpectedly\n");
} else {
LOG(HANDLE_VERBOSE_2,
"WARNING: no handle info for adding back\n");
}
return;
}
handle_table_lock();
if (success) {
/* add the pair info */
if (options.filter_handle_leaks)
open_close_pair_add(hci, sysnum, mc);
/* closed handle successfully, free the handle info now */
handle_callstack_info_free(hci);
} else {
/* failed to close handle, add handle back */
ASSERT(handle != INVALID_HANDLE_VALUE, "add back invalid handle value");
table = handlecheck_get_handle_table(type
_IF_DEBUG((void *)handle)
_IF_DEBUG("added back"));
ASSERT(table != NULL, "fail to get handle table");
DOLOG(HANDLE_VERBOSE_3, { packed_callstack_log(hci->pcs, INVALID_FILE); });
handlecheck_handle_add(table, handle, hci);
}
handle_table_unlock();
}
#ifdef STATISTICS
void
handlecheck_dump_statistics(file_t f)
{
dr_fprintf(f, "handles opened: %6u, closed: %6u\n",
num_handle_add, num_handle_remove);
}
#endif /* STATISTICS */
void
handlecheck_nudge(void *drcontext)
{
handlecheck_iterate_handles();
}