utils.py
ofrak_patch_maker.toolchain.utils
get_repository_config(section, key=None)
Get config values from toolchain.conf. The toolchain.conf file can be located /etc/ofrak/toolchain.conf
or inside the ofrak_patch_maker
Python package install.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
section |
str |
section name in config file |
required |
key |
Optional[str] |
key in |
None |
Returns:
Type | Description |
---|---|
the result of |
Exceptions:
Type | Description |
---|---|
SystemExit |
If |
Source code in ofrak_patch_maker/toolchain/utils.py
def get_repository_config(section: str, key: Optional[str] = None):
"""
Get config values from toolchain.conf. The toolchain.conf file can be located /etc/ofrak/toolchain.conf
or inside the `ofrak_patch_maker` Python package install.
:param section: section name in config file
:param key: key in `config[section]`
:raises SystemExit: If `config[section]` or `config[section][key]` not found.
:return Union[str, List[Tuple[str, str]]]: the result of ``config.get(section, key)`` or
``config.items(section)``
"""
config = configparser.RawConfigParser()
config_name = "toolchain.conf"
# Should be shared across main OFRAK and use standardized configuration dirs in the future
config_paths = [
os.path.join(p, config_name)
for p in ("/etc/ofrak/", os.path.join(os.path.dirname(__file__), os.path.pardir))
]
error_by_config_file: Dict[str, Exception] = dict()
for conf in config_paths:
if not os.path.exists(conf):
continue
try:
config.read(conf)
if key:
ret = config.get(section, key)
else:
ret = config.items(section) # type: ignore
return ret
except (configparser.NoSectionError, configparser.NoOptionError) as e:
error_by_config_file[conf] = e
continue
if 0 == len(error_by_config_file):
config_paths_str = "\n".join(config_paths)
raise NotFoundError(
f"Configuration file {config_name} not found. Tried the following locations:\n{config_paths_str}"
)
elif 1 == len(error_by_config_file):
_config, _e = next(iter(error_by_config_file.items()))
raise NotFoundError(f"Section or option not found in {_config}", _e)
else:
raise NotFoundError(
f"Section {section:s} or option {key:s} not found in any of the configs searched: "
f"{error_by_config_file}"
)
get_exec_from_config(section, key)
Get an executable name or path from a toolchain.conf. Output instructions for configuring toolchain paths if the exectuable is not available.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
section |
str |
section name in config file |
required |
key |
str |
key in |
required |
Returns:
Type | Description |
---|---|
the result of |
Exceptions:
Type | Description |
---|---|
SystemExit |
If |
Source code in ofrak_patch_maker/toolchain/utils.py
def get_exec_from_config(section: str, key: str):
"""
Get an executable name or path from a toolchain.conf. Output instructions for configuring
toolchain paths if the exectuable is not available.
:param section: section name in config file
:param key: key in `config[section]`
:raises SystemExit: If `config[section]` or `config[section][key]` not found.
:return Union[str, List[Tuple[str, str]]]: the result of ``config.get(section, key)`` or
``config.items(section)``
"""
exec_path = get_repository_config(section, key)
if shutil.which(exec_path) is not None:
return exec_path
else:
try:
download_link = get_repository_config(section, "DOWNLOAD")
download_text = f"download the toolchain from {download_link} and "
except NotFoundError:
download_text = ""
raise NotFoundError(
f"Configured executable {exec_path} ({key} of {section}) not found on the filesystem or PATH. "
f"Please {download_text}edit the toolchain.conf file to point to the toolchain executable."
)
generate_arm_stubs(func_names, out_dir, thumb=False)
Utility function to generate assembly stubs. This is necessary when function calls need to switch between ARM and thumb mode (when code generated by the PatchMaker is ARM and needs to jump to thumb code, or the opposite). With those stubs, the linker has explicit information about the destination mode, so it jumps correctly (exchanging mode or not).
It is not PatchMaker's responsibility to programmatically generate source in this way.
Furthermore, this functionality is much more complex than base_symbols={}
addition implies,
as actual object files are generated and linked against.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
func_names |
Mapping[str, int] |
names to effective address |
required |
out_dir |
str |
object file output directory |
required |
thumb |
bool |
Whether or not to generate thumb stubs |
False |
Returns:
Type | Description |
---|---|
Mapping[str, Tuple[ofrak_patch_maker.toolchain.model.Segment, ofrak_patch_maker.toolchain.model.Segment]] |
maps object file to dummy |
Source code in ofrak_patch_maker/toolchain/utils.py
def generate_arm_stubs(
func_names: Mapping[str, int], out_dir: str, thumb: bool = False
) -> Mapping[str, Tuple[Segment, Segment]]:
"""
Utility function to generate assembly stubs. This is necessary when function calls need to
switch between ARM and thumb mode (when code generated by the PatchMaker is ARM and needs to
jump to thumb code, or the opposite). With those stubs, the linker has explicit information
about the destination mode, so it jumps correctly (exchanging mode or not).
It is not [PatchMaker][ofrak_patch_maker.patch_maker.PatchMaker]'s responsibility to
programmatically generate source in this way.
Furthermore, this functionality is much more complex than `base_symbols={}` addition implies,
as actual object files are generated and linked against.
:param func_names: names to effective address
:param out_dir: object file output directory
:param thumb: Whether or not to generate thumb stubs
:return Dict[str, Tuple[Segment, Segment]: maps object file to dummy
`[text_segment, data segment]`
"""
print(f"Generating ARM stubs...")
names = list(func_names.keys())
addresses = list(func_names.values())
out_dirs = [out_dir] * len(names)
if thumb:
stub_strs = [".thumb_func"] * len(names)
else:
stub_strs = [f".type {name}, %function" for name in names]
args = zip(names, addresses, stub_strs, out_dirs)
workers = math.ceil(0.6 * cpu_count())
with Pool(processes=workers) as pool:
result = pool.starmap(_gen_file, args, chunksize=math.ceil(len(names) / workers))
segment_map: Dict[str, Tuple[Segment, Segment]] = {}
for r in result:
segment_map.update(r)
print(list(r.keys()))
return segment_map
