#!/usr/bin/env vpython3 # # Copyright 2024 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. ''' Embed the address and size of elf sections into the pre-defined symbols. The embedded values are used for performance optimization by mlock(2)ing the sections on ChromeOS. See chromeos/ash/components/memory/memory.cc for details. ''' import argparse import os import subprocess import sys import shutil llvm_readelf = os.path.join( os.path.dirname(sys.argv[0]), '..', '..', 'third_party', 'llvm-build', 'Release+Asserts', 'bin', 'llvm-readelf') TARGET_SECTIONS = { '.rodata': { 'addr': 'kRodataAddr', 'size': 'kRodataSize' }, '.text.hot': { 'addr': 'kTextHotAddr', 'size': 'kTextHotSize' }, } def parse_endianess(objdump_result): for line in objdump_result.splitlines(): line = line.strip() if line.startswith('Data:'): if '1' in line: return 'big' if '2' in line: return 'little' raise ValueError('No endian found') def assert_elf_type(objdump_result): for line in objdump_result.splitlines(): line = line.strip() if line.startswith('Class:'): if 'ELF64' in line or 'ELF32' in line: return raise ValueError('Class is not ELF64 nor ELF32: ' + line) raise ValueError('No class found') def parse_section_info(objdump_result, section_name): for line in objdump_result.splitlines(): row = line.strip().split() if len(row) < 2: continue if row[1] == section_name: # 3: Address, 4: Offset, 5: Size return (int(row[3], base=16), int(row[4], base=16), int(row[5], base=16)) return (0, 0, 0) def create_symbol_map(binary_input, objdump_result): (rodata_section_addr, rodata_section_offset, rodata_section_size) = parse_section_info(objdump_result, '.rodata') command = [llvm_readelf, '--symbols', binary_input] with subprocess.Popen(command, stdout=subprocess.PIPE, text=True) as process: variable_names = [ var for maps in TARGET_SECTIONS.values() for var in maps.values() ] result = {} while len(result) < len(variable_names): line = process.stdout.readline() if not line: break for var in variable_names: if var in line: row = line.strip().split() if row[2] == '8': size = 8 elif row[2] == '4': size = 4 else: raise ValueError('variable size is not 8 or 4: ' + line) addr = int(row[1], base=16) rodata_section_end_addr = rodata_section_addr + rodata_section_size if addr < rodata_section_addr or addr >= rodata_section_end_addr: raise ValueError(var + ' is not in .rodata section') offset = addr - rodata_section_addr + rodata_section_offset result[var] = (offset, size) return result def overwrite_variable(file, symbol_map, endianess, section_name, var_type, value): (var_offset, var_size) = symbol_map[TARGET_SECTIONS[section_name][var_type]] file.seek(var_offset) if file.write( value.to_bytes(length=var_size, byteorder=endianess, signed=False)) != var_size: raise ValueError('failed to write value to file') def main(): argparser = argparse.ArgumentParser( description='embed sections informataion into binary.') argparser.add_argument('--binary-input', help='exe file path.') argparser.add_argument('--binary-output', help='embedded file path.') args = argparser.parse_args() objdump_result = subprocess.run([llvm_readelf, '-e', args.binary_input], stdout=subprocess.PIPE, check=True, text=True).stdout assert_elf_type(objdump_result) symbol_map = create_symbol_map(args.binary_input, objdump_result) if len(symbol_map) != len(set(symbol_map.values())): raise ValueError(f'symbol_map overlaps: {symbol_map}') endianess = parse_endianess(objdump_result) shutil.copyfile(args.binary_input, args.binary_output) with open(args.binary_output, 'r+b') as file: for section_name in TARGET_SECTIONS: (addr, _, size) = parse_section_info(objdump_result, section_name) overwrite_variable(file, symbol_map, endianess, section_name, 'addr', addr) overwrite_variable(file, symbol_map, endianess, section_name, 'size', size) objdump_result_after = subprocess.run( [llvm_readelf, '-e', args.binary_output], stdout=subprocess.PIPE, check=True, text=True).stdout if objdump_result_after != objdump_result: raise ValueError('realelf result has changed') return 0 if __name__ == '__main__': sys.exit(main())