Skip to content

Instantly share code, notes, and snippets.

@rnapier
Created March 29, 2025 16:58
Show Gist options
  • Save rnapier/f6f47d446846332621564702149d9307 to your computer and use it in GitHub Desktop.
Save rnapier/f6f47d446846332621564702149d9307 to your computer and use it in GitHub Desktop.
Read .o's and output stack allocations per-function
#! env python3
import os
import re
import subprocess
import argparse
parser = argparse.ArgumentParser(
description="Process .o files to output stack allocation info."
)
parser.add_argument("paths", nargs="+",
help="List of directories and/or .o files to process")
parser.add_argument("-s", "--size", type=int, default=10*1024,
help="Minimum stack allocation size to output (default: 1024)")
parser.add_argument("-n", "--no-demangle", action="store_true",
help="Don't demangle symbols")
args = parser.parse_args()
func_pattern = re.compile(
r'^(?P<addr>[0-9a-f]+)\s+<(?P<name>[^>]+)>:\n'
r'(?P<body>(?:.+\n)+?)(?=\n)',
re.MULTILINE)
alloc_pattern = re.compile(
r'sub\s+sp,\s+sp,\s+#(0x[0-9a-f]+|\d+)(?:,\s+lsl\s+#(\d+))?',
re.IGNORECASE)
def process_file(file_path, min_size, no_demangle):
file_name = os.path.basename(file_path)
try:
output = subprocess.check_output(['objdump', '-d', file_path],
text=True)
except subprocess.CalledProcessError as e:
print(f"Error processing {file_path}: {e}")
return
for m in func_pattern.finditer(output):
name = m.group('name')
body = m.group('body')
alloc = None
for line in body.splitlines():
am = alloc_pattern.search(line)
if am:
imm = int(am.group(1), 0)
shift = int(am.group(2)) if am.group(2) else 0
alloc = imm << shift
break
if alloc is not None and alloc >= min_size:
if no_demangle:
demangled = name
else:
try:
demangled = subprocess.check_output(
['xcrun', 'swift-demangle', '--compact', '--simplified', name],
text=True).strip()
except subprocess.CalledProcessError:
demangled = name
print(f"{alloc}\t\t{file_name}\t{demangled}")
for path in args.paths:
if os.path.isdir(path):
for filename in os.listdir(path):
if filename.endswith('.o'):
file_path = os.path.join(path, filename)
process_file(file_path, args.size, args.no_demangle)
elif os.path.isfile(path):
if path.endswith('.o'):
process_file(path, args.size, args.no_demangle)
else:
print(f"Skipping {path}: not an .o file")
else:
print(f"Skipping {path}: not a valid file or directory")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment