gdb: continue start command if 'main' can be resolved

Message ID 20241204131644.458913-1-stephan.rohr@intel.com
State New
Headers
Series gdb: continue start command if 'main' can be resolved |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Test passed

Commit Message

Rohr, Stephan Dec. 4, 2024, 1:16 p.m. UTC
  From: "Rohr, Stephan" <stephan.rohr@intel.com>

GDB aborts the 'start' command if the minimal symbols cannot be
resolved.  On Windows, GDB reads the minimal symbols from the COFF
header of the PE file.  The symbol table is deprecated and the
number of symbols in the COFF header may be zero [1].  For example,
the LLVM clang compiler for Windows MSVC can be instructed to generate
DWARF:

  clang++ -g -O0 -gdwarf -fuse-ld=lld test.cpp -o test_clang

The corresponding COFF file header shows:

  FILE HEADER VALUES
        8664 machine (x64)
           E number of sections
    66E889EC time date stamp Mon Sep 16 21:41:32 2024
       FB400 file pointer to symbol table
           0 number of symbols
          F0 size of optional header
          22 characteristics

GDB is not able to read the minimal symbols; the `start' command fails
with an error:

  (gdb) start
  No symbol table loaded.  Use the "file" command.

Manually inserting a breakpoint in main works fine:

  (gdb) tbreak main
  Temporary breakpoint 1 at 0x14000100c: file test.cpp, line 6.
  (gdb) run
  Starting program: C:\test-clang

  Temporary breakpoint 1, main () at test.cpp:6
  6         std::cout << "Hello World.\n";

Fix this by testing if `main' can be resolved instead of checking the
minimal symbol table:

  (gdb) start
  Temporary breakpoint 1 at 0x14000100c: file test.cpp, line 6.
  Starting program: C:\test-clang

  Temporary breakpoint 1, main () at test.cpp:6
  6         std::cout << "Hello World.\n";
  (gdb)

[1]: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
---
 gdb/infcmd.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)
  

Comments

Guinevere Larsen Dec. 4, 2024, 1:43 p.m. UTC | #1
On 12/4/24 10:16 AM, Stephan Rohr wrote:
> From: "Rohr, Stephan" <stephan.rohr@intel.com>
>
> GDB aborts the 'start' command if the minimal symbols cannot be
> resolved.  On Windows, GDB reads the minimal symbols from the COFF
> header of the PE file.  The symbol table is deprecated and the
> number of symbols in the COFF header may be zero [1].  For example,
> the LLVM clang compiler for Windows MSVC can be instructed to generate
> DWARF:
>
>    clang++ -g -O0 -gdwarf -fuse-ld=lld test.cpp -o test_clang
>
> The corresponding COFF file header shows:
>
>    FILE HEADER VALUES
>          8664 machine (x64)
>             E number of sections
>      66E889EC time date stamp Mon Sep 16 21:41:32 2024
>         FB400 file pointer to symbol table
>             0 number of symbols
>            F0 size of optional header
>            22 characteristics
>
> GDB is not able to read the minimal symbols; the `start' command fails
> with an error:
>
>    (gdb) start
>    No symbol table loaded.  Use the "file" command.
>
> Manually inserting a breakpoint in main works fine:
>
>    (gdb) tbreak main
>    Temporary breakpoint 1 at 0x14000100c: file test.cpp, line 6.
>    (gdb) run
>    Starting program: C:\test-clang
>
>    Temporary breakpoint 1, main () at test.cpp:6
>    6         std::cout << "Hello World.\n";
>
> Fix this by testing if `main' can be resolved instead of checking the
> minimal symbol table:
>
>    (gdb) start
>    Temporary breakpoint 1 at 0x14000100c: file test.cpp, line 6.
>    Starting program: C:\test-clang
>
>    Temporary breakpoint 1, main () at test.cpp:6
>    6         std::cout << "Hello World.\n";
>    (gdb)
>
> [1]: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
> ---
>   gdb/infcmd.c | 9 +++++----
>   1 file changed, 5 insertions(+), 4 deletions(-)
>
> diff --git a/gdb/infcmd.c b/gdb/infcmd.c
> index 5c0e3f51162..39b7e50b582 100644
> --- a/gdb/infcmd.c
> +++ b/gdb/infcmd.c
> @@ -516,10 +516,11 @@ run_command (const char *args, int from_tty)
>   static void
>   start_command (const char *args, int from_tty)
>   {
> -  /* Some languages such as Ada need to search inside the program
> -     minimal symbols for the location where to put the temporary
> -     breakpoint before starting.  */
> -  if (!have_minimal_symbols (current_program_space))
> +  /* Abort the start command if `main` cannot be resolved, e.g., the
> +     minimal / partial symbols are not available.  Some languages such
> +     as Ada need to search inside the program minimal symbols for the
> +     location where to put the temporary breakpoint before starting.  */
> +  if (main_name () == nullptr)
>       error (_("No symbol table loaded.  Use the \"file\" command."));
>   
>     /* Run the program until reaching the main procedure...  */

I agree that minimal symbols shouldn't be required (obviously by your 
example), but I don't think knowing the name of the main function is enough.

