[PATCH 4/8 part 1] ntdll: Add process-local handle tracking

Daniel Santos daniel.santos at pobox.com
Thu Sep 10 01:50:53 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.

(I had to split this patch up due to SMTP server, sorry)

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