| author | |
| committer | |
| log | 5b82b40043e3a930e6693867c83de00ab3d20ef7 |
| tree | acc4e2cec84c50612cf0dc5428ec648d32ae76dd |
| parent | c964e10821c417ceb7d0efcf1625d67484e734f7 |
| parent | 908ccce064a898d5db1d43dbdc4a3590fd84d4ba |
| signature |
coff: add hot-code swapping PoC6 files changed, 383 insertions(+), 149 deletions(-)
lib/std/os/windows.zig+199| ... | ... | @@ -1514,6 +1514,24 @@ pub fn VirtualProtect(lpAddress: ?LPVOID, dwSize: SIZE_T, flNewProtect: DWORD, l |
| 1514 | 1514 | } |
| 1515 | 1515 | } |
| 1516 | 1516 | |
| 1517 | pub fn VirtualProtectEx(handle: HANDLE, addr: ?LPVOID, size: SIZE_T, new_prot: DWORD) VirtualProtectError!DWORD { | |
| 1518 | var old_prot: DWORD = undefined; | |
| 1519 | var out_addr = addr; | |
| 1520 | var out_size = size; | |
| 1521 | switch (ntdll.NtProtectVirtualMemory( | |
| 1522 | handle, | |
| 1523 | &out_addr, | |
| 1524 | &out_size, | |
| 1525 | new_prot, | |
| 1526 | &old_prot, | |
| 1527 | )) { | |
| 1528 | .SUCCESS => return old_prot, | |
| 1529 | .INVALID_ADDRESS => return error.InvalidAddress, | |
| 1530 | // TODO: map errors | |
| 1531 | else => |rc| return std.os.windows.unexpectedStatus(rc), | |
| 1532 | } | |
| 1533 | } | |
| 1534 | ||
| 1517 | 1535 | pub const VirtualQueryError = error{Unexpected}; |
| 1518 | 1536 | |
| 1519 | 1537 | pub fn VirtualQuery(lpAddress: ?LPVOID, lpBuffer: PMEMORY_BASIC_INFORMATION, dwLength: SIZE_T) VirtualQueryError!SIZE_T { |
| ... | ... | @@ -4457,3 +4475,184 @@ pub const MODULEENTRY32 = extern struct { |
| 4457 | 4475 | szModule: [MAX_MODULE_NAME32 + 1]CHAR, |
| 4458 | 4476 | szExePath: [MAX_PATH]CHAR, |
| 4459 | 4477 | }; |
| 4478 | ||
| 4479 | pub const THREADINFOCLASS = enum(c_int) { | |
| 4480 | ThreadBasicInformation, | |
| 4481 | ThreadTimes, | |
| 4482 | ThreadPriority, | |
| 4483 | ThreadBasePriority, | |
| 4484 | ThreadAffinityMask, | |
| 4485 | ThreadImpersonationToken, | |
| 4486 | ThreadDescriptorTableEntry, | |
| 4487 | ThreadEnableAlignmentFaultFixup, | |
| 4488 | ThreadEventPair_Reusable, | |
| 4489 | ThreadQuerySetWin32StartAddress, | |
| 4490 | ThreadZeroTlsCell, | |
| 4491 | ThreadPerformanceCount, | |
| 4492 | ThreadAmILastThread, | |
| 4493 | ThreadIdealProcessor, | |
| 4494 | ThreadPriorityBoost, | |
| 4495 | ThreadSetTlsArrayAddress, | |
| 4496 | ThreadIsIoPending, | |
| 4497 | // Windows 2000+ from here | |
| 4498 | ThreadHideFromDebugger, | |
| 4499 | // Windows XP+ from here | |
| 4500 | ThreadBreakOnTermination, | |
| 4501 | ThreadSwitchLegacyState, | |
| 4502 | ThreadIsTerminated, | |
| 4503 | // Windows Vista+ from here | |
| 4504 | ThreadLastSystemCall, | |
| 4505 | ThreadIoPriority, | |
| 4506 | ThreadCycleTime, | |
| 4507 | ThreadPagePriority, | |
| 4508 | ThreadActualBasePriority, | |
| 4509 | ThreadTebInformation, | |
| 4510 | ThreadCSwitchMon, | |
| 4511 | // Windows 7+ from here | |
| 4512 | ThreadCSwitchPmu, | |
| 4513 | ThreadWow64Context, | |
| 4514 | ThreadGroupInformation, | |
| 4515 | ThreadUmsInformation, | |
| 4516 | ThreadCounterProfiling, | |
| 4517 | ThreadIdealProcessorEx, | |
| 4518 | // Windows 8+ from here | |
| 4519 | ThreadCpuAccountingInformation, | |
| 4520 | // Windows 8.1+ from here | |
| 4521 | ThreadSuspendCount, | |
| 4522 | // Windows 10+ from here | |
| 4523 | ThreadHeterogeneousCpuPolicy, | |
| 4524 | ThreadContainerId, | |
| 4525 | ThreadNameInformation, | |
| 4526 | ThreadSelectedCpuSets, | |
| 4527 | ThreadSystemThreadInformation, | |
| 4528 | ThreadActualGroupAffinity, | |
| 4529 | }; | |
| 4530 | ||
| 4531 | pub const PROCESSINFOCLASS = enum(c_int) { | |
| 4532 | ProcessBasicInformation, | |
| 4533 | ProcessQuotaLimits, | |
| 4534 | ProcessIoCounters, | |
| 4535 | ProcessVmCounters, | |
| 4536 | ProcessTimes, | |
| 4537 | ProcessBasePriority, | |
| 4538 | ProcessRaisePriority, | |
| 4539 | ProcessDebugPort, | |
| 4540 | ProcessExceptionPort, | |
| 4541 | ProcessAccessToken, | |
| 4542 | ProcessLdtInformation, | |
| 4543 | ProcessLdtSize, | |
| 4544 | ProcessDefaultHardErrorMode, | |
| 4545 | ProcessIoPortHandlers, | |
| 4546 | ProcessPooledUsageAndLimits, | |
| 4547 | ProcessWorkingSetWatch, | |
| 4548 | ProcessUserModeIOPL, | |
| 4549 | ProcessEnableAlignmentFaultFixup, | |
| 4550 | ProcessPriorityClass, | |
| 4551 | ProcessWx86Information, | |
| 4552 | ProcessHandleCount, | |
| 4553 | ProcessAffinityMask, | |
| 4554 | ProcessPriorityBoost, | |
| 4555 | ProcessDeviceMap, | |
| 4556 | ProcessSessionInformation, | |
| 4557 | ProcessForegroundInformation, | |
| 4558 | ProcessWow64Information, | |
| 4559 | ProcessImageFileName, | |
| 4560 | ProcessLUIDDeviceMapsEnabled, | |
| 4561 | ProcessBreakOnTermination, | |
| 4562 | ProcessDebugObjectHandle, | |
| 4563 | ProcessDebugFlags, | |
| 4564 | ProcessHandleTracing, | |
| 4565 | ProcessIoPriority, | |
| 4566 | ProcessExecuteFlags, | |
| 4567 | ProcessTlsInformation, | |
| 4568 | ProcessCookie, | |
| 4569 | ProcessImageInformation, | |
| 4570 | ProcessCycleTime, | |
| 4571 | ProcessPagePriority, | |
| 4572 | ProcessInstrumentationCallback, | |
| 4573 | ProcessThreadStackAllocation, | |
| 4574 | ProcessWorkingSetWatchEx, | |
| 4575 | ProcessImageFileNameWin32, | |
| 4576 | ProcessImageFileMapping, | |
| 4577 | ProcessAffinityUpdateMode, | |
| 4578 | ProcessMemoryAllocationMode, | |
| 4579 | ProcessGroupInformation, | |
| 4580 | ProcessTokenVirtualizationEnabled, | |
| 4581 | ProcessConsoleHostProcess, | |
| 4582 | ProcessWindowInformation, | |
| 4583 | MaxProcessInfoClass, | |
| 4584 | }; | |
| 4585 | ||
| 4586 | pub const PROCESS_BASIC_INFORMATION = extern struct { | |
| 4587 | ExitStatus: NTSTATUS, | |
| 4588 | PebBaseAddress: *PEB, | |
| 4589 | AffinityMask: ULONG_PTR, | |
| 4590 | BasePriority: KPRIORITY, | |
| 4591 | UniqueProcessId: ULONG_PTR, | |
| 4592 | InheritedFromUniqueProcessId: ULONG_PTR, | |
| 4593 | }; | |
| 4594 | ||
| 4595 | pub const ReadMemoryError = error{ | |
| 4596 | Unexpected, | |
| 4597 | }; | |
| 4598 | ||
| 4599 | pub fn ReadProcessMemory(handle: HANDLE, addr: ?LPVOID, buffer: []u8) ReadMemoryError![]u8 { | |
| 4600 | var nread: usize = 0; | |
| 4601 | switch (ntdll.NtReadVirtualMemory( | |
| 4602 | handle, | |
| 4603 | addr, | |
| 4604 | buffer.ptr, | |
| 4605 | buffer.len, | |
| 4606 | &nread, | |
| 4607 | )) { | |
| 4608 | .SUCCESS => return buffer[0..nread], | |
| 4609 | // TODO: map errors | |
| 4610 | else => |rc| return unexpectedStatus(rc), | |
| 4611 | } | |
| 4612 | } | |
| 4613 | ||
| 4614 | pub const WriteMemoryError = error{ | |
| 4615 | Unexpected, | |
| 4616 | }; | |
| 4617 | ||
| 4618 | pub fn WriteProcessMemory(handle: HANDLE, addr: ?LPVOID, buffer: []const u8) WriteMemoryError!usize { | |
| 4619 | var nwritten: usize = 0; | |
| 4620 | switch (ntdll.NtWriteVirtualMemory( | |
| 4621 | handle, | |
| 4622 | addr, | |
| 4623 | @ptrCast(*const anyopaque, buffer.ptr), | |
| 4624 | buffer.len, | |
| 4625 | &nwritten, | |
| 4626 | )) { | |
| 4627 | .SUCCESS => return nwritten, | |
| 4628 | // TODO: map errors | |
| 4629 | else => |rc| return unexpectedStatus(rc), | |
| 4630 | } | |
| 4631 | } | |
| 4632 | ||
| 4633 | pub const ProcessBaseAddressError = GetProcessMemoryInfoError || ReadMemoryError; | |
| 4634 | ||
| 4635 | /// Returns the base address of the process loaded into memory. | |
| 4636 | pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE { | |
| 4637 | var info: PROCESS_BASIC_INFORMATION = undefined; | |
| 4638 | var nread: DWORD = 0; | |
| 4639 | const rc = ntdll.NtQueryInformationProcess( | |
| 4640 | handle, | |
| 4641 | .ProcessBasicInformation, | |
| 4642 | &info, | |
| 4643 | @sizeOf(PROCESS_BASIC_INFORMATION), | |
| 4644 | &nread, | |
| 4645 | ); | |
| 4646 | switch (rc) { | |
| 4647 | .SUCCESS => {}, | |
| 4648 | .ACCESS_DENIED => return error.AccessDenied, | |
| 4649 | .INVALID_HANDLE => return error.InvalidHandle, | |
| 4650 | .INVALID_PARAMETER => unreachable, | |
| 4651 | else => return unexpectedStatus(rc), | |
| 4652 | } | |
| 4653 | ||
| 4654 | var peb_buf: [@sizeOf(PEB)]u8 align(@alignOf(PEB)) = undefined; | |
| 4655 | const peb_out = try ReadProcessMemory(handle, info.PebBaseAddress, &peb_buf); | |
| 4656 | const ppeb = @ptrCast(*const PEB, @alignCast(@alignOf(PEB), peb_out.ptr)); | |
| 4657 | return ppeb.ImageBaseAddress; | |
| 4658 | } |
lib/std/os/windows/ntdll.zig+22-108| ... | ... | @@ -31,61 +31,10 @@ const UNWIND_HISTORY_TABLE = windows.UNWIND_HISTORY_TABLE; |
| 31 | 31 | const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION; |
| 32 | 32 | const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS; |
| 33 | 33 | const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE; |
| 34 | ||
| 35 | pub const PROCESSINFOCLASS = enum(c_int) { | |
| 36 | ProcessBasicInformation, | |
| 37 | ProcessQuotaLimits, | |
| 38 | ProcessIoCounters, | |
| 39 | ProcessVmCounters, | |
| 40 | ProcessTimes, | |
| 41 | ProcessBasePriority, | |
| 42 | ProcessRaisePriority, | |
| 43 | ProcessDebugPort, | |
| 44 | ProcessExceptionPort, | |
| 45 | ProcessAccessToken, | |
| 46 | ProcessLdtInformation, | |
| 47 | ProcessLdtSize, | |
| 48 | ProcessDefaultHardErrorMode, | |
| 49 | ProcessIoPortHandlers, | |
| 50 | ProcessPooledUsageAndLimits, | |
| 51 | ProcessWorkingSetWatch, | |
| 52 | ProcessUserModeIOPL, | |
| 53 | ProcessEnableAlignmentFaultFixup, | |
| 54 | ProcessPriorityClass, | |
| 55 | ProcessWx86Information, | |
| 56 | ProcessHandleCount, | |
| 57 | ProcessAffinityMask, | |
| 58 | ProcessPriorityBoost, | |
| 59 | ProcessDeviceMap, | |
| 60 | ProcessSessionInformation, | |
| 61 | ProcessForegroundInformation, | |
| 62 | ProcessWow64Information, | |
| 63 | ProcessImageFileName, | |
| 64 | ProcessLUIDDeviceMapsEnabled, | |
| 65 | ProcessBreakOnTermination, | |
| 66 | ProcessDebugObjectHandle, | |
| 67 | ProcessDebugFlags, | |
| 68 | ProcessHandleTracing, | |
| 69 | ProcessIoPriority, | |
| 70 | ProcessExecuteFlags, | |
| 71 | ProcessTlsInformation, | |
| 72 | ProcessCookie, | |
| 73 | ProcessImageInformation, | |
| 74 | ProcessCycleTime, | |
| 75 | ProcessPagePriority, | |
| 76 | ProcessInstrumentationCallback, | |
| 77 | ProcessThreadStackAllocation, | |
| 78 | ProcessWorkingSetWatchEx, | |
| 79 | ProcessImageFileNameWin32, | |
| 80 | ProcessImageFileMapping, | |
| 81 | ProcessAffinityUpdateMode, | |
| 82 | ProcessMemoryAllocationMode, | |
| 83 | ProcessGroupInformation, | |
| 84 | ProcessTokenVirtualizationEnabled, | |
| 85 | ProcessConsoleHostProcess, | |
| 86 | ProcessWindowInformation, | |
| 87 | MaxProcessInfoClass, | |
| 88 | }; | |
| 34 | const THREADINFOCLASS = windows.THREADINFOCLASS; | |
| 35 | const PROCESSINFOCLASS = windows.PROCESSINFOCLASS; | |
| 36 | const LPVOID = windows.LPVOID; | |
| 37 | const LPCVOID = windows.LPCVOID; | |
| 89 | 38 | |
| 90 | 39 | pub extern "ntdll" fn NtQueryInformationProcess( |
| 91 | 40 | ProcessHandle: HANDLE, |
| ... | ... | @@ -95,57 +44,6 @@ pub extern "ntdll" fn NtQueryInformationProcess( |
| 95 | 44 | ReturnLength: ?*ULONG, |
| 96 | 45 | ) callconv(WINAPI) NTSTATUS; |
| 97 | 46 | |
| 98 | pub const THREADINFOCLASS = enum(c_int) { | |
| 99 | ThreadBasicInformation, | |
| 100 | ThreadTimes, | |
| 101 | ThreadPriority, | |
| 102 | ThreadBasePriority, | |
| 103 | ThreadAffinityMask, | |
| 104 | ThreadImpersonationToken, | |
| 105 | ThreadDescriptorTableEntry, | |
| 106 | ThreadEnableAlignmentFaultFixup, | |
| 107 | ThreadEventPair_Reusable, | |
| 108 | ThreadQuerySetWin32StartAddress, | |
| 109 | ThreadZeroTlsCell, | |
| 110 | ThreadPerformanceCount, | |
| 111 | ThreadAmILastThread, | |
| 112 | ThreadIdealProcessor, | |
| 113 | ThreadPriorityBoost, | |
| 114 | ThreadSetTlsArrayAddress, | |
| 115 | ThreadIsIoPending, | |
| 116 | // Windows 2000+ from here | |
| 117 | ThreadHideFromDebugger, | |
| 118 | // Windows XP+ from here | |
| 119 | ThreadBreakOnTermination, | |
| 120 | ThreadSwitchLegacyState, | |
| 121 | ThreadIsTerminated, | |
| 122 | // Windows Vista+ from here | |
| 123 | ThreadLastSystemCall, | |
| 124 | ThreadIoPriority, | |
| 125 | ThreadCycleTime, | |
| 126 | ThreadPagePriority, | |
| 127 | ThreadActualBasePriority, | |
| 128 | ThreadTebInformation, | |
| 129 | ThreadCSwitchMon, | |
| 130 | // Windows 7+ from here | |
| 131 | ThreadCSwitchPmu, | |
| 132 | ThreadWow64Context, | |
| 133 | ThreadGroupInformation, | |
| 134 | ThreadUmsInformation, | |
| 135 | ThreadCounterProfiling, | |
| 136 | ThreadIdealProcessorEx, | |
| 137 | // Windows 8+ from here | |
| 138 | ThreadCpuAccountingInformation, | |
| 139 | // Windows 8.1+ from here | |
| 140 | ThreadSuspendCount, | |
| 141 | // Windows 10+ from here | |
| 142 | ThreadHeterogeneousCpuPolicy, | |
| 143 | ThreadContainerId, | |
| 144 | ThreadNameInformation, | |
| 145 | ThreadSelectedCpuSets, | |
| 146 | ThreadSystemThreadInformation, | |
| 147 | ThreadActualGroupAffinity, | |
| 148 | }; | |
| 149 | 47 | pub extern "ntdll" fn NtQueryInformationThread( |
| 150 | 48 | ThreadHandle: HANDLE, |
| 151 | 49 | ThreadInformationClass: THREADINFOCLASS, |
| ... | ... | @@ -364,10 +262,26 @@ pub extern "ntdll" fn RtlQueryRegistryValues( |
| 364 | 262 | Environment: ?*anyopaque, |
| 365 | 263 | ) callconv(WINAPI) NTSTATUS; |
| 366 | 264 | |
| 265 | pub extern "ntdll" fn NtReadVirtualMemory( | |
| 266 | ProcessHandle: HANDLE, | |
| 267 | BaseAddress: ?PVOID, | |
| 268 | Buffer: LPVOID, | |
| 269 | NumberOfBytesToRead: SIZE_T, | |
| 270 | NumberOfBytesRead: ?*SIZE_T, | |
| 271 | ) callconv(WINAPI) NTSTATUS; | |
| 272 | ||
| 273 | pub extern "ntdll" fn NtWriteVirtualMemory( | |
| 274 | ProcessHandle: HANDLE, | |
| 275 | BaseAddress: ?PVOID, | |
| 276 | Buffer: LPCVOID, | |
| 277 | NumberOfBytesToWrite: SIZE_T, | |
| 278 | NumberOfBytesWritten: ?*SIZE_T, | |
| 279 | ) callconv(WINAPI) NTSTATUS; | |
| 280 | ||
| 367 | 281 | pub extern "ntdll" fn NtProtectVirtualMemory( |
| 368 | 282 | ProcessHandle: HANDLE, |
| 369 | BaseAddress: *PVOID, | |
| 370 | NumberOfBytesToProtect: *ULONG, | |
| 283 | BaseAddress: *?PVOID, | |
| 284 | NumberOfBytesToProtect: *SIZE_T, | |
| 371 | 285 | NewAccessProtection: ULONG, |
| 372 | 286 | OldAccessProtection: *ULONG, |
| 373 | 287 | ) callconv(WINAPI) NTSTATUS; |
src/link.zig+24-18| ... | ... | @@ -379,24 +379,30 @@ pub const File = struct { |
| 379 | 379 | if (base.file != null) return; |
| 380 | 380 | const emit = base.options.emit orelse return; |
| 381 | 381 | if (base.child_pid) |pid| { |
| 382 | // If we try to open the output file in write mode while it is running, | |
| 383 | // it will return ETXTBSY. So instead, we copy the file, atomically rename it | |
| 384 | // over top of the exe path, and then proceed normally. This changes the inode, | |
| 385 | // avoiding the error. | |
| 386 | const tmp_sub_path = try std.fmt.allocPrint(base.allocator, "{s}-{x}", .{ | |
| 387 | emit.sub_path, std.crypto.random.int(u32), | |
| 388 | }); | |
| 389 | try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{}); | |
| 390 | try emit.directory.handle.rename(tmp_sub_path, emit.sub_path); | |
| 391 | switch (builtin.os.tag) { | |
| 392 | .linux => std.os.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { | |
| 393 | log.warn("ptrace failure: {s}", .{@errorName(err)}); | |
| 394 | }, | |
| 395 | .macos => base.cast(MachO).?.ptraceAttach(pid) catch |err| { | |
| 382 | if (builtin.os.tag == .windows) { | |
| 383 | base.cast(Coff).?.ptraceAttach(pid) catch |err| { | |
| 396 | 384 | log.warn("attaching failed with error: {s}", .{@errorName(err)}); |
| 397 | }, | |
| 398 | .windows => {}, | |
| 399 | else => return error.HotSwapUnavailableOnHostOperatingSystem, | |
| 385 | }; | |
| 386 | } else { | |
| 387 | // If we try to open the output file in write mode while it is running, | |
| 388 | // it will return ETXTBSY. So instead, we copy the file, atomically rename it | |
| 389 | // over top of the exe path, and then proceed normally. This changes the inode, | |
| 390 | // avoiding the error. | |
| 391 | const tmp_sub_path = try std.fmt.allocPrint(base.allocator, "{s}-{x}", .{ | |
| 392 | emit.sub_path, std.crypto.random.int(u32), | |
| 393 | }); | |
| 394 | try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{}); | |
| 395 | try emit.directory.handle.rename(tmp_sub_path, emit.sub_path); | |
| 396 | switch (builtin.os.tag) { | |
| 397 | .linux => std.os.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { | |
| 398 | log.warn("ptrace failure: {s}", .{@errorName(err)}); | |
| 399 | }, | |
| 400 | .macos => base.cast(MachO).?.ptraceAttach(pid) catch |err| { | |
| 401 | log.warn("attaching failed with error: {s}", .{@errorName(err)}); | |
| 402 | }, | |
| 403 | .windows => unreachable, | |
| 404 | else => return error.HotSwapUnavailableOnHostOperatingSystem, | |
| 405 | } | |
| 400 | 406 | } |
| 401 | 407 | } |
| 402 | 408 | base.file = try emit.directory.handle.createFile(emit.sub_path, .{ |
| ... | ... | @@ -437,7 +443,7 @@ pub const File = struct { |
| 437 | 443 | .macos => base.cast(MachO).?.ptraceDetach(pid) catch |err| { |
| 438 | 444 | log.warn("detaching failed with error: {s}", .{@errorName(err)}); |
| 439 | 445 | }, |
| 440 | .windows => {}, | |
| 446 | .windows => base.cast(Coff).?.ptraceDetach(pid), | |
| 441 | 447 | else => return error.HotSwapUnavailableOnHostOperatingSystem, |
| 442 | 448 | } |
| 443 | 449 | } |
src/link/Coff.zig+110-11| ... | ... | @@ -89,6 +89,20 @@ relocs: RelocTable = .{}, |
| 89 | 89 | /// this will be a table indexed by index into the list of Atoms. |
| 90 | 90 | base_relocs: BaseRelocationTable = .{}, |
| 91 | 91 | |
| 92 | /// Hot-code swapping state. | |
| 93 | hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, | |
| 94 | ||
| 95 | const is_hot_update_compatible = switch (builtin.target.os.tag) { | |
| 96 | .windows => true, | |
| 97 | else => false, | |
| 98 | }; | |
| 99 | ||
| 100 | const HotUpdateState = struct { | |
| 101 | /// Base address at which the process (image) got loaded. | |
| 102 | /// We need this info to correctly slide pointers when relocating. | |
| 103 | loaded_base_address: ?std.os.windows.HMODULE = null, | |
| 104 | }; | |
| 105 | ||
| 92 | 106 | const Entry = struct { |
| 93 | 107 | target: SymbolWithLoc, |
| 94 | 108 | // Index into the synthetic symbol table (i.e., file == null). |
| ... | ... | @@ -772,13 +786,87 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void { |
| 772 | 786 | const sym = atom.getSymbol(self); |
| 773 | 787 | const section = self.sections.get(@enumToInt(sym.section_number) - 1); |
| 774 | 788 | const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address; |
| 789 | ||
| 775 | 790 | log.debug("writing atom for symbol {s} at file offset 0x{x} to 0x{x}", .{ |
| 776 | 791 | atom.getName(self), |
| 777 | 792 | file_offset, |
| 778 | 793 | file_offset + code.len, |
| 779 | 794 | }); |
| 780 | self.resolveRelocs(atom_index, code); | |
| 795 | ||
| 796 | const gpa = self.base.allocator; | |
| 797 | ||
| 798 | // Gather relocs which can be resolved. | |
| 799 | // We need to do this as we will be applying different slide values depending | |
| 800 | // if we are running in hot-code swapping mode or not. | |
| 801 | // TODO: how crazy would it be to try and apply the actual image base of the loaded | |
| 802 | // process for the in-file values rather than the Windows defaults? | |
| 803 | var relocs = std.ArrayList(*Relocation).init(gpa); | |
| 804 | defer relocs.deinit(); | |
| 805 | ||
| 806 | if (self.relocs.getPtr(atom_index)) |rels| { | |
| 807 | try relocs.ensureTotalCapacityPrecise(rels.items.len); | |
| 808 | for (rels.items) |*reloc| { | |
| 809 | if (reloc.isResolvable(self)) relocs.appendAssumeCapacity(reloc); | |
| 810 | } | |
| 811 | } | |
| 812 | ||
| 813 | if (is_hot_update_compatible) { | |
| 814 | if (self.base.child_pid) |handle| { | |
| 815 | const slide = @ptrToInt(self.hot_state.loaded_base_address.?); | |
| 816 | ||
| 817 | const mem_code = try gpa.dupe(u8, code); | |
| 818 | defer gpa.free(mem_code); | |
| 819 | self.resolveRelocs(atom_index, relocs.items, mem_code, slide); | |
| 820 | ||
| 821 | const vaddr = sym.value + slide; | |
| 822 | const pvaddr = @intToPtr(*anyopaque, vaddr); | |
| 823 | ||
| 824 | log.debug("writing to memory at address {x}", .{vaddr}); | |
| 825 | ||
| 826 | if (build_options.enable_logging) { | |
| 827 | try debugMem(gpa, handle, pvaddr, mem_code); | |
| 828 | } | |
| 829 | ||
| 830 | if (section.header.flags.MEM_WRITE == 0) { | |
| 831 | writeMemProtected(handle, pvaddr, mem_code) catch |err| { | |
| 832 | log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)}); | |
| 833 | }; | |
| 834 | } else { | |
| 835 | writeMem(handle, pvaddr, mem_code) catch |err| { | |
| 836 | log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)}); | |
| 837 | }; | |
| 838 | } | |
| 839 | } | |
| 840 | } | |
| 841 | ||
| 842 | self.resolveRelocs(atom_index, relocs.items, code, self.getImageBase()); | |
| 781 | 843 | try self.base.file.?.pwriteAll(code, file_offset); |
| 844 | ||
| 845 | // Now we can mark the relocs as resolved. | |
| 846 | while (relocs.popOrNull()) |reloc| { | |
| 847 | reloc.dirty = false; | |
| 848 | } | |
| 849 | } | |
| 850 | ||
| 851 | fn debugMem(allocator: Allocator, handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void { | |
| 852 | var buffer = try allocator.alloc(u8, code.len); | |
| 853 | defer allocator.free(buffer); | |
| 854 | const memread = try std.os.windows.ReadProcessMemory(handle, pvaddr, buffer); | |
| 855 | log.debug("to write: {x}", .{std.fmt.fmtSliceHexLower(code)}); | |
| 856 | log.debug("in memory: {x}", .{std.fmt.fmtSliceHexLower(memread)}); | |
| 857 | } | |
| 858 | ||
| 859 | fn writeMemProtected(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void { | |
| 860 | const old_prot = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, std.os.windows.PAGE_EXECUTE_WRITECOPY); | |
| 861 | try writeMem(handle, pvaddr, code); | |
| 862 | // TODO: We can probably just set the pages writeable and leave it at that without having to restore the attributes. | |
| 863 | // For that though, we want to track which page has already been modified. | |
| 864 | _ = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, old_prot); | |
| 865 | } | |
| 866 | ||
| 867 | fn writeMem(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void { | |
| 868 | const amt = try std.os.windows.WriteProcessMemory(handle, pvaddr, code); | |
| 869 | if (amt != code.len) return error.InputOutput; | |
| 782 | 870 | } |
| 783 | 871 | |
| 784 | 872 | fn writePtrWidthAtom(self: *Coff, atom_index: Atom.Index) !void { |
| ... | ... | @@ -814,19 +902,30 @@ fn markRelocsDirtyByAddress(self: *Coff, addr: u32) void { |
| 814 | 902 | } |
| 815 | 903 | } |
| 816 | 904 | |
| 817 | fn resolveRelocs(self: *Coff, atom_index: Atom.Index, code: []u8) void { | |
| 818 | const relocs = self.relocs.getPtr(atom_index) orelse return; | |
| 819 | ||
| 905 | fn resolveRelocs(self: *Coff, atom_index: Atom.Index, relocs: []*const Relocation, code: []u8, image_base: u64) void { | |
| 820 | 906 | log.debug("relocating '{s}'", .{self.getAtom(atom_index).getName(self)}); |
| 821 | ||
| 822 | for (relocs.items) |*reloc| { | |
| 823 | if (!reloc.dirty) continue; | |
| 824 | if (reloc.resolve(atom_index, code, self)) { | |
| 825 | reloc.dirty = false; | |
| 826 | } | |
| 907 | for (relocs) |reloc| { | |
| 908 | reloc.resolve(atom_index, code, image_base, self); | |
| 827 | 909 | } |
| 828 | 910 | } |
| 829 | 911 | |
| 912 | pub fn ptraceAttach(self: *Coff, handle: std.ChildProcess.Id) !void { | |
| 913 | if (!is_hot_update_compatible) return; | |
| 914 | ||
| 915 | log.debug("attaching to process with handle {*}", .{handle}); | |
| 916 | self.hot_state.loaded_base_address = std.os.windows.ProcessBaseAddress(handle) catch |err| { | |
| 917 | log.warn("failed to get base address for the process with error: {s}", .{@errorName(err)}); | |
| 918 | return; | |
| 919 | }; | |
| 920 | } | |
| 921 | ||
| 922 | pub fn ptraceDetach(self: *Coff, handle: std.ChildProcess.Id) void { | |
| 923 | if (!is_hot_update_compatible) return; | |
| 924 | ||
| 925 | log.debug("detaching from process with handle {*}", .{handle}); | |
| 926 | self.hot_state.loaded_base_address = null; | |
| 927 | } | |
| 928 | ||
| 830 | 929 | fn freeAtom(self: *Coff, atom_index: Atom.Index) void { |
| 831 | 930 | log.debug("freeAtom {d}", .{atom_index}); |
| 832 | 931 | |
| ... | ... | @@ -1421,7 +1520,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod |
| 1421 | 1520 | |
| 1422 | 1521 | for (self.relocs.keys(), self.relocs.values()) |atom_index, relocs| { |
| 1423 | 1522 | const needs_update = for (relocs.items) |reloc| { |
| 1424 | if (reloc.dirty) break true; | |
| 1523 | if (reloc.isResolvable(self)) break true; | |
| 1425 | 1524 | } else false; |
| 1426 | 1525 | |
| 1427 | 1526 | if (!needs_update) continue; |
src/link/Coff/Relocation.zig+9-7| ... | ... | @@ -72,14 +72,18 @@ pub fn getTargetAddress(self: Relocation, coff_file: *const Coff) ?u32 { |
| 72 | 72 | } |
| 73 | 73 | } |
| 74 | 74 | |
| 75 | /// Returns `false` if obtaining the target address has been deferred until `flushModule`. | |
| 76 | /// This can happen when trying to resolve address of an import table entry ahead of time. | |
| 77 | pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, coff_file: *Coff) bool { | |
| 75 | /// Returns true if and only if the reloc is dirty AND the target address is available. | |
| 76 | pub fn isResolvable(self: Relocation, coff_file: *Coff) bool { | |
| 77 | _ = self.getTargetAddress(coff_file) orelse return false; | |
| 78 | return self.dirty; | |
| 79 | } | |
| 80 | ||
| 81 | pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, image_base: u64, coff_file: *Coff) void { | |
| 78 | 82 | const atom = coff_file.getAtom(atom_index); |
| 79 | 83 | const source_sym = atom.getSymbol(coff_file); |
| 80 | 84 | const source_vaddr = source_sym.value + self.offset; |
| 81 | 85 | |
| 82 | const target_vaddr = self.getTargetAddress(coff_file) orelse return false; | |
| 86 | const target_vaddr = self.getTargetAddress(coff_file).?; // Oops, you didn't check if the relocation can be resolved with isResolvable(). | |
| 83 | 87 | const target_vaddr_with_addend = target_vaddr + self.addend; |
| 84 | 88 | |
| 85 | 89 | log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) ", .{ |
| ... | ... | @@ -92,7 +96,7 @@ pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, coff_file: |
| 92 | 96 | const ctx: Context = .{ |
| 93 | 97 | .source_vaddr = source_vaddr, |
| 94 | 98 | .target_vaddr = target_vaddr_with_addend, |
| 95 | .image_base = coff_file.getImageBase(), | |
| 99 | .image_base = image_base, | |
| 96 | 100 | .code = code, |
| 97 | 101 | .ptr_width = coff_file.ptr_width, |
| 98 | 102 | }; |
| ... | ... | @@ -102,8 +106,6 @@ pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, coff_file: |
| 102 | 106 | .x86, .x86_64 => self.resolveX86(ctx), |
| 103 | 107 | else => unreachable, // unhandled target architecture |
| 104 | 108 | } |
| 105 | ||
| 106 | return true; | |
| 107 | 109 | } |
| 108 | 110 | |
| 109 | 111 | const Context = struct { |
src/main.zig+19-5| ... | ... | @@ -3817,11 +3817,25 @@ fn runOrTestHotSwap( |
| 3817 | 3817 | runtime_args_start: ?usize, |
| 3818 | 3818 | ) !std.ChildProcess.Id { |
| 3819 | 3819 | const exe_emit = comp.bin_file.options.emit.?; |
| 3820 | // A naive `directory.join` here will indeed get the correct path to the binary, | |
| 3821 | // however, in the case of cwd, we actually want `./foo` so that the path can be executed. | |
| 3822 | const exe_path = try fs.path.join(gpa, &[_][]const u8{ | |
| 3823 | exe_emit.directory.path orelse ".", exe_emit.sub_path, | |
| 3824 | }); | |
| 3820 | ||
| 3821 | const exe_path = switch (builtin.target.os.tag) { | |
| 3822 | // On Windows it seems impossible to perform an atomic rename of a file that is currently | |
| 3823 | // running in a process. Therefore, we do the opposite. We create a copy of the file in | |
| 3824 | // tmp zig-cache and use it to spawn the child process. This way we are free to update | |
| 3825 | // the binary with each requested hot update. | |
| 3826 | .windows => blk: { | |
| 3827 | try exe_emit.directory.handle.copyFile(exe_emit.sub_path, comp.local_cache_directory.handle, exe_emit.sub_path, .{}); | |
| 3828 | break :blk try fs.path.join(gpa, &[_][]const u8{ | |
| 3829 | comp.local_cache_directory.path orelse ".", exe_emit.sub_path, | |
| 3830 | }); | |
| 3831 | }, | |
| 3832 | ||
| 3833 | // A naive `directory.join` here will indeed get the correct path to the binary, | |
| 3834 | // however, in the case of cwd, we actually want `./foo` so that the path can be executed. | |
| 3835 | else => try fs.path.join(gpa, &[_][]const u8{ | |
| 3836 | exe_emit.directory.path orelse ".", exe_emit.sub_path, | |
| 3837 | }), | |
| 3838 | }; | |
| 3825 | 3839 | defer gpa.free(exe_path); |
| 3826 | 3840 | |
| 3827 | 3841 | var argv = std.ArrayList([]const u8).init(gpa); |