Files
revng-revng/scripts/PipelineC_add_tracing.py
Giacomo Vercesi a2b7d0e0bf PipelineC: add tracing
Add support for tracing onto the PipelineC. This is done by:
1. Creating wrapper functions for each PipelineC function with the
   script in `scripts/PipelineC_add_tracing.py`. These will call a
   special function called `wrap` which will ultimately call a method
   with a `_` prepended to the name
2. Conversion of all PipelineC methods in `PipelineC.cpp` to `static`
   and their rename with a `_` in front, in order for them to work with
   the wrapper function in (1)
3. Generation of 2 additional include files, one for types and one for
   functions, to be used by users of tracing files in order to have
   introspection.

These steps allow the creation of a trace file with the use of the
`REVNG_C_API_TRACE_PATH` environment variable. The traces can then be
used in conjunction with the `revng trace run` and `revng trace
inspect` commands.
2023-04-20 14:43:13 +02:00

132 lines
4.2 KiB
Python

#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
import argparse
import re
from dataclasses import dataclass
from pathlib import Path
from typing import List
from pycparser.c_ast import FuncDecl, NodeVisitor, TypeDecl, Typedef
from pycparser.c_generator import CGenerator
from pycparser.c_parser import CParser
# Regex that's used to remove all comments within the source headers
comment_re = re.compile(r"/\*.*?\*/|//([^\n\\]|\\.)*?$", re.DOTALL | re.MULTILINE)
# Regex matching function names in PipelineC
name_re = re.compile("[a-zA-Z0-9_]+$")
@dataclass
class Argument:
# Name of the argument (e.g. "foo")
name: str
# Declaration of the argument (with type, e.g. "int *foo[]")
decl: str
@dataclass
class Function:
# Name of the function
name: str
# List of arguments
arguments: List[Argument]
# Return type
return_type: str
class Visitor(NodeVisitor):
def __init__(self):
super().__init__()
self.c_gen = CGenerator()
self.functions: List[Function] = []
self.types: List[str] = []
def visit_FuncDecl(self, node: FuncDecl): # noqa: N802
name = node.type.declname if isinstance(node.type, TypeDecl) else node.type.type.declname
return_type = self.c_gen.visit(node.type)
arguments = []
if node.args is not None:
arguments = [self.parse_argument(arg) for arg in node.args.params]
self.functions.append(Function(name, arguments, return_type))
def visit_Typedef(self, node: Typedef): # noqa: N802
if node.name not in {"bool", "uint8_t", "uint32_t", "uint64_t"}:
self.types.append(node.name)
def parse_argument(self, arg: str) -> Argument:
string: str = self.c_gen.visit(arg)
name_match = name_re.search(string.removesuffix("[]"))
assert name_match is not None
return Argument(name_match[0], string)
def parse_headers(headers: List[Path]):
# Fixup definitions for types not present in C99
text = """
typedef struct bool bool;
typedef struct uint8_t uint8_t;
typedef struct uint32_t uint32_t;
typedef struct uint64_t uint64_t;
"""
for header in headers:
text += header.read_text()
# Remove comments and pre-processor directives
text = comment_re.sub("", text)
text = "".join(
ln if not (ln.startswith("#") or ln.startswith("LENGTH_HINT")) else "\n"
for ln in text.splitlines(True)
)
parser = CParser()
visitor = Visitor()
visitor.visit(parser.parse(text, "input"))
return (visitor.functions, visitor.types)
def generate(output: Path, functions: List[Function], types: List[str]):
# This file exposes a wrapper for each PipelineC function
# e.g. `void foo(int i) { return wrap<"foo">(_foo, i); }`
with open(output / "Wrappers.h", "w") as output_file:
for f in functions:
args = ", ".join(a.decl for a in f.arguments)
arg_names = "".join(f", {a.name}" for a in f.arguments)
# Write the function wrapper
output_file.write(
f"{f.return_type} {f.name}({args}) "
f'{{ return wrap<"{f.name}">(_{f.name}{arg_names}); }};\n'
)
# Create an include file that contains the string `registerFunction<Name>(function)`
# This is to be used with trace-runner-like programs to allow function enumeration
with open(output / "Functions.inc", "w") as output_file:
for f in functions:
output_file.write(f"FUNCTION({f.name})\n")
with open(output / "Types.inc", "w") as output_file:
for type_name in types:
output_file.write(f"TYPE({type_name})\n")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--header", type=str, action="append", help="Header file to parse")
parser.add_argument("output", type=str, help="Output File")
args = parser.parse_args()
if args.header is None or len(args.header) == 0:
raise ValueError("At least one header must be specified")
functions, types = parse_headers([Path(p) for p in args.header])
generate(
Path(args.output),
functions,
types,
)
if __name__ == "__main__":
main()