1# Common functions and variables for testing the Python pretty printers. 2# 3# Copyright (C) 2016-2021 Free Software Foundation, Inc. 4# This file is part of the GNU C Library. 5# 6# The GNU C Library is free software; you can redistribute it and/or 7# modify it under the terms of the GNU Lesser General Public 8# License as published by the Free Software Foundation; either 9# version 2.1 of the License, or (at your option) any later version. 10# 11# The GNU C Library is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14# Lesser General Public License for more details. 15# 16# You should have received a copy of the GNU Lesser General Public 17# License along with the GNU C Library; if not, see 18# <https://www.gnu.org/licenses/>. 19 20"""These tests require PExpect 4.0 or newer. 21 22Exported constants: 23 PASS, FAIL, UNSUPPORTED (int): Test exit codes, as per evaluate-test.sh. 24""" 25 26import os 27import re 28from test_printers_exceptions import * 29 30PASS = 0 31FAIL = 1 32UNSUPPORTED = 77 33 34gdb_bin = 'gdb' 35gdb_options = '-q -nx' 36gdb_invocation = '{0} {1}'.format(gdb_bin, gdb_options) 37pexpect_min_version = 4 38gdb_min_version = (7, 8) 39encoding = 'utf-8' 40 41try: 42 import pexpect 43except ImportError: 44 print('PExpect 4.0 or newer must be installed to test the pretty printers.') 45 exit(UNSUPPORTED) 46 47pexpect_version = pexpect.__version__.split('.')[0] 48 49if int(pexpect_version) < pexpect_min_version: 50 print('PExpect 4.0 or newer must be installed to test the pretty printers.') 51 exit(UNSUPPORTED) 52 53if not pexpect.which(gdb_bin): 54 print('gdb 7.8 or newer must be installed to test the pretty printers.') 55 exit(UNSUPPORTED) 56 57timeout = 5 58TIMEOUTFACTOR = os.environ.get('TIMEOUTFACTOR') 59 60if TIMEOUTFACTOR: 61 timeout = int(TIMEOUTFACTOR) 62 63# Otherwise GDB is run in interactive mode and readline may send escape 64# sequences confusing output for pexpect. 65os.environ["TERM"]="dumb" 66 67try: 68 # Check the gdb version. 69 version_cmd = '{0} --version'.format(gdb_invocation, timeout=timeout) 70 gdb_version_out = pexpect.run(version_cmd, encoding=encoding) 71 72 # The gdb version string is "GNU gdb <PKGVERSION><version>", where 73 # PKGVERSION can be any text. We assume that there'll always be a space 74 # between PKGVERSION and the version number for the sake of the regexp. 75 version_match = re.search(r'GNU gdb .* ([1-9][0-9]*)\.([0-9]+)', 76 gdb_version_out) 77 78 if not version_match: 79 print('The gdb version string (gdb -v) is incorrectly formatted.') 80 exit(UNSUPPORTED) 81 82 gdb_version = (int(version_match.group(1)), int(version_match.group(2))) 83 84 if gdb_version < gdb_min_version: 85 print('gdb 7.8 or newer must be installed to test the pretty printers.') 86 exit(UNSUPPORTED) 87 88 # Check if gdb supports Python. 89 gdb_python_cmd = '{0} -ex "python import os" -batch'.format(gdb_invocation, 90 timeout=timeout) 91 gdb_python_error = pexpect.run(gdb_python_cmd, encoding=encoding) 92 93 if gdb_python_error: 94 print('gdb must have python support to test the pretty printers.') 95 print('gdb output: {!r}'.format(gdb_python_error)) 96 exit(UNSUPPORTED) 97 98 # If everything's ok, spawn the gdb process we'll use for testing. 99 gdb = pexpect.spawn(gdb_invocation, echo=False, timeout=timeout, 100 encoding=encoding) 101 gdb_prompt = u'\(gdb\)' 102 gdb.expect(gdb_prompt) 103 104except pexpect.ExceptionPexpect as exception: 105 print('Error: {0}'.format(exception)) 106 exit(FAIL) 107 108def test(command, pattern=None): 109 """Sends 'command' to gdb and expects the given 'pattern'. 110 111 If 'pattern' is None, simply consumes everything up to and including 112 the gdb prompt. 113 114 Args: 115 command (string): The command we'll send to gdb. 116 pattern (raw string): A pattern the gdb output should match. 117 118 Returns: 119 string: The string that matched 'pattern', or an empty string if 120 'pattern' was None. 121 """ 122 123 match = '' 124 125 gdb.sendline(command) 126 127 if pattern: 128 # PExpect does a non-greedy match for '+' and '*'. Since it can't look 129 # ahead on the gdb output stream, if 'pattern' ends with a '+' or a '*' 130 # we may end up matching only part of the required output. 131 # To avoid this, we'll consume 'pattern' and anything that follows it 132 # up to and including the gdb prompt, then extract 'pattern' later. 133 index = gdb.expect([u'{0}.+{1}'.format(pattern, gdb_prompt), 134 pexpect.TIMEOUT]) 135 136 if index == 0: 137 # gdb.after now contains the whole match. Extract the text that 138 # matches 'pattern'. 139 match = re.match(pattern, gdb.after, re.DOTALL).group() 140 elif index == 1: 141 # We got a timeout exception. Print information on what caused it 142 # and bail out. 143 error = ('Response does not match the expected pattern.\n' 144 'Command: {0}\n' 145 'Expected pattern: {1}\n' 146 'Response: {2}'.format(command, pattern, gdb.before)) 147 148 raise pexpect.TIMEOUT(error) 149 else: 150 # Consume just the the gdb prompt. 151 gdb.expect(gdb_prompt) 152 153 return match 154 155def init_test(test_bin, printer_files, printer_names): 156 """Loads the test binary file and the required pretty printers to gdb. 157 158 Args: 159 test_bin (string): The name of the test binary file. 160 pretty_printers (list of strings): A list with the names of the pretty 161 printer files. 162 """ 163 164 # Load all the pretty printer files. We're assuming these are safe. 165 for printer_file in printer_files: 166 test('source {0}'.format(printer_file)) 167 168 # Disable all the pretty printers. 169 test('disable pretty-printer', r'0 of [0-9]+ printers enabled') 170 171 # Enable only the required printers. 172 for printer in printer_names: 173 test('enable pretty-printer {0}'.format(printer), 174 r'[1-9][0-9]* of [1-9]+ printers enabled') 175 176 # Finally, load the test binary. 177 test('file {0}'.format(test_bin)) 178 179 # Disable lock elision. 180 test('set environment GLIBC_TUNABLES glibc.elision.enable=0') 181 182def go_to_main(): 183 """Executes a gdb 'start' command, which takes us to main.""" 184 185 test('start', r'main') 186 187def get_line_number(file_name, string): 188 """Returns the number of the line in which 'string' appears within a file. 189 190 Args: 191 file_name (string): The name of the file we'll search through. 192 string (string): The string we'll look for. 193 194 Returns: 195 int: The number of the line in which 'string' appears, starting from 1. 196 """ 197 number = -1 198 199 with open(file_name) as src_file: 200 for i, line in enumerate(src_file): 201 if string in line: 202 number = i + 1 203 break 204 205 if number == -1: 206 raise NoLineError(file_name, string) 207 208 return number 209 210def break_at(file_name, string, temporary=True, thread=None): 211 """Places a breakpoint on the first line in 'file_name' containing 'string'. 212 213 'string' is usually a comment like "Stop here". Notice this may fail unless 214 the comment is placed inline next to actual code, e.g.: 215 216 ... 217 /* Stop here */ 218 ... 219 220 may fail, while: 221 222 ... 223 some_func(); /* Stop here */ 224 ... 225 226 will succeed. 227 228 If 'thread' isn't None, the breakpoint will be set for all the threads. 229 Otherwise, it'll be set only for 'thread'. 230 231 Args: 232 file_name (string): The name of the file we'll place the breakpoint in. 233 string (string): A string we'll look for inside the file. 234 We'll place a breakpoint on the line which contains it. 235 temporary (bool): Whether the breakpoint should be automatically deleted 236 after we reach it. 237 thread (int): The number of the thread we'll place the breakpoint for, 238 as seen by gdb. If specified, it should be greater than zero. 239 """ 240 241 if not thread: 242 thread_str = '' 243 else: 244 thread_str = 'thread {0}'.format(thread) 245 246 if temporary: 247 command = 'tbreak' 248 break_type = 'Temporary breakpoint' 249 else: 250 command = 'break' 251 break_type = 'Breakpoint' 252 253 line_number = str(get_line_number(file_name, string)) 254 255 test('{0} {1}:{2} {3}'.format(command, file_name, line_number, thread_str), 256 r'{0} [0-9]+ at 0x[a-f0-9]+: file {1}, line {2}\.'.format(break_type, 257 file_name, 258 line_number)) 259 260def continue_cmd(thread=None): 261 """Executes a gdb 'continue' command. 262 263 If 'thread' isn't None, the command will be applied to all the threads. 264 Otherwise, it'll be applied only to 'thread'. 265 266 Args: 267 thread (int): The number of the thread we'll apply the command to, 268 as seen by gdb. If specified, it should be greater than zero. 269 """ 270 271 if not thread: 272 command = 'continue' 273 else: 274 command = 'thread apply {0} continue'.format(thread) 275 276 test(command) 277 278def next_cmd(count=1, thread=None): 279 """Executes a gdb 'next' command. 280 281 If 'thread' isn't None, the command will be applied to all the threads. 282 Otherwise, it'll be applied only to 'thread'. 283 284 Args: 285 count (int): The 'count' argument of the 'next' command. 286 thread (int): The number of the thread we'll apply the command to, 287 as seen by gdb. If specified, it should be greater than zero. 288 """ 289 290 if not thread: 291 command = 'next' 292 else: 293 command = 'thread apply {0} next' 294 295 test('{0} {1}'.format(command, count)) 296 297def select_thread(thread): 298 """Selects the thread indicated by 'thread'. 299 300 Args: 301 thread (int): The number of the thread we'll switch to, as seen by gdb. 302 This should be greater than zero. 303 """ 304 305 if thread > 0: 306 test('thread {0}'.format(thread)) 307 308def get_current_thread_lwpid(): 309 """Gets the current thread's Lightweight Process ID. 310 311 Returns: 312 string: The current thread's LWP ID. 313 """ 314 315 # It's easier to get the LWP ID through the Python API than the gdb CLI. 316 command = 'python print(gdb.selected_thread().ptid[1])' 317 318 return test(command, r'[0-9]+') 319 320def set_scheduler_locking(mode): 321 """Executes the gdb 'set scheduler-locking' command. 322 323 Args: 324 mode (bool): Whether the scheduler locking mode should be 'on'. 325 """ 326 modes = { 327 True: 'on', 328 False: 'off' 329 } 330 331 test('set scheduler-locking {0}'.format(modes[mode])) 332 333def test_printer(var, to_string, children=None, is_ptr=True): 334 """ Tests the output of a pretty printer. 335 336 For a variable called 'var', this tests whether its associated printer 337 outputs the expected 'to_string' and children (if any). 338 339 Args: 340 var (string): The name of the variable we'll print. 341 to_string (raw string): The expected output of the printer's 'to_string' 342 method. 343 children (map {raw string->raw string}): A map with the expected output 344 of the printer's children' method. 345 is_ptr (bool): Whether 'var' is a pointer, and thus should be 346 dereferenced. 347 """ 348 349 if is_ptr: 350 var = '*{0}'.format(var) 351 352 test('print {0}'.format(var), to_string) 353 354 if children: 355 for name, value in children.items(): 356 # Children are shown as 'name = value'. 357 test('print {0}'.format(var), r'{0} = {1}'.format(name, value)) 358 359def check_debug_symbol(symbol): 360 """ Tests whether a given debugging symbol exists. 361 362 If the symbol doesn't exist, raises a DebugError. 363 364 Args: 365 symbol (string): The symbol we're going to check for. 366 """ 367 368 try: 369 test('ptype {0}'.format(symbol), r'type = {0}'.format(symbol)) 370 371 except pexpect.TIMEOUT: 372 # The symbol doesn't exist. 373 raise DebugError(symbol) 374