1#!/usr/bin/env python3
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2019, Linaro Limited
5#
6
7from __future__ import print_function
8from __future__ import division
9
10import argparse
11import sys
12try:
13    from elftools.elf.elffile import ELFFile
14    from elftools.elf.sections import SymbolTableSection
15    from elftools.elf.constants import P_FLAGS
16except ImportError:
17    print("""
18***
19Can't find elftools module. Probably it is not installed on your system.
20You can install this module with
21
22$ apt install python3-pyelftools
23
24if you are using Ubuntu. Or try to search for "pyelftools" or "elftools" in
25your package manager if you are using some other distribution.
26***
27""")
28    raise
29
30
31def round_up(n, m):
32    if n == 0:
33        return 0
34    else:
35        return (((n - 1) // m) + 1) * m
36
37
38def emit_load_segments(elffile, outf):
39    load_size = 0
40    code_size = 0
41    data_size = 0
42    load_segments = [s for s in elffile.iter_segments()
43                     if s['p_type'] == 'PT_LOAD']
44    prev_segment = None
45    pad = 0
46    pad_size = []
47    w_found = False
48    n = 0
49    # Check that load segments ordered by VA have the expected layout:
50    # read only first, then read-write. Compute padding at end of each segment,
51    # 0 if none is required.
52    for segment in load_segments:
53        if prev_segment:
54            pad = segment['p_vaddr'] - (prev_segment['p_vaddr'] +
55                                        prev_segment['p_filesz'])
56        else:
57            if segment['p_flags'] & P_FLAGS.PF_W:
58                print('Expected RO load segment(s) first')
59                sys.exit(1)
60        if segment['p_flags'] & P_FLAGS.PF_W:
61            if not w_found:
62                # End of RO segments, discard padding for the last one (it
63                # would just take up space in the generated C file)
64                pad = 0
65                w_found = True
66        else:
67            if w_found:
68                print('RO load segment found after RW one(s) (m={})'.format(n))
69                sys.exit(1)
70        if prev_segment:
71            if pad > 31:
72                # We expect segments to be tightly packed together for memory
73                # efficiency. 31 is an arbitrary, "sounds reasonable" value
74                # which might need to be adjusted -- who knows what the
75                # compiler/linker can do.
76                print('Warning: suspiciously large padding ({}) after load '
77                      'segment {}, please check'.format(pad, n-1))
78            pad_size.append(pad)
79        prev_segment = segment
80        n = n + 1
81    pad_size.append(0)
82    n = 0
83    # Compute code_size, data_size and load_size
84    for segment in load_segments:
85        sz = segment['p_filesz'] + pad_size[n]
86        if segment['p_flags'] & P_FLAGS.PF_W:
87            data_size += sz
88        else:
89            code_size += sz
90        load_size += sz
91        n = n + 1
92    n = 0
93    i = 0
94    # Output data to C file
95    outf.write(b'const uint8_t ldelf_data[%d]' % round_up(load_size, 4096))
96    outf.write(b' __aligned(4096) = {\n')
97    for segment in load_segments:
98        data = segment.data()
99        if pad_size[n]:
100            # Pad with zeros if needed
101            data += bytearray(pad_size[n])
102        for j in range(len(data)):
103            if i % 8 == 0:
104                outf.write(b'\t')
105            outf.write(b'0x' + '{:02x}'.format(data[j]).encode('utf-8')
106                       + b',')
107            i = i + 1
108            if i % 8 == 0 or i == load_size:
109                outf.write(b'\n')
110            else:
111                outf.write(b' ')
112        n = n + 1
113    outf.write(b'};\n')
114
115    outf.write(b'const unsigned int ldelf_code_size = %d;\n' % code_size)
116    outf.write(b'const unsigned int ldelf_data_size = %d;\n' % data_size)
117
118
119def get_args():
120    parser = argparse.ArgumentParser()
121
122    parser.add_argument('--input',
123                        required=True, type=argparse.FileType('rb'),
124                        help='The input ldelf.elf')
125
126    parser.add_argument('--output',
127                        required=True, type=argparse.FileType('wb'),
128                        help='The output ldelf_hex.c')
129
130    return parser.parse_args()
131
132
133def main():
134    args = get_args()
135    inf = args.input
136    outf = args.output
137
138    elffile = ELFFile(inf)
139
140    outf.write(b'/* Automatically generated, do no edit */\n')
141    outf.write(b'#include <compiler.h>\n')
142    outf.write(b'#include <stdint.h>\n')
143    emit_load_segments(elffile, outf)
144    outf.write(b'const unsigned long ldelf_entry = %lu;\n' %
145               elffile.header['e_entry'])
146
147    inf.close()
148    outf.close()
149
150
151if __name__ == "__main__":
152    main()
153