diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c
index 3d23a65ad8f..d465028add4 100644
--- a/gdb/dwarf2/cooked-index.c
+++ b/gdb/dwarf2/cooked-index.c
@@ -48,6 +48,16 @@ to_string (cooked_index_flag flags)
 
 /* See cooked-index.h.  */
 
+bool
+language_requires_canonicalization (enum language lang)
+{
+  return (lang == language_ada
+	  || lang == language_c
+	  || lang == language_cplus);
+}
+
+/* See cooked-index.h.  */
+
 int
 cooked_index_entry::compare (const char *stra, const char *strb,
 			     comparison_mode mode)
@@ -162,10 +172,12 @@ test_compare ()
 /* See cooked-index.h.  */
 
 const char *
-cooked_index_entry::full_name (struct obstack *storage) const
+cooked_index_entry::full_name (struct obstack *storage, bool for_main) const
 {
+  const char *local_name = for_main ? name : canonical;
+
   if ((flags & IS_LINKAGE) != 0 || parent_entry == nullptr)
-    return canonical;
+    return local_name;
 
   const char *sep = nullptr;
   switch (per_cu->lang ())
@@ -182,11 +194,11 @@ cooked_index_entry::full_name (struct obstack *storage) const
       break;
 
     default:
-      return canonical;
+      return local_name;
     }
 
-  parent_entry->write_scope (storage, sep);
-  obstack_grow0 (storage, canonical, strlen (canonical));
+  parent_entry->write_scope (storage, sep, for_main);
+  obstack_grow0 (storage, local_name, strlen (local_name));
   return (const char *) obstack_finish (storage);
 }
 
@@ -194,11 +206,13 @@ cooked_index_entry::full_name (struct obstack *storage) const
 
 void
 cooked_index_entry::write_scope (struct obstack *storage,
-				 const char *sep) const
+				 const char *sep,
+				 bool for_main) const
 {
   if (parent_entry != nullptr)
-    parent_entry->write_scope (storage, sep);
-  obstack_grow (storage, canonical, strlen (canonical));
+    parent_entry->write_scope (storage, sep, for_main);
+  const char *local_name = for_main ? name : canonical;
+  obstack_grow (storage, local_name, strlen (local_name));
   obstack_grow (storage, sep, strlen (sep));
 }
 
@@ -218,10 +232,6 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag,
      implicit "main" discovery.  */
   if ((flags & IS_MAIN) != 0)
     m_main = result;
-  else if (per_cu->lang () != language_ada
-	   && m_main == nullptr
-	   && strcmp (name, "main") == 0)
-    m_main = result;
 
   return result;
 }
@@ -323,6 +333,8 @@ cooked_index_shard::do_finalize ()
 
   for (cooked_index_entry *entry : m_entries)
     {
+      /* Note that this code must be kept in sync with
+	 language_requires_canonicalization.  */
       gdb_assert (entry->canonical == nullptr);
       if ((entry->flags & IS_LINKAGE) != 0)
 	entry->canonical = entry->name;
@@ -474,11 +486,15 @@ cooked_index::get_main () const
   for (const auto &index : m_vector)
     {
       const cooked_index_entry *entry = index->get_main ();
-      if (result == nullptr
-	  || ((result->flags & IS_MAIN) == 0
-	      && entry != nullptr
-	      && (entry->flags & IS_MAIN) != 0))
-	result = entry;
+      /* Choose the first "main" we see.  The choice among several is
+	 arbitrary.  See the comment by the sole caller to understand
+	 the rationale for filtering by language.  */
+      if (entry != nullptr
+	  && !language_requires_canonicalization (entry->per_cu->lang ()))
+	{
+	  result = entry;
+	  break;
+	}
     }
 
   return result;
diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h
index 7fa78d5e87e..e90544f7906 100644
--- a/gdb/dwarf2/cooked-index.h
+++ b/gdb/dwarf2/cooked-index.h
@@ -58,6 +58,13 @@ DEF_ENUM_FLAGS_TYPE (enum cooked_index_flag_enum, cooked_index_flag);
 
 std::string to_string (cooked_index_flag flags);
 
+/* Return true if LANG requires canonicalization.  This is used
+   primarily to work around an issue computing the name of "main".
+   This function must be kept in sync with
+   cooked_index_shard::do_finalize.  */
+
+extern bool language_requires_canonicalization (enum language lang);
+
 /* A cooked_index_entry represents a single item in the index.  Note
    that two entries can be created for the same DIE -- one using the
    name, and another one using the linkage name, if any.
@@ -144,8 +151,12 @@ struct cooked_index_entry : public allocate_on_obstack
 
   /* Construct the fully-qualified name of this entry and return a
      pointer to it.  If allocation is needed, it will be done on
-     STORAGE.  */
-  const char *full_name (struct obstack *storage) const;
+     STORAGE.  FOR_MAIN is true if we are computing the name of the
+     "main" entry -- one marked DW_AT_main_subprogram.  This matters
+     for avoiding name canonicalization (see comments about this
+     elsewhere) and also a related race (if "main" computation is done
+     during finalization).  */
+  const char *full_name (struct obstack *storage, bool for_main = false) const;
 
   /* Comparison modes for the 'compare' function.  See the function
      for a description.  */
@@ -220,7 +231,8 @@ struct cooked_index_entry : public allocate_on_obstack
 
 private:
 
-  void write_scope (struct obstack *storage, const char *sep) const;
+  void write_scope (struct obstack *storage, const char *sep,
+		    bool for_name) const;
 };
 
 class cooked_index;
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 470ff125c5b..382603c2936 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -7167,8 +7167,17 @@ dwarf2_build_psymtabs_hard (dwarf2_per_objfile *per_objfile)
 
   const cooked_index_entry *main_entry = vec->get_main ();
   if (main_entry != nullptr)
