]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdbserver: prevent assertion caused by passing empty program name
authorAndrew Burgess <aburgess@redhat.com>
Mon, 15 Sep 2025 13:20:52 +0000 (14:20 +0100)
committerAndrew Burgess <aburgess@redhat.com>
Fri, 3 Oct 2025 12:20:50 +0000 (13:20 +0100)
While testing another patch I'm working on I discovered that passing
an empty program name to gdbserver would trigger an assertion, like
this:

  $ gdbserver --multi :54321 ""
  ../../gdb/gdbserver/../gdb/nat/fork-inferior.c:240: A problem internal to GDBserver has been detected.
  fork_inferior: Assertion `exec_file != nullptr' failed.

User input, no matter how weird, shouldn't be triggering an assertion,
so lets fix that.

In extended mode, it is valid to start gdbserver without an executable
name, like this:

  $ gdbserver --multi :54321

Here gdbserver doesn't start an inferior, and it is up to GDB to
connect, and tell gdbserver what to run, and to then start it running.

I did wonder if the empty string case should handled like the no
executable name case, but then you get into the situation where the
user can specify command line arguments without an inferior, like:

  $ gdbserver --multi :54321 "" a b c

And while there's nothing really wrong with this, and I'm sure someone
could come up with a use case for it.  I'd like to propose that for
now at least, we take the simple approach of not allowing an empty
executable name, instead we should give an error, like this:

  $ gdbserver --multi :54321 ""
  No program to debug
  Exiting

We can always relax this requirement in the future, and allow the
empty executable with or without inferior arguments, if we decide
there's a compelling reason for it.  It would be simple enough to add
this in the future, but once we add support for it, it's much harder
to remove the feature in the future, so lets start simple.

The non-extended remote case works much the same.  It too triggers the
assertion currently, and after this patch exits with the same error.

Of course, the non-extended remote case never supported not having an
inferior, if you did:

  $ gdbserver :54321

You'd be shown the usage text and gdbserver would exit.

Approved-By: Tom Tromey <tom@tromey.com>
gdb/testsuite/gdb.server/server-empty-exec-name.exp [new file with mode: 0644]
gdbserver/server.cc

diff --git a/gdb/testsuite/gdb.server/server-empty-exec-name.exp b/gdb/testsuite/gdb.server/server-empty-exec-name.exp
new file mode 100644 (file)
index 0000000..a9cb9ab
--- /dev/null
@@ -0,0 +1,93 @@
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 2025 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 what happens if we try to start gdbserver with an empty string
+# for the executable name.  In theory we could choose to accept this
+# for the extended-remote case, indeed, in the extended-remote case
+# it's OK to start gdbserver with no program name or arguments.
+#
+# But actually specifying an empty executable name seems different,
+# and wrong, so for now at least, we don't allow this.  If that
+# position changes in the future, then this test will need updating.
+
+# Skip test if target does not support argument passing.
+require {!target_info exists noargs}
+
+load_lib gdbserver-support.exp
+
+standard_testfile
+
+require allow_gdbserver_tests
+
+set gdbserver [find_gdbserver]
+if { $gdbserver == "" } {
+    unsupported "could not find gdbserver"
+    return
+}
+
+clean_restart
+
+# Try starting gdbserver in extended, and non-extended mode.
+foreach_with_prefix proto { extended-remote remote } {
+
+    if { $proto eq "extended-remote" } {
+       set server_flag "--multi"
+    } else {
+       set server_flag ""
+    }
+
+    # Try starting gdbserver with and without arguments.
+    foreach_with_prefix args { " a b c" "" } {
+       # Start gdbserver on its own.  This makes it easier to check
+       # the error message that gdbserver emits.  The program name is
+       # the empty string.  gdbserver should exit.
+       set gdbserver_cmd \
+           "$gdbserver $server_flag --once - \"\"${args}"
+
+       set spawn_id [remote_spawn target $gdbserver_cmd]
+       set testname "start gdbserver"
+       expect {
+           -i $spawn_id
+
+           -re "No program to debug\r\nExiting" { pass $testname }
+
+           eof { fail $testname }
+           timeout { fail $testname }
+       }
+
+       catch {
+           close -i $spawn_id
+           wait -nowait -i $spawn_id
+       }
+
+       # Try connecting to gdbserver from GDB.  The connection should
+       # immediately close as gdbserver exits.  The inferior should
+       # be left with no connection.
+       gdb_test "disconnect" ".*" \
+           "disconnect before remote connection"
+
+       gdb_test "target $proto | ${::gdbserver} $server_flag --once - \"\"${args}" \
+           [multi_line \
+                "Remote debugging using \\| \[^\r\n\]+" \
+                "Remote communication error\\.  Target disconnected: .*"] \
+           "start gdbserver over stdin"
+
+       gdb_test "info inferiors" \
+           "\\*\\s+1\\s+<null>\\s*" \
+           "check inferior 1 has no connection"
+    }
+}
index 5b40600f457c6427b2e3b541f9d45e870069693d..cb1a63e2c8faa3a71be00bb782ac3470317deb20 100644 (file)
@@ -4545,6 +4545,9 @@ captured_main (int argc, char *argv[])
     {
       program_path.set (next_arg[0]);
 
+      if (program_path.get () == nullptr)
+       error (_("No program to debug"));
+
       int n = argc - (next_arg - argv);
       program_args
        = construct_inferior_arguments ({&next_arg[1], &next_arg[n]},