authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-18 09:51:17+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-19 18:30:34+01:00
log7deea319501cee74a46de704521b25ebccfc8e6f
tree9da471a98b4e8e4c467c47acba1168c8cf097942
parent4317e1b12a084284a3cd3bf660c49cd42a627266
signature Commit is signed but in an unrecognized format.

Elf2: improve entry point handling

* report undefined entry point symbol * report entry point symbol only defined in input DSO * allow entry point symbol to resolve to default symbol version

1 files changed, 45 insertions(+), 14 deletions(-)

src/link/Elf2.zig+45-14
...@@ -246,6 +246,7 @@ changed_symtab_index: std.array_hash_map.Auto(Symbol.Global.Index, void),...@@ -246,6 +246,7 @@ changed_symtab_index: std.array_hash_map.Auto(Symbol.Global.Index, void),
246/// entry in the `.dynamic` section. This allows adding `DT_TEXTREL` to the output `.dynamic`246/// entry in the `.dynamic` section. This allows adding `DT_TEXTREL` to the output `.dynamic`
247/// section in `flush` only when it is actually necessary. See also `nodeAllowsDynamicReloc`.247/// section in `flush` only when it is actually necessary. See also `nodeAllowsDynamicReloc`.
248textrel_count: u32,248textrel_count: u32,
249entry_gsi: Symbol.Global.Index.Optional,
249250
250dwarf: Dwarf,251dwarf: Dwarf,
251dwarf_shared: std.enums.EnumArray(Dwarf.SharedSection, dwarf_relocs.Shared),252dwarf_shared: std.enums.EnumArray(Dwarf.SharedSection, dwarf_relocs.Shared),
...@@ -3073,6 +3074,8 @@ fn addGlobalSymbol(elf: *Elf, opts: Symbol.Global.AddOptions) (error{...@@ -3073,6 +3074,8 @@ fn addGlobalSymbol(elf: *Elf, opts: Symbol.Global.AddOptions) (error{
3073} || Error)!Symbol.Global.Index {3074} || Error)!Symbol.Global.Index {
3074 _ = opts.lib_name; // TODO3075 _ = opts.lib_name; // TODO
30753076
3077 log.debug("addGlobalSymbol({q})", .{opts.name});
3078
3076 const gpa = elf.base.comp.gpa;3079 const gpa = elf.base.comp.gpa;
30773080
3078 try elf.symtab.ensureUnusedCapacity(gpa, 1);3081 try elf.symtab.ensureUnusedCapacity(gpa, 1);
...@@ -5138,6 +5141,7 @@ fn create(...@@ -5138,6 +5141,7 @@ fn create(
5138 .section_by_name = .empty,5141 .section_by_name = .empty,
5139 .changed_symtab_index = .empty,5142 .changed_symtab_index = .empty,
5140 .textrel_count = 0,5143 .textrel_count = 0,
5144 .entry_gsi = undefined,
51415145
5142 .dwarf = .init(&elf.base, switch (comp.config.debug_format) {5146 .dwarf = .init(&elf.base, switch (comp.config.debug_format) {
5143 .strip => .@"32", // for .eh_frame5147 .strip => .@"32", // for .eh_frame
...@@ -6561,6 +6565,31 @@ fn initHeaders(...@@ -6561,6 +6565,31 @@ fn initHeaders(
6561 .code_view => unreachable,6565 .code_view => unreachable,
6562 }6566 }
65636567
6568 // If we have an entry point symbol, create the undefined reference now.
6569 elf.entry_gsi = entry: switch (elf.options.entry) {
6570 .default => switch (comp.config.output_mode) {
6571 .Exe => continue :entry .enabled,
6572 .Lib, .Obj => continue :entry .disabled,
6573 },
6574 .enabled => continue :entry .{ .named = "_start" },
6575 .disabled => .none,
6576 .named => |sym_name| .wrap(elf.addGlobalSymbol(.{
6577 .node = .none,
6578 .name = sym_name,
6579 .value = 0,
6580 .size = 0,
6581 .type = .NOTYPE,
6582 .bind = .strong,
6583 .visibility = .DEFAULT,
6584 .shndx = .UNDEF,
6585 }) catch |err| switch (err) {
6586 error.MultipleDefinitions => unreachable, // shndx is undef
6587 error.MultipleDefaultVersions => unreachable, // not a versioned symbol
6588 error.UndefinedDefaultVersion => unreachable, // not a versioned symbol
6589 else => |e| return e,
6590 }),
6591 };
6592
6564 assert(elf.nodes.len == expected_nodes_len);6593 assert(elf.nodes.len == expected_nodes_len);
6565 assert(elf.shdrs.items.len == shnum - 1); // -1 to exclude SHN_UNDEF6594 assert(elf.shdrs.items.len == shnum - 1); // -1 to exclude SHN_UNDEF
65666595
...@@ -10884,20 +10913,22 @@ fn flushInner(...@@ -10884,20 +10913,22 @@ fn flushInner(
10884 elf.flushDynamic();10913 elf.flushDynamic();
1088510914
10886 const entry_addr: u64 = entry: {10915 const entry_addr: u64 = entry: {
10887 const sym_name: []const u8 = name: switch (elf.options.entry) {10916 const entry_gsi = elf.entry_gsi.unwrap() orelse break :entry 0;
10888 .default => switch (comp.config.output_mode) {10917
10889 .Exe => continue :name .enabled,10918 // Even though we already reported undefined globals earlier, we also need to check here
10890 .Lib, .Obj => continue :name .disabled,10919 // whether the entry point is defined. There are two reasons for this:
10891 },10920 //
10892 .disabled => break :entry 0,10921 // * If we are emitting something other than an executable, we skipped the undefined globals
10893 .enabled => "_start",10922 // check, but there could still be an entry point (e.g. due to `-fentry=foo` on the CLI).
10894 .named => |named| named,10923 //
10895 };10924 // * The entry point might be defined in an input DSO, which would satisfy the undefined
10896 const gsi = elf.globalByName(.{10925 // symbol reference but is insufficient here: the entry point needs a local definition.
10897 .name = sym_name,10926
10898 .version = null,10927 if (!entry_gsi.resolveAlias(elf).defined(elf)) {
10899 }) orelse break :entry 0;10928 return diags.fail("undefined entry point symbol '{s}'", .{entry_gsi.name(elf)});
10900 break :entry Symbol.Id.global(gsi).value(elf);10929 }
10930
10931 break :entry Symbol.Id.global(entry_gsi).value(elf);
10901 };10932 };
10902 switch (elf.ehdrPtr()) {10933 switch (elf.ehdrPtr()) {
10903 inline else => |ehdr| elf.targetStore(&ehdr.entry, @intCast(entry_addr)),10934 inline else => |ehdr| elf.targetStore(&ehdr.entry, @intCast(entry_addr)),