Repository: microsoft/dlltracer-python (language: python)

Issue:
Resolve device paths
From #7:

> I didn't like the:
> `LoadLibrary \Device\HarddiskVolume3\Windows\System32\kernel.appcore.dll` syntax.
> IMHO a `LoadLibrary c:\Windows\System32\kernel.appcore.dll` would be nicer. Possible?


Discussion / hints:
It looks like this is possible with:

```
if path.startswith(r"\Device\HarddiskVolume"):
    path = os.path.realpath(r"\\.\" + path[8:])
```

This is probably best to put in `Load[Failed]Event.__str__` so that we aren't doing file system access until we want to display it. That snippet also needs some error handling in case the resolve fails.

(It might also not work in 3.7, but I didn't test that far back. 3.8 seems to be okay.)

Fix patch:
diff --git a/src/dlltracer/_native.pyx b/src/dlltracer/_native.pyx
index 5361243..11550e4 100644
--- a/src/dlltracer/_native.pyx
+++ b/src/dlltracer/_native.pyx
@@ -5,6 +5,7 @@ from libc.string cimport memcpy, memset
 from cpython cimport version as sys_version
 from cpython.ref cimport PyObject, Py_INCREF
 
+from os.path import realpath as _realpath
 from threading import Thread as _Thread
 from uuid import UUID as _UUID
 
@@ -120,13 +121,27 @@ cdef int _audit = 0
 class LoadEvent:
     def __init__(self, path): self.path = path
     def __repr__(self): return f"<LoadLibrary({self.path})>"
-    def __str__(self): return f"LoadLibrary {self.path}"
+    def __str__(self):
+        path = self.path
+        if path.lower().startswith("\\device\\"):
+            try:
+                path = _realpath("\\\\." + path[7:])
+            except OSError:
+                pass
+        return f"LoadLibrary {path}"
 
 
 class LoadFailedEvent:
     def __init__(self, path): self.path = path
     def __repr__(self): return f"<LoadLibraryFailed({self.path})>"
-    def __str__(self): return f"Failed {self.path}"
+    def __str__(self):
+        path = self.path
+        if path.lower().startswith("\\device\\"):
+            try:
+                path = _realpath("\\\\." + path[7:])
+            except OSError:
+                pass
+        return f"Failed {self.path}"
 
 
 class DebugEvent:
@@ -179,14 +194,14 @@ class DebugEvent:
         )
 
 
-cdef void _check(ULONG r, str msg) except * nogil:
+cdef int _check(ULONG r, str msg) except -1 nogil:
     if r == 0:
-        return
+        return 0
     with gil:
         raise OSError(None, f"{msg} (0x{r:08X})", None, r & 0xFFFFFFFF)
 
 
-cdef void __stdcall _event_record_callback(EVENT_RECORD* EventRecord) nogil:
+cdef void __stdcall _event_record_callback(EVENT_RECORD* EventRecord) noexcept nogil:
     cdef EVENT_HEADER* hdr = &EventRecord.EventHeader
     cdef unsigned char* b = <unsigned char*>EventRecord.UserData
     cdef Py_ssize_t cb = <Py_ssize_t>EventRecord.UserDataLength


Test patch:
diff --git a/tests/test_basic.py b/tests/test_basic.py
index b81775b..b44a565 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -71,6 +71,21 @@ def test_audit():
     run(do_test_audit)
 
 
+def do_test_audit_failure():
+    def hook(event, args):
+        if event.startswith("dlltracer"):
+            raise RuntimeError("forced abort")
+    sys.addaudithook(hook)
+
+    with dlltracer.Trace(audit=True):
+        from dlltracer import _dlltracertest
+
+
+@pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason="Requires Python 3.8 or later")
+def test_audit_failure():
+    run(do_test_audit_failure)
+
+
 def do_test_debug():
     with dlltracer.Trace(debug=True, collect=True) as events:
         from dlltracer import _dlltracertest