authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-03-30 12:32:29+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-03-30 21:08:30+02:00
log39d63036441841fb81eebda97fb2932e5c3f79c4
tree961662d9995d6f32c6a82a12472a087ca40ee88e
parent22e1b033607580756329879ba7158a29aca57981

coff: first (not-fully-functional) PoC of HCS


2 files changed, 157 insertions(+), 4 deletions(-)

src/link.zig+1-1
...@@ -443,7 +443,7 @@ pub const File = struct {...@@ -443,7 +443,7 @@ pub const File = struct {
443 .macos => base.cast(MachO).?.ptraceDetach(pid) catch |err| {443 .macos => base.cast(MachO).?.ptraceDetach(pid) catch |err| {
444 log.warn("detaching failed with error: {s}", .{@errorName(err)});444 log.warn("detaching failed with error: {s}", .{@errorName(err)});
445 },445 },
446 .windows => {},446 .windows => base.cast(Coff).?.ptraceDetach(pid),
447 else => return error.HotSwapUnavailableOnHostOperatingSystem,447 else => return error.HotSwapUnavailableOnHostOperatingSystem,
448 }448 }
449 }449 }
src/link/Coff.zig+156-3
...@@ -89,6 +89,13 @@ relocs: RelocTable = .{},...@@ -89,6 +89,13 @@ 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: HotUpdateState = .{},
94
95const HotUpdateState = struct {
96 loaded_base_address: ?u64 = null,
97};
98
92const Entry = struct {99const Entry = struct {
93 target: SymbolWithLoc,100 target: SymbolWithLoc,
94 // Index into the synthetic symbol table (i.e., file == null).101 // Index into the synthetic symbol table (i.e., file == null).
...@@ -778,9 +785,147 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void {...@@ -778,9 +785,147 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void {
778 file_offset + code.len,785 file_offset + code.len,
779 });786 });
780 self.resolveRelocs(atom_index, code);787 self.resolveRelocs(atom_index, code);
788
789 if (self.base.child_pid) |handle| {
790 const vaddr = sym.value + (self.hot_state.loaded_base_address orelse self.getImageBase());
791 log.warn("hcs: writing to memory at address {x}", .{vaddr});
792 try debugMem(self.base.allocator, handle, vaddr, code);
793 if (section.header.flags.MEM_WRITE == 0) {
794 log.warn(" page not mapped for write access; re-mapping...", .{});
795 try writeMemProtected(handle, vaddr, code);
796 } else {
797 try writeMem(handle, vaddr, code);
798 }
799 }
800
781 try self.base.file.?.pwriteAll(code, file_offset);801 try self.base.file.?.pwriteAll(code, file_offset);
782}802}
783803
804extern "kernel32" fn ReadProcessMemory(
805 hProcess: std.os.windows.HANDLE,
806 lpBaseAddress: std.os.windows.LPCVOID,
807 lpBuffer: std.os.windows.LPVOID,
808 nSize: std.os.windows.SIZE_T,
809 lpNumberOfBytesRead: *std.os.windows.SIZE_T,
810) std.os.windows.BOOL;
811
812extern "kernel32" fn WriteProcessMemory(
813 hProcess: std.os.windows.HANDLE,
814 lpBaseAddress: std.os.windows.LPVOID,
815 lpBuffer: std.os.windows.LPCVOID,
816 nSize: std.os.windows.SIZE_T,
817 lpNumberOfBytesWritten: *std.os.windows.SIZE_T,
818) std.os.windows.BOOL;
819
820extern "kernel32" fn VirtualProtectEx(
821 hProcess: std.os.windows.HANDLE,
822 lpAddress: std.os.windows.LPVOID,
823 dwSize: std.os.windows.SIZE_T,
824 flNewProtect: std.os.windows.DWORD,
825 lpflOldProtect: *std.os.windows.DWORD,
826) std.os.windows.BOOL;
827
828const PROCESS_BASIC_INFORMATION = extern struct {
829 ExitStatus: std.os.windows.NTSTATUS,
830 PebBaseAddress: *std.os.windows.PEB,
831 AffinityMask: std.os.windows.ULONG_PTR,
832 BasePriority: std.os.windows.KPRIORITY,
833 UniqueProcessId: std.os.windows.ULONG_PTR,
834 InheritedFromUniqueProcessId: std.os.windows.ULONG_PTR,
835};
836
837fn getProcessBaseAddress(handle: std.ChildProcess.Id) !u64 {
838 var info: PROCESS_BASIC_INFORMATION = undefined;
839 var nread: std.os.windows.DWORD = 0;
840 const rc = std.os.windows.ntdll.NtQueryInformationProcess(
841 handle,
842 .ProcessBasicInformation,
843 &info,
844 @sizeOf(PROCESS_BASIC_INFORMATION),
845 &nread,
846 );
847 switch (rc) {
848 .SUCCESS => {},
849 else => return std.os.windows.unexpectedStatus(rc),
850 }
851
852 var peb_buf: [@sizeOf(std.os.windows.PEB)]u8 align(@alignOf(std.os.windows.PEB)) = undefined;
853 var peb_nread: usize = 0;
854 if (ReadProcessMemory(
855 handle,
856 info.PebBaseAddress,
857 &peb_buf,
858 @sizeOf(std.os.windows.PEB),
859 &peb_nread,
860 ) == 0) {
861 const err = std.os.windows.kernel32.GetLastError();
862 log.warn("hcs: reading from process memory failed with err: {s}({x})", .{ @tagName(err), @enumToInt(err) });
863 return error.FailedToReadPebForProcess;
864 }
865 if (peb_nread != @sizeOf(std.os.windows.PEB)) return error.InputOutput;
866
867 const peb = @ptrCast(*const std.os.windows.PEB, &peb_buf);
868 return @ptrToInt(peb.ImageBaseAddress);
869}
870
871fn debugMem(allocator: Allocator, handle: std.ChildProcess.Id, vaddr: u64, code: []const u8) !void {
872 var buffer = try allocator.alloc(u8, code.len);
873 defer allocator.free(buffer);
874 var nread: usize = 0;
875 if (ReadProcessMemory(
876 handle,
877 @intToPtr(*anyopaque, vaddr),
878 buffer.ptr,
879 code.len,
880 &nread,
881 ) == 0) {
882 const err = std.os.windows.kernel32.GetLastError();
883 log.warn("hcs: reading from process memory failed with err: {s}({x})", .{ @tagName(err), @enumToInt(err) });
884 }
885 if (nread != code.len) {
886 log.warn("hcs: reading from process memory InputOutput error: read != requested: {x} != {x}", .{ nread, code.len });
887 }
888
889 log.warn("in memory: {x}", .{std.fmt.fmtSliceHexLower(buffer)});
890 log.warn("to write: {x}", .{std.fmt.fmtSliceHexLower(code)});
891}
892
893fn writeMemProtected(handle: std.ChildProcess.Id, vaddr: u64, code: []const u8) !void {
894 const pvaddr = @intToPtr(*anyopaque, vaddr);
895 var new_prot: std.os.windows.DWORD = std.os.windows.PAGE_EXECUTE_WRITECOPY;
896 var old_prot: std.os.windows.DWORD = undefined;
897 if (VirtualProtectEx(handle, pvaddr, code.len, new_prot, &old_prot) == 0) {
898 const err = std.os.windows.kernel32.GetLastError();
899 log.warn("hcs: making page(s) writeable failed with error: {s}({x})", .{ @tagName(err), @enumToInt(err) });
900 return;
901 }
902 log.warn("old = {x}, new = {x}", .{ old_prot, new_prot });
903 try writeMem(handle, vaddr, code);
904 // TODO: We can probably just set the pages writeable and leave it at that without having to restore the attributes.
905 // For that though, we want to track which page has already been modified.
906 if (VirtualProtectEx(handle, pvaddr, code.len, old_prot, &new_prot) == 0) {
907 const err = std.os.windows.kernel32.GetLastError();
908 log.warn("hcs: restoring page(s) attributes failed with error: {s}({x})", .{ @tagName(err), @enumToInt(err) });
909 }
910}
911
912fn writeMem(handle: std.ChildProcess.Id, vaddr: u64, code: []const u8) !void {
913 var nwritten: usize = 0;
914 if (WriteProcessMemory(
915 handle,
916 @intToPtr(*anyopaque, vaddr),
917 code.ptr,
918 code.len,
919 &nwritten,
920 ) == 0) {
921 const err = std.os.windows.kernel32.GetLastError();
922 log.warn("hcs: writing to process memory failed with err: {s}({x})", .{ @tagName(err), @enumToInt(err) });
923 }
924 if (nwritten != code.len) {
925 log.warn("hcs: writing to process memory InputOutput error: written != requested: {x} != {x}", .{ nwritten, code.len });
926 }
927}
928
784fn writePtrWidthAtom(self: *Coff, atom_index: Atom.Index) !void {929fn writePtrWidthAtom(self: *Coff, atom_index: Atom.Index) !void {
785 switch (self.ptr_width) {930 switch (self.ptr_width) {
786 .p32 => {931 .p32 => {
...@@ -827,9 +972,17 @@ fn resolveRelocs(self: *Coff, atom_index: Atom.Index, code: []u8) void {...@@ -827,9 +972,17 @@ fn resolveRelocs(self: *Coff, atom_index: Atom.Index, code: []u8) void {
827 }972 }
828}973}
829974
830pub fn ptraceAttach(self: *Coff, handle: std.os.pid_t) !void {975pub fn ptraceAttach(self: *Coff, handle: std.ChildProcess.Id) !void {
831 _ = self;976 log.warn("hcs: attaching to process with handle {*}", .{handle});
832 log.warn("attaching to process with handle {*}", .{handle});977 self.hot_state.loaded_base_address = getProcessBaseAddress(handle) catch |err| {
978 log.warn("hcs: failed to get base address for the process with error: {s}", .{@errorName(err)});
979 return;
980 };
981}
982
983pub fn ptraceDetach(self: *Coff, handle: std.ChildProcess.Id) void {
984 log.warn("hcs: detaching from process with handle {*}", .{handle});
985 self.hot_state.loaded_base_address = null;
833}986}
834987
835fn freeAtom(self: *Coff, atom_index: Atom.Index) void {988fn freeAtom(self: *Coff, atom_index: Atom.Index) void {