1#!/usr/bin/env python3
2#
3# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7"""
8Extract information of interest to the seL4 image build process from ELF files.
9
10THIS IS NOT A STABLE API.  Use as a script, not a module.
11"""
12
13import argparse
14import elftools.elf.elffile
15import sys
16
17from typing import BinaryIO
18
19
20def get_aligned_size(n: int) -> int:
21    """
22    Return the smallest multiple of 4KiB not less than the total of the loadable
23    segments.  (In other words, the size will be returned as-is if it is an
24    exact multiple of 4KiB, otherwise it is rounded up to the next higher
25    multiple of 4KiB.)
26    """
27    return n if n % 4096 == 0 else ((n // 4096) + 1) * 4096
28
29
30def get_memory_usage(elf_file: BinaryIO, align: bool) -> int:
31    """
32    Return the size in bytes occuped in memory of the loadable ELF segments from
33    the ELF object file `elf_file`.
34    """
35
36    total: int = 0
37    elf = elftools.elf.elffile.ELFFile(elf_file)
38
39    # We only care about loadable segments (p_type is "PT_LOAD"), and we
40    # want the size in memory of those segments (p_memsz), which can be
41    # greater than the size in the file (p_filesz).  This is especially
42    # important for the BSS section.  See elf(5).
43    total = sum([seg['p_memsz'] for seg in elf.iter_segments()
44                 if seg['p_type'] == 'PT_LOAD'])
45
46    return get_aligned_size(total) if align else total
47
48
49def get_memory_usage_from_file(filename: str, align: bool) -> int:
50    """
51    Return the size in bytes occuped in memory of the loadable ELF segments from
52    the ELF object file `filename`.
53    """
54
55    with open(filename, 'rb') as f:
56        return get_memory_size(f)
57
58
59def main() -> int:
60    parser = argparse.ArgumentParser(
61        formatter_class=argparse.RawDescriptionHelpFormatter,
62        description="""
63Extract information of interest to the seL4 image build process from ELF files.
64
65We extract the sizes of loadable ELF segments from the ELF files given as
66operands and print their sum.
67
68If the "--align" flag is specified, the space "after" each ELF file is aligned
69to the next 4KiB boundary, increasing the total.
70""")
71    parser.add_argument('elf_file', nargs='+', type=str,
72                        help='ELF object file to examine')
73    parser.add_argument('--align', action='store_true',
74                        help='align to 4KiB between files')
75    parser.add_argument('--reserve', metavar='BYTES', type=int, action='store',
76                        default=0, help='number of additional bytes to reserve')
77    args = parser.parse_args()
78    regions = [get_memory_usage_from_file(elf, args.align)
79               for elf in args.elf_file]
80    regions.append(args.reserve)
81    total = sum(regions)
82
83    if args.align:
84        total = get_aligned_size(total)
85
86    print(total)
87    return 0
88
89
90if __name__ == '__main__':
91    sys.exit(main())
92