Add executable analaysis tool for floating point checks.

pull/11314/head
Hugues Kamba 2019-08-23 15:26:07 +01:00
parent c99b150bcf
commit 487bbe6323
2 changed files with 288 additions and 0 deletions

View File

@ -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)

View File

@ -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,
)