Source code
Revision control
Copy as Markdown
Other Tools
#!/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())