[PATCH 4/9] ntdll: Add process-local handle tracking (part 1)
Daniel Santos
daniel.santos at pobox.com
Thu Sep 10 18:26:06 CDT 2015
Adds a facility for ntdll to track handles locally and attach
information to them, allowing for some operations to be performed
directly by the client process.
Signed-off-by: Daniel Santos <daniel.santos at pobox.com>
---
dlls/ntdll/loader.c | 1 +
dlls/ntdll/ntdll_misc.h | 61 +++++++++++
dlls/ntdll/om.c | 264 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 326 insertions(+)
diff --git a/dlls/ntdll/loader.c b/dlls/ntdll/loader.c
index 8a43310..0c779e2 100644
--- a/dlls/ntdll/loader.c
+++ b/dlls/ntdll/loader.c
@@ -3258,6 +3258,7 @@ void __wine_process_init(void)
umask( FILE_umask );
load_global_options();
+ ntdll_object_db_init();
/* setup the load callback and create ntdll modref */
wine_dll_set_callback( load_builtin_callback );
diff --git a/dlls/ntdll/ntdll_misc.h b/dlls/ntdll/ntdll_misc.h
index cbd19db..08e48d7 100644
--- a/dlls/ntdll/ntdll_misc.h
+++ b/dlls/ntdll/ntdll_misc.h
@@ -28,6 +28,8 @@
#include "winnt.h"
#include "winternl.h"
#include "wine/server.h"
+#include "wine/rbtree.h"
+#include "wine/list.h"
#define MAX_NT_PATH_LENGTH 277
@@ -268,4 +270,63 @@ extern HANDLE keyed_event DECLSPEC_HIDDEN;
NTSTATUS WINAPI RtlHashUnicodeString(PCUNICODE_STRING,BOOLEAN,ULONG,ULONG*);
+enum ntdll_obj_type {
+ NTDLL_OBJ_TYPE_ASYNC, /* async I/O */
+ NTDLL_OBJ_TYPE_EVENT, /* event */
+ NTDLL_OBJ_TYPE_COMPLETION, /* IO completion ports */
+ NTDLL_OBJ_TYPE_FILE, /* file */
+ NTDLL_OBJ_TYPE_MAILSLOT, /* +2 chain mail slot */
+ NTDLL_OBJ_TYPE_MUTEX, /* mutex */
+ NTDLL_OBJ_TYPE_MSG_QUEUE, /* message queue */
+ NTDLL_OBJ_TYPE_PROCESS, /* process */
+ NTDLL_OBJ_TYPE_SEMAPHORE, /* semaphore */
+ NTDLL_OBJ_TYPE_THREAD, /* thread */
+ NTDLL_OBJ_TYPE_WAITABLE_TIMER, /* waitable timer */
+
+ NTDLL_OBJ_TYPE_MAX
+};
+
+struct ntdll_object;
+
+struct ntdll_object_ops {
+ /* wait on the object (TODO) */
+ NTSTATUS (*wait) (struct ntdll_object *obj, const LARGE_INTEGER *timeout);
+ /* attempt to obtain lock on object, return STATUS_SUCCESS or STATUS_WAS_LOCKED upon failure. */
+ NTSTATUS (*trywait) (struct ntdll_object *obj);
+ /* called to release a lock previous obtained by trywait() */
+ NTSTATUS (*trywait_undo)(struct ntdll_object *obj);
+ /* close the client-side object */
+ void (*close) (struct ntdll_object *obj);
+ /* Dump a description of the object to the supplied buffer. The implementation should
+ * generally call ntdll_object_dump_base() to describe it's struct ntdll_object member.*/
+ void (*dump) (const struct ntdll_object *obj, char **start, const char *const end);
+};
+
+/* process-local data for ntdll objects
+ *
+ * currently, fields ops - server_ptr should sit on one cache line in all cases,
+ * list_entry used infrequently */
+struct ntdll_object {
+ struct ntdll_object_ops *ops; /* ops */
+ int refcount; /* reference count */
+ HANDLE h; /* handle to the object (may be more than one handle tot he same server-side obj) */
+ enum ntdll_obj_type type_id; /* redundant? maybe but keep for debugging for now */
+ struct wine_rb_entry tree_entry; /* red-black tree node */
+ ULONG_PTR server_ptr; /* for debugging only */
+ struct list list_entry;
+};
+
+extern NTSTATUS ntdll_object_db_init(void);
+extern struct ntdll_object *ntdll_object_new(const HANDLE h, size_t size, enum ntdll_obj_type type_id, struct ntdll_object_ops *ops);
+extern struct ntdll_object *ntdll_object_grab(struct ntdll_object *obj);
+extern void ntdll_object_release(struct ntdll_object *obj);
+extern void ntdll_object_dump_base(const struct ntdll_object *obj, char **start, const char *const end);
+extern const char *ntdll_object_dump(const struct ntdll_object *obj);
+extern void *ntdll_object_get_debug_buffer(size_t *size);
+extern int ntdll_handle_add(struct ntdll_object *obj);
+extern void ntdll_handle_remove(const HANDLE h);
+extern struct ntdll_object *ntdll_handle_find(const HANDLE h);
+
+extern void ntdll_server_notify_lock_release(void);
+
#endif
diff --git a/dlls/ntdll/om.c b/dlls/ntdll/om.c
index 3fadba7..6233633 100644
--- a/dlls/ntdll/om.c
+++ b/dlls/ntdll/om.c
@@ -20,6 +20,7 @@
*/
#include "config.h"
+#include "wine/port.h"
#include <stdarg.h>
#include <stdlib.h>
@@ -30,6 +31,9 @@
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
#include "ntstatus.h"
#define WIN32_NO_STATUS
@@ -40,6 +44,263 @@
#include "wine/server.h"
WINE_DEFAULT_DEBUG_CHANNEL(ntdll);
+WINE_DECLARE_DEBUG_CHANNEL(ntdll_obj);
+
+
+/*
+ * Process-local object information management
+ *
+ * Theory of Operation
+ * ===================
+ * Information about some ntdll objects are cached locally and used to facilitate
+ * IPC operations natively, keeping the server informed about anything important
+ * via asynchronous messages, preventing the need for the big context switch. This
+ * works for releasing locks and attempting to wait on one or more supported objects.
+ * All other WaitFor* calls are routed to the server.
+ *
+ * Lifecycle of a struct ntdll_object object
+ *
+ * refcount Operation
+ * (no object) Execute a server call that creates an object & returns a handle.
+ * 1 Call ntdll_object_new() passing handle to allocate a new struct ntdll_object.
+ * * allocates & zeros struct ntdll_object
+ * * adds to objects.list (double-linked list)
+ * 1 Initialize derived-type data members
+ * 2 Call ntdll_handle_add() to index handle in handles.tree (red-black tree)
+ * 1 Call ntdll_object_release() when done with the object.
+ * ....
+ * (no object) We receive an API call that we might be able to manage client-side
+ * 2 Call ntdll_handle_find() on the handle and get the object
+ * 2 Perform whatever we need to do locally
+ * 1 Call ntdll_object_release() when done with the object.
+ * ...
+ * (no object) We receive a call to NtClose (CloseHandle)
+ * 2 Call ntdll_handle_find() on the handle and get the object
+ * 1 Call ntdll_handle_remove() to remove it from handles.tree.
+ * 0 Call ntdll_object_release()
+ * * object is removed from list
+ * * refcount reaches zero and destructor ops->close() is called
+ * * memory is freed
+ *
+ * Because we're doing this in the multi-threaded environment of the client process instead of
+ * the safety of the single-threaded server, things won't always go as above. It is possible
+ * for a call to NtClose to be received while another thread is still using the object, so
+ * that the object returned by Call ntdll_handle_find() may have a refcount of 3 or more.
+ * However, the object should be immediately delisted, so we can have many live objects
+ * with the same handle, but only one of those handles (the one in the tree) is the living
+ * one.
+ *
+ * TODO: Make sure that UB is acceptable for when a program tries to use a stale handle, or
+ * should we zero the handle when it's removed from the tree (NtClose returns).
+ */
+
+
+/* FIXME: portability */
+static __thread char *tls_debug_buffer = NULL;
+#define NTDLL_DEBUG_BUFFER_SIZE 0x400
+
+static const char * const obj_type_desc[NTDLL_OBJ_TYPE_MAX] = {
+ "async I/O", /* NTDLL_OBJ_TYPE_ASYNC */
+ "event", /* NTDLL_OBJ_TYPE_EVENT */
+ "I/O completion ports", /* NTDLL_OBJ_TYPE_COMPLETION */
+ "file", /* NTDLL_OBJ_TYPE_FILE */
+ "mail slot", /* NTDLL_OBJ_TYPE_MAILSLOT */
+ "mutex", /* NTDLL_OBJ_TYPE_MUTEX */
+ "message queue", /* NTDLL_OBJ_TYPE_MSG_QUEUE */
+ "process", /* NTDLL_OBJ_TYPE_PROCESS */
+ "semaphore", /* NTDLL_OBJ_TYPE_SEMAPHORE */
+ "thread", /* NTDLL_OBJ_TYPE_THREAD */
+ "waitable timer", /* NTDLL_OBJ_TYPE_WAITABLE_TIMER */
+};
+
+/* List containing all process-locally known objects */
+static struct {
+ struct list list; /* list of all struct ntdll_object ojbects */
+ pthread_mutex_t mutex; /* lock for modifying list */
+} objects = {
+ {NULL, NULL},
+ PTHREAD_MUTEX_INITIALIZER, /* non-recursive fast lock */
+};
+
+/* Allocates and initialises a struct ntdll_object object and adds it to
+ * process-global list. Initial refcount is 1, so ntdll_object_release()
+ * must be called when finished initing derived-type members.
+ *
+ * Locking:
+ * Locks objects.mutex */
+struct ntdll_object *ntdll_object_new(const HANDLE h, size_t size, enum ntdll_obj_type type_id, struct ntdll_object_ops *ops)
+{
+ struct ntdll_object *obj;
+
+ assert(size >= sizeof(*obj));
+ assert(type_id < NTDLL_OBJ_TYPE_MAX);
+
+ TRACE_(ntdll_obj)("%p, %zu, %u (%s), %p\n", h, size, type_id, obj_type_desc[type_id], ops);
+
+ obj = RtlAllocateHeap(GetProcessHeap(), 0, size);
+ if (obj)
+ {
+ memset(obj, 0, size);
+ obj->h = h;
+ obj->refcount = 1;
+ obj->type_id = type_id;
+ obj->ops = ops;
+ obj->server_ptr = 0;
+ pthread_mutex_lock(&objects.mutex);
+ list_add_tail(&objects.list, &obj->list_entry);
+ pthread_mutex_unlock(&objects.mutex);
+ } else
+ ERR("Failed to alloc %zu bytes\n", sizeof(*obj));
+
+ return obj;
+}
+
+struct ntdll_object *ntdll_object_grab(struct ntdll_object *obj)
+{
+ int refcount = interlocked_xchg_add(&obj->refcount, 1);
+ assert(refcount < INT_MAX && refcount > 0);
+ return obj;
+}
+
+void ntdll_object_release(struct ntdll_object *obj)
+{
+ int refcount = interlocked_xchg_add(&obj->refcount, -1);
+ assert(refcount > 0);
+
+ if (!refcount)
+ {
+ /* FIXME: check for any more race conditions */
+ TRACE_(ntdll_obj)("Removing & freeing object %s\n", ntdll_object_dump(obj));
+ pthread_mutex_lock(&objects.mutex);
+ list_remove(&obj->list_entry);
+ pthread_mutex_unlock(&objects.mutex);
+ if (obj->ops->close)
+ obj->ops->close(obj);
+ RtlFreeHeap(GetProcessHeap(), 0, obj);
+ }
+}
+
+/* called by derived class to dump struct ntdll_object to a string buffer */
+void ntdll_object_dump_base(const struct ntdll_object *obj, char **start, const char *const end)
+{
+ int count;
+
+ assert(start && *start && end);
+ assert(end > *start);
+
+ if (!obj)
+ count = snprintf(*start, end - *start, "(NULL)");
+ else
+ {
+ assert(obj->type_id < NTDLL_OBJ_TYPE_MAX);
+
+ count = snprintf(*start, end - *start,
+ "%p {"
+ "ops = %p, "
+ "refcount = %u, "
+ "h = %p, "
+ "type_id = %u (%s), "
+ "tree_entry = {left = %p, right = %p, flags = 0x%x}, "
+ "server_ptr = %lx, "
+ "list_entry = {next = %p, prev = %p}"
+ "}",
+ obj,
+ obj->ops,
+ obj->refcount,
+ obj->h,
+ obj->type_id, obj_type_desc[obj->type_id],
+ obj->tree_entry.left, obj->tree_entry.right, obj->tree_entry.flags,
+ obj->server_ptr,
+ obj->list_entry.next, obj->list_entry.prev);
+ }
+
+ if (count < 0)
+ {
+ perror("snprintf");
+ return;
+ }
+
+ *start += count;
+}
+
+const char *ntdll_object_dump(const struct ntdll_object *obj)
+{
+ char *start;
+ const char *ret;
+ size_t size;
+
+ if (!(start = ntdll_object_get_debug_buffer(&size)))
+ return NULL;
+
+ ret = start;
+ if (!obj)
+ snprintf(start, size, "(NULL)");
+ else if (obj->ops->dump)
+ obj->ops->dump(obj, &start, start + size);
+ else
+ ntdll_object_dump_base(obj, &start, start + size);
+
+ return ret;
+}
+
+/* return a thread-local buffer for dumping object descriptions to */
+void *ntdll_object_get_debug_buffer(size_t *size)
+{
+ if (!tls_debug_buffer)
+ tls_debug_buffer = RtlAllocateHeap(GetProcessHeap(), 0, NTDLL_DEBUG_BUFFER_SIZE);
+
+ if (size)
+ *size = tls_debug_buffer ? NTDLL_DEBUG_BUFFER_SIZE : 0;
+ return tls_debug_buffer;
+}
+
+
+/* red-black tree functions for handle table */
+
+static inline void *ntdll_object_rb_alloc(size_t size)
+{
+ return RtlAllocateHeap(GetProcessHeap(), 0, size);
+}
+
+static inline void *ntdll_object_rb_realloc(void *ptr, size_t size)
+{
+ return RtlReAllocateHeap(GetProcessHeap(), 0, ptr, size);
+}
+
+static inline void ntdll_object_rb_free(void *ptr)
+{
+ RtlFreeHeap(GetProcessHeap(), 0, ptr);
+}
+
+static inline int ntdll_object_handle_compare(const void *key, const struct wine_rb_entry *entry)
+{
+ const HANDLE *_a = key;
+ const HANDLE *_b = &WINE_RB_ENTRY_VALUE(entry, const struct ntdll_object, tree_entry)->h;
+
+#ifdef __GNUC__
+ /* when inlined this is more efficient than "_a - _b" and GCC 4.7+ should inline it */
+ return *_a > *_b ? 1 : (*_a < *_b ? -1 : 0);
+#else
+ return *_a - *_b;
+#endif
+}
+
+static const struct wine_rb_functions obj_handles_rb_ops =
+{
+ ntdll_object_rb_alloc,
+ ntdll_object_rb_realloc,
+ ntdll_object_rb_free,
+ ntdll_object_handle_compare,
+};
+
+/* Tree mapping all process-locally known kernel handles to a struct ntdll_object */
+static struct {
+ struct wine_rb_tree tree;
+ pthread_mutex_t mutex;
+} handles = {
+ { &obj_handles_rb_ops, NULL, {NULL, 0, 0}}, /* static initializer to aid -findirect-inline */
+ PTHREAD_MUTEX_INITIALIZER /* non-recursive fast lock */
+};
/*
@@ -390,6 +651,9 @@ NTSTATUS close_handle( HANDLE handle )
}
SERVER_END_REQ;
if (fd != -1) close( fd );
+
+ ntdll_handle_remove( handle );
+
return ret;
}
--
2.4.6
More information about the wine-devel
mailing list