If you look at how main_name works, it calls find_main_name if the name 
isn't cached yet, and find_main_name has a default fallback of guessing 
"main" with an unknown language if needed. In other words, even if we 
can't find that data in the inferior, we'll still guess something. I 
bring this up because if there is no minimal symbols and no debug info, 
I think GDB wouldn't be able to set a breakpoint because main can't be 
translated in any way, and so "start" would end up being silently 
converted to "run" and confuse the user. If this assumption is wrong, 
feel free to ignore the rest of the message.

I think the central issue for whether "start" works should be whether we 
can set the temporary breakpoint or not, rather than whether we can 
guess where to set it. I'm not sure if setting the tbreak would be 
possible here in the start_command due to all the preparatory work in 
run_command_1 before setting the bp (especially due to the commend in 
infcmd.c:411), but I think it would be ideal if it is possible. If that 
doesn't work, I would imagine run_command_1 should check if 
tbreak_command succeeds and return early if it fails. The issues with 
this second strategy is that the inferior is stopped and info is cleared 
before we're sure that we can run the full command, which is pretty 
annoying for the user, but I'it might be better than having to set a 
breakpoint twice (in case someone is using a slow connection to a 
gdbserver, for instance).
  
Rohr, Stephan Dec. 6, 2024, 10:08 a.m. UTC | #2
Hi Guinevere,

thanks for your feedback.

I agree on your concerns, 'main_name ()' might not be the best solution for this issue.

I also think that moving the whole breakpoint setup into 'start_command' is not
feasible.  I modified ' run_command_1' to report an error if the (temporary) breakpoint
cannot be inserted, this also fixes the issue.  I had to do some modifications:

  * Make 'break_command_1' externally visible.
  * Use 'break_command_1' to insert the temporary breakpoint instead of 'tbreak_command'.

I wonder if making 'break_command_1' externally visible could be an acceptable solution?

Thanks
Stephan

> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Wednesday, 4 December 2024 14:44
> To: Rohr, Stephan <stephan.rohr@intel.com>; gdb-patches@sourceware.org
> Subject: Re: [PATCH] gdb: continue start command if 'main' can be resolved
> 
> On 12/4/24 10:16 AM, Stephan Rohr wrote:
> > From: "Rohr, Stephan" <stephan.rohr@intel.com>
> >
> > GDB aborts the 'start' command if the minimal symbols cannot be
> > resolved.  On Windows, GDB reads the minimal symbols from the COFF
> > header of the PE file.  The symbol table is deprecated and the
> > number of symbols in the COFF header may be zero [1].  For example,
> > the LLVM clang compiler for Windows MSVC can be instructed to generate
> > DWARF:
> >
> >    clang++ -g -O0 -gdwarf -fuse-ld=lld test.cpp -o test_clang
> >
> > The corresponding COFF file header shows:
> >
> >    FILE HEADER VALUES
> >          8664 machine (x64)
> >             E number of sections
> >      66E889EC time date stamp Mon Sep 16 21:41:32 2024
> >         FB400 file pointer to symbol table
> >             0 number of symbols
> >            F0 size of optional header
> >            22 characteristics
> >
> > GDB is not able to read the minimal symbols; the `start' command fails
> > with an error:
> >
> >    (gdb) start
> >    No symbol table loaded.  Use the "file" command.
> >
> > Manually inserting a breakpoint in main works fine:
> >
> >    (gdb) tbreak main
> >    Temporary breakpoint 1 at 0x14000100c: file test.cpp, line 6.
> >    (gdb) run
> >    Starting program: C:\test-clang
> >
> >    Temporary breakpoint 1, main () at test.cpp:6
> >    6         std::cout << "Hello World.\n";
> >
> > Fix this by testing if `main' can be resolved instead of checking the
> > minimal symbol table:
> >
> >    (gdb) start
> >    Temporary breakpoint 1 at 0x14000100c: file test.cpp, line 6.
> >    Starting program: C:\test-clang
> >
> >    Temporary breakpoint 1, main () at test.cpp:6
> >    6         std::cout << "Hello World.\n";
> >    (gdb)
> >
> > [1]: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
> > ---
> >   gdb/infcmd.c | 9 +++++----
> >   1 file changed, 5 insertions(+), 4 deletions(-)
> >
> > diff --git a/gdb/infcmd.c b/gdb/infcmd.c
> > index 5c0e3f51162..39b7e50b582 100644
> > --- a/gdb/infcmd.c
> > +++ b/gdb/infcmd.c
> > @@ -516,10 +516,11 @@ run_command (const char *args, int from_tty)
> >   static void
> >   start_command (const char *args, int from_tty)
> >   {
> > -  /* Some languages such as Ada need to search inside the program
> > -     minimal symbols for the location where to put the temporary
> > -     breakpoint before starting.  */
> > -  if (!have_minimal_symbols (current_program_space))
> > +  /* Abort the start command if `main` cannot be resolved, e.g., the
> > +     minimal / partial symbols are not available.  Some languages such
> > +     as Ada need to search inside the program minimal symbols for the
> > +     location where to put the temporary breakpoint before starting.  */
> > +  if (main_name () == nullptr)
> >       error (_("No symbol table loaded.  Use the \"file\" command."));
> >
> >     /* Run the program until reaching the main procedure...  */
> 
> I agree that minimal symbols shouldn't be required (obviously by your
> example), but I don't think knowing the name of the main function is enough.
> 
> If you look at how main_name works, it calls find_main_name if the name
> isn't cached yet, and find_main_name has a default fallback of guessing
> "main" with an unknown language if needed. In other words, even if we
> can't find that data in the inferior, we'll still guess something. I
> bring this up because if there is no minimal symbols and no debug info,
> I think GDB wouldn't be able to set a breakpoint because main can't be
> translated in any way, and so "start" would end up being silently
> converted to "run" and confuse the user. If this assumption is wrong,
> feel free to ignore the rest of the message.
> 
> I think the central issue for whether "start" works should be whether we
> can set the temporary breakpoint or not, rather than whether we can
> guess where to set it. I'm not sure if setting the tbreak would be
> possible here in the start_command due to all the preparatory work in
> run_command_1 before setting the bp (especially due to the commend in
> infcmd.c:411), but I think it would be ideal if it is possible. If that
> doesn't work, I would imagine run_command_1 should check if
> tbreak_command succeeds and return early if it fails. The issues with
> this second strategy is that the inferior is stopped and info is cleared
> before we're sure that we can run the full command, which is pretty
> annoying for the user, but I'it might be better than having to set a
> breakpoint twice (in case someone is using a slow connection to a
> gdbserver, for instance).
> 
> --
> Cheers,
> Guinevere Larsen
> She/Her/Hers

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  
Guinevere Larsen Dec. 6, 2024, 11:50 a.m. UTC | #3
On 12/6/24 7:08 AM, Rohr, Stephan wrote:
> Hi Guinevere,
>
> thanks for your feedback.
>
> I agree on your concerns, 'main_name ()' might not be the best solution for this issue.
>
> I also think that moving the whole breakpoint setup into 'start_command' is not
> feasible.  I modified ' run_command_1' to report an error if the (temporary) breakpoint
> cannot be inserted, this also fixes the issue.  I had to do some modifications:
>
>    * Make 'break_command_1' externally visible.
>    * Use 'break_command_1' to insert the temporary breakpoint instead of 'tbreak_command'.
>
> I wonder if making 'break_command_1' externally visible could be an acceptable solution?

