authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-21 18:47:29-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-21 18:47:29-07:00
logdefb30901388127f4e640e0a8b1107d327877108
tree781fbcc21eb9d004d522b0d8e51542b4ca249326
parent3cf48d0fd77dac3c2efa000eb97364e916987b74

hot code swapping PoC working

- improve fn prototypes of process_vm_writev - make the memory writable in the ELF file - force the linker to always append the function - write updates with process_vm_writev

2 files changed, 48 insertions(+), 76 deletions(-)

lib/std/os/linux.zig+10-10
...@@ -1563,26 +1563,26 @@ pub fn pidfd_send_signal(pidfd: fd_t, sig: i32, info: ?*siginfo_t, flags: u32) u...@@ -1563,26 +1563,26 @@ pub fn pidfd_send_signal(pidfd: fd_t, sig: i32, info: ?*siginfo_t, flags: u32) u
1563 );1563 );
1564}1564}
15651565
1566pub fn process_vm_readv(pid: pid_t, local: [*]const iovec, local_count: usize, remote: [*]const iovec, remote_count: usize, flags: usize) usize {1566pub fn process_vm_readv(pid: pid_t, local: []iovec, remote: []const iovec_const, flags: usize) usize {
1567 return syscall6(1567 return syscall6(
1568 .process_vm_readv,1568 .process_vm_readv,
1569 @bitCast(usize, @as(isize, pid)),1569 @bitCast(usize, @as(isize, pid)),
1570 @ptrToInt(local),1570 @ptrToInt(local.ptr),
1571 local_count,1571 local.len,
1572 @ptrToInt(remote),1572 @ptrToInt(remote.ptr),
1573 remote_count,1573 remote.len,
1574 flags,1574 flags,
1575 );1575 );
1576}1576}
15771577
1578pub fn process_vm_writev(pid: pid_t, local: [*]const iovec, local_count: usize, remote: [*]const iovec, remote_count: usize, flags: usize) usize {1578pub fn process_vm_writev(pid: pid_t, local: []const iovec_const, remote: []const iovec_const, flags: usize) usize {
1579 return syscall6(1579 return syscall6(
1580 .process_vm_writev,1580 .process_vm_writev,
1581 @bitCast(usize, @as(isize, pid)),1581 @bitCast(usize, @as(isize, pid)),
1582 @ptrToInt(local),1582 @ptrToInt(local.ptr),
1583 local_count,1583 local.len,
1584 @ptrToInt(remote),1584 @ptrToInt(remote.ptr),
1585 remote_count,1585 remote.len,
1586 flags,1586 flags,
1587 );1587 );
1588}1588}
src/link/Elf.zig+38-66
...@@ -477,7 +477,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {...@@ -477,7 +477,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
477 .p_paddr = entry_addr,477 .p_paddr = entry_addr,
478 .p_memsz = file_size,478 .p_memsz = file_size,
479 .p_align = p_align,479 .p_align = p_align,
480 .p_flags = elf.PF_X | elf.PF_R,480 .p_flags = elf.PF_X | elf.PF_R | elf.PF_W,
481 });481 });
482 self.entry_addr = null;482 self.entry_addr = null;
483 self.phdr_table_dirty = true;483 self.phdr_table_dirty = true;
...@@ -502,7 +502,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {...@@ -502,7 +502,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
502 .p_paddr = got_addr,502 .p_paddr = got_addr,
503 .p_memsz = file_size,503 .p_memsz = file_size,
504 .p_align = p_align,504 .p_align = p_align,
505 .p_flags = elf.PF_R,505 .p_flags = elf.PF_R | elf.PF_W,
506 });506 });
507 self.phdr_table_dirty = true;507 self.phdr_table_dirty = true;
508 }508 }
...@@ -525,7 +525,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {...@@ -525,7 +525,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
525 .p_paddr = rodata_addr,525 .p_paddr = rodata_addr,
526 .p_memsz = file_size,526 .p_memsz = file_size,
527 .p_align = p_align,527 .p_align = p_align,
528 .p_flags = elf.PF_R,528 .p_flags = elf.PF_R | elf.PF_W,
529 });529 });
530 self.phdr_table_dirty = true;530 self.phdr_table_dirty = true;
531 }531 }
...@@ -2097,7 +2097,6 @@ fn growTextBlock(self: *Elf, block_list: *TextBlockList, text_block: *TextBlock,...@@ -2097,7 +2097,6 @@ fn growTextBlock(self: *Elf, block_list: *TextBlockList, text_block: *TextBlock,
2097fn allocateTextBlock(self: *Elf, block_list: *TextBlockList, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {2097fn allocateTextBlock(self: *Elf, block_list: *TextBlockList, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
2098 const phdr = &self.program_headers.items[block_list.phdr_index.?];2098 const phdr = &self.program_headers.items[block_list.phdr_index.?];
2099 const shdr = &self.sections.items[block_list.section_index.?];2099 const shdr = &self.sections.items[block_list.section_index.?];
2100 const new_block_ideal_capacity = padToIdeal(new_block_size);
21012100
2102 // We use these to indicate our intention to update metadata, placing the new block,2101 // We use these to indicate our intention to update metadata, placing the new block,
2103 // and possibly removing a free list node.2102 // and possibly removing a free list node.
...@@ -2110,42 +2109,7 @@ fn allocateTextBlock(self: *Elf, block_list: *TextBlockList, text_block: *TextBl...@@ -2110,42 +2109,7 @@ fn allocateTextBlock(self: *Elf, block_list: *TextBlockList, text_block: *TextBl
2110 // First we look for an appropriately sized free list node.2109 // First we look for an appropriately sized free list node.
2111 // The list is unordered. We'll just take the first thing that works.2110 // The list is unordered. We'll just take the first thing that works.
2112 const vaddr = blk: {2111 const vaddr = blk: {
2113 var i: usize = 0;2112 if (block_list.last_block) |last| {
2114 while (i < block_list.free_list.items.len) {
2115 const big_block = block_list.free_list.items[i];
2116 // We now have a pointer to a live text block that has too much capacity.
2117 // Is it enough that we could fit this new text block?
2118 const sym = self.local_symbols.items[big_block.local_sym_index];
2119 const capacity = big_block.capacity(self.*);
2120 const ideal_capacity = padToIdeal(capacity);
2121 const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity;
2122 const capacity_end_vaddr = sym.st_value + capacity;
2123 const new_start_vaddr_unaligned = capacity_end_vaddr - new_block_ideal_capacity;
2124 const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment);
2125 if (new_start_vaddr < ideal_capacity_end_vaddr) {
2126 // Additional bookkeeping here to notice if this free list node
2127 // should be deleted because the block that it points to has grown to take up
2128 // more of the extra capacity.
2129 if (!big_block.freeListEligible(self.*)) {
2130 _ = block_list.free_list.swapRemove(i);
2131 } else {
2132 i += 1;
2133 }
2134 continue;
2135 }
2136 // At this point we know that we will place the new block here. But the
2137 // remaining question is whether there is still yet enough capacity left
2138 // over for there to still be a free list node.
2139 const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
2140 const keep_free_list_node = remaining_capacity >= min_text_capacity;
2141
2142 // Set up the metadata to be updated, after errors are no longer possible.
2143 block_placement = big_block;
2144 if (!keep_free_list_node) {
2145 free_list_removal = i;
2146 }
2147 break :blk new_start_vaddr;
2148 } else if (block_list.last_block) |last| {
2149 const sym = self.local_symbols.items[last.local_sym_index];2113 const sym = self.local_symbols.items[last.local_sym_index];
2150 const ideal_capacity = padToIdeal(sym.st_size);2114 const ideal_capacity = padToIdeal(sym.st_size);
2151 const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity;2115 const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity;
...@@ -2318,37 +2282,12 @@ fn updateDeclCode(self: *Elf, decl: *Module.Decl, code: []const u8, stt_bits: u8...@@ -2318,37 +2282,12 @@ fn updateDeclCode(self: *Elf, decl: *Module.Decl, code: []const u8, stt_bits: u8
23182282
2319 assert(decl.link.elf.local_sym_index != 0); // Caller forgot to allocateDeclIndexes()2283 assert(decl.link.elf.local_sym_index != 0); // Caller forgot to allocateDeclIndexes()
2320 const local_sym = &self.local_symbols.items[decl.link.elf.local_sym_index];2284 const local_sym = &self.local_symbols.items[decl.link.elf.local_sym_index];
2321 if (local_sym.st_size != 0) {2285 {
2322 const capacity = decl.link.elf.capacity(self.*);
2323 const need_realloc = code.len > capacity or
2324 !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment);
2325 if (need_realloc) {
2326 const vaddr = try self.growTextBlock(block_list, &decl.link.elf, code.len, required_alignment);
2327 log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl.name, local_sym.st_value, vaddr });
2328 if (vaddr != local_sym.st_value) {
2329 local_sym.st_value = vaddr;
2330
2331 log.debug(" (writing new offset table entry)", .{});
2332 self.offset_table.items[decl.link.elf.offset_table_index] = vaddr;
2333 try self.writeOffsetTableEntry(decl.link.elf.offset_table_index);
2334 }
2335 } else if (code.len < local_sym.st_size) {
2336 self.shrinkTextBlock(block_list, &decl.link.elf, code.len);
2337 }
2338 local_sym.st_size = code.len;
2339 local_sym.st_name = try self.updateString(local_sym.st_name, mem.sliceTo(decl.name, 0));
2340 local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits;
2341 local_sym.st_other = 0;
2342 local_sym.st_shndx = block_list.section_index.?;
2343 // TODO this write could be avoided if no fields of the symbol were changed.
2344 try self.writeSymbol(decl.link.elf.local_sym_index);
2345 } else {
2346 const decl_name = mem.sliceTo(decl.name, 0);2286 const decl_name = mem.sliceTo(decl.name, 0);
2347 const name_str_index = try self.makeString(decl_name);2287 const name_str_index = try self.makeString(decl_name);
2348 const vaddr = try self.allocateTextBlock(block_list, &decl.link.elf, code.len, required_alignment);2288 const vaddr = try self.allocateTextBlock(block_list, &decl.link.elf, code.len, required_alignment);
2349 errdefer self.freeTextBlock(block_list, &decl.link.elf);2289 errdefer self.freeTextBlock(block_list, &decl.link.elf);
2350 log.debug("allocated text block for {s} at 0x{x}", .{ decl_name, vaddr });2290 log.debug("allocated text block for {s} at 0x{x}", .{ decl_name, vaddr });
2351 errdefer self.freeTextBlock(block_list, &decl.link.elf);
23522291
2353 local_sym.* = .{2292 local_sym.* = .{
2354 .st_name = name_str_index,2293 .st_name = name_str_index,
...@@ -2366,6 +2305,22 @@ fn updateDeclCode(self: *Elf, decl: *Module.Decl, code: []const u8, stt_bits: u8...@@ -2366,6 +2305,22 @@ fn updateDeclCode(self: *Elf, decl: *Module.Decl, code: []const u8, stt_bits: u8
23662305
2367 const section_offset = local_sym.st_value - self.program_headers.items[block_list.phdr_index.?].p_vaddr;2306 const section_offset = local_sym.st_value - self.program_headers.items[block_list.phdr_index.?].p_vaddr;
2368 const file_offset = self.sections.items[block_list.section_index.?].sh_offset + section_offset;2307 const file_offset = self.sections.items[block_list.section_index.?].sh_offset + section_offset;
2308
2309 if (self.base.child_pid) |pid| {
2310 var code_vec: [1]std.os.iovec_const = .{.{
2311 .iov_base = code.ptr,
2312 .iov_len = code.len,
2313 }};
2314 var remote_vec: [1]std.os.iovec_const = .{.{
2315 .iov_base = @intToPtr([*]u8, local_sym.st_value),
2316 .iov_len = code.len,
2317 }};
2318 const rc = std.os.linux.process_vm_writev(pid, &code_vec, &remote_vec, 0);
2319 switch (std.os.errno(rc)) {
2320 .SUCCESS => assert(rc == code.len),
2321 else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}),
2322 }
2323 }
2369 try self.base.file.?.pwriteAll(code, file_offset);2324 try self.base.file.?.pwriteAll(code, file_offset);
23702325
2371 return local_sym;2326 return local_sym;
...@@ -3033,6 +2988,7 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {...@@ -3033,6 +2988,7 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
3033 }2988 }
3034 const endian = self.base.options.target.cpu.arch.endian();2989 const endian = self.base.options.target.cpu.arch.endian();
3035 const off = shdr.sh_offset + @as(u64, entry_size) * index;2990 const off = shdr.sh_offset + @as(u64, entry_size) * index;
2991 const vaddr = phdr.p_vaddr + @as(u64, entry_size) * index;
3036 switch (entry_size) {2992 switch (entry_size) {
3037 2 => {2993 2 => {
3038 var buf: [2]u8 = undefined;2994 var buf: [2]u8 = undefined;
...@@ -3048,6 +3004,22 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {...@@ -3048,6 +3004,22 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
3048 var buf: [8]u8 = undefined;3004 var buf: [8]u8 = undefined;
3049 mem.writeInt(u64, &buf, self.offset_table.items[index], endian);3005 mem.writeInt(u64, &buf, self.offset_table.items[index], endian);
3050 try self.base.file.?.pwriteAll(&buf, off);3006 try self.base.file.?.pwriteAll(&buf, off);
3007
3008 if (self.base.child_pid) |pid| {
3009 var local_vec: [1]std.os.iovec_const = .{.{
3010 .iov_base = &buf,
3011 .iov_len = buf.len,
3012 }};
3013 var remote_vec: [1]std.os.iovec_const = .{.{
3014 .iov_base = @intToPtr([*]u8, vaddr),
3015 .iov_len = buf.len,
3016 }};
3017 const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0);
3018 switch (std.os.errno(rc)) {
3019 .SUCCESS => assert(rc == buf.len),
3020 else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}),
3021 }
3022 }
3051 },3023 },
3052 else => unreachable,3024 else => unreachable,
3053 }3025 }