mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
8773f3bbd6
Improve text-size detection in the case of PE and Mach-O files. In both cases if a suitable `.text`-like section is not found then the text size will be the sum of all segments with an executable flag.
135 lines
4.0 KiB
Python
Executable File
135 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
#
|
|
# This file is distributed under the MIT License. See LICENSE.md for details.
|
|
#
|
|
|
|
# This script prints some information about an executable's sections to stdout,
|
|
# depending on the command line options passed:
|
|
# * With --text-size: the program outputs the size of the `.text` segment (or
|
|
# equivalent) as an integer (in bytes)
|
|
# * Without --text-size: a JSON is returned containing information about all
|
|
# sections
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from subprocess import DEVNULL, PIPE, run
|
|
|
|
import yaml
|
|
|
|
|
|
def find_section_attribute(data: dict, name: str, attr_name: str) -> int | None:
|
|
for section in data["Sections"]:
|
|
if section["Name"]["Value"] == name:
|
|
return section[attr_name]
|
|
return None
|
|
|
|
|
|
def _set_or_add(num1: int | None, num2: int):
|
|
return num2 if num1 is None else num1 + num2
|
|
|
|
|
|
def _flag_overlap(flag_set: dict, flags: frozenset[str]):
|
|
new_flags = {f["Name"] for f in flag_set}
|
|
return bool(new_flags & flags)
|
|
|
|
|
|
def compute_segment_load_size(data: dict):
|
|
size = None
|
|
for segment in data["ProgramHeaders"]:
|
|
if segment["Type"]["Value"] != "PT_LOAD":
|
|
continue
|
|
if _flag_overlap(segment["Flags"]["Flags"], frozenset(("PF_X",))):
|
|
size = _set_or_add(size, segment["FileSize"])
|
|
return size
|
|
|
|
|
|
def compute_executable_sections_macho(data: dict):
|
|
size = None
|
|
executable_flags = frozenset(("PureInstructions", "SomeInstructions"))
|
|
for section in data["Sections"]:
|
|
if _flag_overlap(section["Attributes"]["Flags"], executable_flags):
|
|
size = _set_or_add(size, section["Size"])
|
|
return size
|
|
|
|
|
|
def compute_executable_sections_pe(data: dict):
|
|
size = None
|
|
executable_flags = frozenset(("IMAGE_SCN_CNT_CODE", "IMAGE_SCN_MEM_EXECUTE"))
|
|
for section in data["Sections"]:
|
|
if _flag_overlap(section["Characteristics"]["Flags"], executable_flags):
|
|
size = _set_or_add(size, section["RawDataSize"])
|
|
return size
|
|
|
|
|
|
def get_text_size(data: dict):
|
|
format_: str = data["Format"]
|
|
if format_.startswith("elf"):
|
|
res = find_section_attribute(data, ".text", "Size")
|
|
if res is None:
|
|
res = compute_segment_load_size(data)
|
|
elif format_.startswith("Mach-O"):
|
|
res = find_section_attribute(data, "__text", "Size")
|
|
if res is None:
|
|
res = compute_executable_sections_macho(data)
|
|
elif format_.startswith("COFF"):
|
|
res = find_section_attribute(data, ".text", "RawDataSize")
|
|
if res is None:
|
|
res = compute_executable_sections_pe(data)
|
|
else:
|
|
raise ValueError(f"Invalid format: {format_}")
|
|
|
|
return res if res is not None else -1
|
|
|
|
|
|
def readobj(filepath: str):
|
|
proc = run(
|
|
["llvm-readobj", "--elf-output-style=JSON", "--sections", "--segments", filepath],
|
|
stdout=PIPE,
|
|
stderr=DEVNULL,
|
|
check=True,
|
|
text=True,
|
|
)
|
|
data = yaml.safe_load(proc.stdout)
|
|
|
|
result = {}
|
|
if len(data) == 1:
|
|
# ELF output
|
|
result.update(data[0]["FileSummary"])
|
|
result["Sections"] = []
|
|
for section in data[0]["Sections"]:
|
|
result["Sections"].append(section["Section"])
|
|
result["ProgramHeaders"] = []
|
|
for segment in data[0]["ProgramHeaders"]:
|
|
result["ProgramHeaders"].append(segment["ProgramHeader"])
|
|
elif len(data) == 5:
|
|
# PE/MACH-O output
|
|
for i in range(0, 4):
|
|
result.update(data[i])
|
|
result["Sections"] = []
|
|
for section in data[4]["Sections"]:
|
|
result["Sections"].append(section["Section"])
|
|
else:
|
|
raise ValueError()
|
|
return result
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--text-size", action="store_true", help="Output the size of the .text section"
|
|
)
|
|
parser.add_argument("input", help="Input file")
|
|
args = parser.parse_args()
|
|
|
|
readobj_data = readobj(args.input)
|
|
if args.text_size:
|
|
print(get_text_size(readobj_data))
|
|
else:
|
|
json.dump(readobj_data, sys.stdout)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|