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