| ... | @@ -153,14 +153,149 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN | ... | @@ -153,14 +153,149 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 153 | } | 153 | } |
| 154 | } | 154 | } |
| 155 | | 155 | |
| 156 | pub const CreatePipeError = error{Unexpected}; | 156 | pub fn GetCurrentProcess() HANDLE { |
| | 157 | const process_pseudo_handle: usize = @bitCast(@as(isize, -1)); |
| | 158 | return @ptrFromInt(process_pseudo_handle); |
| | 159 | } |
| | 160 | |
| | 161 | pub fn GetCurrentProcessId() DWORD { |
| | 162 | return @truncate(@intFromPtr(teb().ClientId.UniqueProcess)); |
| | 163 | } |
| | 164 | |
| | 165 | pub fn GetCurrentThread() HANDLE { |
| | 166 | const thread_pseudo_handle: usize = @bitCast(@as(isize, -2)); |
| | 167 | return @ptrFromInt(thread_pseudo_handle); |
| | 168 | } |
| | 169 | |
| | 170 | pub fn GetCurrentThreadId() DWORD { |
| | 171 | return @truncate(@intFromPtr(teb().ClientId.UniqueThread)); |
| | 172 | } |
| | 173 | |
| | 174 | pub const CreatePipeError = error{ Unexpected, SystemResources }; |
| 157 | | 175 | |
| | 176 | var npfs: ?HANDLE = null; |
| | 177 | |
| | 178 | /// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls. |
| | 179 | /// It implements similar behavior to `CreatePipe` and is meant to serve |
| | 180 | /// as a direct substitute for that call. |
| 158 | pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void { | 181 | pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void { |
| 159 | if (kernel32.CreatePipe(rd, wr, sattr, 0) == 0) { | 182 | // Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to: |
| 160 | switch (kernel32.GetLastError()) { | 183 | // |
| 161 | else => |err| return unexpectedError(err), | 184 | // \??\pipe\Win32Pipes.{pid}.{count} |
| | 185 | // |
| | 186 | // where `pid` is the process id and count is a incrementing counter. |
| | 187 | // The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System |
| | 188 | // and use that as the root directory for `NtCreateNamedPipeFile`. |
| | 189 | // This object is visible under the NPFS but has no filename attached to it. |
| | 190 | // |
| | 191 | // This implementation replicates how `CreatePipe` works in modern Windows versions. |
| | 192 | const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst); |
| | 193 | const dev_handle = opt_dev_handle orelse blk: { |
| | 194 | const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\"); |
| | 195 | const len: u16 = @truncate(str.len * @sizeOf(u16)); |
| | 196 | const name = UNICODE_STRING{ |
| | 197 | .Length = len, |
| | 198 | .MaximumLength = len, |
| | 199 | .Buffer = @constCast(@ptrCast(str)), |
| | 200 | }; |
| | 201 | const attrs = OBJECT_ATTRIBUTES{ |
| | 202 | .ObjectName = @constCast(&name), |
| | 203 | .Length = @sizeOf(OBJECT_ATTRIBUTES), |
| | 204 | .RootDirectory = null, |
| | 205 | .Attributes = 0, |
| | 206 | .SecurityDescriptor = null, |
| | 207 | .SecurityQualityOfService = null, |
| | 208 | }; |
| | 209 | |
| | 210 | var iosb: IO_STATUS_BLOCK = undefined; |
| | 211 | var handle: HANDLE = undefined; |
| | 212 | switch (ntdll.NtCreateFile( |
| | 213 | &handle, |
| | 214 | GENERIC_READ | SYNCHRONIZE, |
| | 215 | @constCast(&attrs), |
| | 216 | &iosb, |
| | 217 | null, |
| | 218 | 0, |
| | 219 | FILE_SHARE_READ | FILE_SHARE_WRITE, |
| | 220 | FILE_OPEN, |
| | 221 | FILE_SYNCHRONOUS_IO_NONALERT, |
| | 222 | null, |
| | 223 | 0, |
| | 224 | )) { |
| | 225 | .SUCCESS => {}, |
| | 226 | // Judging from the ReactOS sources this is technically possible. |
| | 227 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| | 228 | .INVALID_PARAMETER => unreachable, |
| | 229 | else => |e| return unexpectedStatus(e), |
| 162 | } | 230 | } |
| | 231 | if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| { |
| | 232 | CloseHandle(handle); |
| | 233 | break :blk xchg.?; |
| | 234 | } else break :blk handle; |
| | 235 | }; |
| | 236 | |
| | 237 | const name = UNICODE_STRING{ .Buffer = null, .Length = 0, .MaximumLength = 0 }; |
| | 238 | var attrs = OBJECT_ATTRIBUTES{ |
| | 239 | .ObjectName = @constCast(&name), |
| | 240 | .Length = @sizeOf(OBJECT_ATTRIBUTES), |
| | 241 | .RootDirectory = dev_handle, |
| | 242 | .Attributes = OBJ_CASE_INSENSITIVE, |
| | 243 | .SecurityDescriptor = sattr.lpSecurityDescriptor, |
| | 244 | .SecurityQualityOfService = null, |
| | 245 | }; |
| | 246 | if (sattr.bInheritHandle != 0) attrs.Attributes |= OBJ_INHERIT; |
| | 247 | |
| | 248 | // 120 second relative timeout in 100ns units. |
| | 249 | const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100; |
| | 250 | var iosb: IO_STATUS_BLOCK = undefined; |
| | 251 | var read: HANDLE = undefined; |
| | 252 | switch (ntdll.NtCreateNamedPipeFile( |
| | 253 | &read, |
| | 254 | GENERIC_READ | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE, |
| | 255 | &attrs, |
| | 256 | &iosb, |
| | 257 | FILE_SHARE_READ | FILE_SHARE_WRITE, |
| | 258 | FILE_CREATE, |
| | 259 | FILE_SYNCHRONOUS_IO_NONALERT, |
| | 260 | FILE_PIPE_BYTE_STREAM_TYPE, |
| | 261 | FILE_PIPE_BYTE_STREAM_MODE, |
| | 262 | FILE_PIPE_QUEUE_OPERATION, |
| | 263 | 1, |
| | 264 | 4096, |
| | 265 | 4096, |
| | 266 | @constCast(&default_timeout), |
| | 267 | )) { |
| | 268 | .SUCCESS => {}, |
| | 269 | .INVALID_PARAMETER => unreachable, |
| | 270 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| | 271 | else => |e| return unexpectedStatus(e), |
| 163 | } | 272 | } |
| | 273 | errdefer CloseHandle(read); |
| | 274 | |
| | 275 | attrs.RootDirectory = read; |
| | 276 | |
| | 277 | var write: HANDLE = undefined; |
| | 278 | switch (ntdll.NtCreateFile( |
| | 279 | &write, |
| | 280 | GENERIC_WRITE | SYNCHRONIZE | FILE_READ_ATTRIBUTES, |
| | 281 | &attrs, |
| | 282 | &iosb, |
| | 283 | null, |
| | 284 | 0, |
| | 285 | FILE_SHARE_READ | FILE_SHARE_WRITE, |
| | 286 | FILE_OPEN, |
| | 287 | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE, |
| | 288 | null, |
| | 289 | 0, |
| | 290 | )) { |
| | 291 | .SUCCESS => {}, |
| | 292 | .INVALID_PARAMETER => unreachable, |
| | 293 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| | 294 | else => |e| return unexpectedStatus(e), |
| | 295 | } |
| | 296 | |
| | 297 | rd.* = read; |
| | 298 | wr.* = write; |
| 164 | } | 299 | } |
| 165 | | 300 | |
| 166 | pub fn CreateEventEx(attributes: ?*SECURITY_ATTRIBUTES, name: []const u8, flags: DWORD, desired_access: DWORD) !HANDLE { | 301 | pub fn CreateEventEx(attributes: ?*SECURITY_ATTRIBUTES, name: []const u8, flags: DWORD, desired_access: DWORD) !HANDLE { |
| ... | @@ -1050,35 +1185,32 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { | ... | @@ -1050,35 +1185,32 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { |
| 1050 | return @as(u64, @bitCast(result)); | 1185 | return @as(u64, @bitCast(result)); |
| 1051 | } | 1186 | } |
| 1052 | | 1187 | |
| 1053 | pub fn QueryObjectName( | 1188 | pub fn QueryObjectName(handle: HANDLE, out_buffer: []u16) ![]u16 { |
| 1054 | handle: HANDLE, | | |
| 1055 | out_buffer: []u16, | | |
| 1056 | ) ![]u16 { | | |
| 1057 | const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong; | 1189 | const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong; |
| 1058 | | 1190 | |
| 1059 | const info = @as(*OBJECT_NAME_INFORMATION, @ptrCast(out_buffer_aligned)); | 1191 | const info = @as(*OBJECT_NAME_INFORMATION, @ptrCast(out_buffer_aligned)); |
| 1060 | //buffer size is specified in bytes | 1192 | // buffer size is specified in bytes |
| 1061 | const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(ULONG); | 1193 | const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(ULONG); |
| 1062 | //last argument would return the length required for full_buffer, not exposed here | 1194 | // last argument would return the length required for full_buffer, not exposed here |
| 1063 | const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null); | 1195 | return switch (ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null)) { |
| 1064 | switch (rc) { | 1196 | .SUCCESS => blk: { |
| 1065 | .SUCCESS => { | | |
| 1066 | // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0) | 1197 | // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0) |
| 1067 | // if the object was "unnamed", not sure if this can happen for file handles | 1198 | // if the object was "unnamed", not sure if this can happen for file handles |
| 1068 | if (info.Name.MaximumLength == 0) return error.Unexpected; | 1199 | if (info.Name.MaximumLength == 0) break :blk error.Unexpected; |
| 1069 | // resulting string length is specified in bytes | 1200 | // resulting string length is specified in bytes |
| 1070 | const path_length_unterminated = @divExact(info.Name.Length, 2); | 1201 | const path_length_unterminated = @divExact(info.Name.Length, 2); |
| 1071 | return info.Name.Buffer[0..path_length_unterminated]; | 1202 | break :blk info.Name.Buffer.?[0..path_length_unterminated]; |
| 1072 | }, | 1203 | }, |
| 1073 | .ACCESS_DENIED => return error.AccessDenied, | 1204 | .ACCESS_DENIED => error.AccessDenied, |
| 1074 | .INVALID_HANDLE => return error.InvalidHandle, | 1205 | .INVALID_HANDLE => error.InvalidHandle, |
| 1075 | // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH), | 1206 | // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH), |
| 1076 | // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL) | 1207 | // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL) |
| 1077 | .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong, | 1208 | .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong, |
| 1078 | else => |e| return unexpectedStatus(e), | 1209 | else => |e| unexpectedStatus(e), |
| 1079 | } | 1210 | }; |
| 1080 | } | 1211 | } |
| 1081 | test "QueryObjectName" { | 1212 | |
| | 1213 | test QueryObjectName { |
| 1082 | if (builtin.os.tag != .windows) | 1214 | if (builtin.os.tag != .windows) |
| 1083 | return; | 1215 | return; |
| 1084 | | 1216 | |
| ... | @@ -3015,29 +3147,29 @@ pub const OVERLAPPED_ENTRY = extern struct { | ... | @@ -3015,29 +3147,29 @@ pub const OVERLAPPED_ENTRY = extern struct { |
| 3015 | | 3147 | |
| 3016 | pub const MAX_PATH = 260; | 3148 | pub const MAX_PATH = 260; |
| 3017 | | 3149 | |
| 3018 | // TODO issue #305 | 3150 | pub const FILE_INFO_BY_HANDLE_CLASS = enum(u32) { |
| 3019 | pub const FILE_INFO_BY_HANDLE_CLASS = u32; | 3151 | FileBasicInfo = 0, |
| 3020 | pub const FileBasicInfo = 0; | 3152 | FileStandardInfo = 1, |
| 3021 | pub const FileStandardInfo = 1; | 3153 | FileNameInfo = 2, |
| 3022 | pub const FileNameInfo = 2; | 3154 | FileRenameInfo = 3, |
| 3023 | pub const FileRenameInfo = 3; | 3155 | FileDispositionInfo = 4, |
| 3024 | pub const FileDispositionInfo = 4; | 3156 | FileAllocationInfo = 5, |
| 3025 | pub const FileAllocationInfo = 5; | 3157 | FileEndOfFileInfo = 6, |
| 3026 | pub const FileEndOfFileInfo = 6; | 3158 | FileStreamInfo = 7, |
| 3027 | pub const FileStreamInfo = 7; | 3159 | FileCompressionInfo = 8, |
| 3028 | pub const FileCompressionInfo = 8; | 3160 | FileAttributeTagInfo = 9, |
| 3029 | pub const FileAttributeTagInfo = 9; | 3161 | FileIdBothDirectoryInfo = 10, |
| 3030 | pub const FileIdBothDirectoryInfo = 10; | 3162 | FileIdBothDirectoryRestartInfo = 11, |
| 3031 | pub const FileIdBothDirectoryRestartInfo = 11; | 3163 | FileIoPriorityHintInfo = 12, |
| 3032 | pub const FileIoPriorityHintInfo = 12; | 3164 | FileRemoteProtocolInfo = 13, |
| 3033 | pub const FileRemoteProtocolInfo = 13; | 3165 | FileFullDirectoryInfo = 14, |
| 3034 | pub const FileFullDirectoryInfo = 14; | 3166 | FileFullDirectoryRestartInfo = 15, |
| 3035 | pub const FileFullDirectoryRestartInfo = 15; | 3167 | FileStorageInfo = 16, |
| 3036 | pub const FileStorageInfo = 16; | 3168 | FileAlignmentInfo = 17, |
| 3037 | pub const FileAlignmentInfo = 17; | 3169 | FileIdInfo = 18, |
| 3038 | pub const FileIdInfo = 18; | 3170 | FileIdExtdDirectoryInfo = 19, |
| 3039 | pub const FileIdExtdDirectoryInfo = 19; | 3171 | FileIdExtdDirectoryRestartInfo = 20, |
| 3040 | pub const FileIdExtdDirectoryRestartInfo = 20; | 3172 | }; |
| 3041 | | 3173 | |
| 3042 | pub const BY_HANDLE_FILE_INFORMATION = extern struct { | 3174 | pub const BY_HANDLE_FILE_INFORMATION = extern struct { |
| 3043 | dwFileAttributes: DWORD, | 3175 | dwFileAttributes: DWORD, |
| ... | @@ -3186,6 +3318,25 @@ pub const FILE_ATTRIBUTE_SYSTEM = 0x4; | ... | @@ -3186,6 +3318,25 @@ pub const FILE_ATTRIBUTE_SYSTEM = 0x4; |
| 3186 | pub const FILE_ATTRIBUTE_TEMPORARY = 0x100; | 3318 | pub const FILE_ATTRIBUTE_TEMPORARY = 0x100; |
| 3187 | pub const FILE_ATTRIBUTE_VIRTUAL = 0x10000; | 3319 | pub const FILE_ATTRIBUTE_VIRTUAL = 0x10000; |
| 3188 | | 3320 | |
| | 3321 | pub const FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1ff; |
| | 3322 | pub const FILE_GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE; |
| | 3323 | pub const FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE; |
| | 3324 | pub const FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE; |
| | 3325 | |
| | 3326 | // Flags for NtCreateNamedPipeFile |
| | 3327 | // NamedPipeType |
| | 3328 | pub const FILE_PIPE_BYTE_STREAM_TYPE = 0x0; |
| | 3329 | pub const FILE_PIPE_MESSAGE_TYPE = 0x1; |
| | 3330 | pub const FILE_PIPE_ACCEPT_REMOTE_CLIENTS = 0x0; |
| | 3331 | pub const FILE_PIPE_REJECT_REMOTE_CLIENTS = 0x2; |
| | 3332 | pub const FILE_PIPE_TYPE_VALID_MASK = 0x3; |
| | 3333 | // CompletionMode |
| | 3334 | pub const FILE_PIPE_QUEUE_OPERATION = 0x0; |
| | 3335 | pub const FILE_PIPE_COMPLETE_OPERATION = 0x1; |
| | 3336 | // ReadMode |
| | 3337 | pub const FILE_PIPE_BYTE_STREAM_MODE = 0x0; |
| | 3338 | pub const FILE_PIPE_MESSAGE_MODE = 0x1; |
| | 3339 | |
| 3189 | // flags for CreateEvent | 3340 | // flags for CreateEvent |
| 3190 | pub const CREATE_EVENT_INITIAL_SET = 0x00000002; | 3341 | pub const CREATE_EVENT_INITIAL_SET = 0x00000002; |
| 3191 | pub const CREATE_EVENT_MANUAL_RESET = 0x00000001; | 3342 | pub const CREATE_EVENT_MANUAL_RESET = 0x00000001; |
| ... | @@ -4151,7 +4302,7 @@ pub const OBJ_VALID_ATTRIBUTES = 0x000003F2; | ... | @@ -4151,7 +4302,7 @@ pub const OBJ_VALID_ATTRIBUTES = 0x000003F2; |
| 4151 | pub const UNICODE_STRING = extern struct { | 4302 | pub const UNICODE_STRING = extern struct { |
| 4152 | Length: c_ushort, | 4303 | Length: c_ushort, |
| 4153 | MaximumLength: c_ushort, | 4304 | MaximumLength: c_ushort, |
| 4154 | Buffer: [*]WCHAR, | 4305 | Buffer: ?[*]WCHAR, |
| 4155 | }; | 4306 | }; |
| 4156 | | 4307 | |
| 4157 | pub const ACTIVATION_CONTEXT_DATA = opaque {}; | 4308 | pub const ACTIVATION_CONTEXT_DATA = opaque {}; |
| ... | @@ -4176,7 +4327,11 @@ pub const THREAD_BASIC_INFORMATION = extern struct { | ... | @@ -4176,7 +4327,11 @@ pub const THREAD_BASIC_INFORMATION = extern struct { |
| 4176 | }; | 4327 | }; |
| 4177 | | 4328 | |
| 4178 | pub const TEB = extern struct { | 4329 | pub const TEB = extern struct { |
| 4179 | Reserved1: [12]PVOID, | 4330 | NtTib: NT_TIB, |
| | 4331 | EnvironmentPointer: PVOID, |
| | 4332 | ClientId: CLIENT_ID, |
| | 4333 | ActiveRpcHandle: PVOID, |
| | 4334 | ThreadLocalStoragePointer: PVOID, |
| 4180 | ProcessEnvironmentBlock: *PEB, | 4335 | ProcessEnvironmentBlock: *PEB, |
| 4181 | Reserved2: [399]PVOID, | 4336 | Reserved2: [399]PVOID, |
| 4182 | Reserved3: [1952]u8, | 4337 | Reserved3: [1952]u8, |
| ... | @@ -4188,6 +4343,25 @@ pub const TEB = extern struct { | ... | @@ -4188,6 +4343,25 @@ pub const TEB = extern struct { |
| 4188 | TlsExpansionSlots: PVOID, | 4343 | TlsExpansionSlots: PVOID, |
| 4189 | }; | 4344 | }; |
| 4190 | | 4345 | |
| | 4346 | comptime { |
| | 4347 | // Offsets taken from WinDbg info and Geoff Chappell[1] (RIP) |
| | 4348 | // [1]: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm |
| | 4349 | assert(@offsetOf(TEB, "NtTib") == 0x00); |
| | 4350 | if (@sizeOf(usize) == 4) { |
| | 4351 | assert(@offsetOf(TEB, "EnvironmentPointer") == 0x1C); |
| | 4352 | assert(@offsetOf(TEB, "ClientId") == 0x20); |
| | 4353 | assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x28); |
| | 4354 | assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x2C); |
| | 4355 | assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x30); |
| | 4356 | } else if (@sizeOf(usize) == 8) { |
| | 4357 | assert(@offsetOf(TEB, "EnvironmentPointer") == 0x38); |
| | 4358 | assert(@offsetOf(TEB, "ClientId") == 0x40); |
| | 4359 | assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x50); |
| | 4360 | assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x58); |
| | 4361 | assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x60); |
| | 4362 | } |
| | 4363 | } |
| | 4364 | |
| 4191 | pub const EXCEPTION_REGISTRATION_RECORD = extern struct { | 4365 | pub const EXCEPTION_REGISTRATION_RECORD = extern struct { |
| 4192 | Next: ?*EXCEPTION_REGISTRATION_RECORD, | 4366 | Next: ?*EXCEPTION_REGISTRATION_RECORD, |
| 4193 | Handler: ?*EXCEPTION_DISPOSITION, | 4367 | Handler: ?*EXCEPTION_DISPOSITION, |