mirror of
https://github.com/ziglang/zig.git
synced 2026-01-07 05:55:36 +00:00
Merge pull request #15125 from ziglang/hcs-win-poc
coff: add hot-code swapping PoC
This commit is contained in:
commit
5b82b40043
@ -1514,6 +1514,24 @@ pub fn VirtualProtect(lpAddress: ?LPVOID, dwSize: SIZE_T, flNewProtect: DWORD, l
|
||||
}
|
||||
}
|
||||
|
||||
pub fn VirtualProtectEx(handle: HANDLE, addr: ?LPVOID, size: SIZE_T, new_prot: DWORD) VirtualProtectError!DWORD {
|
||||
var old_prot: DWORD = undefined;
|
||||
var out_addr = addr;
|
||||
var out_size = size;
|
||||
switch (ntdll.NtProtectVirtualMemory(
|
||||
handle,
|
||||
&out_addr,
|
||||
&out_size,
|
||||
new_prot,
|
||||
&old_prot,
|
||||
)) {
|
||||
.SUCCESS => return old_prot,
|
||||
.INVALID_ADDRESS => return error.InvalidAddress,
|
||||
// TODO: map errors
|
||||
else => |rc| return std.os.windows.unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
||||
pub const VirtualQueryError = error{Unexpected};
|
||||
|
||||
pub fn VirtualQuery(lpAddress: ?LPVOID, lpBuffer: PMEMORY_BASIC_INFORMATION, dwLength: SIZE_T) VirtualQueryError!SIZE_T {
|
||||
@ -4457,3 +4475,184 @@ pub const MODULEENTRY32 = extern struct {
|
||||
szModule: [MAX_MODULE_NAME32 + 1]CHAR,
|
||||
szExePath: [MAX_PATH]CHAR,
|
||||
};
|
||||
|
||||
pub const THREADINFOCLASS = enum(c_int) {
|
||||
ThreadBasicInformation,
|
||||
ThreadTimes,
|
||||
ThreadPriority,
|
||||
ThreadBasePriority,
|
||||
ThreadAffinityMask,
|
||||
ThreadImpersonationToken,
|
||||
ThreadDescriptorTableEntry,
|
||||
ThreadEnableAlignmentFaultFixup,
|
||||
ThreadEventPair_Reusable,
|
||||
ThreadQuerySetWin32StartAddress,
|
||||
ThreadZeroTlsCell,
|
||||
ThreadPerformanceCount,
|
||||
ThreadAmILastThread,
|
||||
ThreadIdealProcessor,
|
||||
ThreadPriorityBoost,
|
||||
ThreadSetTlsArrayAddress,
|
||||
ThreadIsIoPending,
|
||||
// Windows 2000+ from here
|
||||
ThreadHideFromDebugger,
|
||||
// Windows XP+ from here
|
||||
ThreadBreakOnTermination,
|
||||
ThreadSwitchLegacyState,
|
||||
ThreadIsTerminated,
|
||||
// Windows Vista+ from here
|
||||
ThreadLastSystemCall,
|
||||
ThreadIoPriority,
|
||||
ThreadCycleTime,
|
||||
ThreadPagePriority,
|
||||
ThreadActualBasePriority,
|
||||
ThreadTebInformation,
|
||||
ThreadCSwitchMon,
|
||||
// Windows 7+ from here
|
||||
ThreadCSwitchPmu,
|
||||
ThreadWow64Context,
|
||||
ThreadGroupInformation,
|
||||
ThreadUmsInformation,
|
||||
ThreadCounterProfiling,
|
||||
ThreadIdealProcessorEx,
|
||||
// Windows 8+ from here
|
||||
ThreadCpuAccountingInformation,
|
||||
// Windows 8.1+ from here
|
||||
ThreadSuspendCount,
|
||||
// Windows 10+ from here
|
||||
ThreadHeterogeneousCpuPolicy,
|
||||
ThreadContainerId,
|
||||
ThreadNameInformation,
|
||||
ThreadSelectedCpuSets,
|
||||
ThreadSystemThreadInformation,
|
||||
ThreadActualGroupAffinity,
|
||||
};
|
||||
|
||||
pub const PROCESSINFOCLASS = enum(c_int) {
|
||||
ProcessBasicInformation,
|
||||
ProcessQuotaLimits,
|
||||
ProcessIoCounters,
|
||||
ProcessVmCounters,
|
||||
ProcessTimes,
|
||||
ProcessBasePriority,
|
||||
ProcessRaisePriority,
|
||||
ProcessDebugPort,
|
||||
ProcessExceptionPort,
|
||||
ProcessAccessToken,
|
||||
ProcessLdtInformation,
|
||||
ProcessLdtSize,
|
||||
ProcessDefaultHardErrorMode,
|
||||
ProcessIoPortHandlers,
|
||||
ProcessPooledUsageAndLimits,
|
||||
ProcessWorkingSetWatch,
|
||||
ProcessUserModeIOPL,
|
||||
ProcessEnableAlignmentFaultFixup,
|
||||
ProcessPriorityClass,
|
||||
ProcessWx86Information,
|
||||
ProcessHandleCount,
|
||||
ProcessAffinityMask,
|
||||
ProcessPriorityBoost,
|
||||
ProcessDeviceMap,
|
||||
ProcessSessionInformation,
|
||||
ProcessForegroundInformation,
|
||||
ProcessWow64Information,
|
||||
ProcessImageFileName,
|
||||
ProcessLUIDDeviceMapsEnabled,
|
||||
ProcessBreakOnTermination,
|
||||
ProcessDebugObjectHandle,
|
||||
ProcessDebugFlags,
|
||||
ProcessHandleTracing,
|
||||
ProcessIoPriority,
|
||||
ProcessExecuteFlags,
|
||||
ProcessTlsInformation,
|
||||
ProcessCookie,
|
||||
ProcessImageInformation,
|
||||
ProcessCycleTime,
|
||||
ProcessPagePriority,
|
||||
ProcessInstrumentationCallback,
|
||||
ProcessThreadStackAllocation,
|
||||
ProcessWorkingSetWatchEx,
|
||||
ProcessImageFileNameWin32,
|
||||
ProcessImageFileMapping,
|
||||
ProcessAffinityUpdateMode,
|
||||
ProcessMemoryAllocationMode,
|
||||
ProcessGroupInformation,
|
||||
ProcessTokenVirtualizationEnabled,
|
||||
ProcessConsoleHostProcess,
|
||||
ProcessWindowInformation,
|
||||
MaxProcessInfoClass,
|
||||
};
|
||||
|
||||
pub const PROCESS_BASIC_INFORMATION = extern struct {
|
||||
ExitStatus: NTSTATUS,
|
||||
PebBaseAddress: *PEB,
|
||||
AffinityMask: ULONG_PTR,
|
||||
BasePriority: KPRIORITY,
|
||||
UniqueProcessId: ULONG_PTR,
|
||||
InheritedFromUniqueProcessId: ULONG_PTR,
|
||||
};
|
||||
|
||||
pub const ReadMemoryError = error{
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub fn ReadProcessMemory(handle: HANDLE, addr: ?LPVOID, buffer: []u8) ReadMemoryError![]u8 {
|
||||
var nread: usize = 0;
|
||||
switch (ntdll.NtReadVirtualMemory(
|
||||
handle,
|
||||
addr,
|
||||
buffer.ptr,
|
||||
buffer.len,
|
||||
&nread,
|
||||
)) {
|
||||
.SUCCESS => return buffer[0..nread],
|
||||
// TODO: map errors
|
||||
else => |rc| return unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
||||
pub const WriteMemoryError = error{
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub fn WriteProcessMemory(handle: HANDLE, addr: ?LPVOID, buffer: []const u8) WriteMemoryError!usize {
|
||||
var nwritten: usize = 0;
|
||||
switch (ntdll.NtWriteVirtualMemory(
|
||||
handle,
|
||||
addr,
|
||||
@ptrCast(*const anyopaque, buffer.ptr),
|
||||
buffer.len,
|
||||
&nwritten,
|
||||
)) {
|
||||
.SUCCESS => return nwritten,
|
||||
// TODO: map errors
|
||||
else => |rc| return unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
||||
pub const ProcessBaseAddressError = GetProcessMemoryInfoError || ReadMemoryError;
|
||||
|
||||
/// Returns the base address of the process loaded into memory.
|
||||
pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE {
|
||||
var info: PROCESS_BASIC_INFORMATION = undefined;
|
||||
var nread: DWORD = 0;
|
||||
const rc = ntdll.NtQueryInformationProcess(
|
||||
handle,
|
||||
.ProcessBasicInformation,
|
||||
&info,
|
||||
@sizeOf(PROCESS_BASIC_INFORMATION),
|
||||
&nread,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => {},
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.INVALID_HANDLE => return error.InvalidHandle,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
else => return unexpectedStatus(rc),
|
||||
}
|
||||
|
||||
var peb_buf: [@sizeOf(PEB)]u8 align(@alignOf(PEB)) = undefined;
|
||||
const peb_out = try ReadProcessMemory(handle, info.PebBaseAddress, &peb_buf);
|
||||
const ppeb = @ptrCast(*const PEB, @alignCast(@alignOf(PEB), peb_out.ptr));
|
||||
return ppeb.ImageBaseAddress;
|
||||
}
|
||||
|
||||
@ -31,61 +31,10 @@ const UNWIND_HISTORY_TABLE = windows.UNWIND_HISTORY_TABLE;
|
||||
const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;
|
||||
const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
|
||||
const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;
|
||||
|
||||
pub const PROCESSINFOCLASS = enum(c_int) {
|
||||
ProcessBasicInformation,
|
||||
ProcessQuotaLimits,
|
||||
ProcessIoCounters,
|
||||
ProcessVmCounters,
|
||||
ProcessTimes,
|
||||
ProcessBasePriority,
|
||||
ProcessRaisePriority,
|
||||
ProcessDebugPort,
|
||||
ProcessExceptionPort,
|
||||
ProcessAccessToken,
|
||||
ProcessLdtInformation,
|
||||
ProcessLdtSize,
|
||||
ProcessDefaultHardErrorMode,
|
||||
ProcessIoPortHandlers,
|
||||
ProcessPooledUsageAndLimits,
|
||||
ProcessWorkingSetWatch,
|
||||
ProcessUserModeIOPL,
|
||||
ProcessEnableAlignmentFaultFixup,
|
||||
ProcessPriorityClass,
|
||||
ProcessWx86Information,
|
||||
ProcessHandleCount,
|
||||
ProcessAffinityMask,
|
||||
ProcessPriorityBoost,
|
||||
ProcessDeviceMap,
|
||||
ProcessSessionInformation,
|
||||
ProcessForegroundInformation,
|
||||
ProcessWow64Information,
|
||||
ProcessImageFileName,
|
||||
ProcessLUIDDeviceMapsEnabled,
|
||||
ProcessBreakOnTermination,
|
||||
ProcessDebugObjectHandle,
|
||||
ProcessDebugFlags,
|
||||
ProcessHandleTracing,
|
||||
ProcessIoPriority,
|
||||
ProcessExecuteFlags,
|
||||
ProcessTlsInformation,
|
||||
ProcessCookie,
|
||||
ProcessImageInformation,
|
||||
ProcessCycleTime,
|
||||
ProcessPagePriority,
|
||||
ProcessInstrumentationCallback,
|
||||
ProcessThreadStackAllocation,
|
||||
ProcessWorkingSetWatchEx,
|
||||
ProcessImageFileNameWin32,
|
||||
ProcessImageFileMapping,
|
||||
ProcessAffinityUpdateMode,
|
||||
ProcessMemoryAllocationMode,
|
||||
ProcessGroupInformation,
|
||||
ProcessTokenVirtualizationEnabled,
|
||||
ProcessConsoleHostProcess,
|
||||
ProcessWindowInformation,
|
||||
MaxProcessInfoClass,
|
||||
};
|
||||
const THREADINFOCLASS = windows.THREADINFOCLASS;
|
||||
const PROCESSINFOCLASS = windows.PROCESSINFOCLASS;
|
||||
const LPVOID = windows.LPVOID;
|
||||
const LPCVOID = windows.LPCVOID;
|
||||
|
||||
pub extern "ntdll" fn NtQueryInformationProcess(
|
||||
ProcessHandle: HANDLE,
|
||||
@ -95,57 +44,6 @@ pub extern "ntdll" fn NtQueryInformationProcess(
|
||||
ReturnLength: ?*ULONG,
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
|
||||
pub const THREADINFOCLASS = enum(c_int) {
|
||||
ThreadBasicInformation,
|
||||
ThreadTimes,
|
||||
ThreadPriority,
|
||||
ThreadBasePriority,
|
||||
ThreadAffinityMask,
|
||||
ThreadImpersonationToken,
|
||||
ThreadDescriptorTableEntry,
|
||||
ThreadEnableAlignmentFaultFixup,
|
||||
ThreadEventPair_Reusable,
|
||||
ThreadQuerySetWin32StartAddress,
|
||||
ThreadZeroTlsCell,
|
||||
ThreadPerformanceCount,
|
||||
ThreadAmILastThread,
|
||||
ThreadIdealProcessor,
|
||||
ThreadPriorityBoost,
|
||||
ThreadSetTlsArrayAddress,
|
||||
ThreadIsIoPending,
|
||||
// Windows 2000+ from here
|
||||
ThreadHideFromDebugger,
|
||||
// Windows XP+ from here
|
||||
ThreadBreakOnTermination,
|
||||
ThreadSwitchLegacyState,
|
||||
ThreadIsTerminated,
|
||||
// Windows Vista+ from here
|
||||
ThreadLastSystemCall,
|
||||
ThreadIoPriority,
|
||||
ThreadCycleTime,
|
||||
ThreadPagePriority,
|
||||
ThreadActualBasePriority,
|
||||
ThreadTebInformation,
|
||||
ThreadCSwitchMon,
|
||||
// Windows 7+ from here
|
||||
ThreadCSwitchPmu,
|
||||
ThreadWow64Context,
|
||||
ThreadGroupInformation,
|
||||
ThreadUmsInformation,
|
||||
ThreadCounterProfiling,
|
||||
ThreadIdealProcessorEx,
|
||||
// Windows 8+ from here
|
||||
ThreadCpuAccountingInformation,
|
||||
// Windows 8.1+ from here
|
||||
ThreadSuspendCount,
|
||||
// Windows 10+ from here
|
||||
ThreadHeterogeneousCpuPolicy,
|
||||
ThreadContainerId,
|
||||
ThreadNameInformation,
|
||||
ThreadSelectedCpuSets,
|
||||
ThreadSystemThreadInformation,
|
||||
ThreadActualGroupAffinity,
|
||||
};
|
||||
pub extern "ntdll" fn NtQueryInformationThread(
|
||||
ThreadHandle: HANDLE,
|
||||
ThreadInformationClass: THREADINFOCLASS,
|
||||
@ -364,10 +262,26 @@ pub extern "ntdll" fn RtlQueryRegistryValues(
|
||||
Environment: ?*anyopaque,
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
|
||||
pub extern "ntdll" fn NtReadVirtualMemory(
|
||||
ProcessHandle: HANDLE,
|
||||
BaseAddress: ?PVOID,
|
||||
Buffer: LPVOID,
|
||||
NumberOfBytesToRead: SIZE_T,
|
||||
NumberOfBytesRead: ?*SIZE_T,
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
|
||||
pub extern "ntdll" fn NtWriteVirtualMemory(
|
||||
ProcessHandle: HANDLE,
|
||||
BaseAddress: ?PVOID,
|
||||
Buffer: LPCVOID,
|
||||
NumberOfBytesToWrite: SIZE_T,
|
||||
NumberOfBytesWritten: ?*SIZE_T,
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
|
||||
pub extern "ntdll" fn NtProtectVirtualMemory(
|
||||
ProcessHandle: HANDLE,
|
||||
BaseAddress: *PVOID,
|
||||
NumberOfBytesToProtect: *ULONG,
|
||||
BaseAddress: *?PVOID,
|
||||
NumberOfBytesToProtect: *SIZE_T,
|
||||
NewAccessProtection: ULONG,
|
||||
OldAccessProtection: *ULONG,
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
|
||||
42
src/link.zig
42
src/link.zig
@ -379,24 +379,30 @@ pub const File = struct {
|
||||
if (base.file != null) return;
|
||||
const emit = base.options.emit orelse return;
|
||||
if (base.child_pid) |pid| {
|
||||
// If we try to open the output file in write mode while it is running,
|
||||
// it will return ETXTBSY. So instead, we copy the file, atomically rename it
|
||||
// over top of the exe path, and then proceed normally. This changes the inode,
|
||||
// avoiding the error.
|
||||
const tmp_sub_path = try std.fmt.allocPrint(base.allocator, "{s}-{x}", .{
|
||||
emit.sub_path, std.crypto.random.int(u32),
|
||||
});
|
||||
try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{});
|
||||
try emit.directory.handle.rename(tmp_sub_path, emit.sub_path);
|
||||
switch (builtin.os.tag) {
|
||||
.linux => std.os.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| {
|
||||
log.warn("ptrace failure: {s}", .{@errorName(err)});
|
||||
},
|
||||
.macos => base.cast(MachO).?.ptraceAttach(pid) catch |err| {
|
||||
if (builtin.os.tag == .windows) {
|
||||
base.cast(Coff).?.ptraceAttach(pid) catch |err| {
|
||||
log.warn("attaching failed with error: {s}", .{@errorName(err)});
|
||||
},
|
||||
.windows => {},
|
||||
else => return error.HotSwapUnavailableOnHostOperatingSystem,
|
||||
};
|
||||
} else {
|
||||
// If we try to open the output file in write mode while it is running,
|
||||
// it will return ETXTBSY. So instead, we copy the file, atomically rename it
|
||||
// over top of the exe path, and then proceed normally. This changes the inode,
|
||||
// avoiding the error.
|
||||
const tmp_sub_path = try std.fmt.allocPrint(base.allocator, "{s}-{x}", .{
|
||||
emit.sub_path, std.crypto.random.int(u32),
|
||||
});
|
||||
try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{});
|
||||
try emit.directory.handle.rename(tmp_sub_path, emit.sub_path);
|
||||
switch (builtin.os.tag) {
|
||||
.linux => std.os.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| {
|
||||
log.warn("ptrace failure: {s}", .{@errorName(err)});
|
||||
},
|
||||
.macos => base.cast(MachO).?.ptraceAttach(pid) catch |err| {
|
||||
log.warn("attaching failed with error: {s}", .{@errorName(err)});
|
||||
},
|
||||
.windows => unreachable,
|
||||
else => return error.HotSwapUnavailableOnHostOperatingSystem,
|
||||
}
|
||||
}
|
||||
}
|
||||
base.file = try emit.directory.handle.createFile(emit.sub_path, .{
|
||||
@ -437,7 +443,7 @@ pub const File = struct {
|
||||
.macos => base.cast(MachO).?.ptraceDetach(pid) catch |err| {
|
||||
log.warn("detaching failed with error: {s}", .{@errorName(err)});
|
||||
},
|
||||
.windows => {},
|
||||
.windows => base.cast(Coff).?.ptraceDetach(pid),
|
||||
else => return error.HotSwapUnavailableOnHostOperatingSystem,
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,6 +89,20 @@ relocs: RelocTable = .{},
|
||||
/// this will be a table indexed by index into the list of Atoms.
|
||||
base_relocs: BaseRelocationTable = .{},
|
||||
|
||||
/// Hot-code swapping state.
|
||||
hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
|
||||
|
||||
const is_hot_update_compatible = switch (builtin.target.os.tag) {
|
||||
.windows => true,
|
||||
else => false,
|
||||
};
|
||||
|
||||
const HotUpdateState = struct {
|
||||
/// Base address at which the process (image) got loaded.
|
||||
/// We need this info to correctly slide pointers when relocating.
|
||||
loaded_base_address: ?std.os.windows.HMODULE = null,
|
||||
};
|
||||
|
||||
const Entry = struct {
|
||||
target: SymbolWithLoc,
|
||||
// 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 {
|
||||
const sym = atom.getSymbol(self);
|
||||
const section = self.sections.get(@enumToInt(sym.section_number) - 1);
|
||||
const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address;
|
||||
|
||||
log.debug("writing atom for symbol {s} at file offset 0x{x} to 0x{x}", .{
|
||||
atom.getName(self),
|
||||
file_offset,
|
||||
file_offset + code.len,
|
||||
});
|
||||
self.resolveRelocs(atom_index, code);
|
||||
|
||||
const gpa = self.base.allocator;
|
||||
|
||||
// Gather relocs which can be resolved.
|
||||
// We need to do this as we will be applying different slide values depending
|
||||
// if we are running in hot-code swapping mode or not.
|
||||
// TODO: how crazy would it be to try and apply the actual image base of the loaded
|
||||
// process for the in-file values rather than the Windows defaults?
|
||||
var relocs = std.ArrayList(*Relocation).init(gpa);
|
||||
defer relocs.deinit();
|
||||
|
||||
if (self.relocs.getPtr(atom_index)) |rels| {
|
||||
try relocs.ensureTotalCapacityPrecise(rels.items.len);
|
||||
for (rels.items) |*reloc| {
|
||||
if (reloc.isResolvable(self)) relocs.appendAssumeCapacity(reloc);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_hot_update_compatible) {
|
||||
if (self.base.child_pid) |handle| {
|
||||
const slide = @ptrToInt(self.hot_state.loaded_base_address.?);
|
||||
|
||||
const mem_code = try gpa.dupe(u8, code);
|
||||
defer gpa.free(mem_code);
|
||||
self.resolveRelocs(atom_index, relocs.items, mem_code, slide);
|
||||
|
||||
const vaddr = sym.value + slide;
|
||||
const pvaddr = @intToPtr(*anyopaque, vaddr);
|
||||
|
||||
log.debug("writing to memory at address {x}", .{vaddr});
|
||||
|
||||
if (build_options.enable_logging) {
|
||||
try debugMem(gpa, handle, pvaddr, mem_code);
|
||||
}
|
||||
|
||||
if (section.header.flags.MEM_WRITE == 0) {
|
||||
writeMemProtected(handle, pvaddr, mem_code) catch |err| {
|
||||
log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)});
|
||||
};
|
||||
} else {
|
||||
writeMem(handle, pvaddr, mem_code) catch |err| {
|
||||
log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.resolveRelocs(atom_index, relocs.items, code, self.getImageBase());
|
||||
try self.base.file.?.pwriteAll(code, file_offset);
|
||||
|
||||
// Now we can mark the relocs as resolved.
|
||||
while (relocs.popOrNull()) |reloc| {
|
||||
reloc.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn debugMem(allocator: Allocator, handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
|
||||
var buffer = try allocator.alloc(u8, code.len);
|
||||
defer allocator.free(buffer);
|
||||
const memread = try std.os.windows.ReadProcessMemory(handle, pvaddr, buffer);
|
||||
log.debug("to write: {x}", .{std.fmt.fmtSliceHexLower(code)});
|
||||
log.debug("in memory: {x}", .{std.fmt.fmtSliceHexLower(memread)});
|
||||
}
|
||||
|
||||
fn writeMemProtected(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
|
||||
const old_prot = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, std.os.windows.PAGE_EXECUTE_WRITECOPY);
|
||||
try writeMem(handle, pvaddr, code);
|
||||
// TODO: We can probably just set the pages writeable and leave it at that without having to restore the attributes.
|
||||
// For that though, we want to track which page has already been modified.
|
||||
_ = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, old_prot);
|
||||
}
|
||||
|
||||
fn writeMem(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
|
||||
const amt = try std.os.windows.WriteProcessMemory(handle, pvaddr, code);
|
||||
if (amt != code.len) return error.InputOutput;
|
||||
}
|
||||
|
||||
fn writePtrWidthAtom(self: *Coff, atom_index: Atom.Index) !void {
|
||||
@ -814,19 +902,30 @@ fn markRelocsDirtyByAddress(self: *Coff, addr: u32) void {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolveRelocs(self: *Coff, atom_index: Atom.Index, code: []u8) void {
|
||||
const relocs = self.relocs.getPtr(atom_index) orelse return;
|
||||
|
||||
fn resolveRelocs(self: *Coff, atom_index: Atom.Index, relocs: []*const Relocation, code: []u8, image_base: u64) void {
|
||||
log.debug("relocating '{s}'", .{self.getAtom(atom_index).getName(self)});
|
||||
|
||||
for (relocs.items) |*reloc| {
|
||||
if (!reloc.dirty) continue;
|
||||
if (reloc.resolve(atom_index, code, self)) {
|
||||
reloc.dirty = false;
|
||||
}
|
||||
for (relocs) |reloc| {
|
||||
reloc.resolve(atom_index, code, image_base, self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ptraceAttach(self: *Coff, handle: std.ChildProcess.Id) !void {
|
||||
if (!is_hot_update_compatible) return;
|
||||
|
||||
log.debug("attaching to process with handle {*}", .{handle});
|
||||
self.hot_state.loaded_base_address = std.os.windows.ProcessBaseAddress(handle) catch |err| {
|
||||
log.warn("failed to get base address for the process with error: {s}", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ptraceDetach(self: *Coff, handle: std.ChildProcess.Id) void {
|
||||
if (!is_hot_update_compatible) return;
|
||||
|
||||
log.debug("detaching from process with handle {*}", .{handle});
|
||||
self.hot_state.loaded_base_address = null;
|
||||
}
|
||||
|
||||
fn freeAtom(self: *Coff, atom_index: Atom.Index) void {
|
||||
log.debug("freeAtom {d}", .{atom_index});
|
||||
|
||||
@ -1421,7 +1520,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
|
||||
|
||||
for (self.relocs.keys(), self.relocs.values()) |atom_index, relocs| {
|
||||
const needs_update = for (relocs.items) |reloc| {
|
||||
if (reloc.dirty) break true;
|
||||
if (reloc.isResolvable(self)) break true;
|
||||
} else false;
|
||||
|
||||
if (!needs_update) continue;
|
||||
|
||||
@ -72,14 +72,18 @@ pub fn getTargetAddress(self: Relocation, coff_file: *const Coff) ?u32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `false` if obtaining the target address has been deferred until `flushModule`.
|
||||
/// This can happen when trying to resolve address of an import table entry ahead of time.
|
||||
pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, coff_file: *Coff) bool {
|
||||
/// Returns true if and only if the reloc is dirty AND the target address is available.
|
||||
pub fn isResolvable(self: Relocation, coff_file: *Coff) bool {
|
||||
_ = self.getTargetAddress(coff_file) orelse return false;
|
||||
return self.dirty;
|
||||
}
|
||||
|
||||
pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, image_base: u64, coff_file: *Coff) void {
|
||||
const atom = coff_file.getAtom(atom_index);
|
||||
const source_sym = atom.getSymbol(coff_file);
|
||||
const source_vaddr = source_sym.value + self.offset;
|
||||
|
||||
const target_vaddr = self.getTargetAddress(coff_file) orelse return false;
|
||||
const target_vaddr = self.getTargetAddress(coff_file).?; // Oops, you didn't check if the relocation can be resolved with isResolvable().
|
||||
const target_vaddr_with_addend = target_vaddr + self.addend;
|
||||
|
||||
log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) ", .{
|
||||
@ -92,7 +96,7 @@ pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, coff_file:
|
||||
const ctx: Context = .{
|
||||
.source_vaddr = source_vaddr,
|
||||
.target_vaddr = target_vaddr_with_addend,
|
||||
.image_base = coff_file.getImageBase(),
|
||||
.image_base = image_base,
|
||||
.code = code,
|
||||
.ptr_width = coff_file.ptr_width,
|
||||
};
|
||||
@ -102,8 +106,6 @@ pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, coff_file:
|
||||
.x86, .x86_64 => self.resolveX86(ctx),
|
||||
else => unreachable, // unhandled target architecture
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const Context = struct {
|
||||
|
||||
24
src/main.zig
24
src/main.zig
@ -3817,11 +3817,25 @@ fn runOrTestHotSwap(
|
||||
runtime_args_start: ?usize,
|
||||
) !std.ChildProcess.Id {
|
||||
const exe_emit = comp.bin_file.options.emit.?;
|
||||
// A naive `directory.join` here will indeed get the correct path to the binary,
|
||||
// however, in the case of cwd, we actually want `./foo` so that the path can be executed.
|
||||
const exe_path = try fs.path.join(gpa, &[_][]const u8{
|
||||
exe_emit.directory.path orelse ".", exe_emit.sub_path,
|
||||
});
|
||||
|
||||
const exe_path = switch (builtin.target.os.tag) {
|
||||
// On Windows it seems impossible to perform an atomic rename of a file that is currently
|
||||
// running in a process. Therefore, we do the opposite. We create a copy of the file in
|
||||
// tmp zig-cache and use it to spawn the child process. This way we are free to update
|
||||
// the binary with each requested hot update.
|
||||
.windows => blk: {
|
||||
try exe_emit.directory.handle.copyFile(exe_emit.sub_path, comp.local_cache_directory.handle, exe_emit.sub_path, .{});
|
||||
break :blk try fs.path.join(gpa, &[_][]const u8{
|
||||
comp.local_cache_directory.path orelse ".", exe_emit.sub_path,
|
||||
});
|
||||
},
|
||||
|
||||
// A naive `directory.join` here will indeed get the correct path to the binary,
|
||||
// however, in the case of cwd, we actually want `./foo` so that the path can be executed.
|
||||
else => try fs.path.join(gpa, &[_][]const u8{
|
||||
exe_emit.directory.path orelse ".", exe_emit.sub_path,
|
||||
}),
|
||||
};
|
||||
defer gpa.free(exe_path);
|
||||
|
||||
var argv = std.ArrayList([]const u8).init(gpa);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user