| # -*- coding: utf-8 -*- | 
 |  | 
 | """ | 
 | Machinery for generating tracing-related intermediate files. | 
 | """ | 
 |  | 
 | __author__     = "Lluís Vilanova <vilanova@ac.upc.edu>" | 
 | __copyright__  = "Copyright 2012-2017, Lluís Vilanova <vilanova@ac.upc.edu>" | 
 | __license__    = "GPL version 2 or (at your option) any later version" | 
 |  | 
 | __maintainer__ = "Stefan Hajnoczi" | 
 | __email__      = "stefanha@redhat.com" | 
 |  | 
 |  | 
 | import re | 
 | import sys | 
 | import weakref | 
 |  | 
 | import tracetool.format | 
 | import tracetool.backend | 
 |  | 
 |  | 
 | def error_write(*lines): | 
 |     """Write a set of error lines.""" | 
 |     sys.stderr.writelines("\n".join(lines) + "\n") | 
 |  | 
 | def error(*lines): | 
 |     """Write a set of error lines and exit.""" | 
 |     error_write(*lines) | 
 |     sys.exit(1) | 
 |  | 
 |  | 
 | out_lineno = 1 | 
 | out_filename = '<none>' | 
 | out_fobj = sys.stdout | 
 |  | 
 | def out_open(filename): | 
 |     global out_filename, out_fobj | 
 |     out_filename = filename | 
 |     out_fobj = open(filename, 'wt') | 
 |  | 
 | def out(*lines, **kwargs): | 
 |     """Write a set of output lines. | 
 |  | 
 |     You can use kwargs as a shorthand for mapping variables when formatting all | 
 |     the strings in lines. | 
 |  | 
 |     The 'out_lineno' kwarg is automatically added to reflect the current output | 
 |     file line number. The 'out_next_lineno' kwarg is also automatically added | 
 |     with the next output line number. The 'out_filename' kwarg is automatically | 
 |     added with the output filename. | 
 |     """ | 
 |     global out_lineno | 
 |     output = [] | 
 |     for l in lines: | 
 |         kwargs['out_lineno'] = out_lineno | 
 |         kwargs['out_next_lineno'] = out_lineno + 1 | 
 |         kwargs['out_filename'] = out_filename | 
 |         output.append(l % kwargs) | 
 |         out_lineno += 1 | 
 |  | 
 |     out_fobj.writelines("\n".join(output) + "\n") | 
 |  | 
 | # We only want to allow standard C types or fixed sized | 
 | # integer types. We don't want QEMU specific types | 
 | # as we can't assume trace backends can resolve all the | 
 | # typedefs | 
 | ALLOWED_TYPES = [ | 
 |     "int", | 
 |     "long", | 
 |     "short", | 
 |     "char", | 
 |     "bool", | 
 |     "unsigned", | 
 |     "signed", | 
 |     "int8_t", | 
 |     "uint8_t", | 
 |     "int16_t", | 
 |     "uint16_t", | 
 |     "int32_t", | 
 |     "uint32_t", | 
 |     "int64_t", | 
 |     "uint64_t", | 
 |     "void", | 
 |     "size_t", | 
 |     "ssize_t", | 
 |     "uintptr_t", | 
 |     "ptrdiff_t", | 
 | ] | 
 |  | 
 | def validate_type(name): | 
 |     bits = name.split(" ") | 
 |     for bit in bits: | 
 |         bit = re.sub("\*", "", bit) | 
 |         if bit == "": | 
 |             continue | 
 |         if bit == "const": | 
 |             continue | 
 |         if bit not in ALLOWED_TYPES: | 
 |             raise ValueError("Argument type '%s' is not allowed. " | 
 |                              "Only standard C types and fixed size integer " | 
 |                              "types should be used. struct, union, and " | 
 |                              "other complex pointer types should be " | 
 |                              "declared as 'void *'" % name) | 
 |  | 
 | class Arguments: | 
 |     """Event arguments description.""" | 
 |  | 
 |     def __init__(self, args): | 
 |         """ | 
 |         Parameters | 
 |         ---------- | 
 |         args : | 
 |             List of (type, name) tuples or Arguments objects. | 
 |         """ | 
 |         self._args = [] | 
 |         for arg in args: | 
 |             if isinstance(arg, Arguments): | 
 |                 self._args.extend(arg._args) | 
 |             else: | 
 |                 self._args.append(arg) | 
 |  | 
 |     def copy(self): | 
 |         """Create a new copy.""" | 
 |         return Arguments(list(self._args)) | 
 |  | 
 |     @staticmethod | 
 |     def build(arg_str): | 
 |         """Build and Arguments instance from an argument string. | 
 |  | 
 |         Parameters | 
 |         ---------- | 
 |         arg_str : str | 
 |             String describing the event arguments. | 
 |         """ | 
 |         res = [] | 
 |         for arg in arg_str.split(","): | 
 |             arg = arg.strip() | 
 |             if not arg: | 
 |                 raise ValueError("Empty argument (did you forget to use 'void'?)") | 
 |             if arg == 'void': | 
 |                 continue | 
 |  | 
 |             if '*' in arg: | 
 |                 arg_type, identifier = arg.rsplit('*', 1) | 
 |                 arg_type += '*' | 
 |                 identifier = identifier.strip() | 
 |             else: | 
 |                 arg_type, identifier = arg.rsplit(None, 1) | 
 |  | 
 |             validate_type(arg_type) | 
 |             res.append((arg_type, identifier)) | 
 |         return Arguments(res) | 
 |  | 
 |     def __getitem__(self, index): | 
 |         if isinstance(index, slice): | 
 |             return Arguments(self._args[index]) | 
 |         else: | 
 |             return self._args[index] | 
 |  | 
 |     def __iter__(self): | 
 |         """Iterate over the (type, name) pairs.""" | 
 |         return iter(self._args) | 
 |  | 
 |     def __len__(self): | 
 |         """Number of arguments.""" | 
 |         return len(self._args) | 
 |  | 
 |     def __str__(self): | 
 |         """String suitable for declaring function arguments.""" | 
 |         if len(self._args) == 0: | 
 |             return "void" | 
 |         else: | 
 |             return ", ".join([ " ".join([t, n]) for t,n in self._args ]) | 
 |  | 
 |     def __repr__(self): | 
 |         """Evaluable string representation for this object.""" | 
 |         return "Arguments(\"%s\")" % str(self) | 
 |  | 
 |     def names(self): | 
 |         """List of argument names.""" | 
 |         return [ name for _, name in self._args ] | 
 |  | 
 |     def types(self): | 
 |         """List of argument types.""" | 
 |         return [ type_ for type_, _ in self._args ] | 
 |  | 
 |     def casted(self): | 
 |         """List of argument names casted to their type.""" | 
 |         return ["(%s)%s" % (type_, name) for type_, name in self._args] | 
 |  | 
 |  | 
 | class Event(object): | 
 |     """Event description. | 
 |  | 
 |     Attributes | 
 |     ---------- | 
 |     name : str | 
 |         The event name. | 
 |     fmt : str | 
 |         The event format string. | 
 |     properties : set(str) | 
 |         Properties of the event. | 
 |     args : Arguments | 
 |         The event arguments. | 
 |     lineno : int | 
 |         The line number in the input file. | 
 |     filename : str | 
 |         The path to the input file. | 
 |  | 
 |     """ | 
 |  | 
 |     _CRE = re.compile("((?P<props>[\w\s]+)\s+)?" | 
 |                       "(?P<name>\w+)" | 
 |                       "\((?P<args>[^)]*)\)" | 
 |                       "\s*" | 
 |                       "(?:(?:(?P<fmt_trans>\".+),)?\s*(?P<fmt>\".+))?" | 
 |                       "\s*") | 
 |  | 
 |     _VALID_PROPS = set(["disable", "vcpu"]) | 
 |  | 
 |     def __init__(self, name, props, fmt, args, lineno, filename, orig=None, | 
 |                  event_trans=None, event_exec=None): | 
 |         """ | 
 |         Parameters | 
 |         ---------- | 
 |         name : string | 
 |             Event name. | 
 |         props : list of str | 
 |             Property names. | 
 |         fmt : str, list of str | 
 |             Event printing format string(s). | 
 |         args : Arguments | 
 |             Event arguments. | 
 |         lineno : int | 
 |             The line number in the input file. | 
 |         filename : str | 
 |             The path to the input file. | 
 |         orig : Event or None | 
 |             Original Event before transformation/generation. | 
 |         event_trans : Event or None | 
 |             Generated translation-time event ("tcg" property). | 
 |         event_exec : Event or None | 
 |             Generated execution-time event ("tcg" property). | 
 |  | 
 |         """ | 
 |         self.name = name | 
 |         self.properties = props | 
 |         self.fmt = fmt | 
 |         self.args = args | 
 |         self.lineno = int(lineno) | 
 |         self.filename = str(filename) | 
 |         self.event_trans = event_trans | 
 |         self.event_exec = event_exec | 
 |  | 
 |         if len(args) > 10: | 
 |             raise ValueError("Event '%s' has more than maximum permitted " | 
 |                              "argument count" % name) | 
 |  | 
 |         if orig is None: | 
 |             self.original = weakref.ref(self) | 
 |         else: | 
 |             self.original = orig | 
 |  | 
 |         unknown_props = set(self.properties) - self._VALID_PROPS | 
 |         if len(unknown_props) > 0: | 
 |             raise ValueError("Unknown properties: %s" | 
 |                              % ", ".join(unknown_props)) | 
 |         assert isinstance(self.fmt, str) or len(self.fmt) == 2 | 
 |  | 
 |     def copy(self): | 
 |         """Create a new copy.""" | 
 |         return Event(self.name, list(self.properties), self.fmt, | 
 |                      self.args.copy(), self.lineno, self.filename, | 
 |                      self, self.event_trans, self.event_exec) | 
 |  | 
 |     @staticmethod | 
 |     def build(line_str, lineno, filename): | 
 |         """Build an Event instance from a string. | 
 |  | 
 |         Parameters | 
 |         ---------- | 
 |         line_str : str | 
 |             Line describing the event. | 
 |         lineno : int | 
 |             Line number in input file. | 
 |         filename : str | 
 |             Path to input file. | 
 |         """ | 
 |         m = Event._CRE.match(line_str) | 
 |         assert m is not None | 
 |         groups = m.groupdict('') | 
 |  | 
 |         name = groups["name"] | 
 |         props = groups["props"].split() | 
 |         fmt = groups["fmt"] | 
 |         fmt_trans = groups["fmt_trans"] | 
 |         if fmt.find("%m") != -1 or fmt_trans.find("%m") != -1: | 
 |             raise ValueError("Event format '%m' is forbidden, pass the error " | 
 |                              "as an explicit trace argument") | 
 |         if fmt.endswith(r'\n"'): | 
 |             raise ValueError("Event format must not end with a newline " | 
 |                              "character") | 
 |  | 
 |         if len(fmt_trans) > 0: | 
 |             fmt = [fmt_trans, fmt] | 
 |         args = Arguments.build(groups["args"]) | 
 |  | 
 |         event = Event(name, props, fmt, args, lineno, filename) | 
 |  | 
 |         # add implicit arguments when using the 'vcpu' property | 
 |         import tracetool.vcpu | 
 |         event = tracetool.vcpu.transform_event(event) | 
 |  | 
 |         return event | 
 |  | 
 |     def __repr__(self): | 
 |         """Evaluable string representation for this object.""" | 
 |         if isinstance(self.fmt, str): | 
 |             fmt = self.fmt | 
 |         else: | 
 |             fmt = "%s, %s" % (self.fmt[0], self.fmt[1]) | 
 |         return "Event('%s %s(%s) %s')" % (" ".join(self.properties), | 
 |                                           self.name, | 
 |                                           self.args, | 
 |                                           fmt) | 
 |     # Star matching on PRI is dangerous as one might have multiple | 
 |     # arguments with that format, hence the non-greedy version of it. | 
 |     _FMT = re.compile("(%[\d\.]*\w+|%.*?PRI\S+)") | 
 |  | 
 |     def formats(self): | 
 |         """List conversion specifiers in the argument print format string.""" | 
 |         assert not isinstance(self.fmt, list) | 
 |         return self._FMT.findall(self.fmt) | 
 |  | 
 |     QEMU_TRACE               = "trace_%(name)s" | 
 |     QEMU_TRACE_NOCHECK       = "_nocheck__" + QEMU_TRACE | 
 |     QEMU_TRACE_TCG           = QEMU_TRACE + "_tcg" | 
 |     QEMU_DSTATE              = "_TRACE_%(NAME)s_DSTATE" | 
 |     QEMU_BACKEND_DSTATE      = "TRACE_%(NAME)s_BACKEND_DSTATE" | 
 |     QEMU_EVENT               = "_TRACE_%(NAME)s_EVENT" | 
 |  | 
 |     def api(self, fmt=None): | 
 |         if fmt is None: | 
 |             fmt = Event.QEMU_TRACE | 
 |         return fmt % {"name": self.name, "NAME": self.name.upper()} | 
 |  | 
 |  | 
 | def read_events(fobj, fname): | 
 |     """Generate the output for the given (format, backends) pair. | 
 |  | 
 |     Parameters | 
 |     ---------- | 
 |     fobj : file | 
 |         Event description file. | 
 |     fname : str | 
 |         Name of event file | 
 |  | 
 |     Returns a list of Event objects | 
 |     """ | 
 |  | 
 |     events = [] | 
 |     for lineno, line in enumerate(fobj, 1): | 
 |         if line[-1] != '\n': | 
 |             raise ValueError("%s does not end with a new line" % fname) | 
 |         if not line.strip(): | 
 |             continue | 
 |         if line.lstrip().startswith('#'): | 
 |             continue | 
 |  | 
 |         try: | 
 |             event = Event.build(line, lineno, fname) | 
 |         except ValueError as e: | 
 |             arg0 = 'Error at %s:%d: %s' % (fname, lineno, e.args[0]) | 
 |             e.args = (arg0,) + e.args[1:] | 
 |             raise | 
 |  | 
 |         events.append(event) | 
 |  | 
 |     return events | 
 |  | 
 |  | 
 | class TracetoolError (Exception): | 
 |     """Exception for calls to generate.""" | 
 |     pass | 
 |  | 
 |  | 
 | def try_import(mod_name, attr_name=None, attr_default=None): | 
 |     """Try to import a module and get an attribute from it. | 
 |  | 
 |     Parameters | 
 |     ---------- | 
 |     mod_name : str | 
 |         Module name. | 
 |     attr_name : str, optional | 
 |         Name of an attribute in the module. | 
 |     attr_default : optional | 
 |         Default value if the attribute does not exist in the module. | 
 |  | 
 |     Returns | 
 |     ------- | 
 |     A pair indicating whether the module could be imported and the module or | 
 |     object or attribute value. | 
 |     """ | 
 |     try: | 
 |         module = __import__(mod_name, globals(), locals(), ["__package__"]) | 
 |         if attr_name is None: | 
 |             return True, module | 
 |         return True, getattr(module, str(attr_name), attr_default) | 
 |     except ImportError: | 
 |         return False, None | 
 |  | 
 |  | 
 | def generate(events, group, format, backends, | 
 |              binary=None, probe_prefix=None): | 
 |     """Generate the output for the given (format, backends) pair. | 
 |  | 
 |     Parameters | 
 |     ---------- | 
 |     events : list | 
 |         list of Event objects to generate for | 
 |     group: str | 
 |         Name of the tracing group | 
 |     format : str | 
 |         Output format name. | 
 |     backends : list | 
 |         Output backend names. | 
 |     binary : str or None | 
 |         See tracetool.backend.dtrace.BINARY. | 
 |     probe_prefix : str or None | 
 |         See tracetool.backend.dtrace.PROBEPREFIX. | 
 |     """ | 
 |     # fix strange python error (UnboundLocalError tracetool) | 
 |     import tracetool | 
 |  | 
 |     format = str(format) | 
 |     if len(format) == 0: | 
 |         raise TracetoolError("format not set") | 
 |     if not tracetool.format.exists(format): | 
 |         raise TracetoolError("unknown format: %s" % format) | 
 |  | 
 |     if len(backends) == 0: | 
 |         raise TracetoolError("no backends specified") | 
 |     for backend in backends: | 
 |         if not tracetool.backend.exists(backend): | 
 |             raise TracetoolError("unknown backend: %s" % backend) | 
 |     backend = tracetool.backend.Wrapper(backends, format) | 
 |  | 
 |     import tracetool.backend.dtrace | 
 |     tracetool.backend.dtrace.BINARY = binary | 
 |     tracetool.backend.dtrace.PROBEPREFIX = probe_prefix | 
 |  | 
 |     tracetool.format.generate(events, format, backend, group) |