mirror of https://github.com/ARMmbed/mbed-os.git
Add executable analaysis tool for floating point checks.
parent
c99b150bcf
commit
487bbe6323
|
@ -0,0 +1,168 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2019 Arm Limited and Contributors. All rights reserved.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""Script for checking for floating point symbols in ELF files."""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
|
||||
FLOATING_POINT_SYMBOL_REGEX = r"__aeabi_(cd.+|cf.+|h2f.+|d.+|f.+|.+2d|.+2f)"
|
||||
OBJECT_FILE_ANALYSIS_CMD = ["objdump", "-t"]
|
||||
|
||||
class SymbolParser:
|
||||
"""Parse the given ELF format file."""
|
||||
|
||||
def get_symbols_from_table(self, symbol_table, symbol_regex):
|
||||
"""Get symbols matching a regular expression pattern from a table."""
|
||||
pattern = re.compile(symbol_regex, re.X)
|
||||
matched_symbols = []
|
||||
symbol_table_lines = symbol_table.split("\n")
|
||||
for symbol_table_line in symbol_table_lines:
|
||||
match = pattern.search(symbol_table_line)
|
||||
if match:
|
||||
log.debug("Symbol line: {}".format(symbol_table_line))
|
||||
log.debug("Match found: {}".format(match))
|
||||
matched_symbols.append(match.group(0))
|
||||
|
||||
log.debug("Symbols found:\n'{}'".format(matched_symbols))
|
||||
return matched_symbols
|
||||
|
||||
def get_symbol_table(self, elf_file):
|
||||
"""Get the symbol table from an ELF format file."""
|
||||
log.debug(
|
||||
"Get the symbol table for ELF format file '{}'".format(elf_file)
|
||||
)
|
||||
|
||||
cmd = [*OBJECT_FILE_ANALYSIS_CMD, elf_file]
|
||||
log.debug("command: '{}'".format(cmd))
|
||||
try:
|
||||
process = subprocess.run(
|
||||
cmd,
|
||||
check=True,
|
||||
stdin=None,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
err_output = error.stdout.decode()
|
||||
msg = (
|
||||
"Getting symbol table for ELF format file '{}' failed,"
|
||||
" error: {}".format(elf_file, err_output)
|
||||
)
|
||||
raise SymbolTableError(msg)
|
||||
|
||||
symbol_table = process.stdout.decode()
|
||||
log.debug("Symbol table:\n{}\n".format(symbol_table))
|
||||
|
||||
return symbol_table
|
||||
|
||||
|
||||
class SymbolTableError(Exception):
|
||||
"""An exception for a failure to obtain a symbol table."""
|
||||
|
||||
|
||||
class ArgumentParserWithDefaultHelp(argparse.ArgumentParser):
|
||||
"""Subclass that always shows the help message on invalid arguments."""
|
||||
|
||||
def error(self, message):
|
||||
"""Error handler."""
|
||||
sys.stderr.write("error: {}\n".format(message))
|
||||
self.print_help()
|
||||
|
||||
|
||||
def set_log_verbosity(increase_verbosity):
|
||||
"""Set the verbosity of the log output."""
|
||||
log_level = logging.DEBUG if increase_verbosity else logging.INFO
|
||||
|
||||
log.setLevel(log_level)
|
||||
logging.basicConfig(level=log_level, format=LOG_FORMAT)
|
||||
|
||||
|
||||
def check_float_symbols(elf_file):
|
||||
"""Check for floating point symbols in ELF format file.
|
||||
|
||||
Return the floating point symbols found.
|
||||
"""
|
||||
parser = SymbolParser()
|
||||
symbol_table = parser.get_symbol_table(elf_file)
|
||||
|
||||
float_symbols = parser.get_symbols_from_table(
|
||||
symbol_table, FLOATING_POINT_SYMBOL_REGEX
|
||||
)
|
||||
|
||||
return float_symbols
|
||||
|
||||
|
||||
def check_action(args):
|
||||
"""Entry point for checking the ELF file."""
|
||||
float_symbols = check_float_symbols(args.elf_file)
|
||||
if float_symbols:
|
||||
print("Found float symbols:")
|
||||
for float_symbol in float_symbols:
|
||||
print(float_symbol)
|
||||
else:
|
||||
print("No float symbols found.")
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse the command line args."""
|
||||
parser = ArgumentParserWithDefaultHelp(
|
||||
description="ELF floats checker",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"elf_file",
|
||||
type=str,
|
||||
help=(
|
||||
"the Executable and Linkable Format (ELF) file to check"
|
||||
" for floating point instruction inclusion."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="increase verbosity of status information.",
|
||||
)
|
||||
|
||||
parser.set_defaults(func=check_action)
|
||||
|
||||
args_namespace = parser.parse_args()
|
||||
|
||||
# We want to fail gracefully, with a consistent
|
||||
# help message, in the no argument case.
|
||||
# So here's an obligatory hasattr hack.
|
||||
if not hasattr(args_namespace, "func"):
|
||||
parser.error("No arguments given!")
|
||||
else:
|
||||
return args_namespace
|
||||
|
||||
|
||||
def run_elf_floats_checker():
|
||||
"""Application main algorithm."""
|
||||
args = parse_args()
|
||||
|
||||
set_log_verbosity(args.verbose)
|
||||
|
||||
log.debug("Starting elf-floats-checker")
|
||||
log.debug("Command line arguments:{}".format(args))
|
||||
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""Run elf-floats-checker."""
|
||||
try:
|
||||
run_elf_floats_checker()
|
||||
except Exception as error:
|
||||
print(error)
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2019 Arm Limited and Contributors. All rights reserved.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""Pytest for testing elf-float-checker."""
|
||||
|
||||
import importlib
|
||||
import mock
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
TARGET = importlib.import_module("elf-float-checker")
|
||||
|
||||
SYMBOL_TABLE_WITHOUT_FLOATS = (
|
||||
" Symbol table '.symtab' contains 2723 entries:\n"
|
||||
"Num: Value Size Type Bind Vis Ndx Name\n"
|
||||
" 0: 00000000 0 NOTYPE LOCAL DEFAULT UND \n"
|
||||
" 1: 000045fd 16 FUNC GLOBAL HIDDEN 3 lp_ticker_clear_interrupt\n"
|
||||
" 2: 00004609 16 FUNC GLOBAL HIDDEN 3 __aeabi_uwrite4\n"
|
||||
" 3: 00004615 16 FUNC GLOBAL HIDDEN 3 lp_ticker_fire_interrupt\n"
|
||||
" 4: 00004625 36 FUNC GLOBAL HIDDEN 3 lp_ticker_free\n"
|
||||
" 5: 00004645 8 FUNC GLOBAL HIDDEN 3 lp_ticker_get_info\n"
|
||||
" 6: 0000464d 116 FUNC GLOBAL HIDDEN 3 __aeabi_lasr\n"
|
||||
" 7: 000046bd 20 FUNC GLOBAL HIDDEN 3 lp_ticker_irq_handler\n"
|
||||
" 8: 000046d1 16 FUNC GLOBAL HIDDEN 3 lp_ticker_read\n"
|
||||
" 9: 000046e1 52 FUNC GLOBAL HIDDEN 3 __aeabi_lmul\n"
|
||||
)
|
||||
|
||||
FLOAT_SYMBOLS = [
|
||||
"__aeabi_cdcmpeq",
|
||||
"__aeabi_cfcmpeq",
|
||||
"__aeabi_f2iz",
|
||||
"__aeabi_h2f_alt",
|
||||
"__aeabi_i2d",
|
||||
"__aeabi_d2iz",
|
||||
"__aeabi_i2f",
|
||||
]
|
||||
|
||||
SYMBOL_TABLE_WITH_FLOATS = (
|
||||
" Symbol table '.symtab' contains 2723 entries:\n"
|
||||
"Num: Value Size Type Bind Vis Ndx Name\n"
|
||||
f" 0: 00000000 0 NOTYPE LOCAL DEFAULT UND \n"
|
||||
f" 1: 000045fd 16 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[0]}\n"
|
||||
f" 2: 00004609 16 FUNC GLOBAL HIDDEN 3 lp_ticker_disable_interrupt\n"
|
||||
f" 3: 00004615 16 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[1]}\n"
|
||||
f" 4: 00004625 36 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[2]}\n"
|
||||
f" 5: 00004645 8 FUNC GLOBAL HIDDEN 3 lp_ticker_get_info\n"
|
||||
f" 6: 0000464d 116 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[3]}\n"
|
||||
f" 7: 000046bd 20 FUNC GLOBAL HIDDEN 3 lp_ticker_irq_handler\n"
|
||||
f" 8: 000046d1 16 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[4]}\n"
|
||||
f" 9: 000046e1 52 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[5]}\n"
|
||||
f" 10: 000046f1 52 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[6]}\n"
|
||||
)
|
||||
|
||||
|
||||
ELF_FORMAT_FILE = "mbed-os-example.elf"
|
||||
OBJECT_FILE_ANALYSIS_CMD = [*(TARGET.OBJECT_FILE_ANALYSIS_CMD), f"{ELF_FORMAT_FILE}"]
|
||||
|
||||
class TestElfFloatChecker:
|
||||
"""Test class"""
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
# Create a dummy ELF format file
|
||||
Path(ELF_FORMAT_FILE).touch()
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
# Remove the dummy ELF format file
|
||||
Path(ELF_FORMAT_FILE).unlink()
|
||||
|
||||
@mock.patch("subprocess.run")
|
||||
def test_correctly_detect_absence_of_float_symbols(
|
||||
self, mock_subprocess_run
|
||||
):
|
||||
"""Test that no false positive occur."""
|
||||
mock_subprocess_run.return_value = subprocess.CompletedProcess(
|
||||
args=(
|
||||
f"{OBJECT_FILE_ANALYSIS_CMD}"
|
||||
" check=True, stderr=-2, stdin=None, stdout=-1"
|
||||
),
|
||||
returncode=0,
|
||||
stdout=SYMBOL_TABLE_WITHOUT_FLOATS.encode(),
|
||||
stderr=None,
|
||||
)
|
||||
assert [] == TARGET.check_float_symbols(ELF_FORMAT_FILE)
|
||||
mock_subprocess_run.assert_called_with(
|
||||
OBJECT_FILE_ANALYSIS_CMD,
|
||||
check=True,
|
||||
stdin=None,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
|
||||
@mock.patch("subprocess.run")
|
||||
def test_correctly_detect_presence_of_float_symbols(
|
||||
self, mock_subprocess_run
|
||||
):
|
||||
"""Test that float symbols can be discovered in a symbol table."""
|
||||
mock_subprocess_run.return_value = subprocess.CompletedProcess(
|
||||
args=(
|
||||
f"{OBJECT_FILE_ANALYSIS_CMD}"
|
||||
" check=True, stderr=-2, stdin=None, stdout=-1"
|
||||
),
|
||||
returncode=0,
|
||||
stdout=SYMBOL_TABLE_WITH_FLOATS.encode(),
|
||||
stderr=None,
|
||||
)
|
||||
assert FLOAT_SYMBOLS == TARGET.check_float_symbols(
|
||||
ELF_FORMAT_FILE
|
||||
)
|
||||
mock_subprocess_run.assert_called_with(
|
||||
OBJECT_FILE_ANALYSIS_CMD,
|
||||
check=True,
|
||||
stdin=None,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
Loading…
Reference in New Issue