-
Notifications
You must be signed in to change notification settings - Fork 3k
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
0xc0170
merged 1 commit into
ARMmbed:master
from
hugueskamba:hk-iotcore-1279-executable_analysis_tools
Sep 4, 2019
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
120
tools/executable_analysis_tools/test_elf-float-checker.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.