Skip to content

Commit 4f1a7c2

Browse files
committed
Add executable analaysis tool for floating point checks.
1 parent 19ebb29 commit 4f1a7c2

File tree

2 files changed

+288
-0
lines changed

2 files changed

+288
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2019 Arm Limited and Contributors. All rights reserved.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
"""Script for checking for floating point symbols in ELF files."""
7+
8+
import argparse
9+
import logging
10+
import re
11+
import subprocess
12+
import sys
13+
14+
log = logging.getLogger(__name__)
15+
16+
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
17+
FLOATING_POINT_SYMBOL_REGEX = r"__aeabi_(cd.+|cf.+|h2f.+|d.+|f.+|.+2d|.+2f)"
18+
OBJECT_FILE_ANALYSIS_CMD = ["objdump", "-t"]
19+
20+
class SymbolParser:
21+
"""Parse the given ELF format file."""
22+
23+
def get_symbols_from_table(self, symbol_table, symbol_regex):
24+
"""Get symbols matching a regular expression pattern from a table."""
25+
pattern = re.compile(symbol_regex, re.X)
26+
matched_symbols = []
27+
symbol_table_lines = symbol_table.split("\n")
28+
for symbol_table_line in symbol_table_lines:
29+
match = pattern.search(symbol_table_line)
30+
if match:
31+
log.debug("Symbol line: {}".format(symbol_table_line))
32+
log.debug("Match found: {}".format(match))
33+
matched_symbols.append(match.group(0))
34+
35+
log.debug("Symbols found:\n'{}'".format(matched_symbols))
36+
return matched_symbols
37+
38+
def get_symbol_table(self, elf_file):
39+
"""Get the symbol table from an ELF format file."""
40+
log.debug(
41+
"Get the symbol table for ELF format file '{}'".format(elf_file)
42+
)
43+
44+
cmd = [*OBJECT_FILE_ANALYSIS_CMD, elf_file]
45+
log.debug("command: '{}'".format(cmd))
46+
try:
47+
process = subprocess.run(
48+
cmd,
49+
check=True,
50+
stdin=None,
51+
stdout=subprocess.PIPE,
52+
stderr=subprocess.STDOUT,
53+
)
54+
except subprocess.CalledProcessError as error:
55+
err_output = error.stdout.decode()
56+
msg = (
57+
"Getting symbol table for ELF format file '{}' failed,"
58+
" error: {}".format(elf_file, err_output)
59+
)
60+
raise SymbolTableError(msg)
61+
62+
symbol_table = process.stdout.decode()
63+
log.debug("Symbol table:\n{}\n".format(symbol_table))
64+
65+
return symbol_table
66+
67+
68+
class SymbolTableError(Exception):
69+
"""An exception for a failure to obtain a symbol table."""
70+
71+
72+
class ArgumentParserWithDefaultHelp(argparse.ArgumentParser):
73+
"""Subclass that always shows the help message on invalid arguments."""
74+
75+
def error(self, message):
76+
"""Error handler."""
77+
sys.stderr.write("error: {}\n".format(message))
78+
self.print_help()
79+
80+
81+
def set_log_verbosity(increase_verbosity):
82+
"""Set the verbosity of the log output."""
83+
log_level = logging.DEBUG if increase_verbosity else logging.INFO
84+
85+
log.setLevel(log_level)
86+
logging.basicConfig(level=log_level, format=LOG_FORMAT)
87+
88+
89+
def check_float_symbols(elf_file):
90+
"""Check for floating point symbols in ELF format file.
91+
92+
Return the floating point symbols found.
93+
"""
94+
parser = SymbolParser()
95+
symbol_table = parser.get_symbol_table(elf_file)
96+
97+
float_symbols = parser.get_symbols_from_table(
98+
symbol_table, FLOATING_POINT_SYMBOL_REGEX
99+
)
100+
101+
return float_symbols
102+
103+
104+
def check_action(args):
105+
"""Entry point for checking the ELF file."""
106+
float_symbols = check_float_symbols(args.elf_file)
107+
if float_symbols:
108+
print("Found float symbols:")
109+
for float_symbol in float_symbols:
110+
print(float_symbol)
111+
else:
112+
print("No float symbols found.")
113+
114+
115+
def parse_args():
116+
"""Parse the command line args."""
117+
parser = ArgumentParserWithDefaultHelp(
118+
description="ELF floats checker",
119+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
120+
)
121+
122+
parser.add_argument(
123+
"elf_file",
124+
type=str,
125+
help=(
126+
"the Executable and Linkable Format (ELF) file to check"
127+
" for floating point instruction inclusion."
128+
),
129+
)
130+
131+
parser.add_argument(
132+
"-v",
133+
"--verbose",
134+
action="store_true",
135+
help="increase verbosity of status information.",
136+
)
137+
138+
parser.set_defaults(func=check_action)
139+
140+
args_namespace = parser.parse_args()
141+
142+
# We want to fail gracefully, with a consistent
143+
# help message, in the no argument case.
144+
# So here's an obligatory hasattr hack.
145+
if not hasattr(args_namespace, "func"):
146+
parser.error("No arguments given!")
147+
else:
148+
return args_namespace
149+
150+
151+
def run_elf_floats_checker():
152+
"""Application main algorithm."""
153+
args = parse_args()
154+
155+
set_log_verbosity(args.verbose)
156+
157+
log.debug("Starting elf-floats-checker")
158+
log.debug("Command line arguments:{}".format(args))
159+
160+
args.func(args)
161+
162+
163+
if __name__ == "__main__":
164+
"""Run elf-floats-checker."""
165+
try:
166+
run_elf_floats_checker()
167+
except Exception as error:
168+
print(error)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2019 Arm Limited and Contributors. All rights reserved.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
"""Pytest for testing elf-float-checker."""
7+
8+
import importlib
9+
import mock
10+
import subprocess
11+
from pathlib import Path
12+
13+
14+
TARGET = importlib.import_module("elf-float-checker")
15+
16+
SYMBOL_TABLE_WITHOUT_FLOATS = (
17+
" Symbol table '.symtab' contains 2723 entries:\n"
18+
"Num: Value Size Type Bind Vis Ndx Name\n"
19+
" 0: 00000000 0 NOTYPE LOCAL DEFAULT UND \n"
20+
" 1: 000045fd 16 FUNC GLOBAL HIDDEN 3 lp_ticker_clear_interrupt\n"
21+
" 2: 00004609 16 FUNC GLOBAL HIDDEN 3 __aeabi_uwrite4\n"
22+
" 3: 00004615 16 FUNC GLOBAL HIDDEN 3 lp_ticker_fire_interrupt\n"
23+
" 4: 00004625 36 FUNC GLOBAL HIDDEN 3 lp_ticker_free\n"
24+
" 5: 00004645 8 FUNC GLOBAL HIDDEN 3 lp_ticker_get_info\n"
25+
" 6: 0000464d 116 FUNC GLOBAL HIDDEN 3 __aeabi_lasr\n"
26+
" 7: 000046bd 20 FUNC GLOBAL HIDDEN 3 lp_ticker_irq_handler\n"
27+
" 8: 000046d1 16 FUNC GLOBAL HIDDEN 3 lp_ticker_read\n"
28+
" 9: 000046e1 52 FUNC GLOBAL HIDDEN 3 __aeabi_lmul\n"
29+
)
30+
31+
FLOAT_SYMBOLS = [
32+
"__aeabi_cdcmpeq",
33+
"__aeabi_cfcmpeq",
34+
"__aeabi_f2iz",
35+
"__aeabi_h2f_alt",
36+
"__aeabi_i2d",
37+
"__aeabi_d2iz",
38+
"__aeabi_i2f",
39+
]
40+
41+
SYMBOL_TABLE_WITH_FLOATS = (
42+
" Symbol table '.symtab' contains 2723 entries:\n"
43+
"Num: Value Size Type Bind Vis Ndx Name\n"
44+
f" 0: 00000000 0 NOTYPE LOCAL DEFAULT UND \n"
45+
f" 1: 000045fd 16 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[0]}\n"
46+
f" 2: 00004609 16 FUNC GLOBAL HIDDEN 3 lp_ticker_disable_interrupt\n"
47+
f" 3: 00004615 16 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[1]}\n"
48+
f" 4: 00004625 36 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[2]}\n"
49+
f" 5: 00004645 8 FUNC GLOBAL HIDDEN 3 lp_ticker_get_info\n"
50+
f" 6: 0000464d 116 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[3]}\n"
51+
f" 7: 000046bd 20 FUNC GLOBAL HIDDEN 3 lp_ticker_irq_handler\n"
52+
f" 8: 000046d1 16 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[4]}\n"
53+
f" 9: 000046e1 52 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[5]}\n"
54+
f" 10: 000046f1 52 FUNC GLOBAL HIDDEN 3 {FLOAT_SYMBOLS[6]}\n"
55+
)
56+
57+
58+
ELF_FORMAT_FILE = "mbed-os-example.elf"
59+
OBJECT_FILE_ANALYSIS_CMD = [*(TARGET.OBJECT_FILE_ANALYSIS_CMD), f"{ELF_FORMAT_FILE}"]
60+
61+
class TestElfFloatChecker:
62+
"""Test class"""
63+
64+
@classmethod
65+
def setup_class(cls):
66+
# Create a dummy ELF format file
67+
Path(ELF_FORMAT_FILE).touch()
68+
69+
@classmethod
70+
def teardown_class(cls):
71+
# Remove the dummy ELF format file
72+
Path(ELF_FORMAT_FILE).unlink()
73+
74+
@mock.patch("subprocess.run")
75+
def test_correctly_detect_absence_of_float_symbols(
76+
self, mock_subprocess_run
77+
):
78+
"""Test that no false positive occur."""
79+
mock_subprocess_run.return_value = subprocess.CompletedProcess(
80+
args=(
81+
f"{OBJECT_FILE_ANALYSIS_CMD}"
82+
" check=True, stderr=-2, stdin=None, stdout=-1"
83+
),
84+
returncode=0,
85+
stdout=SYMBOL_TABLE_WITHOUT_FLOATS.encode(),
86+
stderr=None,
87+
)
88+
assert [] == TARGET.check_float_symbols(ELF_FORMAT_FILE)
89+
mock_subprocess_run.assert_called_with(
90+
OBJECT_FILE_ANALYSIS_CMD,
91+
check=True,
92+
stdin=None,
93+
stdout=subprocess.PIPE,
94+
stderr=subprocess.STDOUT,
95+
)
96+
97+
@mock.patch("subprocess.run")
98+
def test_correctly_detect_presence_of_float_symbols(
99+
self, mock_subprocess_run
100+
):
101+
"""Test that float symbols can be discovered in a symbol table."""
102+
mock_subprocess_run.return_value = subprocess.CompletedProcess(
103+
args=(
104+
f"{OBJECT_FILE_ANALYSIS_CMD}"
105+
" check=True, stderr=-2, stdin=None, stdout=-1"
106+
),
107+
returncode=0,
108+
stdout=SYMBOL_TABLE_WITH_FLOATS.encode(),
109+
stderr=None,
110+
)
111+
assert FLOAT_SYMBOLS == TARGET.check_float_symbols(
112+
ELF_FORMAT_FILE
113+
)
114+
mock_subprocess_run.assert_called_with(
115+
OBJECT_FILE_ANALYSIS_CMD,
116+
check=True,
117+
stdin=None,
118+
stdout=subprocess.PIPE,
119+
stderr=subprocess.STDOUT,
120+
)

0 commit comments

Comments
 (0)