| ... | @@ -3558,48 +3558,55 @@ fn initHeaders( | ... | @@ -3558,48 +3558,55 @@ fn initHeaders( |
| 3558 | .EXEC, .DYN => {}, | 3558 | .EXEC, .DYN => {}, |
| 3559 | } | 3559 | } |
| 3560 | var phnum: u32 = 0; | 3560 | var phnum: u32 = 0; |
| 3561 | break :ph .{ .{ | 3561 | break :ph .{ |
| 3562 | .phdr = phndx: { | 3562 | .{ |
| 3563 | defer phnum += 1; | 3563 | .phdr = phndx: { |
| 3564 | break :phndx phnum; | 3564 | defer phnum += 1; |
| 3565 | }, | 3565 | break :phndx phnum; |
| 3566 | .interp = if (maybe_interp) |_| phndx: { | 3566 | }, |
| 3567 | defer phnum += 1; | 3567 | .interp = if (maybe_interp) |_| phndx: { |
| 3568 | break :phndx phnum; | 3568 | defer phnum += 1; |
| 3569 | } else undefined, | 3569 | break :phndx phnum; |
| 3570 | .rodata = phndx: { | 3570 | } else undefined, |
| 3571 | defer phnum += 1; | 3571 | .rodata = phndx: { |
| 3572 | break :phndx phnum; | 3572 | defer phnum += 1; |
| 3573 | }, | 3573 | break :phndx phnum; |
| 3574 | .text = phndx: { | 3574 | }, |
| 3575 | defer phnum += 1; | 3575 | .text = phndx: { |
| 3576 | break :phndx phnum; | 3576 | defer phnum += 1; |
| 3577 | }, | 3577 | break :phndx phnum; |
| 3578 | .data = phndx: { | 3578 | }, |
| 3579 | defer phnum += 1; | 3579 | .plt = if (plt.got_plt == null) phndx: { |
| 3580 | break :phndx phnum; | 3580 | defer phnum += 1; |
| 3581 | }, | 3581 | break :phndx phnum; |
| 3582 | .plt = if (plt.got_plt == null) phndx: { | 3582 | } else undefined, |
| 3583 | defer phnum += 1; | 3583 | // `data` must be assigned after all other loadable segments so that it has the greatest |
| 3584 | break :phndx phnum; | 3584 | // phndx of any loadable segment. This is so that `targetSegmentLoadAddressRestrictions` |
| 3585 | } else undefined, | 3585 | // can be obeyed (specifically, the `.data_last` restriction, needed on SPARC). |
| 3586 | .tls = if (comp.config.any_non_single_threaded) phndx: { | 3586 | .data = phndx: { |
| 3587 | defer phnum += 1; | 3587 | defer phnum += 1; |
| 3588 | break :phndx phnum; | 3588 | break :phndx phnum; |
| 3589 | } else undefined, | 3589 | }, |
| 3590 | .dynamic = if (have_dynamic_section) phndx: { | 3590 | .tls = if (comp.config.any_non_single_threaded) phndx: { |
| 3591 | defer phnum += 1; | 3591 | defer phnum += 1; |
| 3592 | break :phndx phnum; | 3592 | break :phndx phnum; |
| 3593 | } else undefined, | 3593 | } else undefined, |
| 3594 | .relro = phndx: { | 3594 | .dynamic = if (have_dynamic_section) phndx: { |
| 3595 | defer phnum += 1; | 3595 | defer phnum += 1; |
| 3596 | break :phndx phnum; | 3596 | break :phndx phnum; |
| 3597 | }, | 3597 | } else undefined, |
| 3598 | .gnu_stack = phndx: { | 3598 | .relro = phndx: { |
| 3599 | defer phnum += 1; | 3599 | defer phnum += 1; |
| 3600 | break :phndx phnum; | 3600 | break :phndx phnum; |
| | 3601 | }, |
| | 3602 | .gnu_stack = phndx: { |
| | 3603 | defer phnum += 1; |
| | 3604 | break :phndx phnum; |
| | 3605 | }, |
| 3601 | }, | 3606 | }, |
| 3602 | }, phnum }; | 3607 | // (I don't actually want the trailing comma below, but a `zig fmt` bug forces it.) |
| | 3608 | phnum, |
| | 3609 | }; |
| 3603 | }; | 3610 | }; |
| 3604 | | 3611 | |
| 3605 | const expected_nodes_len = @as(usize, if (is_archive) 3 else 0) + // .archive, .archive_header, .archive_elf_footer | 3612 | const expected_nodes_len = @as(usize, if (is_archive) 3 else 0) + // .archive, .archive_header, .archive_elf_footer |
| ... | @@ -4539,6 +4546,22 @@ fn initHeaders( | ... | @@ -4539,6 +4546,22 @@ fn initHeaders( |
| 4539 | break :str try elf.string(.dynstr, slice); | 4546 | break :str try elf.string(.dynstr, slice); |
| 4540 | }, | 4547 | }, |
| 4541 | }; | 4548 | }; |
| | 4549 | |
| | 4550 | if (@"type" != .REL) switch (elf.targetSegmentLoadAddressRestrictions()) { |
| | 4551 | .none => {}, |
| | 4552 | .data_last => switch (elf.phdrSlice()) { |
| | 4553 | inline else => |phdr| { |
| | 4554 | // Ensure that the segment after `.data` (if any) is not a loadable segment. |
| | 4555 | const next_phndx = phndx.data + 1; |
| | 4556 | if (next_phndx < phdr.len) { |
| | 4557 | switch (elf.targetLoad(&phdr[next_phndx].type)) { |
| | 4558 | .NULL, .LOAD => unreachable, // data segment should be the last loadable segment |
| | 4559 | else => {}, |
| | 4560 | } |
| | 4561 | } |
| | 4562 | }, |
| | 4563 | }, |
| | 4564 | }; |
| 4542 | } | 4565 | } |
| 4543 | | 4566 | |
| 4544 | pub fn startProgress(elf: *Elf, prog_node: std.Progress.Node) void { | 4567 | pub fn startProgress(elf: *Elf, prog_node: std.Progress.Node) void { |
| ... | @@ -4888,7 +4911,31 @@ fn targetDynsymHashInfo(elf: *const Elf) DynsymHashInfo { | ... | @@ -4888,7 +4911,31 @@ fn targetDynsymHashInfo(elf: *const Elf) DynsymHashInfo { |
| 4888 | // TODO: Alpha and S390x will need to use either `."@4"` or `.@"8"` depending on `elf.identClass()`. | 4911 | // TODO: Alpha and S390x will need to use either `."@4"` or `.@"8"` depending on `elf.identClass()`. |
| 4889 | }; | 4912 | }; |
| 4890 | } | 4913 | } |
| 4891 | pub fn targetLoad(elf: *const Elf, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child { | 4914 | /// Specifies any restrictions the current target has regarding how segments are ordered in the |
| | 4915 | /// virtual address space. Most targets do not have any such restrictions. |
| | 4916 | fn targetSegmentLoadAddressRestrictions(elf: *const Elf) enum { |
| | 4917 | none, |
| | 4918 | /// The "mutable data" segment must be the last loadable segment in the virtual address space. |
| | 4919 | data_last, |
| | 4920 | } { |
| | 4921 | return switch (elf.ehdrMachine()) { |
| | 4922 | .AARCH64, |
| | 4923 | .PPC64, |
| | 4924 | .RISCV, |
| | 4925 | .X86_64, |
| | 4926 | .LOONGARCH, |
| | 4927 | => .none, |
| | 4928 | |
| | 4929 | // SPARC uses `R_SPARC_PC{10,22}` relocations to construct pointers to the GOT, but these |
| | 4930 | // relocations write an *unsigned* PC-relative offset. This cannot even be worked around by |
| | 4931 | // using a larger code model, because the crt `_start` assembly always uses these specific |
| | 4932 | // relocations. Therefore, to avoid relocation errors, all code must appear before the GOT |
| | 4933 | // in the virtual address space. The easiest way for us to do that is to ensure that the |
| | 4934 | // "mutable data" segment, containing the GOT, is the last segment in the address space. |
| | 4935 | .SPARCV9 => .data_last, |
| | 4936 | }; |
| | 4937 | } |
| | 4938 | fn targetLoad(elf: *const Elf, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child { |
| 4892 | const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer; | 4939 | const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer; |
| 4893 | const Child = pointer_ty.child; | 4940 | const Child = pointer_ty.child; |
| 4894 | const alignment = pointer_ty.attrs.@"align" orelse @alignOf(Child); | 4941 | const alignment = pointer_ty.attrs.@"align" orelse @alignOf(Child); |
| ... | @@ -8114,6 +8161,18 @@ fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Erro | ... | @@ -8114,6 +8161,18 @@ fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Erro |
| 8114 | const page_align = elf.targetPageAlign(); | 8161 | const page_align = elf.targetPageAlign(); |
| 8115 | const node_align = segment_ni.alignment(&elf.mf); | 8162 | const node_align = segment_ni.alignment(&elf.mf); |
| 8116 | const ph_align = page_align.max(node_align); | 8163 | const ph_align = page_align.max(node_align); |
| | 8164 | |
| | 8165 | // If we determine that the segment's virtual address needs to move, then it's a good idea to |
| | 8166 | // make it less likely that it needs to move *again* in the future, because it is expensive to |
| | 8167 | // change a segment's load address (a lot of re-flushing is necessary). To do that, we reserve |
| | 8168 | // more virtual address space than we need (multiplying the actual size by this value). That |
| | 8169 | // way, there will usually be padding between segments which they can grow into. |
| | 8170 | // |
| | 8171 | // TODO: we might want to decrease this multiplier, or even omit it entirely, in cases where |
| | 8172 | // virtual address space is constrained. For instance, 32-bit targets, or targets where short |
| | 8173 | // PC-relative relocations between segments are common. |
| | 8174 | const reserve_size_multiplier = 4; |
| | 8175 | |
| 8117 | switch (elf.phdrSlice()) { | 8176 | switch (elf.phdrSlice()) { |
| 8118 | inline else => |phdr| { | 8177 | inline else => |phdr| { |
| 8119 | const offset = elf.targetLoad(&phdr[orig_phndx].offset); | 8178 | const offset = elf.targetLoad(&phdr[orig_phndx].offset); |
| ... | @@ -8172,15 +8231,46 @@ fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Erro | ... | @@ -8172,15 +8231,46 @@ fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Erro |
| 8172 | // backwards to the start of the page. | 8231 | // backwards to the start of the page. |
| 8173 | const next_page_vaddr = std.mem.alignBackward(u64, next_vaddr, page_align.toByteUnits()); | 8232 | const next_page_vaddr = std.mem.alignBackward(u64, next_vaddr, page_align.toByteUnits()); |
| 8174 | | 8233 | |
| 8175 | // If we're at the same vaddr we started at, then all we're worried about is the | 8234 | // Check if the segment fits here. We apply `reserve_size_multiplier`, but only if |
| 8176 | // segment fitting here. However, if we've already changed our virtual address, then | 8235 | // the segment is already known to be moving---making it easier to grow in-place is |
| 8177 | // we might as well try to reserve a bit *more* virtual address space while we're at | 8236 | // the whole point of the multiplier! |
| 8178 | // it, because changing virtual address is quite disruptive (we need to re-flush a | 8237 | { |
| 8179 | // lot of stuff!) and giving ourselves more space will make it less likely to happen | 8238 | const target_size = if (vaddr == orig_vaddr) size else size * reserve_size_multiplier; |
| 8180 | // again. | 8239 | if (vaddr + target_size <= next_page_vaddr) { |
| 8181 | const target_size = if (vaddr == orig_vaddr) size else size * 4; | 8240 | break; // hooray, we fit here! |
| 8182 | if (vaddr + target_size <= next_page_vaddr) { | 8241 | } |
| 8183 | break; // hooray, we fit here! | 8242 | } |
| | 8243 | |
| | 8244 | const next_ni = elf.phdrs.items[next_phndx].unwrap().?; |
| | 8245 | |
| | 8246 | // This segment don't fit here, but before deciding how to proceed, we need to |
| | 8247 | // consider any target-specific restrictions we are subject to. |
| | 8248 | switch (elf.targetSegmentLoadAddressRestrictions()) { |
| | 8249 | .none => {}, |
| | 8250 | .data_last => if (next_ni == elf.ni.data) { |
| | 8251 | // We can't leapfrog over the data segment. Instead, that segment just needs |
| | 8252 | // to be shifted forwards to make space for us, and we'll then `break` with |
| | 8253 | // our current vaddr. |
| | 8254 | |
| | 8255 | if (next_phndx + 1 < phdr.len) switch (elf.targetLoad(&phdr[next_phndx + 1].type)) { |
| | 8256 | .NULL, .LOAD => unreachable, // data segment should be the last loadable segment |
| | 8257 | else => {}, |
| | 8258 | }; |
| | 8259 | |
| | 8260 | const free_vaddr = vaddr + size * reserve_size_multiplier; |
| | 8261 | |
| | 8262 | const next_align = page_align.max(next_ni.alignment(&elf.mf)); |
| | 8263 | const next_offset = elf.targetLoad(&next_ph.offset); |
| | 8264 | const next_new_vaddr = next_align.forward(free_vaddr) + next_offset % next_align.toByteUnits(); |
| | 8265 | |
| | 8266 | // This logic for updating the data segment's vaddr is identical to how we |
| | 8267 | // will update the vaddr of `phndx` when we break from the loop. |
| | 8268 | elf.targetStore(&next_ph.vaddr, @intCast(next_new_vaddr)); |
| | 8269 | elf.targetStore(&next_ph.paddr, @intCast(next_new_vaddr)); |
| | 8270 | try next_ni.childrenMoved(elf.base.comp.gpa, &elf.mf); |
| | 8271 | |
| | 8272 | break; |
| | 8273 | }, |
| 8184 | } | 8274 | } |
| 8185 | | 8275 | |
| 8186 | // We don't fit here, so shift ourselves forward (i.e. swap with `next_phndx`). But | 8276 | // We don't fit here, so shift ourselves forward (i.e. swap with `next_phndx`). But |
| ... | @@ -8192,8 +8282,7 @@ fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Erro | ... | @@ -8192,8 +8282,7 @@ fn allocateSegmentLoadAddress(elf: *Elf, orig_phndx: u32) std.mem.Allocator.Erro |
| 8192 | | 8282 | |
| 8193 | // Now just swap the phdrs and update our `phndx`. | 8283 | // Now just swap the phdrs and update our `phndx`. |
| 8194 | std.mem.swap(@TypeOf(next_ph.*), &phdr[phndx], next_ph); | 8284 | std.mem.swap(@TypeOf(next_ph.*), &phdr[phndx], next_ph); |
| 8195 | const next_ni = elf.phdrs.items[next_phndx]; | 8285 | elf.phdrs.items[phndx] = .wrap(next_ni); |
| 8196 | elf.phdrs.items[phndx] = next_ni; | | |
| 8197 | elf.nodes.items(.data)[@backingInt(next_ni)] = .{ .segment = phndx }; | 8286 | elf.nodes.items(.data)[@backingInt(next_ni)] = .{ .segment = phndx }; |
| 8198 | elf.phdrs.items[next_phndx] = .wrap(segment_ni); | 8287 | elf.phdrs.items[next_phndx] = .wrap(segment_ni); |
| 8199 | elf.nodes.items(.data)[@backingInt(segment_ni)] = .{ .segment = @intCast(next_phndx) }; | 8288 | elf.nodes.items(.data)[@backingInt(segment_ni)] = .{ .segment = @intCast(next_phndx) }; |