authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-03-31 00:38:30+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-03-31 00:38:30+02:00
log5b82b40043e3a930e6693867c83de00ab3d20ef7
treeacc4e2cec84c50612cf0dc5428ec648d32ae76dd
parentc964e10821c417ceb7d0efcf1625d67484e734f7
parent908ccce064a898d5db1d43dbdc4a3590fd84d4ba
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #15125 from ziglang/hcs-win-poc

coff: add hot-code swapping PoC

6 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,6 +1514,24 @@ pub fn VirtualProtect(lpAddress: ?LPVOID, dwSize: SIZE_T, flNewProtect: DWORD, l
1514 }1514 }
1515}1515}
15161516
1517pub 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
1517pub const VirtualQueryError = error{Unexpected};1535pub const VirtualQueryError = error{Unexpected};
15181536
1519pub fn VirtualQuery(lpAddress: ?LPVOID, lpBuffer: PMEMORY_BASIC_INFORMATION, dwLength: SIZE_T) VirtualQueryError!SIZE_T {1537pub fn VirtualQuery(lpAddress: ?LPVOID, lpBuffer: PMEMORY_BASIC_INFORMATION, dwLength: SIZE_T) VirtualQueryError!SIZE_T {
...@@ -4457,3 +4475,184 @@ pub const MODULEENTRY32 = extern struct {...@@ -4457,3 +4475,184 @@ pub const MODULEENTRY32 = extern struct {
4457 szModule: [MAX_MODULE_NAME32 + 1]CHAR,4475 szModule: [MAX_MODULE_NAME32 + 1]CHAR,
4458 szExePath: [MAX_PATH]CHAR,4476 szExePath: [MAX_PATH]CHAR,
4459};4477};
4478
4479pub 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
4531pub 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
4586pub 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
4595pub const ReadMemoryError = error{
4596 Unexpected,
4597};
4598
4599pub 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
4614pub const WriteMemoryError = error{
4615 Unexpected,
4616};
4617
4618pub 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
4633pub const ProcessBaseAddressError = GetProcessMemoryInfoError || ReadMemoryError;
4634
4635/// Returns the base address of the process loaded into memory.
4636pub 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,61 +31,10 @@ const UNWIND_HISTORY_TABLE = windows.UNWIND_HISTORY_TABLE;
31const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;31const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;
32const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;32const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
33const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;33const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;
3434const THREADINFOCLASS = windows.THREADINFOCLASS;
35pub const PROCESSINFOCLASS = enum(c_int) {35const PROCESSINFOCLASS = windows.PROCESSINFOCLASS;
36 ProcessBasicInformation,36const LPVOID = windows.LPVOID;
37 ProcessQuotaLimits,37const LPCVOID = windows.LPCVOID;
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};
8938
90pub extern "ntdll" fn NtQueryInformationProcess(39pub extern "ntdll" fn NtQueryInformationProcess(
91 ProcessHandle: HANDLE,40 ProcessHandle: HANDLE,
...@@ -95,57 +44,6 @@ pub extern "ntdll" fn NtQueryInformationProcess(...@@ -95,57 +44,6 @@ pub extern "ntdll" fn NtQueryInformationProcess(
95 ReturnLength: ?*ULONG,44 ReturnLength: ?*ULONG,
96) callconv(WINAPI) NTSTATUS;45) callconv(WINAPI) NTSTATUS;
9746
98pub 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};
149pub extern "ntdll" fn NtQueryInformationThread(47pub extern "ntdll" fn NtQueryInformationThread(
150 ThreadHandle: HANDLE,48 ThreadHandle: HANDLE,
151 ThreadInformationClass: THREADINFOCLASS,49 ThreadInformationClass: THREADINFOCLASS,
...@@ -364,10 +262,26 @@ pub extern "ntdll" fn RtlQueryRegistryValues(...@@ -364,10 +262,26 @@ pub extern "ntdll" fn RtlQueryRegistryValues(
364 Environment: ?*anyopaque,262 Environment: ?*anyopaque,
365) callconv(WINAPI) NTSTATUS;263) callconv(WINAPI) NTSTATUS;
366264
265pub 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
273pub 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
367pub extern "ntdll" fn NtProtectVirtualMemory(281pub extern "ntdll" fn NtProtectVirtualMemory(
368 ProcessHandle: HANDLE,282 ProcessHandle: HANDLE,
369 BaseAddress: *PVOID,283 BaseAddress: *?PVOID,
370 NumberOfBytesToProtect: *ULONG,284 NumberOfBytesToProtect: *SIZE_T,
371 NewAccessProtection: ULONG,285 NewAccessProtection: ULONG,
372 OldAccessProtection: *ULONG,286 OldAccessProtection: *ULONG,
373) callconv(WINAPI) NTSTATUS;287) callconv(WINAPI) NTSTATUS;
src/link.zig+24-18
...@@ -379,24 +379,30 @@ pub const File = struct {...@@ -379,24 +379,30 @@ pub const File = struct {
379 if (base.file != null) return;379 if (base.file != null) return;
380 const emit = base.options.emit orelse return;380 const emit = base.options.emit orelse return;
381 if (base.child_pid) |pid| {381 if (base.child_pid) |pid| {
382 // If we try to open the output file in write mode while it is running,382 if (builtin.os.tag == .windows) {
383 // it will return ETXTBSY. So instead, we copy the file, atomically rename it383 base.cast(Coff).?.ptraceAttach(pid) catch |err| {
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| {
396 log.warn("attaching failed with error: {s}", .{@errorName(err)});384 log.warn("attaching failed with error: {s}", .{@errorName(err)});
397 },385 };
398 .windows => {},386 } else {
399 else => return error.HotSwapUnavailableOnHostOperatingSystem,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 base.file = try emit.directory.handle.createFile(emit.sub_path, .{408 base.file = try emit.directory.handle.createFile(emit.sub_path, .{
...@@ -437,7 +443,7 @@ pub const File = struct {...@@ -437,7 +443,7 @@ pub const File = struct {
437 .macos => base.cast(MachO).?.ptraceDetach(pid) catch |err| {443 .macos => base.cast(MachO).?.ptraceDetach(pid) catch |err| {
438 log.warn("detaching failed with error: {s}", .{@errorName(err)});444 log.warn("detaching failed with error: {s}", .{@errorName(err)});
439 },445 },
440 .windows => {},446 .windows => base.cast(Coff).?.ptraceDetach(pid),
441 else => return error.HotSwapUnavailableOnHostOperatingSystem,447 else => return error.HotSwapUnavailableOnHostOperatingSystem,
442 }448 }
443 }449 }
src/link/Coff.zig+110-11
...@@ -89,6 +89,20 @@ relocs: RelocTable = .{},...@@ -89,6 +89,20 @@ relocs: RelocTable = .{},
89/// this will be a table indexed by index into the list of Atoms.89/// this will be a table indexed by index into the list of Atoms.
90base_relocs: BaseRelocationTable = .{},90base_relocs: BaseRelocationTable = .{},
9191
92/// Hot-code swapping state.
93hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
94
95const is_hot_update_compatible = switch (builtin.target.os.tag) {
96 .windows => true,
97 else => false,
98};
99
100const 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
92const Entry = struct {106const Entry = struct {
93 target: SymbolWithLoc,107 target: SymbolWithLoc,
94 // Index into the synthetic symbol table (i.e., file == null).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,13 +786,87 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void {
772 const sym = atom.getSymbol(self);786 const sym = atom.getSymbol(self);
773 const section = self.sections.get(@enumToInt(sym.section_number) - 1);787 const section = self.sections.get(@enumToInt(sym.section_number) - 1);
774 const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address;788 const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address;
789
775 log.debug("writing atom for symbol {s} at file offset 0x{x} to 0x{x}", .{790 log.debug("writing atom for symbol {s} at file offset 0x{x} to 0x{x}", .{
776 atom.getName(self),791 atom.getName(self),
777 file_offset,792 file_offset,
778 file_offset + code.len,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 try self.base.file.?.pwriteAll(code, file_offset);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
851fn 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
859fn 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
867fn 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}
783871
784fn writePtrWidthAtom(self: *Coff, atom_index: Atom.Index) !void {872fn writePtrWidthAtom(self: *Coff, atom_index: Atom.Index) !void {
...@@ -814,19 +902,30 @@ fn markRelocsDirtyByAddress(self: *Coff, addr: u32) void {...@@ -814,19 +902,30 @@ fn markRelocsDirtyByAddress(self: *Coff, addr: u32) void {
814 }902 }
815}903}
816904
817fn resolveRelocs(self: *Coff, atom_index: Atom.Index, code: []u8) void {905fn resolveRelocs(self: *Coff, atom_index: Atom.Index, relocs: []*const Relocation, code: []u8, image_base: u64) void {
818 const relocs = self.relocs.getPtr(atom_index) orelse return;
819
820 log.debug("relocating '{s}'", .{self.getAtom(atom_index).getName(self)});906 log.debug("relocating '{s}'", .{self.getAtom(atom_index).getName(self)});
821907 for (relocs) |reloc| {
822 for (relocs.items) |*reloc| {908 reloc.resolve(atom_index, code, image_base, self);
823 if (!reloc.dirty) continue;
824 if (reloc.resolve(atom_index, code, self)) {
825 reloc.dirty = false;
826 }
827 }909 }
828}910}
829911
912pub 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
922pub 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
830fn freeAtom(self: *Coff, atom_index: Atom.Index) void {929fn freeAtom(self: *Coff, atom_index: Atom.Index) void {
831 log.debug("freeAtom {d}", .{atom_index});930 log.debug("freeAtom {d}", .{atom_index});
832931
...@@ -1421,7 +1520,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -1421,7 +1520,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
14211520
1422 for (self.relocs.keys(), self.relocs.values()) |atom_index, relocs| {1521 for (self.relocs.keys(), self.relocs.values()) |atom_index, relocs| {
1423 const needs_update = for (relocs.items) |reloc| {1522 const needs_update = for (relocs.items) |reloc| {
1424 if (reloc.dirty) break true;1523 if (reloc.isResolvable(self)) break true;
1425 } else false;1524 } else false;
14261525
1427 if (!needs_update) continue;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,14 +72,18 @@ pub fn getTargetAddress(self: Relocation, coff_file: *const Coff) ?u32 {
72 }72 }
73}73}
7474
75/// Returns `false` if obtaining the target address has been deferred until `flushModule`.75/// Returns true if and only if the reloc is dirty AND the target address is available.
76/// This can happen when trying to resolve address of an import table entry ahead of time.76pub fn isResolvable(self: Relocation, coff_file: *Coff) bool {
77pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, coff_file: *Coff) bool {77 _ = self.getTargetAddress(coff_file) orelse return false;
78 return self.dirty;
79}
80
81pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, image_base: u64, coff_file: *Coff) void {
78 const atom = coff_file.getAtom(atom_index);82 const atom = coff_file.getAtom(atom_index);
79 const source_sym = atom.getSymbol(coff_file);83 const source_sym = atom.getSymbol(coff_file);
80 const source_vaddr = source_sym.value + self.offset;84 const source_vaddr = source_sym.value + self.offset;
8185
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 const target_vaddr_with_addend = target_vaddr + self.addend;87 const target_vaddr_with_addend = target_vaddr + self.addend;
8488
85 log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) ", .{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,7 +96,7 @@ pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, coff_file:
92 const ctx: Context = .{96 const ctx: Context = .{
93 .source_vaddr = source_vaddr,97 .source_vaddr = source_vaddr,
94 .target_vaddr = target_vaddr_with_addend,98 .target_vaddr = target_vaddr_with_addend,
95 .image_base = coff_file.getImageBase(),99 .image_base = image_base,
96 .code = code,100 .code = code,
97 .ptr_width = coff_file.ptr_width,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,8 +106,6 @@ pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, coff_file:
102 .x86, .x86_64 => self.resolveX86(ctx),106 .x86, .x86_64 => self.resolveX86(ctx),
103 else => unreachable, // unhandled target architecture107 else => unreachable, // unhandled target architecture
104 }108 }
105
106 return true;
107}109}
108110
109const Context = struct {111const Context = struct {
src/main.zig+19-5
...@@ -3817,11 +3817,25 @@ fn runOrTestHotSwap(...@@ -3817,11 +3817,25 @@ fn runOrTestHotSwap(
3817 runtime_args_start: ?usize,3817 runtime_args_start: ?usize,
3818) !std.ChildProcess.Id {3818) !std.ChildProcess.Id {
3819 const exe_emit = comp.bin_file.options.emit.?;3819 const exe_emit = comp.bin_file.options.emit.?;
3820 // A naive `directory.join` here will indeed get the correct path to the binary,3820
3821 // however, in the case of cwd, we actually want `./foo` so that the path can be executed.3821 const exe_path = switch (builtin.target.os.tag) {
3822 const exe_path = try fs.path.join(gpa, &[_][]const u8{3822 // On Windows it seems impossible to perform an atomic rename of a file that is currently
3823 exe_emit.directory.path orelse ".", exe_emit.sub_path,3823 // running in a process. Therefore, we do the opposite. We create a copy of the file in
3824 });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 defer gpa.free(exe_path);3839 defer gpa.free(exe_path);
38263840
3827 var argv = std.ArrayList([]const u8).init(gpa);3841 var argv = std.ArrayList([]const u8).init(gpa);