Skip to content

Add executable analaysis tool for floating point checks. #11314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions tools/executable_analysis_tools/elf-float-checker.py
Original file line number Diff line number Diff line change
@@ -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)
120 changes: 120 additions & 0 deletions tools/executable_analysis_tools/test_elf-float-checker.py
Original file line number Diff line number Diff line change
@@ -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,
)