Cache dispatch table between expr parses
authorEli Bendersky <eliben@gmail.com>
Sat, 14 Mar 2020 12:37:53 +0000 (05:37 -0700)
committerEli Bendersky <eliben@gmail.com>
Sat, 14 Mar 2020 12:37:53 +0000 (05:37 -0700)
In descriptions, ExprDumper invokes parse_expr many times on small
expressions. Initializing the dispatch table for every parse is
wasteful.

Wrap parse_expr with a simple object that generates and caches the
dispatch table during initialization. parse_expr remains stateless.

Updates #298

elftools/dwarf/descriptions.py
elftools/dwarf/dwarf_expr.py
test/test_dwarf_expr.py

index 08bfccc4c5c3db187c05471a42383c04619ed445..fdf5768e71e95c8a33eed7f27997e0dff88d2e00 100644 (file)
@@ -9,7 +9,7 @@
 from collections import defaultdict
 
 from .constants import *
-from .dwarf_expr import parse_expr
+from .dwarf_expr import DWARFExprParser
 from .die import DIE
 from ..common.utils import preserve_stream_pos, dwarf_assert
 from ..common.py3compat import bytes2str
@@ -540,6 +540,7 @@ class ExprDumper(object):
     """
     def __init__(self, structs):
         self.structs = structs
+        self.expr_parser = DWARFExprParser(self.structs)
         self._init_lookups()
         self._str_parts = []
 
@@ -547,7 +548,7 @@ class ExprDumper(object):
         """ Parse and process a DWARF expression. expr should be a list of
             (integer) byte values.
         """
-        parsed = parse_expr(expr, self.structs)
+        parsed = self.expr_parser.parse_expr(expr)
         for deo in parsed:
             self._str_parts.append(self._dump_to_string(deo.op, deo.op_name, deo.args))
 
index f92d597d550470191d01a7faf434ab7ad3a0339f..6de0b59a8fd9a589b02011156de90d01a81ce92a 100644 (file)
@@ -106,37 +106,45 @@ DW_OP_opcode2name = dict((v, k) for k, v in iteritems(DW_OP_name2opcode))
 
 # Each parsed DWARF expression is returned as this type with its numeric opcode,
 # op name (as a string) and a list of arguments.
-DwarfExprOp = namedtuple('DwarfExprOp', 'op op_name args')
+DWARFExprOp = namedtuple('DWARFExprOp', 'op op_name args')
 
 
-def parse_expr(expr, structs):
-    """ Parses expr (a list of integers) into a list of DwarfExprOp.
+class DWARFExprParser(object):
+    """DWARF expression parser.
 
-    The list can potentially be nested.
+    When initialized, requires structs to cache a dispatch table. After that,
+    parse_expr can be called repeatedly - it's stateless.
     """
-    dispatch_table = _init_dispatch_table(structs)
 
-    stream = BytesIO(bytelist2string(expr))
-    parsed = []
+    def __init__(self, structs):
+        self._dispatch_table = _init_dispatch_table(structs)
 
-    while True:
-        # Get the next opcode from the stream. If nothing is left in the
-        # stream, we're done.
-        byte = stream.read(1)
-        if len(byte) == 0:
-            break
+    def parse_expr(self, expr):
+        """ Parses expr (a list of integers) into a list of DWARFExprOp.
 
-        # Decode the opcode and its name.
-        op = ord(byte)
-        op_name = DW_OP_opcode2name.get(op, 'OP:0x%x' % op)
+        The list can potentially be nested.
+        """
+        stream = BytesIO(bytelist2string(expr))
+        parsed = []
 
-        # Use dispatch table to parse args.
-        arg_parser = dispatch_table[op]
-        args = arg_parser(stream)
+        while True:
+            # Get the next opcode from the stream. If nothing is left in the
+            # stream, we're done.
+            byte = stream.read(1)
+            if len(byte) == 0:
+                break
 
-        parsed.append(DwarfExprOp(op=op, op_name=op_name, args=args))
+            # Decode the opcode and its name.
+            op = ord(byte)
+            op_name = DW_OP_opcode2name.get(op, 'OP:0x%x' % op)
 
-    return parsed
+            # Use dispatch table to parse args.
+            arg_parser = self._dispatch_table[op]
+            args = arg_parser(stream)
+
+            parsed.append(DWARFExprOp(op=op, op_name=op_name, args=args))
+
+        return parsed
 
 
 def _init_dispatch_table(structs):
index 1bd80d78de172b32bd96814fba9d1e68f76a360a..0cc7f61467fe5436ebfa935d0494a277cb5bfba8 100644 (file)
@@ -7,7 +7,7 @@
 import unittest
 
 from elftools.dwarf.descriptions import ExprDumper, set_global_machine_arch
-from elftools.dwarf.dwarf_expr import parse_expr, DwarfExprOp
+from elftools.dwarf.dwarf_expr import DWARFExprParser, DWARFExprOp
 from elftools.dwarf.structs import DWARFStructs
 
 
@@ -77,11 +77,12 @@ class TestParseExpr(unittest.TestCase):
         set_global_machine_arch('x64')
 
     def test_single(self):
-        lst = parse_expr([0x1b], self.structs32)
-        self.assertEqual(lst, [DwarfExprOp(op=0x1B, op_name='DW_OP_div', args=[])])
+        p = DWARFExprParser(self.structs32)
+        lst = p.parse_expr([0x1b])
+        self.assertEqual(lst, [DWARFExprOp(op=0x1B, op_name='DW_OP_div', args=[])])
 
-        lst = parse_expr([0x90, 16], self.structs32)
-        self.assertEqual(lst, [DwarfExprOp(op=0x90, op_name='DW_OP_regx', args=[16])])
+        lst = p.parse_expr([0x90, 16])
+        self.assertEqual(lst, [DWARFExprOp(op=0x90, op_name='DW_OP_regx', args=[16])])
 
 
 if __name__ == '__main__':