[PATCHv2 2/3] gdb: process startup files and startup command line options

Andrew Burgess
2021-01-26T09:48:35Z

Adds the ability to process commands at a new phase during GDB's
startup. This phase is earlier than the current initialisation file
processing, before GDB has produced any output.

The number of commands that can be processed at this early stage will
be limited, and it is expected that the only commands that would be
processed at this stage will relate to some of the fundamentals of how
GDB starts up.

Currently the only commands that it makes sense to add to this startup
file are those like 'set style version ....' as the version string is
displayed during startup before the standard initialization files are
parsed. As such this commit fully resolved bug cli/25956.

This commit adds a mechanism to find these startup files as well as
some corresponding command line flags.

The startup files that GDB will currently check for are
~/.config/gdb/gdbstartup (on Linux like systems) or ~/.gdbstartup if
the former is not found.

The output of 'gdb --help' has been extended to include a list of the
startup files being processed.

gdb/ChangeLog:

PR cli/25956
* NEWS: Mention new startup files and command line options.
* config.in: Regenerate.
* configure: Regenerate.
* configure.ac: Define GDBSTARTUP.
* main.c (get_startup_files): New function.
(enum cmdarg_kind): Add CMDARG_STARTUP_FILE and
CMDARG_STARTUP_COMMAND.
(captured_main_1): Add support for new command line flags, and for
processing startup files.
(print_gdb_help): Include startup files in the output.

gdb/doc/ChangeLog:

PR cli/25956
* gdb.texinfo (File Options): Mention new command line options.
(Startup): Discuss when startup files are processed.
(Initialization Files): Add description of startup files.
(Output Styling): Update description of 'version' style.
(gdb man): Mention startup files.

gdb/testsuite/ChangeLog:

PR cli/25956
* gdb.base/startup-file.c: New file.
* gdb.base/startup-file.exp: New file.
* lib/gdb-utils.exp (style): Handle style 'none'.
---
gdb/ChangeLog | 14 +++
gdb/NEWS | 12 +++
gdb/config.in | 3 +
gdb/configure | 6 ++
gdb/configure.ac | 3 +
gdb/doc/ChangeLog | 9 ++
gdb/doc/gdb.texinfo | 111 ++++++++++++++++++++++--
gdb/main.c | 62 ++++++++++++-
gdb/testsuite/ChangeLog | 7 ++
gdb/testsuite/gdb.base/startup-file.c | 22 +++++
gdb/testsuite/gdb.base/startup-file.exp | 110 +++++++++++++++++++++++
gdb/testsuite/lib/gdb-utils.exp | 1 +
12 files changed, 350 insertions(+), 10 deletions(-)
create mode 100644 gdb/testsuite/gdb.base/startup-file.c
create mode 100644 gdb/testsuite/gdb.base/startup-file.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index d2ed28857b0..368cab5fec6 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -33,6 +33,18 @@
the use of the GNAT encoding (based on information added to the type's
name following a GNAT-specific format).

+* GDB will now load and process commands from ~/.config/gdb/gdbstartup
+ or ~/.gdbstartup if these files are present. These files are
+ processed earlier than any of the previous initialization files and
+ can affect parts of GDB's startup that previously had already been
+ completed before the initialization files were read, for example
+ styling of the initial GDB greeting.
+
+* GDB now has two new options "--startup-command" and
+ "--startup-eval-command" with corresponding short options "-sx" and
+ "-sex" that allow options (that would normally appear in a
+ gdbstartup file) to be passed on the command line.
+
* New commands

set debug event-loop
diff --git a/gdb/config.in b/gdb/config.in
index 14a77c661d5..5924bf008cb 100644
--- a/gdb/config.in
+++ b/gdb/config.in
@@ -46,6 +46,9 @@
/* The .gdbinit filename. */
#undef GDBINIT

+/* The .gdbstartup filename. */
+#undef GDBSTARTUP
+
/* look for global separate data files in this path [DATADIR/gdb] */
#undef GDB_DATADIR

diff --git a/gdb/configure b/gdb/configure
index 51b4d1921c5..a9057fab307 100755
--- a/gdb/configure
+++ b/gdb/configure
@@ -16778,6 +16778,12 @@ _ACEOF

+cat >>confdefs.h <<_ACEOF
+#define GDBSTARTUP ".gdbstartup"
+_ACEOF
+
+
+
# Support for --with-sysroot is a copy of GDB_AC_WITH_DIR,
# except that the argument to --with-sysroot is optional.
# --with-sysroot (or --with-sysroot=yes) sets the default sysroot path.
diff --git a/gdb/configure.ac b/gdb/configure.ac
index 618c59166e4..5daa7aef40a 100644
--- a/gdb/configure.ac
+++ b/gdb/configure.ac
@@ -1819,6 +1819,9 @@ case $host_os in
esac
AC_DEFINE_UNQUOTED(GDBINIT,"$gdbinit",[The .gdbinit filename.])

+dnl Set the host's .gdbstartup filename
+AC_DEFINE_UNQUOTED(GDBSTARTUP,".gdbstartup",[The .gdbstartup filename.])
+
dnl Handle optional features that can be enabled.

# Support for --with-sysroot is a copy of GDB_AC_WITH_DIR,
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index e43819f0f70..09f64302ba6 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -1040,6 +1040,22 @@
after loading gdbinit files).
@xref{Startup}.

+@item -startup-command @var{file}
+@itemx -sx @var{file}
+@cindex @code{--startup-command}
+@cindex @code{-sx}
+Execute commands from file @var{file} as early as possible during the
+initialization process, before any output is produced.
+@xref{Startup}.
+
+@item -startup-eval-command @var{command}
+@itemx -sex @var{command}
+@cindex @code{--startup-eval-command}
+@cindex @code{-sex}
+Execute a single @value{GDBN} command as early as possible during the
+initialization process, before any output is produced.
+@xref{Startup}.
+
@item -directory @var{directory}
@itemx -d @var{directory}
@cindex @code{--directory}
@@ -1291,6 +1307,22 @@
Here's the description of what @value{GDBN} does during session startup:

@enumerate
+
+@item
+Performs minimal setup required to initialise basic internal state.
+
+@item
+@cindex startup file
+Read commands from the startup file (if any) in your home directory.
+For details on which commands can be used in a startup file
+@pxref{Initialization Files}.
+
+@item
+Executes commands and command files specified by the @samp{-sex} and
+@samp{-sx} command line options in their specified order. Only a
+restricted set of commands can be used within @samp{-sex} and
+@samp{sx}, @pxref{Initialization Files} for details.
+
@item
Sets up the command interpreter as specified by the command line
(@pxref{Mode Options, interpreter}).
@@ -1367,13 +1399,73 @@
startup, in the order they will be loaded, you can use @kbd{gdb
--help}.

+The files processed are split into two categories @dfn{startup files}
+and @dfn{initialization files}.
+
+The startup file is loaded as early as possible during
+@value{GDBN}'s startup, before the interpreter (@pxref{Interpreters})
+has been initialised, and before the default target (@pxref{Targets}
+is initialised. Only @code{set} or @code{source} commands should be
+placed into a startup file, and the only @code{set} commands that
+should be used are those that control how @value{GDBN} starts up.
+Commands that should be placed into a startup file will be documented
+as such throughout this manual. Any command that is not documented as
+being suitable for a startup file should be placed into a general
+initialization file. Command files passed to @code{--startup-command}
+or @code{-sx} are also startup files, with the same command
+restrictions. Only commands that can appear in a startup file shold
+be passed to @code{--startup-eval-command} or @code{-sex}.
+
+The more general initialization files are processed later after
+@value{GDBN} has finished its own internal startup process, any
+commands can be used in these files.
+
As the system wide and home directory initialization files are
processed before most command line options, changes to settings
(e.g. @samp{set complaints}) can affect subsequent processing of
command line options and operands.

The following sections describe where @value{GDBN} looks for the
-initialization and the order that the files are searched for.
+startup and initialization files, and the order that the files are
+searched for.
+
+@subsubsection Home directory startup files
+
+@value{GDBN} initially looks for a startup file in the users home
+directory@footnote{On DOS/Windows systems, the home directory is the
+one pointed to by the @code{HOME} environment variable.}. There are a
+number of locations that @value{GDBN} will search in the home
+directory, these locations are searched in order and @value{GDBN} will
+load the first file that it finds, and subsequent locations will not
+be checked.
+
+On non-Apple hosts the locations searched are:
+@itemize
+@item
+The file @file{gdb/gdbstartup} within the directory pointed to by the
+environment variable @env{XDG_CONFIG_HOME}, if it is defined.
+@item
+The file @file{.config/gdb/gdbstartup} within the directory pointed to
+by the environment variable @env{HOME}, if it is defined.
+@item
+The file @file{.gdbstartup} within the directory pointed to by the
+environment variable @env{HOME}, if it is defined.
+@end itemize
+
+While on Apple hosts the locations searched are:
+@itemize
+@item
+The file @file{Library/Preferences/gdb/gdbstartup} within the
+directory pointed to by the environment variable @env{HOME}, if it is
+defined.
+@item
+The file @file{.gdbstartup} within the directory pointed to by the
+environment variable @env{HOME}, if it is defined.
+@end itemize
+
+It is possible to prevent the home directory startup file from
+being loaded using the @samp{-nx} or @samp{-nh} command line options,
+@pxref{Mode Options,,Choosing Modes}.

@anchor{System Wide Init Files}
@subsubsection System wide initialization files
@@ -25794,11 +25886,10 @@
intensity. The version number is displayed in two places, the output
of @command{show version}, and when @value{GDBN} starts up.

-Currently the version string displayed at startup is printed before
-@value{GDBN} has parsed any command line options, or parsed any
-command files, so there is currently no way to control the styling of
-this string. However, @value{GDBN}'s @code{--quiet} command line option
-can be used to disable printing of the version string on startup.
+In order to control how @value{GDBN} styles the version number at
+startup, add the @code{set style version} family of commands to the
+startup command file (@pxref{Initialization Files,,Initialization
+Files}).

@item title
Control the styling of titles. These are managed with the
@@ -46603,12 +46694,14 @@
Add @var{directory} to the path to search for source files.

@item -nh
-Do not execute commands from @file{~/.config/gdb/gdbinit} or
-@file{~/.gdbinit}.
+Do not execute commands from @file{~/.config/gdb/gdbinit},
+@file{~/.gdbinit}, @file{~/.config/gdb/gdbstartup}, or
+@file{~/.gdbstartup}

@item -nx
@itemx -n
-Do not execute commands from any @file{.gdbinit} initialization files.
+Do not execute commands from any @file{.gdbinit} or @file{.gdbstartup}
+initialization files.

@item -quiet
@itemx -q
diff --git a/gdb/main.c b/gdb/main.c
index 2d2fded7158..fe1ca27c255 100644
--- a/gdb/main.c
+++ b/gdb/main.c
@@ -389,6 +389,24 @@ get_init_files (std::vector<std::string> *system_gdbinit,
*local_gdbinit = init_files->local_file ();
}

+/* Compute the location of the startup file GDB should source and return
+ it in HOME_GDBSTARTUP. HOME_GDBSTARTUP could be returned as an empty
+ string if there is not startup file found. */
+
+static void
+get_startup_files (std::string *home_gdbstartup)
+{
+ /* Cache the file lookup object so we only actually search for the files
+ once. */
+ static std::unique_ptr<gdb_initfile_finder> init_files;
+ if (init_files == nullptr)
+ init_files = std::unique_ptr<gdb_initfile_finder>
+ (new gdb_initfile_finder (GDBSTARTUP,
+ nullptr, false, nullptr, false, false));
+
+ *home_gdbstartup = init_files->home_file ();
+}
+
/* Start up the event loop. This is the entry point to the event loop
from the command loop. */

@@ -563,7 +581,13 @@ enum cmdarg_kind
CMDARG_INIT_FILE,

/* Option type -iex. */
- CMDARG_INIT_COMMAND
+ CMDARG_INIT_COMMAND,
+
+ /* Option type -sx. */
+ CMDARG_STARTUP_FILE,
+
+ /* Option type -sex. */
+ CMDARG_STARTUP_COMMAND
};

/* Arguments of --command option and its counterpart. */
@@ -741,6 +765,8 @@ captured_main_1 (struct captured_main_args *context)
OPT_WINDOWS,
OPT_IX,
OPT_IEX,
+ OPT_SX,
+ OPT_SEX,
OPT_READNOW,
OPT_READNEVER
};
@@ -790,6 +816,10 @@ captured_main_1 (struct captured_main_args *context)
{"init-eval-command", required_argument, 0, OPT_IEX},
{"ix", required_argument, 0, OPT_IX},
{"iex", required_argument, 0, OPT_IEX},
+ {"startup-command", required_argument, 0, OPT_SX},
+ {"startup-eval-command", required_argument, 0, OPT_SEX},
+ {"sx", required_argument, 0, OPT_SX},
+ {"sex", required_argument, 0, OPT_SEX},
#ifdef GDBTK
{"tclcommand", required_argument, 0, 'z'},
{"enable-external-editor", no_argument, 0, 'y'},
@@ -902,6 +932,12 @@ captured_main_1 (struct captured_main_args *context)
case OPT_IEX:
cmdarg_vec.emplace_back (CMDARG_INIT_COMMAND, optarg);
break;
+ case OPT_SX:
+ cmdarg_vec.emplace_back (CMDARG_STARTUP_FILE, optarg);
+ break;
+ case OPT_SEX:
+ cmdarg_vec.emplace_back (CMDARG_STARTUP_COMMAND, optarg);
+ break;
case 'B':
batch_flag = batch_silent = 1;
gdb_stdout = new null_file ();
@@ -1010,6 +1046,18 @@ captured_main_1 (struct captured_main_args *context)
/* Initialize all files. */
gdb_init (gdb_program_name);

+ /* Process startup files and startup options from the command line. */
+ if (!inhibit_gdbinit)
+ {
+ std::string home_gdbstartup;
+ get_startup_files (&home_gdbstartup);
+ if (!home_gdbstartup.empty () && !inhibit_home_gdbinit)
+ ret = catch_command_errors (source_script,
+ home_gdbstartup.c_str (), 0);
+ }
+ execute_cmdargs (&cmdarg_vec, CMDARG_STARTUP_FILE,
+ CMDARG_STARTUP_COMMAND, &ret);
+
/* Now that gdb_init has created the initial inferior, we're in
position to set args for that inferior. */
if (set_args)
@@ -1337,8 +1385,10 @@ print_gdb_help (struct ui_file *stream)
std::vector<std::string> system_gdbinit;
std::string home_gdbinit;
std::string local_gdbinit;
+ std::string home_gdbstartup;

get_init_files (&system_gdbinit, &home_gdbinit, &local_gdbinit);
+ get_startup_files (&home_gdbstartup);

/* Note: The options in the list below are only approximately sorted
in the alphabetical order, so as to group closely related options
@@ -1412,6 +1462,16 @@ Other options:\n\n\
Set GDB's data-directory to DIR.\n\
"), stream);
fputs_unfiltered (_("\n\
+At startup, GDB reads the following startup files and executes their commands:\n\
+"), stream);
+ if (!home_gdbstartup.empty ())
+ fprintf_unfiltered (stream, _("\
+ * user-specific startup file: %s\n\
+"), home_gdbstartup.c_str ());
+ if (home_gdbstartup.empty ())
+ fprintf_unfiltered (stream, _("\
+ None found.\n"));
+ fputs_unfiltered (_("\n\
At startup, GDB reads the following init files and executes their commands:\n\
"), stream);
if (!system_gdbinit.empty ())
diff --git a/gdb/testsuite/gdb.base/startup-file.c b/gdb/testsuite/gdb.base/startup-file.c
new file mode 100644
index 00000000000..9811b15f06d
--- /dev/null
+++ b/gdb/testsuite/gdb.base/startup-file.c
@@ -0,0 +1,22 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2021 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/>. */
+
+int
+main ()
+{
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.base/startup-file.exp b/gdb/testsuite/gdb.base/startup-file.exp
new file mode 100644
index 00000000000..e871a193ffa
--- /dev/null
+++ b/gdb/testsuite/gdb.base/startup-file.exp
@@ -0,0 +1,110 @@
+# Copyright 2021 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 GDB's startup file mechanism.
+
+standard_testfile
+
+# Compile the test executable.
+if {[build_executable "failed to build" $testfile $srcfile]} {
+ return -1
+}
+
+# Start gdb and ensure that the initial version string is styled in
+# STYLE, use MESSAGE as the name of the test.
+proc check_gdb_startup_version_string { style { message "" } } {
+ if { $message == "" } {
+ set message "check startup version string has style $style"
+ }
+
+ gdb_exit
+ gdb_spawn
+ set vers [style "GNU gdb.*" $style]
+ gdb_test "" "^${vers}.*" $message
+}
+
+# Return a list containing two directory paths for newly created home
+# directories.
+#
+# The first directory is a HOME style home directory, it contains a
+# .gdbstartup file containing CONTENT.
+#
+# The second directory is an XDG_CONFIG_HOME style home directory, it
+# contains a sub-directory gdb/, inside which is a file gdbstartup
+# that also contains CONTENT.
+#
+# The PREFIX is used in both directory names and should be unique for
+# each call to this function.
+proc setup_home_directories { prefix content } {
+ set home_dir [standard_output_file "${prefix}-home"]
+ set xdg_home_dir [standard_output_file "${prefix}-xdg"]
+
+ file mkdir $home_dir
+ file mkdir "$xdg_home_dir/gdb"
+
+ # Write the content into the HOME directory.
+ set fd [open "$home_dir/.gdbstartup" w]
+ puts $fd $content
+ close $fd
+
+ # Copy this from the HOME directory into the XDG_CONFIG_HOME
+ # directory.
+ file copy -force "$home_dir/.gdbstartup" "$xdg_home_dir/gdb/gdbstartup"
+
+ return [list $home_dir $xdg_home_dir]
+}
+
+save_vars { env(TERM) } {
+ # We need an ANSI-capable terminal to get the output.
+ setenv TERM ansi
+
+ # Start GDB and confirm that the version string is styled.
+ check_gdb_startup_version_string version
+
+ # Create an empty directory we can use as HOME for some of the
+ # tests below. When we set XDG_CONFIG_HOME we still need to point
+ # HOME at something otherwise GDB complains that it doesn't know
+ # where to create the index cache.
+ set empty_home_dir [standard_output_file fake-empty-home]
+
+ # Create two directories to use for the style setting test.
+ set dirs [setup_home_directories "style" \
+ [multi_line_input \
+ "set style version foreground none" \
+ "set style version background none" \
+ "set style version intensity normal"]]
+ set home_dir [lindex $dirs 0]
+ set xdg_home_dir [lindex $dirs 1]
+
+ # Now arrange to use the fake home directory startup file.
+ save_vars { INTERNAL_GDBFLAGS env(HOME) env(XDG_CONFIG_HOME) } {
+ set INTERNAL_GDBFLAGS [string map {"-nx" ""} $INTERNAL_GDBFLAGS]
+
+ # Now test GDB when using the HOME directory.
+ set env(HOME) $home_dir
+ unset -nocomplain env(XDG_CONFIG_HOME)
+ check_gdb_startup_version_string none \
+ "check version string is unstyled using HOME"
+
+ # Now test using the XDG_CONFIG_HOME folder. We still need to
+ # have a HOME directory set otherwise GDB will issue an error
+ # about not knowing where to place the index cache.
+ set env(XDG_CONFIG_HOME) $xdg_home_dir
+ set env(HOME) $empty_home_dir
+ check_gdb_startup_version_string none \
+ "check version string is unstyled using XDG_CONFIG_HOME"
+ }
+}
+
diff --git a/gdb/testsuite/lib/gdb-utils.exp b/gdb/testsuite/lib/gdb-utils.exp
index ad7d2884aae..cec157196b3 100644
--- a/gdb/testsuite/lib/gdb-utils.exp
+++ b/gdb/testsuite/lib/gdb-utils.exp
@@ -56,6 +56,7 @@ proc style {str style} {
address { set style 34 }
metadata { set style 2 }
version { set style "35;1" }
+ none { return $str }
}
return "\033\\\[${style}m${str}\033\\\[m"
}
--
2.25.4