Repository: davidlattimore/wild (rust)

Issue:
Rust tests/run-make/branch-protection-check-IBT fails with missing _start symbol
The test fails with:
```
=== STDERR ===
error: linking with `cc` failed: exit status: 1
  |
  = note:  "cc" "-m64" "<1 object files omitted>" "-Wl,--as-needed" "-L" "/tmp/rustcDdWazH/raw-dylibs" "-Wl,-Bdynamic" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "main" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" "-nostartfiles"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: Error: Failed to activate <prelude>

          Caused by:
              Missing _start symbol

          Stack backtrace:
             0: libwild::layout::PreludeLayoutState::activate
             1: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &mut F>::call_once
             2: rayon::iter::plumbing::Folder::consume_iter
             3: rayon::iter::plumbing::bridge_producer_consumer::helper
             4: rayon_core::join::join_context::{{closure}}
             5: <rayon_core::job::StackJob<L,F,R> as rayon_core::job::Job>::execute
             6: rayon_core::registry::WorkerThread::wait_until_cold
             7: rayon_core::registry::ThreadBuilder::run
             8: std::sys::backtrace::__rust_begin_short_backtrace
             9: core::ops::function::FnOnce::call_once{{vtable.shim}}
            10: std::sys::pal::unix::thread::Thread::new::thread_start
            11: <unknown>
            12: <unknown>
          collect2: error: ld returned 255 exit status
          WARNING: wild: --plugin /usr/lib/gcc/x86_64-pc-linux-gnu/14.2.1/liblto_plugin.so is not yet supported
```

Apparently LD doesn't threat missing `_start` as an error (at least in this case):
```
❯ ./run-with ~/Projects/wild/target/release/wild
WARNING: wild: --plugin /usr/lib/gcc/x86_64-pc-linux-gnu/14.2.1/liblto_plugin.so is not yet supported
Error: Failed to activate <prelude>

Caused by:
    Missing _start symbol

Stack backtrace:
   0: libwild::layout::PreludeLayoutState::activate
   1: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &mut F>::call_once
   2: rayon::iter::plumbing::Folder::consume_iter
   3: rayon::iter::plumbing::bridge_producer_consumer::helper
   4: rayon_core::join::join_context::{{closure}}
   5: <rayon_core::job::StackJob<L,F,R> as rayon_core::job::Job>::execute
   6: rayon_core::registry::WorkerThread::wait_until_cold
   7: rayon_core::registry::ThreadBuilder::run
   8: std::sys::backtrace::__rust_begin_short_backtrace
   9: core::ops::function::FnOnce::call_once{{vtable.shim}}
  10: std::sys::pal::unix::thread::Thread::new::thread_start
  11: <unknown>
  12: <unknown>

❯ ./run-with ld
ld: warning: cannot find entry symbol _start; not setting start address
```

Pull request:
Don't error if _start isn't defined
Instead, issue a warning and possibly set entry point to the start of .text provided it's not empty. This matches what GNU ld does. LLD also just warns in this case, but seems to always leave the entry point as 0.

Fixes #613

Patch:
diff --git a/libwild/src/layout.rs b/libwild/src/layout.rs
index 8db0c48..8756dbc 100644
--- a/libwild/src/layout.rs
+++ b/libwild/src/layout.rs
@@ -1364,22 +1364,40 @@ impl<'data> Layout<'data> {
             // Shared objects don't have an entry point.
             return Ok(0);
         }
-        let symbol_id = self
-            .prelude()
-            .entry_symbol_id
-            .context("Entry point is undefined")?;
+
+        let Some(symbol_id) = self.prelude().entry_symbol_id else {
+            // There's no entry point specified, set it to the start of .text. This is pretty weird,
+            // but it's what GNU ld does.
+            let text_layout = self.section_layouts.get(output_section_id::TEXT);
+            if text_layout.mem_size == 0 {
+                crate::error::warning(
+                    "cannot find entry symbol `_start` and .text is empty, not setting entry point",
+                );
+
+                return Ok(0);
+            }
+
+            crate::error::warning(&format!(
+                "cannot find entry symbol `_start`, defaulting to 0x{}",
+                text_layout.mem_offset
+            ));
+            return Ok(text_layout.mem_offset);
+        };
+
         let resolution = self.local_symbol_resolution(symbol_id).with_context(|| {
             format!(
                 "Entry point symbol was defined, but didn't get loaded. {}",
                 self.symbol_debug(symbol_id)
             )
         })?;
+
         if !resolution.value_flags().contains(ValueFlags::ADDRESS) {
             bail!(
                 "Entry point must be an address. {}",
                 self.symbol_debug(symbol_id)
             );
         }
+
         Ok(resolution.value())
     }
 
@@ -2673,7 +2691,7 @@ impl PreludeLayoutState {
         }
 
         if resources.symbol_db.args.output_kind().is_executable() {
-            self.load_entry_point(resources, queue)?;
+            self.load_entry_point(resources, queue);
         }
 
         if resources.symbol_db.args.needs_dynsym() {
@@ -2699,15 +2717,15 @@ impl PreludeLayoutState {
         Ok(())
     }
 
-    fn load_entry_point(
-        &mut self,
-        resources: &GraphResources,
-        queue: &mut LocalWorkQueue,
-    ) -> Result {
-        let symbol_id = resources
+    fn load_entry_point(&mut self, resources: &GraphResources, queue: &mut LocalWorkQueue) {
+        let Some(symbol_id) = resources
             .symbol_db
             .get_unversioned(&UnversionedSymbolName::prehashed(b"_start"))
-            .context("Missing _start symbol")?;
+        else {
+            // We'll emit a warning when writing the file.
+            return;
+        };
+
         self.entry_symbol_id = Some(symbol_id);
         let file_id = resources.symbol_db.file_id_for_symbol(symbol_id);
         let old_flags = resources.symbol_resolution_flags[symbol_id.as_usize()]
@@ -2715,7 +2733,6 @@ impl PreludeLayoutState {
         if old_flags.is_empty() {
             queue.send_work(resources, file_id, WorkItem::LoadGlobalSymbol(symbol_id));
         }
-        Ok(())
     }
 
     fn pre_finalise_sizes(


Test patch:
diff --git a/wild/tests/integration_tests.rs b/wild/tests/integration_tests.rs
index 226a617..69a916f 100644
--- a/wild/tests/integration_tests.rs
+++ b/wild/tests/integration_tests.rs
@@ -2142,6 +2142,7 @@ fn integration_test(
         "tls.c",
         "tlsdesc.c",
         "tls-variant.c",
+        "no_start.c",
         "old_init.c",
         "custom_section.c",
         "stack_alignment.s",
diff --git a/wild/tests/sources/no_start.c b/wild/tests/sources/no_start.c
new file mode 100644
index 0000000..94ea390
--- /dev/null
+++ b/wild/tests/sources/no_start.c
@@ -0,0 +1,19 @@
+//#AbstractConfig:default
+//#Object:exit.c
+
+//#Config:no-gc:default
+//#LinkArgs:-z now --no-gc-sections
+
+// With --gc-sections enabled, all code gets eliminated and there's nothing to run. If we try to
+// execute this binary, it will segfault, so we don't.
+//#Config:gc:default
+//#LinkArgs:-z now --gc-sections
+//#RunEnabled:false
+
+#include "exit.h"
+
+// Provided this is the first function, it'll get used as the entry point - at least by GNU ld. LLD
+// doesn't set an entry point in this case.
+void this_is_the_entry_point(void) {
+    exit_syscall(42);
+}