I don't quite understand why you needed to use break_command_1 to get 
this information, since I don't see any extra information you'd get from 
that, but maybe I'm just missing something, this is my first time 
looking at breakpoint code... with that said, have you considered making 
the "*break_command" functions return bool instead?

Since the last thing that break_command_1 does is a call to 
create_breakpoint, which already returns true for a successful insertion 
and false otherwise (using ints), you could just change break_command_1 
to return bool, and then each of the *break_commands could return the 
result to break_command_1 without needing to export the original function.

This looks to me like it would allow run_command_1 to detect and report 
the problem of the breakpoint creation without needing to change 
visibilities.
  
Rohr, Stephan Dec. 9, 2024, 8 a.m. UTC | #4
> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Friday, 6 December 2024 12:51
> To: Rohr, Stephan <stephan.rohr@intel.com>; gdb-patches@sourceware.org
> Subject: Re: [PATCH] gdb: continue start command if 'main' can be resolved
> 
> On 12/6/24 7:08 AM, Rohr, Stephan wrote:
> > Hi Guinevere,
> >
> > thanks for your feedback.
> >
> > I agree on your concerns, 'main_name ()' might not be the best solution for
> this issue.
> >
> > I also think that moving the whole breakpoint setup into 'start_command' is
> not
> > feasible.  I modified ' run_command_1' to report an error if the (temporary)
> breakpoint
> > cannot be inserted, this also fixes the issue.  I had to do some modifications:
> >
> >    * Make 'break_command_1' externally visible.
> >    * Use 'break_command_1' to insert the temporary breakpoint instead of
> 'tbreak_command'.
> >
> > I wonder if making 'break_command_1' externally visible could be an
> acceptable solution?
> 
> I don't quite understand why you needed to use break_command_1 to get
> this information, since I don't see any extra information you'd get from
> that, but maybe I'm just missing something, this is my first time
> looking at breakpoint code... with that said, have you considered making
> the "*break_command" functions return bool instead?
> 
> Since the last thing that break_command_1 does is a call to
> create_breakpoint, which already returns true for a successful insertion
> and false otherwise (using ints), you could just change break_command_1
> to return bool, and then each of the *break_commands could return the
> result to break_command_1 without needing to export the original function.
> 
> This looks to me like it would allow run_command_1 to detect and report
> the problem of the breakpoint creation without needing to change
> visibilities.
> 

I tried that direction at first, too.  This would require additional changes in the way
CLI commands are added.  The current implementation only accepts commands
with a void return type:

  /* The "simple" signature of command callbacks, which doesn't include a
       cmd_list_element parameter.  */
  
  typedef void cmd_simple_func_ftype (const char *args, int from_tty);

See 'command.h:388' for details.  I don't think changing this would be a good
idea.   I tried one more approach:

  * Check if the 'main' symbol can be found inside 'start_command', e.g., with
     ' lookup_symbol_search_name ()'.
  * If that fails, abort with the same error message as of today.

This also doesn't leave us with a stopped inferior, i.e., when adding the check to
'tbreak_command ()'. 

Would this be a better solution?

Thanks
Stephan

