| #!/usr/bin/env python3 |
| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| '''A wrapper around riscv32-unknown-elf-objdump for OTBN''' |
| |
| import re |
| import subprocess |
| import sys |
| from typing import List |
| |
| from shared.insn_yaml import InsnsFile, load_insns_yaml |
| from shared.toolchain import find_tool |
| |
| |
| def snoop_disasm_flags(argv: List[str]) -> bool: |
| '''Look through objdump's flags for -d, -D etc.''' |
| for arg in argv: |
| # We're only interested in flags (starting with '-' or '--') |
| if not arg.startswith('-'): |
| continue |
| |
| # If the argument starts with a single hyphen, the following characters |
| # explode into multiple flags (so -fhSD is the same as -f -h -S -D). |
| # Handle -d and -D here. |
| if not arg.startswith('--'): |
| if 'd' in arg[1:] or 'D' in arg[1:]: |
| return True |
| else: |
| continue |
| |
| # The argument starts with two hyphens. |
| if arg in ['--disassemble', '--disassemble-all']: |
| return True |
| |
| # --disassemble=symbol |
| if arg.startswith('--disassemble='): |
| return True |
| |
| return False |
| |
| |
| # OTBN instructions are 32 bit wide, so there's just one "word" in the second |
| # column. The stuff that gets passed through looks like this: |
| # |
| # 84: 8006640b 0x8006640b |
| # |
| # We don't use a back-ref for the second copy of the data, because if the raw |
| # part has leading zeros, they don't appear there. For example: |
| # |
| # 6d0: 0000418b 0x418b |
| # |
| _RAW_INSN_RE = re.compile(r'([\s]*([0-9a-f]+):[\s]+([0-9a-f]{8})[\s]+)' |
| r'0x[0-9a-f]+\s*$') |
| |
| |
| def transform_disasm_line(line: str, insns_file: InsnsFile) -> str: |
| '''Transform filter to insert OTBN disasm as needed''' |
| match = _RAW_INSN_RE.match(line) |
| if match is None: |
| return line |
| |
| # match.group(3) is the raw instruction word. Parse it as an integer. It |
| # was exactly 8 hex characters, so will fit in a u32. |
| raw = int(match.group(3), 16) |
| assert 0 <= raw < (1 << 32) |
| |
| mnem = insns_file.mnem_for_word(raw) |
| if mnem is None: |
| # No match for this instruction pattern. Leave as-is. |
| return line |
| |
| insn = insns_file.mnemonic_to_insn[mnem] |
| |
| # match.group(2) is the PC in hex. |
| pc = int(match.group(2), 16) |
| |
| # Extract the encoded values. We know we have an encoding (otherwise the |
| # instruction wouldn't have appeared in the the masks list). |
| assert insn.encoding is not None |
| enc_vals = insn.encoding.extract_operands(raw) |
| |
| # Make sense of these encoded values as "operand values" (doing any |
| # shifting, sign interpretation etc.) |
| op_vals = insn.enc_vals_to_op_vals(pc, enc_vals) |
| |
| # Similarly, we know we have a syntax (again, get_insn_masks requires it). |
| # The rendering of the fields is done by the syntax object. |
| return match.group(1) + insn.disassemble(pc, op_vals) |
| |
| |
| def main() -> int: |
| args = sys.argv[1:] |
| has_disasm = snoop_disasm_flags(args) |
| |
| objdump = find_tool('objdump') |
| try: |
| if not has_disasm: |
| cmd = [objdump] + args |
| return subprocess.run(cmd).returncode |
| else: |
| cmd = [objdump, '-M', 'numeric,no-aliases'] + args |
| proc = subprocess.run(cmd, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| universal_newlines=True) |
| if proc.returncode: |
| # Dump any lines that objdump wrote before it died |
| sys.stderr.write(proc.stderr) |
| sys.stdout.write(proc.stdout) |
| return proc.returncode |
| except FileNotFoundError: |
| sys.stderr.write('Unknown command: {!r}.\n' |
| .format(objdump)) |
| return 127 |
| |
| try: |
| insns_file = load_insns_yaml() |
| except RuntimeError as err: |
| sys.stderr.write('{}\n'.format(err)) |
| return 1 |
| |
| # If we get here, we think we're disassembling something, objdump ran |
| # successfully and we have its results in proc.stdout |
| for line in proc.stdout.split('\n'): |
| transformed = transform_disasm_line(line, insns_file) |
| sys.stdout.write(transformed + '\n') |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |