| #!/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()) |