1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
#!/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())
|