> --
> Cheers,
> Guinevere Larsen
> She/Her/Hers
> 
> >
> > Thanks
> > Stephan
> >
> >> -----Original Message-----
> >> From: Guinevere Larsen <guinevere@redhat.com>
> >> Sent: Wednesday, 4 December 2024 14:44
> >> To: Rohr, Stephan <stephan.rohr@intel.com>; gdb-
> patches@sourceware.org
> >> Subject: Re: [PATCH] gdb: continue start command if 'main' can be resolved
> >>
> >> On 12/4/24 10:16 AM, Stephan Rohr wrote:
> >>> From: "Rohr, Stephan" <stephan.rohr@intel.com>
> >>>
> >>> GDB aborts the 'start' command if the minimal symbols cannot be
> >>> resolved.  On Windows, GDB reads the minimal symbols from the COFF
> >>> header of the PE file.  The symbol table is deprecated and the
> >>> number of symbols in the COFF header may be zero [1].  For example,
> >>> the LLVM clang compiler for Windows MSVC can be instructed to generate
> >>> DWARF:
> >>>
> >>>     clang++ -g -O0 -gdwarf -fuse-ld=lld test.cpp -o test_clang
> >>>
> >>> The corresponding COFF file header shows:
> >>>
> >>>     FILE HEADER VALUES
> >>>           8664 machine (x64)
> >>>              E number of sections
> >>>       66E889EC time date stamp Mon Sep 16 21:41:32 2024
> >>>          FB400 file pointer to symbol table
> >>>              0 number of symbols
> >>>             F0 size of optional header
> >>>             22 characteristics
> >>>
> >>> GDB is not able to read the minimal symbols; the `start' command fails
> >>> with an error:
> >>>
> >>>     (gdb) start
> >>>     No symbol table loaded.  Use the "file" command.
> >>>
> >>> Manually inserting a breakpoint in main works fine:
> >>>
> >>>     (gdb) tbreak main
> >>>     Temporary breakpoint 1 at 0x14000100c: file test.cpp, line 6.
> >>>     (gdb) run
> >>>     Starting program: C:\test-clang
> >>>
> >>>     Temporary breakpoint 1, main () at test.cpp:6
> >>>     6         std::cout << "Hello World.\n";
> >>>
> >>> Fix this by testing if `main' can be resolved instead of checking the
> >>> minimal symbol table:
> >>>
> >>>     (gdb) start
> >>>     Temporary breakpoint 1 at 0x14000100c: file test.cpp, line 6.
> >>>     Starting program: C:\test-clang
> >>>
> >>>     Temporary breakpoint 1, main () at test.cpp:6
> >>>     6         std::cout << "Hello World.\n";
> >>>     (gdb)
> >>>
> >>> [1]: https://learn.microsoft.com/en-us/windows/win32/debug/pe-
> format
> >>> ---
> >>>    gdb/infcmd.c | 9 +++++----
> >>>    1 file changed, 5 insertions(+), 4 deletions(-)
> >>>
> >>> diff --git a/gdb/infcmd.c b/gdb/infcmd.c
> >>> index 5c0e3f51162..39b7e50b582 100644
> >>> --- a/gdb/infcmd.c
> >>> +++ b/gdb/infcmd.c
> >>> @@ -516,10 +516,11 @@ run_command (const char *args, int from_tty)
> >>>    static void
> >>>    start_command (const char *args, int from_tty)
> >>>    {
> >>> -  /* Some languages such as Ada need to search inside the program
> >>> -     minimal symbols for the location where to put the temporary
> >>> -     breakpoint before starting.  */
> >>> -  if (!have_minimal_symbols (current_program_space))
> >>> +  /* Abort the start command if `main` cannot be resolved, e.g., the
> >>> +     minimal / partial symbols are not available.  Some languages such
> >>> +     as Ada need to search inside the program minimal symbols for the
> >>> +     location where to put the temporary breakpoint before starting.  */
> >>> +  if (main_name () == nullptr)
> >>>        error (_("No symbol table loaded.  Use the \"file\" command."));
> >>>
> >>>      /* Run the program until reaching the main procedure...  */
> >> I agree that minimal symbols shouldn't be required (obviously by your
> >> example), but I don't think knowing the name of the main function is
> enough.
> >>
> >> If you look at how main_name works, it calls find_main_name if the name
> >> isn't cached yet, and find_main_name has a default fallback of guessing
> >> "main" with an unknown language if needed. In other words, even if we
> >> can't find that data in the inferior, we'll still guess something. I
> >> bring this up because if there is no minimal symbols and no debug info,
> >> I think GDB wouldn't be able to set a breakpoint because main can't be
> >> translated in any way, and so "start" would end up being silently
> >> converted to "run" and confuse the user. If this assumption is wrong,
> >> feel free to ignore the rest of the message.
> >>
> >> I think the central issue for whether "start" works should be whether we
> >> can set the temporary breakpoint or not, rather than whether we can
> >> guess where to set it. I'm not sure if setting the tbreak would be
> >> possible here in the start_command due to all the preparatory work in
> >> run_command_1 before setting the bp (especially due to the commend in
> >> infcmd.c:411), but I think it would be ideal if it is possible. If that
> >> doesn't work, I would imagine run_command_1 should check if
> >> tbreak_command succeeds and return early if it fails. The issues with
> >> this second strategy is that the inferior is stopped and info is cleared
> >> before we're sure that we can run the full command, which is pretty
> >> annoying for the user, but I'it might be better than having to set a
> >> breakpoint twice (in case someone is using a slow connection to a
> >> gdbserver, for instance).
> >>
> >> --
> >> Cheers,
> >> Guinevere Larsen
> >> She/Her/Hers
> > Intel Deutschland GmbH
> > Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
> > Tel: +49 89 99 8853-0, www.intel.de
> > Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
> > Chairperson of the Supervisory Board: Nicole Lau
> > Registered Office: Munich
> > Commercial Register: Amtsgericht Muenchen HRB 186928

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  
Guinevere Larsen Dec. 9, 2024, 11:55 a.m. UTC | #5
On 12/9/24 5:00 AM, Rohr, Stephan wrote:
>
>> -----Original Message-----
>> From: Guinevere Larsen <guinevere@redhat.com>
>> Sent: Friday, 6 December 2024 12:51
>> To: Rohr, Stephan <stephan.rohr@intel.com>; gdb-patches@sourceware.org
>> Subject: Re: [PATCH] gdb: continue start command if 'main' can be resolved
>>
>> On 12/6/24 7:08 AM, Rohr, Stephan wrote:
>>> Hi Guinevere,
>>>
>>> thanks for your feedback.
>>>
>>> I agree on your concerns, 'main_name ()' might not be the best solution for
>> this issue.
>>> I also think that moving the whole breakpoint setup into 'start_command' is
>> not
>>> feasible.  I modified ' run_command_1' to report an error if the (temporary)
>> breakpoint
>>> cannot be inserted, this also fixes the issue.  I had to do some modifications:
>>>
>>>     * Make 'break_command_1' externally visible.
>>>     * Use 'break_command_1' to insert the temporary breakpoint instead of
>> 'tbreak_command'.
>>> I wonder if making 'break_command_1' externally visible could be an
>> acceptable solution?
>>
>> I don't quite understand why you needed to use break_command_1 to get
>> this information, since I don't see any extra information you'd get from
>> that, but maybe I'm just missing something, this is my first time
>> looking at breakpoint code... with that said, have you considered making
>> the "*break_command" functions return bool instead?
>>
>> Since the last thing that break_command_1 does is a call to
>> create_breakpoint, which already returns true for a successful insertion
>> and false otherwise (using ints), you could just change break_command_1
>> to return bool, and then each of the *break_commands could return the
>> result to break_command_1 without needing to export the original function.
>>
>> This looks to me like it would allow run_command_1 to detect and report
>> the problem of the breakpoint creation without needing to change
>> visibilities.
>>
> I tried that direction at first, too.  This would require additional changes in the way
> CLI commands are added.  The current implementation only accepts commands
> with a void return type:
>
>    /* The "simple" signature of command callbacks, which doesn't include a
>         cmd_list_element parameter.  */
>    
>    typedef void cmd_simple_func_ftype (const char *args, int from_tty);
>
> See 'command.h:388' for details.  I don't think changing this would be a good
> idea.

