Skip to content
Open
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
2 changes: 1 addition & 1 deletion hw/ip/otbn/util/analyze_information_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def main() -> int:
'Initially secret information-flow nodes. If provided, the final '
'secrets will be printed.'))
args = parser.parse_args()
program = decode_elf(args.elf)
program = decode_elf(args.elf, [])

# Compute control-flow graph.
if args.subroutine is None:
Expand Down
2 changes: 1 addition & 1 deletion hw/ip/otbn/util/check_call_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def main() -> int:
parser.add_argument('elf', help=('The .elf file to check.'))
parser.add_argument('-v', '--verbose', action='store_true')
args = parser.parse_args()
program = decode_elf(args.elf)
program = decode_elf(args.elf, [])
result = check_call_stack(program)
if args.verbose or result.has_errors() or result.has_warnings():
print(result.report())
Expand Down
17 changes: 3 additions & 14 deletions hw/ip/otbn/util/check_const_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def main() -> int:
constants = parse_required_constants(args.constants)

# Compute control graph and get all nodes that influence control flow.
program = decode_elf(args.elf)
program = decode_elf(args.elf, args.ignore or [])
if args.subroutine is None:
graph = program_control_graph(program)
to_analyze = 'entire program'
Expand All @@ -75,14 +75,6 @@ def main() -> int:
_, _, control_deps = get_subroutine_iflow(program, graph,
args.subroutine, constants)

control_deps_ignore = {}
if args.ignore is not None:
for subroutine in args.ignore:
graph = subroutine_control_graph(program, subroutine)
_, _, control_deps_ignore_temp = get_subroutine_iflow(program, graph,
subroutine, {})
control_deps_ignore.update(control_deps_ignore_temp)

if args.secrets is None:
if args.verbose:
print(
Expand All @@ -100,15 +92,12 @@ def main() -> int:
for node, pcs in control_deps.items() if node in args.secrets
}

secret_control_deps_filt = {k: v for k, v in secret_control_deps.items()
if v not in control_deps_ignore.values()}

out = CheckResult()

if len(secret_control_deps_filt) != 0:
if len(secret_control_deps) != 0:
msg = 'The following secrets may influence control flow:\n '
msg += '\n '.join(stringify_control_deps(program,
secret_control_deps_filt))
secret_control_deps))
out.err(msg)

if args.verbose or out.has_errors() or out.has_warnings():
Expand Down
2 changes: 1 addition & 1 deletion hw/ip/otbn/util/check_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def main() -> int:
parser.add_argument('elf', help=('The .elf file to check.'))
parser.add_argument('-v', '--verbose', action='store_true')
args = parser.parse_args()
program = decode_elf(args.elf)
program = decode_elf(args.elf, [])
result = check_loop(program)
if args.verbose or result.has_errors() or result.has_warnings():
print(result.report())
Expand Down
2 changes: 1 addition & 1 deletion hw/ip/otbn/util/get_instruction_count_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def main() -> int:
help=('The specific subroutine to check. If not provided, the start '
'point is _imem_start (whole program).'))
args = parser.parse_args()
program = decode_elf(args.elf)
program = decode_elf(args.elf, [])

# Compute instruction count range.
if args.subroutine is None:
Expand Down
66 changes: 55 additions & 11 deletions hw/ip/otbn/util/shared/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,63 @@

class OTBNProgram:
def __init__(self, symbols: Dict[str, int], insns: Dict[int, int],
data: Dict[int, int]):
data: Dict[int, int], nop_subfuncs: List[str]):
self.symbols = symbols # label -> PC
self.data = data # addr -> data (32b word)
self.data = data # addr -> data (32b word)

# For each function name in nop_subfuncs, we assume that it will have
# control flow limited in the following way. If the function starts at
# address A and the first JALR instruction after A is at address B,
# then execution will not leave the interval [A, B] until the function
# returns by executing the JALR at B.
#
# With this assumption, we can imagine replacing the instructions in
# that interval with a "nop sled". The ranges of the these sleds are
# stored in the nop_ranges list, as pairs (A, B).
nop_ranges: list[tuple[int, int]] = []

for symbol in nop_subfuncs:
# Get the start address of the function. If the function dosen't
# appear in the list of symbols, there's nothing to do for it.
start_addr = symbols.get(symbol)
if start_addr is None:
continue

for pc in range(start_addr, 1 << 32, 4):
opcode = insns.get(pc)
if opcode is None:
raise RuntimeError("Fell off the end of the binary "
"when searching for a JALR for a "
f"function with symbol {symbol}, "
f"starting at {start_addr:#x}")

# Check whether we just found 'B' (see note above nop_ranges)
if INSNS_FILE.mnem_for_word(opcode) == 'jalr':
nop_ranges.append((start_addr, pc))
break

self.insns = {}
for pc, opcode in insns.items():
mnem = INSNS_FILE.mnem_for_word(opcode)
if mnem is None:
raise ValueError(
'No legal decoding for mnemonic: {}'.format(mnem))
insn = INSNS_FILE.mnemonic_to_insn[mnem]
assert insn.encoding is not None
enc_vals = insn.encoding.extract_operands(opcode)
# Check if PC lies within one of the NOP ranges (equal to or after
# the start of a nop subfunc and strictly before the JALR
# instruction at the end).
in_nop_region = any(a <= pc < b for a, b in nop_ranges)

# If the PC *is* in a NOP region, interpret the opcode at PC as a
# NOP (addi x0, x0, x0). If not, decode the opcode and find the
# appropriate instruction and operands.
if in_nop_region:
insn = INSNS_FILE.mnemonic_to_insn["addi"]
enc_vals = {'imm': 0, 'grs1': 0, 'grd': 0}
else:
mnem = INSNS_FILE.mnem_for_word(opcode)
if mnem is None:
raise ValueError(f'No mnemonic for opcode {opcode:#08x}')

insn = INSNS_FILE.mnemonic_to_insn[mnem]
assert insn.encoding is not None
enc_vals = insn.encoding.extract_operands(opcode)

op_vals = insn.enc_vals_to_op_vals(pc, enc_vals)
self.insns[pc] = (insn, op_vals)

Expand Down Expand Up @@ -69,7 +113,7 @@ def _decode_mem(base_addr: int, data: bytes) -> Dict[int, int]:
for offset, int_val in enumerate(struct.iter_unpack('<I', data))}


def decode_elf(path: str) -> OTBNProgram:
def decode_elf(path: str, nop_subfuncs: List[str]) -> OTBNProgram:
'''Read ELF file at path and decode contents into an OTBNProgram instance

Returns the OTBNProgram instance representing the program in the ELF file.
Expand All @@ -79,4 +123,4 @@ def decode_elf(path: str) -> OTBNProgram:
insns = _decode_mem(0, imem_bytes)
data = _decode_mem(0, dmem_bytes)

return OTBNProgram(symbols, insns, data)
return OTBNProgram(symbols, insns, data, nop_subfuncs)
Loading