-    set_objfile_main_name (objfile, main_entry->name,
-			   main_entry->per_cu->lang ());
+    {
+      /* We only do this for names not requiring canonicalization.  At
+	 this point in the process, names have not been canonicalized.
+	 However, currently, languages that require this step also do
+	 not use DW_AT_main_subprogram.  An assert is appropriate here
+	 because this filtering is done in get_main.  */
+      enum language lang = main_entry->per_cu->lang ();
+      gdb_assert (!language_requires_canonicalization (lang));
+      const char *full_name = main_entry->full_name (&per_bfd->obstack, true);
+      set_objfile_main_name (objfile, full_name, lang);
+    }
 
   dwarf_read_debug_printf ("Done building psymtabs of %s",
 			   objfile_name (objfile));
diff --git a/gdb/testsuite/gdb.dlang/dlang-start.exp b/gdb/testsuite/gdb.dlang/dlang-start.exp
new file mode 100644
index 00000000000..fd4688b0635
--- /dev/null
+++ b/gdb/testsuite/gdb.dlang/dlang-start.exp
@@ -0,0 +1,38 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test "start" for D.
+
+load_lib d-support.exp
+require allow_d_tests
+
+# This testcase verifies the behavior of the `start' command, which
+# does not work when we use the gdb stub...
+require !use_gdb_stub
+
+standard_testfile simple.d
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug d}]} {
+    return -1
+}
+
+# Verify that "start" lands inside the right procedure.
+if {[gdb_start_cmd] < 0} {
+    unsupported "start failed"
+    return -1
+}
+
+gdb_test "" \
+    "main \\(\\) at .*simple.d.*" \
+    "start"
diff --git a/gdb/testsuite/gdb.dlang/simple.d b/gdb/testsuite/gdb.dlang/simple.d
new file mode 100644
index 00000000000..b00884b1b9f
--- /dev/null
+++ b/gdb/testsuite/gdb.dlang/simple.d
@@ -0,0 +1,17 @@
+// Copyright (C) 2023 Free Software Foundation, Inc.
+
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+void main() {
+}
diff --git a/gdb/testsuite/gdb.dwarf2/main-subprogram.exp b/gdb/testsuite/gdb.dwarf2/main-subprogram.exp
index 23f02df8513..149b7a801be 100644
--- a/gdb/testsuite/gdb.dwarf2/main-subprogram.exp
+++ b/gdb/testsuite/gdb.dwarf2/main-subprogram.exp
@@ -27,8 +27,9 @@ Dwarf::assemble $asm_file {
     global srcfile
 
     cu {} {
+	# Note we don't want C here as that requires canonicalization.
 	DW_TAG_compile_unit {
-                {DW_AT_language @DW_LANG_C}
+		{DW_AT_language @DW_LANG_PLI}
                 {DW_AT_name     $srcfile}
                 {DW_AT_comp_dir /tmp}
         } {
diff --git a/gdb/testsuite/gdb.rust/rust-start.exp b/gdb/testsuite/gdb.rust/rust-start.exp
new file mode 100644
index 00000000000..96ba2ae3ac8
--- /dev/null
+++ b/gdb/testsuite/gdb.rust/rust-start.exp
@@ -0,0 +1,38 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test "start" for Rust.
+
+load_lib rust-support.exp
+require allow_rust_tests
+
+# This testcase verifies the behavior of the `start' command, which
+# does not work when we use the gdb stub...
+require !use_gdb_stub
+
+standard_testfile simple.rs
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug rust}]} {
+    return -1
+}
+
+# Verify that "start" lands inside the right procedure.
+if {[gdb_start_cmd] < 0} {
+    unsupported "start failed"
+    return -1
+}
+
+gdb_test "" \
+    "simple::main \\(\\) at .*simple.rs.*" \
+    "start"
