Skip to content

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 config[section]

None

Returns:

Type Description

the result of config.get(section, key) or config.items(section)

Exceptions:

Type Description
SystemExit

If config[section] or config[section][key] not found.

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 config[section]

required

Returns:

Type Description

the result of config.get(section, key) or config.items(section)

Exceptions:

Type Description
SystemExit

If config[section] or config[section][key] not found.

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 [text_segment, data segment]

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