blob: 8b0cd09ba0ca9e5f110293510325503405bc8ecd [file]
#!/usr/bin/env python3
"""
Interactive wizard for configuring the LLVM Instrumentor pass.
This script helps users create custom instrumentation configurations by:
1. Generating a default config file using opt
2. Presenting available instrumentation options interactively
3. Allowing users to enable/disable specific instrumentation opportunities
4. Saving the customized configuration to a JSON file
"""
import argparse
import json
import os
import subprocess
import sys
import tempfile
from typing import Dict, List, Any, Optional, Tuple
class InstrumentorConfigWizard:
def __init__(self, opt_path: str = None):
"""Initialize the wizard with the path to opt."""
self.opt_path = opt_path or self.find_opt()
self.config = {}
self.enabled_opportunities = set()
self.same_pre_post = True
self.navigation_stack = []
def find_opt(self) -> str:
"""Find the opt binary in the build directory."""
# Try common locations relative to this script
script_dir = os.path.dirname(os.path.abspath(__file__))
repo_root = os.path.dirname(os.path.dirname(script_dir))
# Check build/bin/opt
opt_candidates = [
os.path.join(repo_root, "build", "bin", "opt"),
os.path.join(repo_root, "build", "Debug", "bin", "opt"),
os.path.join(repo_root, "build", "Release", "bin", "opt"),
"opt", # Try system PATH
]
for candidate in opt_candidates:
if os.path.exists(candidate):
return candidate
# Check if it's in PATH
try:
subprocess.run(
[candidate, "--version"], capture_output=True, check=True, timeout=5
)
return candidate
except (
subprocess.CalledProcessError,
FileNotFoundError,
subprocess.TimeoutExpired,
):
continue
raise FileNotFoundError(
"Could not find 'opt' binary. Please specify the path using --opt-path"
)
def generate_default_config(self) -> Dict[str, Any]:
"""Generate a default configuration by running opt."""
print(f"Generating default configuration using: {self.opt_path}")
# Create a minimal LLVM IR module to trigger config generation
minimal_ir = """
define i32 @main() {
%1 = alloca i32
store i32 0, ptr %1
%2 = load i32, ptr %1
ret i32 %2
}
"""
with tempfile.TemporaryDirectory() as tmpdir:
ir_file = os.path.join(tmpdir, "input.ll")
config_file = os.path.join(tmpdir, "config.json")
# Write minimal IR
with open(ir_file, "w") as f:
f.write(minimal_ir)
# Run opt with instrumentor to generate config
try:
cmd = [
self.opt_path,
"-passes=instrumentor",
f"-instrumentor-write-config-file={config_file}",
"-disable-output",
ir_file,
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode != 0:
print(
f"Warning: opt returned non-zero exit code: {result.returncode}"
)
if result.stderr:
print(f"stderr: {result.stderr}")
# Read the generated config
if not os.path.exists(config_file):
raise FileNotFoundError(
f"Config file was not generated at {config_file}"
)
with open(config_file, "r") as f:
config = json.load(f)
print("✓ Default configuration generated successfully\n")
return config
except subprocess.TimeoutExpired:
raise RuntimeError("opt command timed out")
except Exception as e:
raise RuntimeError(f"Failed to generate config: {e}")
def clear_screen(self):
"""Clear the terminal screen."""
os.system("clear" if os.name != "nt" else "cls")
def print_section_header(self, title: str):
"""Print a formatted section header."""
print("\n" + "=" * 70)
print(f" {title}")
print("=" * 70)
def print_option(self, index: int, name: str, description: str, enabled: bool):
"""Print a formatted option."""
status = "[X]" if enabled else "[ ]"
if description:
print(f" {index:2d}. {status} {name:30s} - {description}")
else:
print(f" {index:2d}. {status} {name}")
def get_user_choice(
self, prompt: str, valid_choices: List[str] = None, allow_back: bool = True
) -> Optional[str]:
"""Get user input with validation."""
while True:
try:
nav_hint = " (b=back, q=quit)" if allow_back else " (q=quit)"
choice = input(prompt + nav_hint + ": ").strip().lower()
if choice == "q":
confirm = input("Really quit? (y/n): ").strip().lower()
if confirm == "y":
print("\nWizard cancelled by user.")
sys.exit(0)
continue
if choice == "b" and allow_back:
return "BACK"
if not choice:
return ""
if valid_choices:
if choice in valid_choices:
return choice
print(f"Please enter one of: {', '.join(valid_choices)}")
else:
return choice
except KeyboardInterrupt:
print("\n\nWizard interrupted by user.")
sys.exit(0)
def get_location_names(self) -> List[str]:
"""Extract all location names from the config, excluding 'configuration'."""
locations = []
for key in self.config.keys():
if key != "configuration":
locations.append(key)
return locations
def get_all_opportunity_types(self) -> List[Tuple[str, str]]:
"""Extract all unique opportunity types from the config."""
opportunities = []
seen = set()
for location in self.get_location_names():
for opp_name in self.config[location].keys():
if opp_name not in seen:
seen.add(opp_name)
# Get description from first occurrence
opp_config = self.config[location][opp_name]
desc = ""
# Look for a "description" property (without dot)
if "description" in opp_config and opp_config["description"]:
desc = opp_config["description"]
opportunities.append((opp_name, desc))
return sorted(opportunities)
def select_opportunities(self) -> bool:
"""Let user select which instrumentation opportunities to enable."""
while True:
self.clear_screen()
self.print_section_header("Step 1: Select Instrumentation Types")
opportunities = self.get_all_opportunity_types()
print("\nSelect which types of instrumentation you want to configure:")
print(
"(You can toggle individual arguments for each type in the next steps)\n"
)
for idx, (opp_name, opp_desc) in enumerate(opportunities, 1):
enabled = opp_name in self.enabled_opportunities
self.print_option(idx, opp_name, opp_desc, enabled)
print("\nCommands:")
print(" - Enter numbers (space-separated) to toggle opportunities")
print(" - 'all' to enable all, 'none' to disable all")
print(" - Press Enter when done to continue")
choice = self.get_user_choice("\nYour choice", allow_back=False)
if choice == "BACK":
continue
elif choice == "":
if not self.enabled_opportunities:
print("\n⚠ Please enable at least one instrumentation type!")
input("Press Enter to continue...")
continue
return True
elif choice == "all":
self.enabled_opportunities = {opp[0] for opp in opportunities}
elif choice == "none":
self.enabled_opportunities.clear()
else:
try:
indices = [int(x) for x in choice.split()]
for idx in indices:
if 1 <= idx <= len(opportunities):
opp_name = opportunities[idx - 1][0]
if opp_name in self.enabled_opportunities:
self.enabled_opportunities.remove(opp_name)
else:
self.enabled_opportunities.add(opp_name)
except ValueError:
print(
"\n⚠ Invalid input. Please enter numbers separated by spaces."
)
input("Press Enter to continue...")
def configure_pre_post_mode(self) -> bool:
"""Ask if PRE and POST should have the same configuration."""
while True:
self.clear_screen()
self.print_section_header("Step 2: PRE vs POST Configuration")
print("\nInstrumentation can happen at two points:")
print(" - PRE: Before the instrumented operation")
print(" - POST: After the instrumented operation")
print("\nFor example, for a load instruction:")
print(" - PRE: Can inspect/modify the pointer before the load")
print(" - POST: Can inspect/modify the loaded value after the load")
print(
f"\nCurrent mode: {'SAME configuration for PRE and POST' if self.same_pre_post else 'DIFFERENT configurations'}"
)
choice = self.get_user_choice(
"\nUse same configuration for PRE and POST? (y/n/Enter to keep)",
valid_choices=["y", "yes", "n", "no", ""],
)
if choice == "BACK":
return False
elif choice in ["y", "yes"]:
self.same_pre_post = True
return True
elif choice in ["n", "no"]:
self.same_pre_post = False
return True
elif choice == "":
return True
def configure_base_options(self) -> bool:
"""Configure base/global options."""
while True:
self.clear_screen()
self.print_section_header("Step 3: Base Configuration")
if "configuration" not in self.config:
self.config["configuration"] = {}
base_config = self.config["configuration"]
# Collect all configuration options (excluding descriptions and runtime_stubs_file)
config_options = []
for key in sorted(base_config.keys()):
if not key.endswith(".description") and key != "runtime_stubs_file":
config_options.append(key)
# Display current settings
print("\nCurrent settings:")
for idx, key in enumerate(config_options, 1):
value = base_config.get(key)
# Format value display
if value == "":
display_value = "(none)"
else:
display_value = value
print(f" {idx}. {key:30s} {display_value}")
print("\nEnter option number to modify, or press Enter to continue")
choice = self.get_user_choice("Option")
if choice == "BACK":
return False
elif choice == "":
return True
else:
# Try to parse as a number
try:
idx = int(choice)
if 1 <= idx <= len(config_options):
key = config_options[idx - 1]
current_value = base_config[key]
description = base_config.get(f"{key}.description", "")
# Show description if available
if description:
print(f"\n{description}")
# Handle different types
if isinstance(current_value, bool):
new_val = self.get_user_choice(
f"Set {key}? (y/n)",
["y", "n"],
allow_back=False,
)
if new_val:
base_config[key] = new_val == "y"
elif isinstance(current_value, str):
# Special handling for filter properties
if "filter" in key.lower():
print(
f"Note: '{key}' requires a valid regular expression"
)
new_val = input(
f"Enter new value for {key} (current: '{current_value}'): "
).strip()
# Allow setting to empty string
base_config[key] = new_val
else:
print(f"Unsupported type for {key}: {type(current_value)}")
input("Press Enter to continue...")
else:
print(f"\n⚠ Invalid option number: {idx}")
input("Press Enter to continue...")
except ValueError:
print("\n⚠ Please enter a valid number")
input("Press Enter to continue...")
def configure_opportunity_args(
self, opp_name: str, location: str, step_prefix: str = "Step 4"
) -> bool:
"""Configure arguments for a specific opportunity at a location."""
while True:
self.clear_screen()
location_desc = "PRE (before)" if "pre" in location else "POST (after)"
self.print_section_header(
f"{step_prefix}: Configure {opp_name} - {location_desc}"
)
if location not in self.config or opp_name not in self.config[location]:
print(f"\n⚠ {opp_name} not found in {location}")
input("Press Enter to continue...")
return True
opp_config = self.config[location][opp_name]
# Show enable/disable status
enabled = opp_config.get("enabled", False)
print(f"\nInstrumentation: {'ENABLED ✓' if enabled else 'DISABLED ✗'}")
# Collect arguments
args = []
for key, value in sorted(opp_config.items()):
if (
key == "enabled"
or key.endswith(".description")
or key.endswith(".replace")
):
continue
desc = opp_config.get(f"{key}.description", "No description")
can_replace = f"{key}.replace" in opp_config
replace_enabled = (
opp_config.get(f"{key}.replace", False) if can_replace else False
)
args.append((key, value, desc, can_replace, replace_enabled))
if args:
print("\nAvailable arguments:")
for idx, (
arg_name,
arg_enabled,
arg_desc,
can_replace,
replace_enabled,
) in enumerate(args, 1):
status = "[X]" if arg_enabled else "[ ]"
if can_replace:
replace_status = "REPLACE" if replace_enabled else "observe"
replace_mark = f" [replaceable: {replace_status}]"
else:
replace_mark = ""
print(
f" {idx:2d}. {status} {arg_name:25s} - {arg_desc}{replace_mark}"
)
print("\nCommands:")
print(" - 'e' to toggle enabled/disabled")
print(" - Enter numbers (space-separated) to toggle arguments")
print(
" - 'r <num>' to toggle replacement for replaceable argument (e.g., 'r 1')"
)
print(" - 'all' to enable all args, 'none' to disable all args")
print(" - Press Enter when done")
choice = self.get_user_choice("\nYour choice")
if choice == "BACK":
return False
elif choice == "":
return True
elif choice == "e":
opp_config["enabled"] = not opp_config["enabled"]
elif choice == "all":
for arg_name, _, _, _, _ in args:
opp_config[arg_name] = True
elif choice == "none":
for arg_name, _, _, _, _ in args:
opp_config[arg_name] = False
elif choice.startswith("r "):
# Toggle replacement
try:
parts = choice.split()
if len(parts) == 2:
idx = int(parts[1])
if 1 <= idx <= len(args):
arg_name, _, _, can_replace, _ = args[idx - 1]
if can_replace:
replace_key = f"{arg_name}.replace"
opp_config[replace_key] = not opp_config.get(
replace_key, False
)
else:
print(f"\n⚠ Argument '{arg_name}' is not replaceable.")
input("Press Enter to continue...")
else:
print(f"\n⚠ Invalid argument number: {idx}")
input("Press Enter to continue...")
else:
print("\n⚠ Usage: r <number>")
input("Press Enter to continue...")
except ValueError:
print("\n⚠ Invalid input for replacement toggle.")
input("Press Enter to continue...")
else:
try:
indices = [int(x) for x in choice.split()]
for idx in indices:
if 1 <= idx <= len(args):
arg_name = args[idx - 1][0]
opp_config[arg_name] = not opp_config[arg_name]
except ValueError:
print("\n⚠ Invalid input.")
input("Press Enter to continue...")
def configure_locations(self) -> bool:
"""Configure all enabled opportunities for PRE and optionally POST."""
# First, disable all opportunities that are not in enabled_opportunities
for location in self.get_location_names():
for opp_name, opp_config in self.config[location].items():
if opp_name not in self.enabled_opportunities:
opp_config["enabled"] = False
# Get all location names and separate into PRE and POST
all_locations = self.get_location_names()
pre_locations = [loc for loc in all_locations if loc.endswith("_pre")]
post_locations = [loc for loc in all_locations if loc.endswith("_post")]
# Configure PRE locations
step_num = 4
for idx, opp_name in enumerate(sorted(self.enabled_opportunities), 1):
# Try to find the opportunity in any PRE location
location = None
for loc in pre_locations:
if opp_name in self.config[loc]:
location = loc
break
if location:
if not self.configure_opportunity_args(opp_name, location):
return False
# If same config, copy PRE to POST
if self.same_pre_post:
for opp_name in self.enabled_opportunities:
# Copy from PRE to POST for each location pair
for pre_loc in pre_locations:
if opp_name not in self.config[pre_loc]:
continue
# Find corresponding POST location (e.g., function_pre -> function_post)
base_name = pre_loc[:-4] # Remove "_pre"
post_loc = base_name + "_post"
if post_loc in self.config and opp_name in self.config[post_loc]:
pre_config = self.config[pre_loc][opp_name]
post_config = self.config[post_loc][opp_name]
# Copy enabled and argument settings
post_config["enabled"] = pre_config.get("enabled", False)
for key in pre_config:
if not key.endswith(".description") and key != "enabled":
if key in post_config:
post_config[key] = pre_config[key]
else:
# Configure POST locations separately
for opp_name in sorted(self.enabled_opportunities):
# Try to find the opportunity in any POST location
location = None
for loc in post_locations:
if opp_name in self.config[loc]:
location = loc
break
if location:
if not self.configure_opportunity_args(opp_name, location):
return False
return True
def generate_runtime_stubs(self, config_path: str, stub_path: str) -> bool:
"""Generate runtime stub files (both .c/.cpp and .h) using the configuration."""
print(f"\nGenerating runtime stubs using: {self.opt_path}")
# Create a minimal LLVM IR module
minimal_ir = """
define i32 @main() {
%1 = alloca i32
store i32 0, ptr %1
%2 = load i32, ptr %1
ret i32 %2
}
"""
# Calculate header path
stub_base = stub_path.rsplit(".", 1)[0] if "." in stub_path else stub_path
stub_header_path = stub_base + ".h"
with tempfile.TemporaryDirectory() as tmpdir:
ir_file = os.path.join(tmpdir, "input.ll")
temp_config = os.path.join(tmpdir, "temp_config.json")
# Write minimal IR
with open(ir_file, "w") as f:
f.write(minimal_ir)
# Create a temporary config with stub file set
temp_cfg = self.config.copy()
if "configuration" not in temp_cfg:
temp_cfg["configuration"] = {}
temp_cfg["configuration"]["runtime_stubs_file"] = stub_path
with open(temp_config, "w") as f:
json.dump(temp_cfg, f, indent=2)
# Run opt with instrumentor to generate stubs
try:
cmd = [
self.opt_path,
"-passes=instrumentor",
f"-instrumentor-read-config-files={temp_config}",
"-disable-output",
ir_file,
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode != 0:
print(
f"Warning: opt returned non-zero exit code: {result.returncode}"
)
if result.stderr:
print(f"stderr: {result.stderr}")
# Check if both stub files were generated
stub_exists = os.path.exists(stub_path)
header_exists = os.path.exists(stub_header_path)
if stub_exists and header_exists:
print(f"✓ Runtime stub files generated:")
print(f" {stub_path}")
print(f" {stub_header_path}")
return True
elif stub_exists:
print(f"✓ Stub file generated: {stub_path}")
print(f"⚠ Header file was not generated: {stub_header_path}")
return True
else:
print(f"✗ Stub files were not generated")
return False
except subprocess.TimeoutExpired:
print("✗ opt command timed out")
return False
except Exception as e:
print(f"✗ Failed to generate stubs: {e}")
return False
def review_and_save(self, output_path: str) -> bool:
"""Review configuration and save."""
stub_path = None
while True:
self.clear_screen()
self.print_section_header("Step 5: Review and Save")
print("\nEnabled instrumentation types:")
for opp in sorted(self.enabled_opportunities):
print(f" ✓ {opp}")
print(
f"\nPRE/POST mode: {'Same configuration' if self.same_pre_post else 'Different configurations'}"
)
print(
f"Runtime prefix: {self.config.get('configuration', {}).get('runtime_prefix', '__instrumentor_')}"
)
# Extract directory and filenames
output_dir = os.path.dirname(os.path.abspath(output_path))
output_filename = os.path.basename(output_path)
if stub_path:
stub_dir = os.path.dirname(os.path.abspath(stub_path))
stub_filename = os.path.basename(stub_path)
# Generate header filename by replacing extension with .h
stub_base = (
stub_path.rsplit(".", 1)[0] if "." in stub_path else stub_path
)
stub_header_path = stub_base + ".h"
stub_header_filename = os.path.basename(stub_header_path)
else:
default_stub = output_path.rsplit(".", 1)[0] + "_stubs.c"
stub_dir = os.path.dirname(os.path.abspath(default_stub))
stub_filename = os.path.basename(default_stub)
stub_base = default_stub.rsplit(".", 1)[0]
stub_header_path = stub_base + ".h"
stub_header_filename = os.path.basename(stub_header_path)
print("\n" + "-" * 70)
print("Output Location:")
print("-" * 70)
print(f"Directory: {output_dir}")
print(f"Config filename: {output_filename}")
print(
f"Stub filenames: {stub_filename}{' (not generated yet)' if not stub_path else ''}"
)
print(
f" {stub_header_filename}{' (not generated yet)' if not stub_path else ''}"
)
print(f"\nFull paths:")
print(f" Config: {output_path}")
if stub_path:
print(f" Stubs: {stub_path}")
print(f" {stub_header_path}")
else:
default_stub = output_path.rsplit(".", 1)[0] + "_stubs.c"
print(f" Stubs: {default_stub} (when generated)")
print(f" {stub_header_path} (when generated)")
print("\nCommands:")
print(" - 's' to save configuration and finish")
print(" - 'g' to generate runtime stub file")
print(" - 'd' to change output directory")
print(" - 'c' to change configuration filename")
print(" - 't' to change stub filename")
print(" - 'b' to go back and modify settings")
choice = self.get_user_choice(
"\nYour choice", valid_choices=["s", "g", "d", "c", "t", "b", ""]
)
if choice == "BACK" or choice == "b":
return False
elif choice == "s":
try:
# Create output directory if it doesn't exist
output_dir = os.path.dirname(os.path.abspath(output_path))
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
print(f"✓ Created directory: {output_dir}")
# Remove runtime_stubs_file from config before saving
config_to_save = json.loads(json.dumps(self.config))
if "configuration" in config_to_save:
config_to_save["configuration"].pop("runtime_stubs_file", None)
config_to_save["configuration"].pop(
"runtime_stubs_file.description", None
)
with open(output_path, "w") as f:
json.dump(config_to_save, f, indent=2)
print(f"\n✓ Configuration saved to: {output_path}")
return True
except Exception as e:
print(f"\n✗ Failed to save configuration: {e}")
input("Press Enter to continue...")
elif choice == "d":
# Change output directory
current_dir = os.path.dirname(os.path.abspath(output_path))
print(f"\nCurrent directory: {current_dir}")
new_dir = input("Enter new output directory: ").strip()
if new_dir:
# Expand user home directory if needed
new_dir = os.path.expanduser(new_dir)
new_dir = os.path.abspath(new_dir)
# Update output_path with new directory
output_filename = os.path.basename(output_path)
output_path = os.path.join(new_dir, output_filename)
# Update stub_path with new directory if it was set
if stub_path:
stub_filename = os.path.basename(stub_path)
stub_path = os.path.join(new_dir, stub_filename)
print(f"✓ Output directory updated to: {new_dir}")
print(f" Config will be saved to: {output_path}")
if stub_path:
print(f" Stubs will be saved to: {stub_path}")
else:
print("Directory unchanged")
input("Press Enter to continue...")
elif choice == "c":
# Change configuration filename only
current_filename = os.path.basename(output_path)
current_dir = os.path.dirname(os.path.abspath(output_path))
print(f"\nCurrent filename: {current_filename}")
new_filename = input("Enter new configuration filename: ").strip()
if new_filename:
output_path = os.path.join(current_dir, new_filename)
print(f"✓ Configuration filename updated to: {new_filename}")
print(f" Full path: {output_path}")
else:
print("Filename unchanged")
input("Press Enter to continue...")
elif choice == "t":
# Change stub filename only
current_dir = os.path.dirname(os.path.abspath(output_path))
if stub_path:
current_stub_filename = os.path.basename(stub_path)
else:
default_stub = output_path.rsplit(".", 1)[0] + "_stubs.c"
current_stub_filename = os.path.basename(default_stub)
print(f"\nCurrent stub filename: {current_stub_filename}")
new_stub_filename = input("Enter new stub filename: ").strip()
if new_stub_filename:
stub_path = os.path.join(current_dir, new_stub_filename)
print(f"✓ Stub filename updated to: {new_stub_filename}")
print(f" Full path: {stub_path}")
else:
print("Filename unchanged")
input("Press Enter to continue...")
elif choice == "g":
print("\nGenerate runtime stub files")
print("This creates C/C++ files with stub implementations of the")
print(
"instrumentation runtime functions that you can use as a template."
)
print(
"Note: Both a .c/.cpp file and a .h header file will be generated."
)
# Use current stub_path or ask for one
if not stub_path:
default_stub = output_path.rsplit(".", 1)[0] + "_stubs.c"
stub_input = input(
f"\nStub file path (default: {default_stub}): "
).strip()
stub_path = stub_input if stub_input else default_stub
# Calculate header path
stub_base = (
stub_path.rsplit(".", 1)[0] if "." in stub_path else stub_path
)
stub_header_path = stub_base + ".h"
# Generate stubs immediately
print(f"\nGenerating stub files:")
print(f" {stub_path}")
print(f" {stub_header_path}")
# First save the config to a temporary location if not already saved
with tempfile.TemporaryDirectory() as tmpdir:
temp_config_path = os.path.join(tmpdir, "temp_config.json")
config_to_save = json.loads(json.dumps(self.config))
if "configuration" in config_to_save:
config_to_save["configuration"].pop("runtime_stubs_file", None)
config_to_save["configuration"].pop(
"runtime_stubs_file.description", None
)
with open(temp_config_path, "w") as f:
json.dump(config_to_save, f, indent=2)
if self.generate_runtime_stubs(temp_config_path, stub_path):
print(f"✓ Stub files generated successfully")
print(f" Created: {stub_path}")
print(f" Created: {stub_header_path}")
else:
print(f"✗ Failed to generate stub files")
input("Press Enter to continue...")
elif choice == "":
continue
def run_interactive(self, output_path: str):
"""Run the interactive configuration wizard."""
self.clear_screen()
print("=" * 70)
print(" LLVM Instrumentor Configuration Wizard")
print("=" * 70)
print(
"\nThis wizard will help you create a custom instrumentation configuration."
)
print("You can enable/disable instrumentation opportunities and configure")
print("what information is passed to the runtime functions.")
print("\nNavigation: Use 'b' to go back, 'q' to quit at any prompt.")
input("\nPress Enter to continue...")
# Generate or load config
try:
self.config = self.generate_default_config()
except Exception as e:
print(f"Error: {e}")
return False
# State machine for navigation
state = 0
while True:
if state == 0: # Select opportunities
if self.select_opportunities():
state = 1
elif state == 1: # PRE/POST mode
if self.configure_pre_post_mode():
state = 2
else:
state = 0
elif state == 2: # Base configuration
if self.configure_base_options():
state = 3
else:
state = 1
elif state == 3: # Configure locations
if self.configure_locations():
state = 4
else:
state = 2
elif state == 4: # Review and save
if self.review_and_save(output_path):
return True
else:
state = 3
def main():
parser = argparse.ArgumentParser(
description="Interactive wizard for configuring LLVM Instrumentor pass",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Interactive mode (recommended)
%(prog)s
# Specify custom output location
%(prog)s -o my_config.json
# Use specific opt binary
%(prog)s --opt-path /path/to/opt
# Load existing config and modify it
%(prog)s --input existing_config.json -o modified_config.json
""",
)
parser.add_argument(
"-o",
"--output",
default="instrumentor_config.json",
help="Output configuration file (default: instrumentor_config.json)",
)
parser.add_argument(
"--opt-path", help="Path to the opt binary (default: auto-detect)"
)
parser.add_argument(
"--input", help="Load and modify an existing configuration file"
)
args = parser.parse_args()
try:
wizard = InstrumentorConfigWizard(opt_path=args.opt_path)
# Load existing config if provided
if args.input:
print(f"Loading existing configuration from: {args.input}")
with open(args.input, "r") as f:
wizard.config = json.load(f)
print("✓ Configuration loaded\n")
# Extract enabled opportunities from loaded config
for location in wizard.get_location_names():
for opp_name, opp_config in wizard.config[location].items():
if opp_config.get("enabled", False):
wizard.enabled_opportunities.add(opp_name)
success = wizard.run_interactive(args.output)
if success:
print("\n" + "=" * 70)
print("Configuration complete!")
print("=" * 70)
print(f"\nTo use this configuration with opt:")
print(f" opt -passes=instrumentor \\")
print(f" -instrumentor-read-config-files={args.output} \\")
print(f" input.ll -S -o output.ll")
print(f"\nTo use with clang:")
print(f" clang -mllvm -enable-instrumentor \\")
print(f" -mllvm -instrumentor-read-config-files={args.output} \\")
print(f" input.c -o output")
return 0
else:
return 1
except Exception as e:
print(f"\nFatal error: {e}", file=sys.stderr)
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())