1 # Copyright 2020-2024 Free Software Foundation, Inc. 2 3 # This program is free software; you can redistribute it and/or modify 4 # it under the terms of the GNU General Public License as published by 5 # the Free Software Foundation; either version 3 of the License, or 6 # (at your option) any later version. 7 # 8 # This program is distributed in the hope that it will be useful, 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 # GNU General Public License for more details. 12 # 13 # You should have received a copy of the GNU General Public License 14 # along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 # Helper functions to make it easier to write debuginfod tests. 17 18 # Return true if the debuginfod tests should be run, otherwise, return 19 # false. 20 proc allow_debuginfod_tests {} { 21 if [is_remote host] { 22 return false 23 } 24 25 if { [which debuginfod] == 0 } { 26 return false 27 } 28 29 if { [which curl] == 0 } { 30 untested "cannot find curl" 31 return false 32 } 33 34 # Skip testing if gdb was not configured with debuginfod. 35 # 36 # If GDB is built with ASan, it warns that some signal handlers 37 # (installed by ASan) exist on startup. That makes TCL's exec throw an 38 # error. This is dealt with by the --quiet in INTERNAL_GDBFLAGS. 39 if { [string first "with-debuginfod" \ 40 [eval exec $::GDB $::INTERNAL_GDBFLAGS \ 41 --configuration]] == -1 } { 42 return false 43 } 44 45 return true 46 } 47 48 # Create two directories within the current output directory. One directory 49 # will be used by GDB as the client cache to hold downloaded debug 50 # information, and the other directory will be used by the debuginfod server 51 # as its cache of the parsed debug files that will be served to GDB. 52 # 53 # Call this proc with the names to two variables, these variables will be 54 # set in the parent scope with the paths to the two directories. 55 # 56 # This proc allocates the names for the directories, but doesn't create 57 # them. In fact, if the directories already exist, this proc will delete 58 # them, this ensures that any existing contents are also deleted. 59 proc prepare_for_debuginfod { cache_var db_var } { 60 upvar $cache_var cache 61 upvar $db_var db 62 63 set cache [standard_output_file ".client_cache"] 64 set db [standard_output_file ".debuginfod.db"] 65 66 # Delete any preexisting test files. 67 file delete -force $cache 68 file delete -force $db 69 } 70 71 # Run BODY with the three environment variables required to control 72 # debuginfod set. The timeout is set based on the usual timeouts used by 73 # GDB within dejagnu (see get_largest_timeout), the debuginfod cache is set 74 # to CACHE (this is where downloaded debug data is placed), and the 75 # debuginfod urls environment variable is set to be the empty string. 76 # 77 # Within BODY you should start a debuginfod server and set the environment 78 # variable DEBUGINFOD_URLS as appropriate (see start_debuginfod for details). 79 # 80 # The reason that this proc doesn't automatically start debuginfod, is that 81 # in some test cases we want to initially test with debuginfod not running 82 # and/or disabled. 83 proc with_debuginfod_env { cache body } { 84 set envlist \ 85 [list \ 86 env(DEBUGINFOD_URLS) \ 87 env(DEBUGINFOD_TIMEOUT) \ 88 env(DEBUGINFOD_CACHE_PATH)] 89 90 save_vars $envlist { 91 setenv DEBUGINFOD_TIMEOUT [get_largest_timeout] 92 setenv DEBUGINFOD_CACHE_PATH $cache 93 setenv DEBUGINFOD_URLS "" 94 95 uplevel 1 $body 96 } 97 } 98 99 # Start a debuginfod server. DB is the directory to use for the server's 100 # database cache, while DEBUGDIR is a directory containing all the debug 101 # information that the server should server. 102 # 103 # This proc will try to find an available port to start the server on, will 104 # start the server, and check that the server has started correctly. 105 # 106 # If the server starts correctly, then this proc will return the url that 107 # should be used to communicate with the server. If the server can't be 108 # started, then an error will be printed, and an empty string returned. 109 # 110 # If the server is successfully started then the global variable 111 # debuginfod_spawn_id will be set with the spawn_id of the debuginfod 112 # process. 113 proc start_debuginfod { db debugdir } { 114 global debuginfod_spawn_id spawn_id 115 116 # Find an unused port. 117 set port 7999 118 set found false 119 while { ! $found } { 120 incr port 121 if { $port == 65536 } { 122 perror "no available ports" 123 return "" 124 } 125 126 if { [info exists spawn_id] } { 127 set old_spawn_id $spawn_id 128 } 129 130 spawn debuginfod -vvvv -d $db -p $port -F $debugdir 131 set debuginfod_spawn_id $spawn_id 132 133 if { [info exists old_spawn_id] } { 134 set spawn_id $old_spawn_id 135 unset old_spawn_id 136 } 137 138 expect { 139 -i $debuginfod_spawn_id 140 "started http server on IPv4 IPv6 port=$port" { set found true } 141 "started http server on IPv4 port=$port" { set found true } 142 "started http server on IPv6 port=$port" {} 143 "failed to bind to port" {} 144 timeout { 145 stop_debuginfod 146 perror "find port timeout" 147 return "" 148 } 149 } 150 if { ! $found } { 151 stop_debuginfod 152 } 153 } 154 155 set url "http://127.0.0.1:$port" 156 157 set metrics [list "ready 1" \ 158 "thread_work_total{role=\"traverse\"} 1" \ 159 "thread_work_pending{role=\"scan\"} 0" \ 160 "thread_busy{role=\"scan\"} 0"] 161 162 # Check server metrics to confirm init has completed. 163 foreach m $metrics { 164 set timelim 20 165 while { $timelim != 0 } { 166 sleep 0.5 167 catch {exec curl -s $url/metrics} got 168 169 if { [regexp $m $got] } { 170 break 171 } 172 173 incr timelim -1 174 } 175 176 if { $timelim == 0 } { 177 stop_debuginfod 178 perror "server init timeout" 179 return "" 180 } 181 } 182 183 return $url 184 } 185 186 # If the global debuginfod_spawn_id exists, then kill that process and unset 187 # the debuginfod_spawn_id global. This can be used to shutdown the 188 # debuginfod server. 189 proc stop_debuginfod { } { 190 global debuginfod_spawn_id 191 192 if [info exists debuginfod_spawn_id] { 193 kill_wait_spawned_process $debuginfod_spawn_id 194 unset debuginfod_spawn_id 195 } 196 } 197