blob: 2446b72694f1598426f9783a8cbb676a3cb385f1 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
import argparse
import sys
from shared.constants import parse_required_constants
from shared.control_flow import program_control_graph, subroutine_control_graph
from shared.decode import decode_elf
from shared.information_flow import InformationFlowGraph
from shared.information_flow_analysis import (get_program_iflow,
get_subroutine_iflow,
stringify_control_deps)
def main() -> int:
parser = argparse.ArgumentParser(description=(
'Analyze the control flow and information flow of an OTBN '
'program or subroutine.'))
parser.add_argument('elf', help=('The .elf file to check.'))
parser.add_argument(
'--verbose',
action='store_true',
help=('Print full control-flow and information-flow graphs.'))
parser.add_argument(
'--subroutine',
required=False,
help=(
'The specific subroutine to check. If not provided, start point is '
'_imem_start (whole program).'))
parser.add_argument(
'--constants',
nargs='+',
type=str,
required=False,
help=('Registers which are required to be constant at the start of the '
'subroutine. Only valid if `--subroutine` is passed. Write '
'in the form "reg:value", e.g. x3:5. Only GPRs are accepted as '
'required constants.'))
parser.add_argument(
'--secrets',
nargs='+',
type=str,
required=False,
help=(
'Initially secret information-flow nodes. If provided, the final '
'secrets will be printed.'))
args = parser.parse_args()
program = decode_elf(args.elf)
# Compute control-flow graph.
if args.subroutine is None:
graph = program_control_graph(program)
else:
graph = subroutine_control_graph(program, args.subroutine)
# Only print the control-flow graph if --verbose is set.
if args.verbose:
print('Control-flow graph:')
print(graph.pretty(program, indent=2))
cycle_pcs = graph.get_cycle_starts()
if cycle_pcs:
print('Control flow has cycles starting at the following PCs:')
for pc in cycle_pcs:
symbols = program.get_symbols_for_pc(pc)
label_str = ' <{}>'.format(
', '.join(symbols)) if symbols else ''
print('{:#x}{}'.format(pc, label_str))
# Parse initial constants.
if args.constants is None:
constants = {}
else:
if args.subroutine is None:
raise ValueError('Cannot require initial constants for a whole '
'program; use --subroutine to analyze a specific '
'subroutine.')
constants = parse_required_constants(args.constants)
# Compute information-flow graph(s).
if args.subroutine is None:
what = 'program'
end_iflow, control_deps = get_program_iflow(program, graph)
ret_iflow = InformationFlowGraph.nonexistent()
else:
what = 'subroutine'
ret_iflow, end_iflow, control_deps = get_subroutine_iflow(
program, graph, args.subroutine, constants)
# If no secrets were given or the --verbose flag is set, then print the
# full information-flow graphs.
if (args.verbose or args.secrets is None):
if ret_iflow.exists:
print(
'Information flow for paths ending in a return to the caller:')
print(ret_iflow.pretty(indent=2))
if end_iflow.exists:
print('--------')
if end_iflow.exists:
print('Information flow for paths ending the program:')
print(end_iflow.pretty(indent=2))
if args.secrets is None:
# If no initial secrets were provided, we will print all nodes that
# could influence control flow.
control_what = 'information-flow nodes'
else:
# If secrets were provided, only show the ways in which those specific
# nodes could influence control flow.
control_what = 'secrets'
control_deps = {
name: pcs
for name, pcs in control_deps.items() if name in args.secrets
}
# Print any (secret) nodes that influence control flow, and the PCs of the
# control-flow instructions they influence.
if len(control_deps) == 0:
print('No {} were found to influence this {}\'s control flow.'.format(
control_what, what))
else:
print('The following {} may influence control flow in this {}:'.format(
control_what, what))
for node in stringify_control_deps(program, control_deps):
print(node)
# Print final secrets (if initial secrets were provided).
if args.secrets is not None:
if ret_iflow.exists:
final_secrets = {
sink
for node in args.secrets for sink in ret_iflow.sinks(node)
}
print('Final secrets for paths ending in a return to the caller:',
', '.join(sorted(final_secrets)))
if end_iflow.exists:
final_secrets = {
sink
for node in args.secrets for sink in end_iflow.sinks(node)
}
print('Final secrets for paths ending the program:',
', '.join(sorted(final_secrets)))
return 0
if __name__ == "__main__":
sys.exit(main())