Skip to content

Instantly share code, notes, and snippets.

@davidberard98
Created July 8, 2025 23:00
Show Gist options
  • Save davidberard98/b60f62cd76faa726662bfbad48174950 to your computer and use it in GitHub Desktop.
Save davidberard98/b60f62cd76faa726662bfbad48174950 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import argparse
import os
import sys
import stat
import subprocess
import re
def parse_glibcxx_version(version_string):
"""Parse GLIBCXX version string and return tuple for comparison."""
# Extract version numbers from GLIBCXX_3.4.21 format
match = re.match(r'GLIBCXX_(\d+)\.(\d+)(?:\.(\d+))?', version_string)
if match:
major = int(match.group(1))
minor = int(match.group(2))
patch = int(match.group(3)) if match.group(3) else 0
return (major, minor, patch)
return (0, 0, 0)
def get_glibcxx_requirements(binary_path):
"""Run objdump on binary and extract GLIBCXX version requirements."""
try:
# Run objdump -T and filter for GLIBCXX
result = subprocess.run(
['objdump', '-T', binary_path],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
print(f"Warning: objdump failed for {binary_path}: {result.stderr}")
return []
# Extract GLIBCXX versions from output
glibcxx_versions = []
for line in result.stdout.splitlines():
if 'GLIBCXX_' in line:
# Extract the GLIBCXX version using regex
match = re.search(r'GLIBCXX_\d+\.\d+(?:\.\d+)?', line)
if match:
version = match.group(0)
if version not in glibcxx_versions:
glibcxx_versions.append(version)
return glibcxx_versions
except subprocess.TimeoutExpired:
print(f"Warning: objdump timeout for {binary_path}")
return []
except FileNotFoundError:
print("Error: objdump command not found. Please install binutils.")
return []
except Exception as e:
print(f"Error running objdump on {binary_path}: {e}")
return []
def find_max_glibcxx_version(versions):
"""Find the maximum GLIBCXX version from a list of version strings."""
if not versions:
return None
max_version = versions[0]
max_parsed = parse_glibcxx_version(max_version)
for version in versions[1:]:
parsed = parse_glibcxx_version(version)
if parsed > max_parsed:
max_version = version
max_parsed = parsed
return max_version
def collect_binary_files(bin_directory):
"""Collect all binary files from the bin directory."""
binary_files = []
if not os.path.exists(bin_directory):
print(f"Warning: bin/ subdirectory not found at '{bin_directory}'")
return binary_files
if not os.path.isdir(bin_directory):
print(f"Warning: '{bin_directory}' exists but is not a directory")
return binary_files
print(f"Scanning bin directory: {bin_directory}")
try:
for item in os.listdir(bin_directory):
item_path = os.path.join(bin_directory, item)
# Skip directories, only process files
if os.path.isfile(item_path):
if os.access(item_path, os.X_OK):
binary_files.append({
'name': item,
'path': item_path,
})
except PermissionError:
print(f"Error: Permission denied accessing '{bin_directory}'")
except OSError as e:
print(f"Error reading directory '{bin_directory}': {e}")
return binary_files
def parse_directory(directory_path):
"""Parse the specified directory for binary files in bin/ subdirectory."""
if not os.path.exists(directory_path):
print(f"Error: Directory '{directory_path}' does not exist.")
return False
if not os.path.isdir(directory_path):
print(f"Error: '{directory_path}' is not a directory.")
return False
print(f"Parsing directory: {directory_path}")
# Look for bin/ subdirectory
bin_directory = os.path.join(directory_path, 'bin')
binary_files = collect_binary_files(bin_directory)
if not binary_files:
print("No binary files found to analyze.")
return True
print("\nAnalyzing GLIBCXX requirements for each binary:")
all_versions = []
binary_results = []
for bf in binary_files:
versions = get_glibcxx_requirements(bf['path'])
if versions:
max_version = find_max_glibcxx_version(versions)
all_versions.extend(versions)
binary_results.append({
'name': bf['name'],
'versions': versions,
'max_version': max_version
})
# Find overall maximum GLIBCXX version required
if all_versions:
overall_max = find_max_glibcxx_version(list(set(all_versions)))
print(f"\n" + "="*60)
print(f"OVERALL ANALYSIS RESULTS:")
print(f"="*60)
print(f"Total binaries analyzed: {len(binary_files)}")
print(f"Binaries with GLIBCXX requirements: {len([r for r in binary_results if r['versions']])}")
print(f"Maximum GLIBCXX version required: {overall_max}")
# Show unique versions found across all binaries
unique_versions = sorted(list(set(all_versions)), key=lambda v: parse_glibcxx_version(v))
print(f"All GLIBCXX versions found: {', '.join(unique_versions)}")
print(f"\Example binary requiring the maximum version ({overall_max}):")
for result in binary_results:
if result['max_version'] == overall_max:
print(f" - {result['name']}")
break
else:
print(f"\n" + "="*60)
print(f"No GLIBCXX requirements found in any binary files.")
print(f"="*60)
return True
def main():
parser = argparse.ArgumentParser(description='Parse directory for glibc++ related content')
parser.add_argument('directory',
help='Directory path to parse')
parser.add_argument('-v', '--verbose',
action='store_true',
help='Enable verbose output')
args = parser.parse_args()
if args.verbose:
print(f"Verbose mode enabled")
print(f"Target directory: {args.directory}")
success = parse_directory(args.directory)
if not success:
sys.exit(1)
print("Parsing completed successfully.")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment