| # Copyright 2020-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/>. |
| |
| # Helper functions to make it easier to write debuginfod tests. |
| |
| # Return true if the debuginfod tests should be run, otherwise, return |
| # false. |
| proc allow_debuginfod_tests {} { |
| if [is_remote host] { |
| return false |
| } |
| |
| if { [which debuginfod] == 0 } { |
| return false |
| } |
| |
| if { [which curl] == 0 } { |
| untested "cannot find curl" |
| return false |
| } |
| |
| # Skip testing if gdb was not configured with debuginfod. |
| # |
| # If GDB is built with ASan, it warns that some signal handlers |
| # (installed by ASan) exist on startup. That makes TCL's exec throw an |
| # error. This is dealt with by the --quiet in INTERNAL_GDBFLAGS. |
| if { [string first "with-debuginfod" \ |
| [eval exec $::GDB $::INTERNAL_GDBFLAGS \ |
| --configuration]] == -1 } { |
| return false |
| } |
| |
| return true |
| } |
| |
| # Create two directories within the current output directory. One directory |
| # will be used by GDB as the client cache to hold downloaded debug |
| # information, and the other directory will be used by the debuginfod server |
| # as its cache of the parsed debug files that will be served to GDB. |
| # |
| # Call this proc with the names to two variables, these variables will be |
| # set in the parent scope with the paths to the two directories. |
| # |
| # This proc allocates the names for the directories, but doesn't create |
| # them. In fact, if the directories already exist, this proc will delete |
| # them, this ensures that any existing contents are also deleted. |
| proc prepare_for_debuginfod { cache_var db_var } { |
| upvar $cache_var cache |
| upvar $db_var db |
| |
| set cache [standard_output_file ".client_cache"] |
| set db [standard_output_file ".debuginfod.db"] |
| |
| # Delete any preexisting test files. |
| file delete -force $cache |
| file delete -force $db |
| } |
| |
| # Run BODY with the three environment variables required to control |
| # debuginfod set. The timeout is set based on the usual timeouts used by |
| # GDB within dejagnu (see get_largest_timeout), the debuginfod cache is set |
| # to CACHE (this is where downloaded debug data is placed), and the |
| # debuginfod urls environment variable is set to be the empty string. |
| # |
| # Within BODY you should start a debuginfod server and set the environment |
| # variable DEBUGINFOD_URLS as appropriate (see start_debuginfod for details). |
| # |
| # The reason that this proc doesn't automatically start debuginfod, is that |
| # in some test cases we want to initially test with debuginfod not running |
| # and/or disabled. |
| proc with_debuginfod_env { cache body } { |
| set envlist \ |
| [list \ |
| env(DEBUGINFOD_URLS) \ |
| env(DEBUGINFOD_TIMEOUT) \ |
| env(DEBUGINFOD_CACHE_PATH)] |
| |
| save_vars $envlist { |
| setenv DEBUGINFOD_TIMEOUT [get_largest_timeout] |
| setenv DEBUGINFOD_CACHE_PATH $cache |
| setenv DEBUGINFOD_URLS "" |
| |
| uplevel 1 $body |
| } |
| } |
| |
| # Start a debuginfod server. DB is the directory to use for the server's |
| # database cache, while DEBUGDIR is a directory containing all the debug |
| # information that the server should server. |
| # |
| # This proc will try to find an available port to start the server on, will |
| # start the server, and check that the server has started correctly. |
| # |
| # If the server starts correctly, then this proc will return the url that |
| # should be used to communicate with the server. If the server can't be |
| # started, then an error will be printed, and an empty string returned. |
| # |
| # If the server is successfully started then the global variable |
| # debuginfod_spawn_id will be set with the spawn_id of the debuginfod |
| # process. |
| proc start_debuginfod { db debugdir } { |
| global debuginfod_spawn_id spawn_id |
| |
| # Find an unused port. |
| set port 7999 |
| set found false |
| while { ! $found } { |
| incr port |
| if { $port == 65536 } { |
| perror "no available ports" |
| return "" |
| } |
| |
| if { [info exists spawn_id] } { |
| set old_spawn_id $spawn_id |
| } |
| |
| spawn debuginfod -vvvv -d $db -p $port -F $debugdir |
| set debuginfod_spawn_id $spawn_id |
| |
| if { [info exists old_spawn_id] } { |
| set spawn_id $old_spawn_id |
| unset old_spawn_id |
| } |
| |
| expect { |
| -i $debuginfod_spawn_id |
| "started http server on IPv4 IPv6 port=$port" { set found true } |
| "started http server on IPv4 port=$port" { set found true } |
| "started http server on IPv6 port=$port" {} |
| "failed to bind to port" {} |
| timeout { |
| stop_debuginfod |
| perror "find port timeout" |
| return "" |
| } |
| } |
| if { ! $found } { |
| stop_debuginfod |
| } |
| } |
| |
| set url "http://127.0.0.1:$port" |
| |
| set metrics [list "ready 1" \ |
| "thread_work_total{role=\"traverse\"} 1" \ |
| "thread_work_pending{role=\"scan\"} 0" \ |
| "thread_busy{role=\"scan\"} 0"] |
| |
| # Check server metrics to confirm init has completed. |
| foreach m $metrics { |
| set timelim 20 |
| while { $timelim != 0 } { |
| sleep 0.5 |
| catch {exec curl -s $url/metrics} got |
| |
| if { [regexp $m $got] } { |
| break |
| } |
| |
| incr timelim -1 |
| } |
| |
| if { $timelim == 0 } { |
| stop_debuginfod |
| perror "server init timeout" |
| return "" |
| } |
| } |
| |
| return $url |
| } |
| |
| # If the global debuginfod_spawn_id exists, then kill that process and unset |
| # the debuginfod_spawn_id global. This can be used to shutdown the |
| # debuginfod server. |
| proc stop_debuginfod { } { |
| global debuginfod_spawn_id |
| |
| if [info exists debuginfod_spawn_id] { |
| kill_wait_spawned_process $debuginfod_spawn_id |
| unset debuginfod_spawn_id |
| } |
| } |