13c51966bSJens Wiklander#!/usr/bin/env python3 23c51966bSJens Wiklander# SPDX-License-Identifier: BSD-2-Clause 33c51966bSJens Wiklander# 43c51966bSJens Wiklander# Copyright (c) 2019, Linaro Limited 53c51966bSJens Wiklander# 63c51966bSJens Wiklander 73c51966bSJens Wiklanderfrom __future__ import print_function 83c51966bSJens Wiklanderfrom __future__ import division 93c51966bSJens Wiklander 103c51966bSJens Wiklanderimport argparse 113c51966bSJens Wiklanderimport sys 123c51966bSJens Wiklanderimport struct 133c51966bSJens Wiklanderimport re 143c51966bSJens Wiklanderimport hashlib 153c51966bSJens Wiklandertry: 163c51966bSJens Wiklander from elftools.elf.elffile import ELFFile 173c51966bSJens Wiklander from elftools.elf.constants import SH_FLAGS 185966660cSJens Wiklander from elftools.elf.enums import ENUM_RELOC_TYPE_ARM 195966660cSJens Wiklander from elftools.elf.enums import ENUM_RELOC_TYPE_AARCH64 203c51966bSJens Wiklander from elftools.elf.sections import SymbolTableSection 215966660cSJens Wiklander from elftools.elf.relocation import RelocationSection 223c51966bSJens Wiklander 233c51966bSJens Wiklanderexcept ImportError: 243c51966bSJens Wiklander print(""" 253c51966bSJens Wiklander*** 263c51966bSJens WiklanderCan't find elftools module. Probably it is not installed on your system. 273c51966bSJens WiklanderYou can install this module with 283c51966bSJens Wiklander 293c51966bSJens Wiklander$ apt install python3-pyelftools 303c51966bSJens Wiklander 313c51966bSJens Wiklanderif you are using Ubuntu. Or try to search for "pyelftools" or "elftools" in 323c51966bSJens Wiklanderyour package manager if you are using some other distribution. 333c51966bSJens Wiklander*** 343c51966bSJens Wiklander""") 353c51966bSJens Wiklander raise 363c51966bSJens Wiklander 373c51966bSJens Wiklandersmall_page_size = 4 * 1024 383c51966bSJens Wiklanderelffile_symbols = None 393c51966bSJens Wiklandertee_pageable_bin = None 403c51966bSJens Wiklandertee_pager_bin = None 415dd1570aSJens Wiklandertee_embdata_bin = None 423c51966bSJens Wiklander 433c51966bSJens Wiklander 443c51966bSJens Wiklanderdef eprint(*args, **kwargs): 453c51966bSJens Wiklander print(*args, file=sys.stderr, **kwargs) 463c51966bSJens Wiklander 473c51966bSJens Wiklander 485dd1570aSJens Wiklanderdef round_up(n, m): 495dd1570aSJens Wiklander if n == 0: 505dd1570aSJens Wiklander return 0 515dd1570aSJens Wiklander else: 525dd1570aSJens Wiklander return (((n - 1) // m) + 1) * m 535dd1570aSJens Wiklander 545dd1570aSJens Wiklander 555dd1570aSJens Wiklanderdef get_arch_id(elffile): 565dd1570aSJens Wiklander e_machine = elffile.header['e_machine'] 575dd1570aSJens Wiklander if e_machine == 'EM_ARM': 585dd1570aSJens Wiklander return 0 595dd1570aSJens Wiklander if e_machine == 'EM_AARCH64': 605dd1570aSJens Wiklander return 1 615dd1570aSJens Wiklander eprint('Unknown e_machine "%s"' % e_machine) 625dd1570aSJens Wiklander sys.exit(1) 635dd1570aSJens Wiklander 645dd1570aSJens Wiklander 653c51966bSJens Wiklanderdef get_symbol(elffile, name): 663c51966bSJens Wiklander global elffile_symbols 67*f77987aeSJens Wiklander global lsyms_def 683c51966bSJens Wiklander if elffile_symbols is None: 693c51966bSJens Wiklander elffile_symbols = dict() 70*f77987aeSJens Wiklander lsyms_def = dict() 713c51966bSJens Wiklander symbol_tables = [s for s in elffile.iter_sections() 723c51966bSJens Wiklander if isinstance(s, SymbolTableSection)] 733c51966bSJens Wiklander for section in symbol_tables: 743c51966bSJens Wiklander for symbol in section.iter_symbols(): 753c51966bSJens Wiklander if symbol['st_info']['bind'] == 'STB_GLOBAL': 763c51966bSJens Wiklander elffile_symbols[symbol.name] = symbol 77*f77987aeSJens Wiklander elif symbol['st_info']['bind'] == 'STB_LOCAL': 78*f77987aeSJens Wiklander if symbol.name not in elffile_symbols.keys(): 79*f77987aeSJens Wiklander elffile_symbols[symbol.name] = symbol 80*f77987aeSJens Wiklander if symbol.name not in lsyms_def.keys(): 81*f77987aeSJens Wiklander lsyms_def[symbol.name] = 1 82*f77987aeSJens Wiklander else: 83*f77987aeSJens Wiklander lsyms_def[symbol.name] += 1 843c51966bSJens Wiklander 85*f77987aeSJens Wiklander if name in lsyms_def.keys() and lsyms_def[name] > 1: 86*f77987aeSJens Wiklander eprint("Multiple definitions of local symbol %s" % name) 87*f77987aeSJens Wiklander sys.exit(1) 88*f77987aeSJens Wiklander if name not in elffile_symbols.keys(): 893c51966bSJens Wiklander eprint("Cannot find symbol %s" % name) 903c51966bSJens Wiklander sys.exit(1) 913c51966bSJens Wiklander 92*f77987aeSJens Wiklander return elffile_symbols[name] 93*f77987aeSJens Wiklander 943c51966bSJens Wiklander 955dd1570aSJens Wiklanderdef get_sections(elffile, pad_to, dump_names): 963c51966bSJens Wiklander last_end = 0 973c51966bSJens Wiklander bin_data = bytearray() 983c51966bSJens Wiklander 993c51966bSJens Wiklander for section in elffile.iter_sections(): 1003c51966bSJens Wiklander if (section['sh_type'] == 'SHT_NOBITS' or 1013c51966bSJens Wiklander not (section['sh_flags'] & SH_FLAGS.SHF_ALLOC) or 1023c51966bSJens Wiklander not dump_names.match(section.name)): 1033c51966bSJens Wiklander continue 1043c51966bSJens Wiklander 1053c51966bSJens Wiklander if last_end == 0: 1063c51966bSJens Wiklander bin_data = section.data() 1073c51966bSJens Wiklander else: 1083c51966bSJens Wiklander if section['sh_addr'] > last_end: 1093c51966bSJens Wiklander bin_data += bytearray(section['sh_addr'] - last_end) 1103c51966bSJens Wiklander bin_data += section.data() 1113c51966bSJens Wiklander 1123c51966bSJens Wiklander last_end = section['sh_addr'] + section['sh_size'] 1133c51966bSJens Wiklander 1143c51966bSJens Wiklander if pad_to > last_end: 1153c51966bSJens Wiklander bin_data += bytearray(pad_to - last_end) 1163c51966bSJens Wiklander last_end = pad_to 1173c51966bSJens Wiklander 1183c51966bSJens Wiklander return bin_data 1193c51966bSJens Wiklander 1203c51966bSJens Wiklander 1213c51966bSJens Wiklanderdef get_pageable_bin(elffile): 1223c51966bSJens Wiklander global tee_pageable_bin 1233c51966bSJens Wiklander if tee_pageable_bin is None: 1243c51966bSJens Wiklander pad_to = 0 1253c51966bSJens Wiklander dump_names = re.compile(r'^\..*_(pageable|init)$') 1265dd1570aSJens Wiklander tee_pageable_bin = get_sections(elffile, pad_to, dump_names) 1273c51966bSJens Wiklander return tee_pageable_bin 1283c51966bSJens Wiklander 1293c51966bSJens Wiklander 1303c51966bSJens Wiklanderdef get_pager_bin(elffile): 1313c51966bSJens Wiklander global tee_pager_bin 1323c51966bSJens Wiklander if tee_pager_bin is None: 1333c51966bSJens Wiklander pad_to = get_symbol(elffile, '__data_end')['st_value'] 1345dd1570aSJens Wiklander dump_names = re.compile( 1355966660cSJens Wiklander r'^\.(text|rodata|got|data|ARM\.exidx|ARM\.extab)$') 1365dd1570aSJens Wiklander tee_pager_bin = get_sections(elffile, pad_to, dump_names) 1373c51966bSJens Wiklander 1383c51966bSJens Wiklander return tee_pager_bin 1393c51966bSJens Wiklander 1403c51966bSJens Wiklander 1415966660cSJens Wiklanderdef get_reloc_bin(elffile): 1425966660cSJens Wiklander if get_arch_id(elffile) == 0: 1435966660cSJens Wiklander exp_rel_type = ENUM_RELOC_TYPE_ARM['R_ARM_RELATIVE'] 1445966660cSJens Wiklander else: 1455966660cSJens Wiklander exp_rel_type = ENUM_RELOC_TYPE_AARCH64['R_AARCH64_RELATIVE'] 1465966660cSJens Wiklander 1475966660cSJens Wiklander link_address = get_symbol(elffile, '__text_start')['st_value'] 1485966660cSJens Wiklander 1495966660cSJens Wiklander addrs = [] 1505966660cSJens Wiklander for section in elffile.iter_sections(): 1515966660cSJens Wiklander if not isinstance(section, RelocationSection): 1525966660cSJens Wiklander continue 1535966660cSJens Wiklander for rel in section.iter_relocations(): 1545966660cSJens Wiklander if rel['r_info_type'] == 0: 1555966660cSJens Wiklander continue 1565966660cSJens Wiklander if rel['r_info_type'] != exp_rel_type: 1575966660cSJens Wiklander eprint("Unexpected relocation type 0x%x" % 1585966660cSJens Wiklander rel['r_info_type']) 1595966660cSJens Wiklander sys.exit(1) 1605966660cSJens Wiklander addrs.append(rel['r_offset'] - link_address) 1615966660cSJens Wiklander 1625966660cSJens Wiklander addrs.sort() 1635966660cSJens Wiklander data = bytearray() 1645966660cSJens Wiklander for a in addrs: 1655966660cSJens Wiklander data += struct.pack('<I', a) 1665966660cSJens Wiklander 1675966660cSJens Wiklander # Relocations has been reduced to only become the relative type with 1685966660cSJens Wiklander # addend at the address (r_offset) of relocation, that is, increase by 1695966660cSJens Wiklander # load_offset. The addresses (r_offset) are also sorted. The format is 1705966660cSJens Wiklander # then: 1715966660cSJens Wiklander # uint32_t: relocation #1 1725966660cSJens Wiklander # uint32_t: relocation #2 1735966660cSJens Wiklander # ... 1745966660cSJens Wiklander # uint32_t: relocation #n 1755966660cSJens Wiklander 1765966660cSJens Wiklander return data 1775966660cSJens Wiklander 1785966660cSJens Wiklander 1795dd1570aSJens Wiklanderdef get_hashes_bin(elffile): 1805dd1570aSJens Wiklander pageable_bin = get_pageable_bin(elffile) 1815dd1570aSJens Wiklander if len(pageable_bin) % small_page_size != 0: 1825dd1570aSJens Wiklander eprint("pageable size not a multiple of 4K: " 1835dd1570aSJens Wiklander "{}".format(paged_area_size)) 1845dd1570aSJens Wiklander sys.exit(1) 1855dd1570aSJens Wiklander 1865dd1570aSJens Wiklander data = bytearray() 1875dd1570aSJens Wiklander for n in range(0, len(pageable_bin), small_page_size): 1885dd1570aSJens Wiklander page = pageable_bin[n:n + small_page_size] 1895dd1570aSJens Wiklander data += hashlib.sha256(page).digest() 1905dd1570aSJens Wiklander 1915dd1570aSJens Wiklander return data 1925dd1570aSJens Wiklander 1935dd1570aSJens Wiklander 1945dd1570aSJens Wiklanderdef get_embdata_bin(elffile): 1955dd1570aSJens Wiklander global tee_embdata_bin 1965dd1570aSJens Wiklander if tee_embdata_bin is None: 1975dd1570aSJens Wiklander hashes_bin = get_hashes_bin(elffile) 1985966660cSJens Wiklander reloc_bin = get_reloc_bin(elffile) 1995dd1570aSJens Wiklander 2005966660cSJens Wiklander num_entries = 2 2015dd1570aSJens Wiklander hash_offs = 2 * 4 + num_entries * (2 * 4) 2025dd1570aSJens Wiklander hash_pad = round_up(len(hashes_bin), 8) - len(hashes_bin) 2035966660cSJens Wiklander reloc_offs = hash_offs + len(hashes_bin) + hash_pad 2045966660cSJens Wiklander reloc_pad = round_up(len(reloc_bin), 8) - len(reloc_bin) 2055966660cSJens Wiklander total_len = reloc_offs + len(reloc_bin) + reloc_pad 2065dd1570aSJens Wiklander 2075966660cSJens Wiklander tee_embdata_bin = struct.pack('<IIIIII', total_len, num_entries, 2085966660cSJens Wiklander hash_offs, len(hashes_bin), 2095966660cSJens Wiklander reloc_offs, len(reloc_bin)) 2105dd1570aSJens Wiklander tee_embdata_bin += hashes_bin + bytearray(hash_pad) 2115966660cSJens Wiklander tee_embdata_bin += reloc_bin + bytearray(reloc_pad) 2125dd1570aSJens Wiklander 2135dd1570aSJens Wiklander # The embedded data region is designed to be easy to extend when 2145dd1570aSJens Wiklander # needed, it's formatted as: 2155966660cSJens Wiklander # +---------------------------------------------------------+ 2165dd1570aSJens Wiklander # | uint32_t: Length of entire area including this field | 2175966660cSJens Wiklander # +---------------------------------------------------------+ 2185966660cSJens Wiklander # | uint32_t: Number of entries "2" | 2195966660cSJens Wiklander # +---------------------------------------------------------+ 2205dd1570aSJens Wiklander # | uint32_t: Offset of hashes from beginning of table | 2215966660cSJens Wiklander # +---------------------------------------------------------+ 2225dd1570aSJens Wiklander # | uint32_t: Length of hashes | 2235966660cSJens Wiklander # +---------------------------------------------------------+ 2245966660cSJens Wiklander # | uint32_t: Offset of relocations from beginning of table | 2255966660cSJens Wiklander # +---------------------------------------------------------+ 2265966660cSJens Wiklander # | uint32_t: Length of relocations | 2275966660cSJens Wiklander # +---------------------------------------------------------+ 2285dd1570aSJens Wiklander # | Data of hashes + eventual padding | 2295966660cSJens Wiklander # +---------------------------------------------------------+ 2305966660cSJens Wiklander # | Data of relocations + eventual padding | 2315966660cSJens Wiklander # +---------------------------------------------------------+ 2325dd1570aSJens Wiklander 2335dd1570aSJens Wiklander return tee_embdata_bin 2345dd1570aSJens Wiklander 2355dd1570aSJens Wiklander 2363c51966bSJens Wiklanderdef output_pager_bin(elffile, outf): 2373c51966bSJens Wiklander outf.write(get_pager_bin(elffile)) 2383c51966bSJens Wiklander 2393c51966bSJens Wiklander 2403c51966bSJens Wiklanderdef output_pageable_bin(elffile, outf): 2413c51966bSJens Wiklander outf.write(get_pageable_bin(elffile)) 2423c51966bSJens Wiklander 2433c51966bSJens Wiklander 2443c51966bSJens Wiklanderdef get_init_load_addr(elffile): 2453c51966bSJens Wiklander init_load_addr = get_symbol(elffile, '_start')['st_value'] 2463c51966bSJens Wiklander init_load_addr_hi = init_load_addr >> 32 2473c51966bSJens Wiklander init_load_addr_lo = init_load_addr & 0xffffffff 2483c51966bSJens Wiklander return init_load_addr_hi, init_load_addr_lo 2493c51966bSJens Wiklander 2503c51966bSJens Wiklander 2513c51966bSJens Wiklanderdef output_header_v1(elffile, outf): 2523c51966bSJens Wiklander arch_id = get_arch_id(elffile) 2533c51966bSJens Wiklander pager_bin = get_pager_bin(elffile) 2543c51966bSJens Wiklander pageable_bin = get_pageable_bin(elffile) 2555dd1570aSJens Wiklander embdata_bin = get_embdata_bin(elffile) 2563c51966bSJens Wiklander init_load_addr = get_init_load_addr(elffile) 2573c51966bSJens Wiklander init_bin_size = get_symbol(elffile, '__init_size')['st_value'] 2583c51966bSJens Wiklander pager_bin_size = len(pager_bin) 2593c51966bSJens Wiklander paged_area_size = len(pageable_bin) 2605dd1570aSJens Wiklander 2615dd1570aSJens Wiklander init_mem_usage = (get_symbol(elffile, '__init_end')['st_value'] - 2625dd1570aSJens Wiklander get_symbol(elffile, '__text_start')['st_value'] + 2635dd1570aSJens Wiklander len(embdata_bin)) 2643c51966bSJens Wiklander 2653c51966bSJens Wiklander init_size = (pager_bin_size + min(init_bin_size, paged_area_size) + 2665dd1570aSJens Wiklander len(embdata_bin)) 2673c51966bSJens Wiklander paged_size = paged_area_size - min(init_bin_size, paged_area_size) 2683c51966bSJens Wiklander 2693c51966bSJens Wiklander magic = 0x4554504f # 'OPTE' 2703c51966bSJens Wiklander version = 1 2713c51966bSJens Wiklander flags = 0 2723c51966bSJens Wiklander outf.write(struct.pack('<IBBHIIIII', magic, version, arch_id, flags, 2733c51966bSJens Wiklander init_size, init_load_addr[0], init_load_addr[1], 2743c51966bSJens Wiklander init_mem_usage, paged_size)) 2753c51966bSJens Wiklander outf.write(pager_bin) 2763c51966bSJens Wiklander outf.write(pageable_bin[:init_bin_size]) 2775dd1570aSJens Wiklander outf.write(embdata_bin) 2783c51966bSJens Wiklander outf.write(pageable_bin[init_bin_size:]) 2793c51966bSJens Wiklander 2803c51966bSJens Wiklander 2813c51966bSJens Wiklanderdef output_header_v2(elffile, outf): 2823c51966bSJens Wiklander arch_id = get_arch_id(elffile) 2833c51966bSJens Wiklander init_load_addr = get_init_load_addr(elffile) 2843c51966bSJens Wiklander init_bin_size = get_symbol(elffile, '__init_size')['st_value'] 2853c51966bSJens Wiklander pager_bin_size = len(get_pager_bin(elffile)) 2863c51966bSJens Wiklander paged_area_size = len(get_pageable_bin(elffile)) 2875dd1570aSJens Wiklander embdata_bin_size = len(get_embdata_bin(elffile)) 2883c51966bSJens Wiklander 2893c51966bSJens Wiklander init_size = (pager_bin_size + min(init_bin_size, paged_area_size) + 2905dd1570aSJens Wiklander embdata_bin_size) 2913c51966bSJens Wiklander paged_size = paged_area_size - min(init_bin_size, paged_area_size) 2923c51966bSJens Wiklander 2933c51966bSJens Wiklander magic = 0x4554504f # 'OPTE' 2943c51966bSJens Wiklander version = 2 2953c51966bSJens Wiklander flags = 0 2963c51966bSJens Wiklander nb_images = 1 if paged_size == 0 else 2 2973c51966bSJens Wiklander outf.write(struct.pack('<IBBHI', magic, version, arch_id, flags, 2983c51966bSJens Wiklander nb_images)) 2993c51966bSJens Wiklander outf.write(struct.pack('<IIII', init_load_addr[0], init_load_addr[1], 3003c51966bSJens Wiklander 0, init_size)) 3013c51966bSJens Wiklander if nb_images == 2: 3023c51966bSJens Wiklander outf.write(struct.pack('<IIII', 0xffffffff, 0xffffffff, 1, paged_size)) 3033c51966bSJens Wiklander 3043c51966bSJens Wiklander 3053c51966bSJens Wiklanderdef output_pager_v2(elffile, outf): 3063c51966bSJens Wiklander init_bin_size = get_symbol(elffile, '__init_size')['st_value'] 3075dd1570aSJens Wiklander pager_bin = get_pager_bin(elffile) 3083c51966bSJens Wiklander pageable_bin = get_pageable_bin(elffile) 3095dd1570aSJens Wiklander embdata_bin = get_embdata_bin(elffile) 3103c51966bSJens Wiklander 3115dd1570aSJens Wiklander outf.write(pager_bin) 3123c51966bSJens Wiklander outf.write(pageable_bin[:init_bin_size]) 3135dd1570aSJens Wiklander outf.write(embdata_bin) 3143c51966bSJens Wiklander 3153c51966bSJens Wiklander 3163c51966bSJens Wiklanderdef output_pageable_v2(elffile, outf): 3173c51966bSJens Wiklander init_bin_size = get_symbol(elffile, '__init_size')['st_value'] 3183c51966bSJens Wiklander outf.write(get_pageable_bin(elffile)[init_bin_size:]) 3193c51966bSJens Wiklander 3203c51966bSJens Wiklander 3213c51966bSJens Wiklanderdef get_args(): 3223c51966bSJens Wiklander parser = argparse.ArgumentParser() 3233c51966bSJens Wiklander 3243c51966bSJens Wiklander parser.add_argument('--input', 3253c51966bSJens Wiklander required=True, type=argparse.FileType('rb'), 3263c51966bSJens Wiklander help='The input tee.elf') 3273c51966bSJens Wiklander 3283c51966bSJens Wiklander parser.add_argument('--out_tee_bin', 3293c51966bSJens Wiklander required=False, type=argparse.FileType('wb'), 3303c51966bSJens Wiklander help='The output tee.bin') 3313c51966bSJens Wiklander 3323c51966bSJens Wiklander parser.add_argument('--out_tee_pager_bin', 3333c51966bSJens Wiklander required=False, type=argparse.FileType('wb'), 3343c51966bSJens Wiklander help='The output tee_pager.bin') 3353c51966bSJens Wiklander 3363c51966bSJens Wiklander parser.add_argument('--out_tee_pageable_bin', 3373c51966bSJens Wiklander required=False, type=argparse.FileType('wb'), 3383c51966bSJens Wiklander help='The output tee_pageable.bin') 3393c51966bSJens Wiklander 3403c51966bSJens Wiklander parser.add_argument('--out_header_v2', 3413c51966bSJens Wiklander required=False, type=argparse.FileType('wb'), 3423c51966bSJens Wiklander help='The output tee_header_v2.bin') 3433c51966bSJens Wiklander 3443c51966bSJens Wiklander parser.add_argument('--out_pager_v2', 3453c51966bSJens Wiklander required=False, type=argparse.FileType('wb'), 3463c51966bSJens Wiklander help='The output tee_pager_v2.bin') 3473c51966bSJens Wiklander 3483c51966bSJens Wiklander parser.add_argument('--out_pageable_v2', 3493c51966bSJens Wiklander required=False, type=argparse.FileType('wb'), 3503c51966bSJens Wiklander help='The output tee_pageable_v2.bin') 3513c51966bSJens Wiklander 3523c51966bSJens Wiklander return parser.parse_args() 3533c51966bSJens Wiklander 3543c51966bSJens Wiklander 3553c51966bSJens Wiklanderdef main(): 3563c51966bSJens Wiklander args = get_args() 3573c51966bSJens Wiklander 3583c51966bSJens Wiklander elffile = ELFFile(args.input) 3593c51966bSJens Wiklander 3603c51966bSJens Wiklander if args.out_tee_bin: 3613c51966bSJens Wiklander output_header_v1(elffile, args.out_tee_bin) 3623c51966bSJens Wiklander 3633c51966bSJens Wiklander if args.out_tee_pager_bin: 3643c51966bSJens Wiklander output_pager_bin(elffile, args.out_tee_pager_bin) 3653c51966bSJens Wiklander 3663c51966bSJens Wiklander if args.out_tee_pageable_bin: 3673c51966bSJens Wiklander output_pageable_bin(elffile, args.out_tee_pageable_bin) 3683c51966bSJens Wiklander 3693c51966bSJens Wiklander if args.out_header_v2: 3703c51966bSJens Wiklander output_header_v2(elffile, args.out_header_v2) 3713c51966bSJens Wiklander 3723c51966bSJens Wiklander if args.out_pager_v2: 3733c51966bSJens Wiklander output_pager_v2(elffile, args.out_pager_v2) 3743c51966bSJens Wiklander 3753c51966bSJens Wiklander if args.out_pageable_v2: 3763c51966bSJens Wiklander output_pageable_v2(elffile, args.out_pageable_v2) 3773c51966bSJens Wiklander 3783c51966bSJens Wiklander 3793c51966bSJens Wiklanderif __name__ == "__main__": 3803c51966bSJens Wiklander main() 381