master
py 586 lines 16.7 KB
Raw
1 # SPDX-License-Identifier: GPL-2.0-or-later
2
3 """
4 Machinery for generating tracing-related intermediate files.
5 """
6
7 __author__ = "Lluís Vilanova <vilanova@ac.upc.edu>"
8 __copyright__ = "Copyright 2012-2017, Lluís Vilanova <vilanova@ac.upc.edu>"
9 __license__ = "GPL version 2 or (at your option) any later version"
10
11 __maintainer__ = "Stefan Hajnoczi"
12 __email__ = "stefanha@redhat.com"
13
14
15 import os
16 import re
17 import sys
18 from pathlib import PurePath
19
20 import tracetool.backend
21 import tracetool.format
22
23
24 def error_write(*lines):
25 """Write a set of error lines."""
26 sys.stderr.writelines("\n".join(lines) + "\n")
27
28 def error(*lines):
29 """Write a set of error lines and exit."""
30 error_write(*lines)
31 sys.exit(1)
32
33 FMT_TOKEN = re.compile(r'''(?:
34 " ( (?: [^"\\] | \\[\\"abfnrt] | # a string literal
35 \\x[0-9a-fA-F][0-9a-fA-F]) *? ) "
36 | ( PRI [duixX] (?:8|16|32|64|PTR|MAX) ) # a PRIxxx macro
37 | \s+ # spaces (ignored)
38 )''', re.X)
39
40 PRI_SIZE_MAP = {
41 '8': 'hh',
42 '16': 'h',
43 '32': '',
44 '64': 'll',
45 'PTR': 't',
46 'MAX': 'j',
47 }
48
49 def expand_format_string(c_fmt, prefix=""):
50 def pri_macro_to_fmt(pri_macro):
51 assert pri_macro.startswith("PRI")
52 fmt_type = pri_macro[3] # 'd', 'i', 'u', or 'x'
53 fmt_size = pri_macro[4:] # '8', '16', '32', '64', 'PTR', 'MAX'
54
55 size = PRI_SIZE_MAP.get(fmt_size, None)
56 if size is None:
57 raise Exception(f"unknown macro {pri_macro}")
58 return size + fmt_type
59
60 result = prefix
61 pos = 0
62 while pos < len(c_fmt):
63 m = FMT_TOKEN.match(c_fmt, pos)
64 if not m:
65 print("No match at position", pos, ":", repr(c_fmt[pos:]), file=sys.stderr)
66 raise Exception("syntax error in trace file")
67 if m[1]:
68 substr = m[1]
69 elif m[2]:
70 substr = pri_macro_to_fmt(m[2])
71 else:
72 substr = ""
73 result += substr
74 pos = m.end()
75 return result
76
77 out_lineno = 1
78 out_filename = '<none>'
79 out_fobj = sys.stdout
80
81 def out_open(filename):
82 global out_filename, out_fobj
83 out_filename = posix_relpath(filename)
84 out_fobj = open(filename, 'wt')
85
86 def out(*lines, **kwargs):
87 """Write a set of output lines.
88
89 You can use kwargs as a shorthand for mapping variables when formatting all
90 the strings in lines.
91
92 The 'out_lineno' kwarg is automatically added to reflect the current output
93 file line number. The 'out_next_lineno' kwarg is also automatically added
94 with the next output line number. The 'out_filename' kwarg is automatically
95 added with the output filename.
96 """
97 global out_lineno
98 output = []
99 for l in lines:
100 kwargs['out_lineno'] = out_lineno
101 kwargs['out_next_lineno'] = out_lineno + 1
102 kwargs['out_filename'] = out_filename
103 output.append(l % kwargs)
104 out_lineno += 1
105
106 out_fobj.writelines("\n".join(output) + "\n")
107
108 # We only want to allow standard C types or fixed sized
109 # integer types. We don't want QEMU specific types
110 # as we can't assume trace backends can resolve all the
111 # typedefs
112 ALLOWED_TYPES = [
113 "int",
114 "long",
115 "short",
116 "char",
117 "bool",
118 "unsigned",
119 "signed",
120 "int8_t",
121 "uint8_t",
122 "int16_t",
123 "uint16_t",
124 "int32_t",
125 "uint32_t",
126 "int64_t",
127 "uint64_t",
128 "void",
129 "size_t",
130 "ssize_t",
131 "uintptr_t",
132 "ptrdiff_t",
133 ]
134
135 C_TYPE_KEYWORDS = {"char", "int", "void", "short", "long", "signed", "unsigned"}
136
137 C_TO_RUST_TYPE_MAP = {
138 "int": "std::ffi::c_int",
139 "long": "std::ffi::c_long",
140 "long long": "std::ffi::c_longlong",
141 "short": "std::ffi::c_short",
142 "char": "std::ffi::c_char",
143 "bool": "bool",
144 "unsigned": "std::ffi::c_uint",
145 # multiple keywords, keep them sorted
146 "long unsigned": "std::ffi::c_long",
147 "long long unsigned": "std::ffi::c_ulonglong",
148 "short unsigned": "std::ffi::c_ushort",
149 "char unsigned": "u8",
150 "int8_t": "i8",
151 "uint8_t": "u8",
152 "int16_t": "i16",
153 "uint16_t": "u16",
154 "int32_t": "i32",
155 "uint32_t": "u32",
156 "int64_t": "i64",
157 "uint64_t": "u64",
158 "void": "()",
159 "size_t": "usize",
160 "ssize_t": "isize",
161 "uintptr_t": "usize",
162 "ptrdiff_t": "isize",
163 }
164
165 # Rust requires manual casting of <32-bit types when passing them to
166 # variable-argument functions.
167 RUST_VARARGS_SMALL_TYPES = {
168 "std::ffi::c_short",
169 "std::ffi::c_ushort",
170 "std::ffi::c_char",
171 "i8",
172 "u8",
173 "i16",
174 "u16",
175 "bool",
176 }
177
178 def validate_type(name):
179 bits = name.split(" ")
180 for bit in bits:
181 bit = re.sub(r"\*", "", bit)
182 if bit == "":
183 continue
184 if bit == "const":
185 continue
186 if bit not in ALLOWED_TYPES:
187 raise ValueError("Argument type '%s' is not allowed. "
188 "Only standard C types and fixed size integer "
189 "types should be used. struct, union, and "
190 "other complex pointer types should be "
191 "declared as 'void *'" % name)
192
193 def c_type_to_rust(name):
194 ptr = False
195 const = False
196 name = name.rstrip()
197 if name[-1] == '*':
198 name = name[:-1].rstrip()
199 ptr = True
200 if name[-1] == '*':
201 # pointers to pointers are the same as void*
202 name = "void"
203
204 bits = name.split()
205 if "const" in bits:
206 const = True
207 bits.remove("const")
208 if bits[0] in C_TYPE_KEYWORDS:
209 if "signed" in bits:
210 bits.remove("signed")
211 if len(bits) > 1 and "int" in bits:
212 bits.remove("int")
213 bits.sort()
214 name = ' '.join(bits)
215 else:
216 if len(bits) > 1:
217 raise ValueError("Invalid type '%s'." % name)
218 name = bits[0]
219
220 ty = C_TO_RUST_TYPE_MAP[name.strip()]
221 if ptr:
222 ty = f'*{"const" if const else "mut"} {ty}'
223 return ty
224
225 class Arguments:
226 """Event arguments description."""
227
228 def __init__(self, args):
229 """
230 Parameters
231 ----------
232 args :
233 List of (type, name) tuples or Arguments objects.
234 """
235 self._args = []
236 for arg in args:
237 if isinstance(arg, Arguments):
238 self._args.extend(arg._args)
239 else:
240 self._args.append(arg)
241
242 @staticmethod
243 def build(arg_str):
244 """Build and Arguments instance from an argument string.
245
246 Parameters
247 ----------
248 arg_str : str
249 String describing the event arguments.
250 """
251 res = []
252 for arg in arg_str.split(","):
253 arg = arg.strip()
254 if not arg:
255 raise ValueError("Empty argument (did you forget to use 'void'?)")
256 if arg == 'void':
257 continue
258
259 if '*' in arg:
260 arg_type, identifier = arg.rsplit('*', 1)
261 arg_type += '*'
262 identifier = identifier.strip()
263 else:
264 arg_type, identifier = arg.rsplit(None, 1)
265
266 validate_type(arg_type)
267 res.append((arg_type, identifier))
268 return Arguments(res)
269
270 def __getitem__(self, index):
271 if isinstance(index, slice):
272 return Arguments(self._args[index])
273 else:
274 return self._args[index]
275
276 def __iter__(self):
277 """Iterate over the (type, name) pairs."""
278 return iter(self._args)
279
280 def __len__(self):
281 """Number of arguments."""
282 return len(self._args)
283
284 def __str__(self):
285 """String suitable for declaring function arguments."""
286 def onearg(t, n):
287 if t[-1] == '*':
288 return "".join([t, n])
289 else:
290 return " ".join([t, n])
291
292 if len(self._args) == 0:
293 return "void"
294 else:
295 return ", ".join([ onearg(t, n) for t,n in self._args ])
296
297 def __repr__(self):
298 """Evaluable string representation for this object."""
299 return "Arguments(\"%s\")" % str(self)
300
301 def names(self):
302 """List of argument names."""
303 return [ name for _, name in self._args ]
304
305 def types(self):
306 """List of argument types."""
307 return [ type_ for type_, _ in self._args ]
308
309 def casted(self):
310 """List of argument names casted to their type."""
311 return ["(%s)%s" % (type_, name) for type_, name in self._args]
312
313 def rust_decl_extern(self):
314 """Return a Rust argument list for an extern "C" function"""
315 return ", ".join((f"_{name}: {c_type_to_rust(type_)}"
316 for type_, name in self._args))
317
318 def rust_decl(self):
319 """Return a Rust argument list for a tracepoint function"""
320 def decl_type(type_):
321 if type_ == "const char *":
322 return "&std::ffi::CStr"
323 return c_type_to_rust(type_)
324
325 return ", ".join((f"_{name}: {decl_type(type_)}"
326 for type_, name in self._args))
327
328 def rust_call_extern(self):
329 """Return a Rust argument list for a call to an extern "C" function"""
330 def rust_cast(name, type_):
331 if type_ == "const char *":
332 return f"_{name}.as_ptr()"
333 return f"_{name}"
334
335 return ", ".join((rust_cast(name, type_) for type_, name in self._args))
336
337 def rust_call_varargs(self):
338 """Return a Rust argument list for a call to a C varargs function"""
339 def rust_cast(name, type_):
340 if type_ == "const char *":
341 return f"_{name}.as_ptr()"
342
343 type_ = c_type_to_rust(type_)
344 if type_ in RUST_VARARGS_SMALL_TYPES:
345 return f"_{name} as std::ffi::c_int"
346 return f"_{name} /* as {type_} */"
347
348 return ", ".join((rust_cast(name, type_) for type_, name in self._args))
349
350
351 class Event(object):
352 """Event description.
353
354 Attributes
355 ----------
356 name : str
357 The event name.
358 fmt : str
359 The event format string.
360 properties : set(str)
361 Properties of the event.
362 args : Arguments
363 The event arguments.
364 lineno : int
365 The line number in the input file.
366 filename : str
367 The path to the input file.
368
369 """
370
371 _CRE = re.compile(r"((?P<props>[\w\s]+)\s+)?"
372 r"(?P<name>\w+)"
373 r"\((?P<args>[^)]*)\)"
374 r"\s*"
375 r"(?P<fmt>\".+)?"
376 r"\s*")
377
378 _VALID_PROPS = set(["disable"])
379
380 def __init__(self, name, props, fmt, args, lineno, filename):
381 """
382 Parameters
383 ----------
384 name : string
385 Event name.
386 props : list of str
387 Property names.
388 fmt : str
389 Event printing format string.
390 args : Arguments
391 Event arguments.
392 lineno : int
393 The line number in the input file.
394 filename : str
395 The path to the input file.
396
397 """
398 self.name = name
399 self.properties = props
400 self.fmt = fmt
401 self.args = args
402 self.lineno = int(lineno)
403 self.filename = str(filename)
404
405 if len(args) > 10:
406 raise ValueError("Event '%s' has more than maximum permitted "
407 "argument count" % name)
408
409 unknown_props = set(self.properties) - self._VALID_PROPS
410 if len(unknown_props) > 0:
411 raise ValueError("Unknown properties: %s"
412 % ", ".join(unknown_props))
413
414
415 @staticmethod
416 def build(line_str, lineno, filename):
417 """Build an Event instance from a string.
418
419 Parameters
420 ----------
421 line_str : str
422 Line describing the event.
423 lineno : int
424 Line number in input file.
425 filename : str
426 Path to input file.
427 """
428 m = Event._CRE.match(line_str)
429 assert m is not None
430 groups = m.groupdict('')
431
432 name = groups["name"]
433 props = groups["props"].split()
434 fmt = groups["fmt"]
435 if fmt.find("%m") != -1:
436 raise ValueError("Event format '%m' is forbidden, pass the error "
437 "as an explicit trace argument")
438 if fmt.endswith(r'\n"'):
439 raise ValueError("Event format must not end with a newline "
440 "character")
441 if '\\n' in fmt:
442 raise ValueError("Event format must not use new line character")
443
444 args = Arguments.build(groups["args"])
445
446 return Event(name, props, fmt, args, lineno, posix_relpath(filename))
447
448 def __repr__(self):
449 """Evaluable string representation for this object."""
450 return "Event('%s %s(%s) %s')" % (" ".join(self.properties),
451 self.name,
452 self.args,
453 self.fmt)
454 # Star matching on PRI is dangerous as one might have multiple
455 # arguments with that format, hence the non-greedy version of it.
456 _FMT = re.compile(r"(%[\d\.]*\w+|%.*?PRI\S+)")
457
458 def formats(self):
459 """List conversion specifiers in the argument print format string."""
460 return self._FMT.findall(self.fmt)
461
462 QEMU_TRACE = "trace_%(name)s"
463 QEMU_TRACE_TCG = QEMU_TRACE + "_tcg"
464 QEMU_RUST_DSTATE = "trace_%(name)s_enabled"
465 QEMU_DSTATE = "_TRACE_%(NAME)s_DSTATE"
466 QEMU_BACKEND_DSTATE = "TRACE_%(NAME)s_BACKEND_DSTATE"
467 QEMU_EVENT = "_TRACE_%(NAME)s_EVENT"
468
469 def api(self, fmt=None):
470 if fmt is None:
471 fmt = Event.QEMU_TRACE
472 return fmt % {"name": self.name, "NAME": self.name.upper()}
473
474
475 def read_events(fobj, fname):
476 """Generate the output for the given (format, backends) pair.
477
478 Parameters
479 ----------
480 fobj : file
481 Event description file.
482 fname : str
483 Name of event file
484
485 Returns a list of Event objects
486 """
487
488 events = []
489 for lineno, line in enumerate(fobj, 1):
490 if line[-1] != '\n':
491 raise ValueError("%s does not end with a new line" % fname)
492 if not line.strip():
493 continue
494 if line.lstrip().startswith('#'):
495 continue
496
497 try:
498 event = Event.build(line, lineno, fname)
499 except ValueError as e:
500 arg0 = 'Error at %s:%d: %s' % (fname, lineno, e.args[0])
501 e.args = (arg0,) + e.args[1:]
502 raise
503
504 events.append(event)
505
506 return events
507
508
509 class TracetoolError (Exception):
510 """Exception for calls to generate."""
511 pass
512
513
514 def try_import(mod_name, attr_name=None, attr_default=None):
515 """Try to import a module and get an attribute from it.
516
517 Parameters
518 ----------
519 mod_name : str
520 Module name.
521 attr_name : str, optional
522 Name of an attribute in the module.
523 attr_default : optional
524 Default value if the attribute does not exist in the module.
525
526 Returns
527 -------
528 A pair indicating whether the module could be imported and the module or
529 object or attribute value.
530 """
531 try:
532 module = __import__(mod_name, globals(), locals(), ["__package__"])
533 if attr_name is None:
534 return True, module
535 return True, getattr(module, str(attr_name), attr_default)
536 except ImportError:
537 return False, None
538
539
540 def generate(events, group, format, backends,
541 binary=None, probe_prefix=None):
542 """Generate the output for the given (format, backends) pair.
543
544 Parameters
545 ----------
546 events : list
547 list of Event objects to generate for
548 group: str
549 Name of the tracing group
550 format : str
551 Output format name.
552 backends : list
553 Output backend names.
554 binary : str or None
555 See tracetool.backend.dtrace.BINARY.
556 probe_prefix : str or None
557 See tracetool.backend.dtrace.PROBEPREFIX.
558 """
559 # fix strange python error (UnboundLocalError tracetool)
560 import tracetool
561
562 format = str(format)
563 if len(format) == 0:
564 raise TracetoolError("format not set")
565 if not tracetool.format.exists(format):
566 raise TracetoolError("unknown format: %s" % format)
567
568 if len(backends) == 0:
569 raise TracetoolError("no backends specified")
570 for backend in backends:
571 if not tracetool.backend.exists(backend):
572 raise TracetoolError("unknown backend: %s" % backend)
573 backend = tracetool.backend.Wrapper(backends, format)
574
575 import tracetool.backend.dtrace
576 tracetool.backend.dtrace.BINARY = binary
577 tracetool.backend.dtrace.PROBEPREFIX = probe_prefix
578
579 tracetool.format.generate(events, format, backend, group)
580
581 def posix_relpath(path, start=None):
582 try:
583 path = os.path.relpath(path, start)
584 except ValueError:
585 pass
586 return PurePath(path).as_posix()