Oh right, this makes perfect sense. Yeah, I agree that changing how 
commands are added would be a bad idea for this.

> I tried one more approach:
>
>    * Check if the 'main' symbol can be found inside 'start_command', e.g., with
>       ' lookup_symbol_search_name ()'.
>    * If that fails, abort with the same error message as of today.
>
> This also doesn't leave us with a stopped inferior, i.e., when adding the check to
> 'tbreak_command ()'.
>
> Would this be a better solution?

I really like this final version you came up with. It feels like the key 
point of the breakpoint test before we mess with the current inferior. 
+1 from me!
  

Patch

diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 5c0e3f51162..39b7e50b582 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -516,10 +516,11 @@  run_command (const char *args, int from_tty)
 static void
 start_command (const char *args, int from_tty)
 {
-  /* Some languages such as Ada need to search inside the program
-     minimal symbols for the location where to put the temporary
-     breakpoint before starting.  */
-  if (!have_minimal_symbols (current_program_space))
+  /* Abort the start command if `main` cannot be resolved, e.g., the
+     minimal / partial symbols are not available.  Some languages such
+     as Ada need to search inside the program minimal symbols for the
+     location where to put the temporary breakpoint before starting.  */
+  if (main_name () == nullptr)
     error (_("No symbol table loaded.  Use the \"file\" command."));
 
   /* Run the program until reaching the main procedure...  */