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),
246246/// entry in the `.dynamic` section. This allows adding `DT_TEXTREL` to the output `.dynamic`
247247/// section in `flush` only when it is actually necessary. See also `nodeAllowsDynamicReloc`.
248248textrel_count: u32,
249entry_gsi: Symbol.Global.Index.Optional,
249250
250251dwarf: Dwarf,
251252dwarf_shared: std.enums.EnumArray(Dwarf.SharedSection, dwarf_relocs.Shared),
......@@ -3073,6 +3074,8 @@ fn addGlobalSymbol(elf: *Elf, opts: Symbol.Global.AddOptions) (error{
30733074} || Error)!Symbol.Global.Index {
30743075 _ = opts.lib_name; // TODO
30753076
3077 log.debug("addGlobalSymbol({q})", .{opts.name});
3078
30763079 const gpa = elf.base.comp.gpa;
30773080
30783081 try elf.symtab.ensureUnusedCapacity(gpa, 1);
......@@ -5138,6 +5141,7 @@ fn create(
51385141 .section_by_name = .empty,
51395142 .changed_symtab_index = .empty,
51405143 .textrel_count = 0,
5144 .entry_gsi = undefined,
51415145
51425146 .dwarf = .init(&elf.base, switch (comp.config.debug_format) {
51435147 .strip => .@"32", // for .eh_frame
......@@ -6561,6 +6565,31 @@ fn initHeaders(
65616565 .code_view => unreachable,
65626566 }
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
65646593 assert(elf.nodes.len == expected_nodes_len);
65656594 assert(elf.shdrs.items.len == shnum - 1); // -1 to exclude SHN_UNDEF
65666595
......@@ -10884,20 +10913,22 @@ fn flushInner(
1088410913 elf.flushDynamic();
1088510914
1088610915 const entry_addr: u64 = entry: {
10887 const sym_name: []const u8 = name: switch (elf.options.entry) {
10888 .default => switch (comp.config.output_mode) {
10889 .Exe => continue :name .enabled,
10890 .Lib, .Obj => continue :name .disabled,
10891 },
10892 .disabled => break :entry 0,
10893 .enabled => "_start",
10894 .named => |named| named,
10895 };
10896 const gsi = elf.globalByName(.{
10897 .name = sym_name,
10898 .version = null,
10899 }) orelse break :entry 0;
10900 break :entry Symbol.Id.global(gsi).value(elf);
10916 const entry_gsi = elf.entry_gsi.unwrap() orelse break :entry 0;
10917
10918 // Even though we already reported undefined globals earlier, we also need to check here
10919 // whether the entry point is defined. There are two reasons for this:
10920 //
10921 // * If we are emitting something other than an executable, we skipped the undefined globals
10922 // check, but there could still be an entry point (e.g. due to `-fentry=foo` on the CLI).
10923 //
10924 // * The entry point might be defined in an input DSO, which would satisfy the undefined
10925 // symbol reference but is insufficient here: the entry point needs a local definition.
10926
10927 if (!entry_gsi.resolveAlias(elf).defined(elf)) {
10928 return diags.fail("undefined entry point symbol '{s}'", .{entry_gsi.name(elf)});
10929 }
10930
10931 break :entry Symbol.Id.global(entry_gsi).value(elf);
1090110932 };
1090210933 switch (elf.ehdrPtr()) {
1090310934 inline else => |ehdr| elf.targetStore(&ehdr.entry, @intCast(entry_addr)),