From 2c8f7dc9b51df9448e74e730f787e7db9db0ef19 Mon Sep 17 00:00:00 2001 From: Eli Bendersky Date: Sat, 14 Mar 2020 05:37:53 -0700 Subject: [PATCH] Cache dispatch table between expr parses 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 | 5 ++-- elftools/dwarf/dwarf_expr.py | 50 ++++++++++++++++++++-------------- test/test_dwarf_expr.py | 11 ++++---- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/elftools/dwarf/descriptions.py b/elftools/dwarf/descriptions.py index 08bfccc..fdf5768 100644 --- a/elftools/dwarf/descriptions.py +++ b/elftools/dwarf/descriptions.py @@ -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)) diff --git a/elftools/dwarf/dwarf_expr.py b/elftools/dwarf/dwarf_expr.py index f92d597..6de0b59 100644 --- a/elftools/dwarf/dwarf_expr.py +++ b/elftools/dwarf/dwarf_expr.py @@ -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): diff --git a/test/test_dwarf_expr.py b/test/test_dwarf_expr.py index 1bd80d7..0cc7f61 100644 --- a/test/test_dwarf_expr.py +++ b/test/test_dwarf_expr.py @@ -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__': -- 2.30.2