1#!/usr/bin/env vpython3 2# 3# Copyright 2024 The Chromium Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6''' 7Embed the address and size of elf sections into the pre-defined symbols. 8 9The embedded values are used for performance optimization by mlock(2)ing the 10sections on ChromeOS. See chromeos/ash/components/memory/memory.cc for details. 11''' 12 13import argparse 14import os 15import subprocess 16import sys 17import shutil 18 19llvm_readelf = os.path.join( 20 os.path.dirname(sys.argv[0]), '..', '..', 'third_party', 'llvm-build', 21 'Release+Asserts', 'bin', 'llvm-readelf') 22 23TARGET_SECTIONS = { 24 '.rodata': { 25 'addr': 'kRodataAddr', 26 'size': 'kRodataSize' 27 }, 28 '.text.hot': { 29 'addr': 'kTextHotAddr', 30 'size': 'kTextHotSize' 31 }, 32} 33 34 35def parse_endianess(objdump_result): 36 for line in objdump_result.splitlines(): 37 line = line.strip() 38 if line.startswith('Data:'): 39 if '1' in line: 40 return 'big' 41 if '2' in line: 42 return 'little' 43 raise ValueError('No endian found') 44 45 46def assert_elf_type(objdump_result): 47 for line in objdump_result.splitlines(): 48 line = line.strip() 49 if line.startswith('Class:'): 50 if 'ELF64' in line or 'ELF32' in line: 51 return 52 raise ValueError('Class is not ELF64 nor ELF32: ' + line) 53 raise ValueError('No class found') 54 55 56def parse_section_info(objdump_result, section_name): 57 for line in objdump_result.splitlines(): 58 row = line.strip().split() 59 if len(row) < 2: 60 continue 61 if row[1] == section_name: 62 # 3: Address, 4: Offset, 5: Size 63 return (int(row[3], base=16), int(row[4], base=16), int(row[5], base=16)) 64 return (0, 0, 0) 65 66 67def create_symbol_map(binary_input, objdump_result): 68 (rodata_section_addr, rodata_section_offset, 69 rodata_section_size) = parse_section_info(objdump_result, '.rodata') 70 71 command = [llvm_readelf, '--symbols', binary_input] 72 with subprocess.Popen(command, stdout=subprocess.PIPE, text=True) as process: 73 74 variable_names = [ 75 var for maps in TARGET_SECTIONS.values() for var in maps.values() 76 ] 77 result = {} 78 while len(result) < len(variable_names): 79 line = process.stdout.readline() 80 if not line: 81 break 82 for var in variable_names: 83 if var in line: 84 row = line.strip().split() 85 if row[2] == '8': 86 size = 8 87 elif row[2] == '4': 88 size = 4 89 else: 90 raise ValueError('variable size is not 8 or 4: ' + line) 91 addr = int(row[1], base=16) 92 rodata_section_end_addr = rodata_section_addr + rodata_section_size 93 if addr < rodata_section_addr or addr >= rodata_section_end_addr: 94 raise ValueError(var + ' is not in .rodata section') 95 offset = addr - rodata_section_addr + rodata_section_offset 96 result[var] = (offset, size) 97 98 return result 99 100 101def overwrite_variable(file, symbol_map, endianess, section_name, var_type, 102 value): 103 (var_offset, var_size) = symbol_map[TARGET_SECTIONS[section_name][var_type]] 104 file.seek(var_offset) 105 if file.write( 106 value.to_bytes(length=var_size, byteorder=endianess, 107 signed=False)) != var_size: 108 raise ValueError('failed to write value to file') 109 110 111def main(): 112 argparser = argparse.ArgumentParser( 113 description='embed sections informataion into binary.') 114 115 argparser.add_argument('--binary-input', help='exe file path.') 116 argparser.add_argument('--binary-output', help='embedded file path.') 117 args = argparser.parse_args() 118 119 objdump_result = subprocess.run([llvm_readelf, '-e', args.binary_input], 120 stdout=subprocess.PIPE, 121 check=True, 122 text=True).stdout 123 124 assert_elf_type(objdump_result) 125 126 symbol_map = create_symbol_map(args.binary_input, objdump_result) 127 if len(symbol_map) != len(set(symbol_map.values())): 128 raise ValueError(f'symbol_map overlaps: {symbol_map}') 129 130 endianess = parse_endianess(objdump_result) 131 132 shutil.copyfile(args.binary_input, args.binary_output) 133 134 with open(args.binary_output, 'r+b') as file: 135 for section_name in TARGET_SECTIONS: 136 (addr, _, size) = parse_section_info(objdump_result, section_name) 137 overwrite_variable(file, symbol_map, endianess, section_name, 'addr', 138 addr) 139 overwrite_variable(file, symbol_map, endianess, section_name, 'size', 140 size) 141 142 objdump_result_after = subprocess.run( 143 [llvm_readelf, '-e', args.binary_output], 144 stdout=subprocess.PIPE, 145 check=True, 146 text=True).stdout 147 if objdump_result_after != objdump_result: 148 raise ValueError('realelf result has changed') 149 150 return 0 151 152 153if __name__ == '__main__': 154 sys.exit(main()) 155