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. --- dlls/ntdll/loader.c | 1 + dlls/ntdll/ntdll_misc.h | 62 ++++++ dlls/ntdll/om.c | 568 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 631 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..f0b702e 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,64 @@ 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); + /* returns true if the object is currently signaled */ + BOOL (*signaled) (struct ntdll_object *obj); + /* 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 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..3ccce4c 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,567 @@ #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 */ +}; + +/************************************************************************* + * ntdll_object_new + * + * Allocates and initializes a new struct ntdll_object object. + * + * PARAMS + * h [I] Handle of the object. + * size [I] Number of bytes to allocate. + * type_id [I] Integral type of the object + * ops [I] Pointer to function table. + * + * RETURNS + * Success: A pointer to the new object. + * Failure: Null, indicating a low memory condition. + * + * NOTES + * Allocates and initialises a struct ntdll_object object and adds it to + * process-global list. Initial refcount is 1. After the derived-type + * initialises its members the object should be added to the handle database + * by calling ntdll_handle_add(), which will increase its refcount to 2. Once + * this is done, the object should be released with ntdll_object_release(). + * If you release it before that, the object will auto-destruct. + * + * LOCKING + * 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; +} + +/************************************************************************* + * ntdll_object_grab + * + * Increases refcount by one. + * + * PARAMS + * obj [I] The object. + */ +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; +} + +/************************************************************************* + * ntdll_object_release + * + * Decrease refcount by one, possibly destroying the object + * + * PARAMS + * obj [I] The object. + * + * NOTES + * While using an object, it is possible for another thread to call + * CloseHandle(), removing it from the handle database (and decreasing its + * refcount). Therefore you should not use the pointer after a call to + * ntdll_object_release() returns. + * + * LOCKING + * objects.mutex + */ +void ntdll_object_release(struct ntdll_object *obj) +{ + int refcount = interlocked_xchg_add(&obj->refcount, -1); + assert(refcount > 0); + + if (!refcount) + { + /* TODO: 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); +#if defined(DEBUG) || defined(DEBUG_OBJECTS) + memset(obj, 0xaa, sizeof(struct ntdll_object)); +#endif + RtlFreeHeap(GetProcessHeap(), 0, obj); + } +} + +/************************************************************************* + * ntdll_object_dump_base + * + * Called by derived-class to dump a text description of base class members. + * + * PARAMS + * obj [I] The object to dump + * start [IO] Pointer to a pointer to the next write position. + * end [I] Pointer to one byte past the end of the buffer. + * + * NOTES + * Called by derived class to dump struct ntdll_object to a string buffer. + * The pointer that start points to will be updated to the new end of the + * text so that subsequent writes may begin there. + */ +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; +} + +/* return a thread-local buffer for dumping object descriptions to */ +static void *ntdll_object_get_debug_buffer(size_t *size) +{ + /* FIXME: free this on thread exit (not sure where that is hooked) */ + 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; +} + +/************************************************************************* + * ntdll_object_dump + * + * Dumps a text description of the object to a thread-local buffer. + * + * PARAMS + * obj [I] The object to dump + * + * RETURNS + * A pointer to a text description of the object. + * + * NOTES + * Because this function uses a thread-local buffer, you must use the result + * before calling it again. For instance, this code will not work: + * + * ERR("%s will look the same as %s\n", ntdll_object_dump(o1), ntdll_object_dump(02)); + */ +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; +} + +/* 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; + + return *_a > *_b ? 1 : (*_a < *_b ? -1 : 0); +} + +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 */ +}; + +/************************************************************************* + * ntdll_handle_add + * + * Adds the object to the ntdll handle database + * + * PARAMS + * obj [I] The object. + * + * NOTES + * Adds the object to the handle database, increasing its reference count to + * account for it being referenced by the handle tree. + * + * LOCKING + * handles.mutex + */ +__attribute__((noinline, flatten)) +int ntdll_handle_add(struct ntdll_object *obj) +{ + int ret = 0; + + TRACE_(ntdll_obj)("%p\n", obj); + +try_again: + pthread_mutex_lock(&handles.mutex); + ret = wine_rb_put(&handles.tree, &obj->h, &obj->tree_entry); + + /* increase the ref count */ + if (!ret) + ntdll_object_grab(obj); + pthread_mutex_unlock(&handles.mutex); + + if (ret) + { + struct ntdll_object *existing = ntdll_handle_find(obj->h); + + /* bug if we get here -- this should be resolved now, but lets leave + * this here for now to be safe */ + ERR("Failed to insert handle %p.\n", obj->h); + if (existing) + { + ERR("Existing object: %s\n", ntdll_object_dump(existing)); + ntdll_object_release(existing); + } + else + ERR("No other object found with that handle...\n"); + ERR("New object: %s\n", ntdll_object_dump(obj)); + + assert(0); + + /* should probably just exit(1) here */ + ntdll_handle_remove(obj->h); + goto try_again; + } + + TRACE("Added obj %s\n", ntdll_object_dump(obj)); + + return ret; +} + +/************************************************************************* + * ntdll_handle_remove + * + * Remove an object from the handle database. + * + * PARAMS + * h [I] A handle to remove. + * + * NOTES + * If no object with the specified handle is found then nothing is done. + * + * LOCKING + * handles.mutex + */ +__attribute__((noinline)) +void ntdll_handle_remove(const HANDLE h) +{ + struct ntdll_object *obj = ntdll_handle_find(h); + + if (!obj) + return; + + TRACE_(ntdll_obj)("h = %p) obj = %s\n", h, ntdll_object_dump(obj)); + + pthread_mutex_lock(&handles.mutex); + /* FIXME: a more efficient wine_rb_remove_by_entry would be nice here */ + wine_rb_remove(&handles.tree, &h); + pthread_mutex_unlock(&handles.mutex); + + ntdll_object_release(obj); /* once for removing it from the tree */ + ntdll_object_release(obj); /* once for the call to ntdll_handle_find() */ +} + +/************************************************************************* + * ntdll_handle_find + * + * Searches the process-local handle database for the specified object. + * + * PARAMS + * h [I] Handle of an object. + * + * NOTES + * If an object with a matching handle is found then the object's refcount is + * incremented and a pointer to the object is returned. It is then the + * responsibility of the caller to release the object when done. + * + * RETURNS + * Success: A pointer to the object. + * Failure: NULL + * + * LOCKING + * handles.mutex + */ +struct ntdll_object *ntdll_handle_find(const HANDLE h) +{ + struct wine_rb_entry *entry; + struct ntdll_object *ret = NULL; + + pthread_mutex_lock(&handles.mutex); + entry = wine_rb_get(&handles.tree, &h); + + if (entry) + { + ret = WINE_RB_ENTRY_VALUE(entry, struct ntdll_object, tree_entry); + ntdll_object_grab(ret); + } + pthread_mutex_unlock(&handles.mutex); + + //TRACE_(ntdll_obj)("(%p) result: %p\n", h, ret); + + return ret; +} + + +/* callback for ntdll_objects_cleanup() + * + * LOCKING + * Locks objects.mutex (via ntdll_object_release()) + * Called when handles.mutex already locked + */ + +static void ntdll_objects_cleanup_cb(struct wine_rb_entry *entry, void *context) +{ + struct ntdll_object *obj = WINE_RB_ENTRY_VALUE(entry, struct ntdll_object, tree_entry); + size_t *leaked_handles = (size_t *)context; + + ++(*leaked_handles); + + ERR_(ntdll_obj)("Leaked object handle: %s\n", ntdll_object_dump(obj)); + + /* handles.mutex already locked */ + wine_rb_remove(&handles.tree, &obj->h); + + ntdll_object_release(obj); +} + +/* atexit() cleanup + * + * Locking: + * Locks handles.mutex --> then objects.mutex + */ +static void ntdll_objects_cleanup(void) +{ + size_t leaked_handles = 0; + size_t leaked_objects = 0; + + TRACE_(ntdll_obj)("\n"); + pthread_mutex_lock(&handles.mutex); + wine_rb_for_each_entry(&handles.tree, ntdll_objects_cleanup_cb, &leaked_handles); + pthread_mutex_unlock(&handles.mutex); + + pthread_mutex_lock(&objects.mutex); + leaked_objects = list_count(&objects.list); + if (leaked_objects) + { + struct ntdll_object *obj; + //struct list *i; + LIST_FOR_EACH_ENTRY( obj, &objects.list, struct ntdll_object , list_entry ) + { + ERR_(ntdll_obj)("Leaked object: %s\n", ntdll_object_dump(obj)); + } + } + pthread_mutex_unlock(&objects.mutex); + + if (leaked_handles || leaked_objects) + ERR_(ntdll_obj)("*** %zu leaked handles found, %zu leaked objects remain.\n", + leaked_handles, leaked_objects); + +} + +/************************************************************************* + * ntdll_object_db_init + * + * Initialize objects list and handles tree + * + * NOTES + * Called via __wine_process_init() in loader.c + * + * LOCKING + * First locks objects.mutex, then releases and locks handles.mutex + */ +NTSTATUS ntdll_object_db_init(void) +{ + NTSTATUS ret = 0; + + TRACE_(ntdll_obj)("\n"); + + /* init objects list if not already inited */ + pthread_mutex_lock(&objects.mutex); + if (!objects.list.next) + list_init(&objects.list); + pthread_mutex_unlock(&objects.mutex); + + /* init red-black handle-to-object tree if not already inited */ + pthread_mutex_lock(&handles.mutex); + if (!handles.tree.stack.entries) + { + /* passing handles.tree.functions instead of obj_handles_rb_ops to aid -findirect-inline + * (it might not matter) */ + if (wine_rb_init(&handles.tree, handles.tree.functions) == -1) + { + ERR("Failed to initialize ntdll object handle rbtree.\n"); + ret = ERROR_OUTOFMEMORY; + } + else + atexit(ntdll_objects_cleanup); + } + pthread_mutex_unlock(&handles.mutex); + + return ret; +}
/* @@ -390,6 +955,9 @@ NTSTATUS close_handle( HANDLE handle ) } SERVER_END_REQ; if (fd != -1) close( fd ); + + ntdll_handle_remove( handle ); + return ret; }