blob: ac87a5ab88d15b4606ca6d876113ca8bb44039e0 [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
'''OTBN ELF file handling'''
from typing import List, Optional, Tuple, Dict
from elftools.elf.elffile import ELFFile, SymbolTableSection # type: ignore
from .mem_layout import get_memory_layout
_SegList = List[Tuple[int, bytes]]
# A _MemDesc is a pair (lma, length). length should be non-negative.
_MemDesc = Tuple[int, int]
def _flatten_segments(segments: _SegList, mem_desc: _MemDesc) -> bytes:
'''Write the given ELF segments to a block of bytes
Let (block_lma, block_len) = _MemDesc. The segments are assumed to be
disjoint. The segment with the smallest address is assumed to start
somewhere above block_lma and the segment with the highest address is
assumed to finish less than block_len bytes above block_lma.
The returned block of bytes will have length at most block_len. Any
internal gaps are padded with zeroes.
'''
block_lma, block_len = mem_desc
assert 0 <= block_len
lma = block_lma
chunks = []
# Walk through the segments in increasing order of LMA.
for seg_lma, seg_data in sorted(segments, key=lambda pr: pr[0]):
assert lma <= seg_lma
if lma < seg_lma:
chunks.append(b'\x00' * (seg_lma - lma))
lma = seg_lma
chunks.append(seg_data)
lma += len(seg_data)
assert block_lma <= lma
block_top = block_lma + block_len
assert lma <= block_top
return b''.join(chunks)
def _get_elf_mem_data(elf_file: ELFFile,
imem_desc: _MemDesc,
dmem_desc: _MemDesc) -> Tuple[bytes, bytes]:
'''Extract imem/dmem segments from elf_file'''
imem_segments = []
dmem_segments = []
imem_lma, imem_len = imem_desc
dmem_lma, dmem_len = dmem_desc
imem_top = imem_lma + imem_len
dmem_top = dmem_lma + dmem_len
assert imem_lma <= imem_top
assert dmem_lma <= dmem_top
for segment in elf_file.iter_segments():
seg_type = segment['p_type']
# We're only interested in PT_LOAD segments
if seg_type != 'PT_LOAD':
continue
seg_lma = segment['p_paddr']
seg_top = seg_lma + segment['p_memsz']
# Does this match an expected imem or dmem address?
if imem_lma <= seg_lma <= imem_top:
if seg_top > imem_top:
raise RuntimeError('Segment has LMA range {:#x}..{:#x}, '
'which intersects the IMEM range '
'({:#x}..{:#x}), but is not fully '
'contained in it.'
.format(seg_lma, seg_top,
imem_lma, imem_top))
imem_segments.append((seg_lma, segment.data()))
continue
if dmem_lma <= seg_lma <= dmem_top:
if seg_top > dmem_top:
raise RuntimeError('Segment has LMA range {:#x}..{:#x}, '
'which intersects the DMEM range '
'({:#x}..{:#x}), but is not fully '
'contained in it.'
.format(seg_lma, seg_top,
dmem_lma, dmem_top))
dmem_segments.append((seg_lma, segment.data()))
continue
# We shouldn't have any loadable segments that don't look like
# either IMEM or DMEM.
raise RuntimeError("Segment has LMA {:#x}, which doesn't start in "
"IMEM range (base {:#x}) or DMEM range "
"(base {:#x})."
.format(seg_lma, imem_lma, dmem_lma))
imem_bytes = _flatten_segments(imem_segments, imem_desc)
dmem_bytes = _flatten_segments(dmem_segments, dmem_desc)
return (imem_bytes, dmem_bytes)
def _get_symtab(elf_file: ELFFile) -> Optional[SymbolTableSection]:
'''Get the symbol table from elf_file if there is one'''
section = elf_file.get_section_by_name('.symtab')
if section is None:
# No symbol table found
return None
if not isinstance(section, SymbolTableSection):
# Huh? The .symtab section isn't a symbol table?? Oh well, nevermind.
return None
return section
def read_elf(path: str) -> Tuple[bytes,
bytes,
Dict[str, int]]:
'''Load the ELF file at path.
Returns a tuple (imem_bytes, dmem_bytes, exp_end_addr, loop_warps). The
first two coordinates are as returned by _get_elf_mem_data. exp_end_addr is
as returned by _get_exp_end_addr. loop_warps is as returned by
_get_loop_warps.
'''
mems = get_memory_layout()
imem_desc = mems['IMEM']
dmem_desc = mems['DMEM']
with open(path, 'rb') as handle:
elf_file = ELFFile(handle)
imem_bytes, dmem_bytes = _get_elf_mem_data(elf_file,
imem_desc, dmem_desc)
symtab = _get_symtab(elf_file)
symbols = {}
if symtab is not None:
for sym in symtab.iter_symbols():
assert isinstance(sym['st_value'], int)
symbols[sym.name] = sym['st_value']
assert len(imem_bytes) <= imem_desc[1]
if len(imem_bytes) & 3:
raise RuntimeError('ELF file at {!r} has imem data of length {}: '
'not a multiple of 4.'
.format(path, len(imem_bytes)))
return (imem_bytes, dmem_bytes, symbols)