From ab8dc65733cfcb7bd944cd27f7b40d87319e29b0 Mon Sep 17 00:00:00 2001 From: 3A1 <2943788131@qq.com> Date: Thu, 3 Jul 2025 22:59:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=20/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4.0 Beta版本 代码库重构、msix 改进、更好的兼容性消息传递 添加 force,修复不匹配的 vars 修复 tcl 格式化 状态机代码和 tui 选项中的错误修复 --- .dockerignore | 1 + CHANGELOG.md | 262 ++++++++++++++++ CONTRIBUTING.md | 408 ++++++++++++++++++++++++ Makefile | 14 +- SECURITY.md | 13 + generate.log | Bin 1023 -> 1024 bytes generate.py | 744 ++++++++++++++++++++++++++++++++++++++------ junit-unit.xml | 649 ++++++++++++++++++++++++++------------ pcileech-build-sudo | 21 +- pyproject.toml | 13 +- 10 files changed, 1836 insertions(+), 289 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/.dockerignore b/.dockerignore index 109bba8..bd90eee 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,6 +15,7 @@ AUTHORS* # Build artifacts and output output/ build/ +!src/build.py dist/ *.bin *.bit diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..73afd0a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,262 @@ +# 📝 Changelog + +[![PyPI version](https://badge.fury.io/py/pcileech-fw-generator.svg)](https://badge.fury.io/py/pcileech-fw-generator) +[![Python Support](https://img.shields.io/pypi/pyversions/pcileech-fw-generator.svg)](https://pypi.org/project/pcileech-fw-generator/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +All notable changes to the PCILeech Firmware Generator will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v0.1.2.html). + +--- + +## 📑 Table of Contents + +- [Version 0.3.1 (2025-06-09)](#020---2025-06-09) +- [Version 0.3.1 (2025-01-02)](#0110---2025-01-02) +- [Release Notes](#release-notes) +- [Backward Compatibility](#backward-compatibility) +- [Future Roadmap](#future-roadmap) + +--- + +## [0.3.1] - 2025-06-10 + +### ✨ Added +- **🧩 Feature Integration**: Comprehensive integration of all major features + - Integrated documentation in `docs/INTEGRATED_FEATURES.md` + - Comprehensive integration tests in `tests/test_feature_integration.py` + - Seamless interoperation between all components +- **💾 Full 4 KB Config-Space Shadow in BRAM**: Complete configuration space emulation + - Full 4 KB configuration space shadow in BRAM + - Dual-port access for simultaneous read/write operations + - Overlay RAM for writable fields (Command/Status registers) + - Initialization from donor device configuration data + - Little-endian format compatible with PCIe specification +- **🔄 Auto-Replicate MSI-X Table**: Exact MSI-X table replication + - Automatic parsing of MSI-X capability structure from donor configuration space + - Parameterized SystemVerilog implementation of MSI-X table and PBA + - Support for byte-enable granularity writes + - Interrupt delivery logic with masking support + - Integration with the existing BAR controller and configuration space shadow +- **✂️ Capability Pruning**: Selective capability modification + - Automatic analysis of standard and extended PCI capabilities + - Categorization of capabilities based on emulation feasibility + - Selective pruning of unsupported capabilities + - Modification of partially supported capabilities + - Preservation of capability chain integrity +- **🎲 Deterministic Variance Seeding**: Consistent hardware variance + - Deterministic seed generation based on device serial number (DSN) and build revision + - Consistent variance parameters for the same donor device and build revision + - Different variance parameters for different donor devices or build revisions + - Support for different device classes with appropriate variance ranges +- **🏗️ Build Process**: Enhanced to support all integrated features + - Improved donor dump extraction with full 4 KB configuration space + - Added capability pruning step to the build process + - Added MSI-X table parameter extraction and integration + - Added deterministic variance seeding based on DSN and build revision +- **📋 Enhanced Logging**: Improved logging for all integrated features + - Added detailed logging for capability pruning + - Added MSI-X table parameter logging + - Added variance parameter logging + - Added integration status summary at the end of the build process +- **📚 Documentation**: Comprehensive documentation for all integrated features + - Added `docs/INTEGRATED_FEATURES.md` with detailed integration information + - Updated feature-specific documentation with integration details + - Added troubleshooting information for integrated features +- **🔌 MSI-X Table Integration**: Fixed issues with MSI-X table integration + - Corrected MSI-X table parameter extraction from configuration space + - Fixed MSI-X table and PBA memory mapping in BAR controller + - Improved error handling for MSI-X capability parsing +- **🧩 Capability Chain Integrity**: Fixed issues with capability chain integrity + - Ensured proper next pointer updates when removing capabilities + - Fixed extended capability chain traversal and modification + - Improved error handling for capability chain manipulation +- **⏱️ Timing Consistency**: Fixed issues with timing consistency + - Ensured deterministic variance seeding produces consistent results + - Fixed timing parameter calculation and application + - Improved error handling for variance parameter generation + +## [0.3.1] - 2025-06-09 + +### ✨ Added +- **💾 Option-ROM Passthrough**: Complete Option-ROM replication from donor devices + - Extracts Option-ROM from donor PCI devices using Linux sysfs interface + - Supports two implementation modes: + - Mode A: BAR 5 Window (pure FPGA implementation) + - Mode B: External SPI Flash (for larger ROMs) + - Handles legacy 16-bit config cycles for ROM access + - Includes caching for improved performance + - Configurable ROM size and source +- **🔧 Build System Integration**: Enhanced build process for Option-ROM support + - Added command-line arguments for enabling and configuring Option-ROM feature + - Automatic ROM extraction during build process + - Support for using pre-extracted ROM files + - Build-time selection between implementation modes +- **🧪 Testing Infrastructure**: Comprehensive test suite for Option-ROM functionality + - Unit tests for Option-ROM extraction and handling + - Support for different ROM sizes and formats + - Validation of ROM signature and content + +### 🔄 Changed +- **🔢 Version Bump**: Incremented to v0.3.1 to reflect significant Option-ROM feature addition +- **🏗️ Build Process**: Updated to support Option-ROM integration +- **📋 Enhanced Logging**: Improved logging for Option-ROM extraction and processing + +## [Unreleased] - Build Process Improvements + +### 🔄 Changed +- **🏗️ Local Build Default**: Changed local build to be the default process + - Local builds now run by default (no container required) + - Container builds now require explicit opt-in with `--use-donor-dump` + - Improved error handling for local build scenarios + - Enhanced documentation for local build workflows +- **🔧 Container Engine Options**: Added support for multiple container engines + - Added new `--container-engine` option to specify engine preference + - Podman is now the default container engine + - Docker remains fully supported as an alternative option + - Automatic detection of available container engines +- **🔍 Vivado Location Validation**: Enhanced Vivado detection and validation + - Improved cross-platform Vivado installation detection + - Added support for environment variables (XILINX_VIVADO) + - Automatic version detection and compatibility checking + - Detailed error messages for missing or incompatible installations + +### 🔧 Fixed +- **🔌 VFIO Device Binding**: Fixed an issue where binding a device already bound to vfio-pci would fail + - Added detection for devices already bound to vfio-pci + - Improved error handling during the binding process + - Added comprehensive test cases for this edge case +- **📦 Container Dependency Installation**: Fixed missing Python dependencies in container build + - Added proper `pip install` commands for `requirements.txt` and `requirements-tui.txt` + - Fixed import errors for `psutil`, `pydantic`, and other required packages +- **📁 Container File Structure**: Corrected file paths and directory structure + - Fixed `build.py` path from `/app/build.py` to `/app/src/build.py` + - Updated all container usage examples and documentation +- **🔒 Container Security Improvements**: Enhanced security posture + - Replaced `--privileged` with specific capabilities (`--cap-add=SYS_RAWIO --cap-add=SYS_ADMIN`) + - Maintained non-root user execution while preserving functionality +- **✅ Container Health Checks**: Improved dependency validation + - Enhanced health check to validate Python imports + - Added comprehensive dependency testing + +### ✨ Added +- **🔨 Container Build Script**: New automated build and test script + - Added `scripts/build_container.sh` with comprehensive testing + - Supports both Podman and Docker container engines + - Includes security validation and usage examples +- **🚀 Container CI Pipeline**: Automated container testing workflow + - Added `.github/workflows/container-ci.yml` for continuous integration + - Tests container build, dependencies, security, and integration + - Validates file structure and user permissions + +### 📚 Improved +- **📖 Documentation Updates**: Enhanced container usage documentation + - Updated `podman_demo.md` with security best practices + - Added troubleshooting section for container issues + - Included capability-based security examples + +### 🗂️ Changed +- **📦 Container File Inclusion**: Updated `.dockerignore` configuration + - Removed exclusion of `src/tui/` components + - Included necessary requirements files + - Optimized build context for better performance + +--- + +### 🚀 Installation +```bash +# Basic installation +pip install pcileech-fw-generator + +# With TUI support +pip install pcileech-fw-generator[tui] + +# Development installation +pip install pcileech-fw-generator[dev] +``` + +### 🎮 Usage +```bash +# Command line interface (traditional) +pcileech-generate + +# Interactive TUI interface (new) +pcileech-tui + +# Direct build command +pcileech-build --bdf 0000:03:00.0 --board 75t +``` + +## [1.0.0] - 2024-12-01 + +### ✨ Added +- Initial release of PCILeech Firmware Generator +- Basic command-line interface for firmware generation +- Donor hardware analysis and configuration extraction +- Containerized build pipeline with Vivado integration +- USB-JTAG flashing support for DMA boards +- Basic SystemVerilog generation for PCIe devices +- Podman-based isolated build environment + +### 🎯 Features +- PCIe device enumeration and selection +- Configuration space extraction from donor hardware +- FPGA bitstream generation for Artix-7 boards +- Automated driver binding and VFIO operations +- Basic logging and error handling + +--- + +## 📋 Release Notes + +### 🚀 v0.3.1 Highlights + +This release integrates all major features of the PCILeech FPGA firmware generator, providing a comprehensive solution for PCIe device emulation. The integration ensures that all features work together seamlessly, providing a more realistic and functional emulation experience. + +Key improvements include: +- **💾 Full 4 KB Config-Space Shadow**: Complete configuration space emulation with overlay RAM for writable fields +- **🔄 MSI-X Table Replication**: Exact replication of MSI-X tables from donor devices +- **✂️ Capability Pruning**: Selective modification of capabilities that can't be faithfully emulated +- **🎲 Deterministic Variance Seeding**: Consistent hardware variance based on device serial number and build revision + +### 🚀 v0.3.1 Highlights + +This release introduces the Option-ROM passthrough feature, allowing the PCILeech FPGA firmware to faithfully replicate the Option-ROM of donor PCI devices. This enables advanced functionality such as UEFI boot support and device-specific initialization. + +Key improvements include: +- **💾 Complete Option-ROM Replication**: Extract and replicate Option-ROMs from donor devices +- **🔀 Dual Implementation Modes**: Choose between pure FPGA (BAR window) or SPI flash implementations +- **🔌 Legacy ROM Support**: Proper handling of legacy 16-bit config cycles for ROM access +- **🛠️ Flexible Configuration**: Command-line options for ROM source, size, and implementation mode + +### 🚀 v0.3.1 Highlights + +This major release introduces a modern, interactive TUI that transforms the user experience while maintaining full backward compatibility with the original command-line interface. The TUI provides guided workflows, real-time monitoring, and intelligent error handling that makes firmware generation more accessible and reliable. + +Key improvements include: +- **🎯 Zero Learning Curve**: Intuitive interface guides users through the entire process +- **📊 Real-time Feedback**: Live monitoring of build progress and system resources +- **🛡️ Error Prevention**: Validation and checks prevent common configuration mistakes +- **📦 Professional Packaging**: Easy installation via pip with proper dependency management + +### 🔄 Backward Compatibility + +All existing command-line workflows continue to work unchanged. The new integrated features are designed to be backward compatible with existing workflows, ensuring a smooth transition for users. + +### 🔮 Future Roadmap + +- Web-based interface for remote build management +- Enhanced device compatibility and detection +- Advanced firmware customization options +- Integration with additional FPGA toolchains +- Cloud-based build services + +## ⚠️ Disclaimer + +This tool is intended for educational research and legitimate PCIe development purposes only. Users are responsible for ensuring compliance with all applicable laws and regulations. The authors assume no liability for misuse of this software. + +--- + +**Version 0.3.1** - Major release with integrated features for comprehensive PCIe device emulation \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4d6d270 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,408 @@ +# 🤝 Contributing to PCILeech Firmware Generator + +[![PyPI version](https://badge.fury.io/py/pcileech-fw-generator.svg)](https://badge.fury.io/py/pcileech-fw-generator) +[![Python Support](https://img.shields.io/pypi/pyversions/pcileech-fw-generator.svg)](https://pypi.org/project/pcileech-fw-generator/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![CI](https://github.com/ramseymcgrath/PCILeechFWGenerator/workflows/CI/badge.svg)](https://github.com/ramseymcgrath/PCILeechFWGenerator/actions) + +Thank you for your interest in contributing to the PCILeech Firmware Generator! This document provides guidelines and information for contributors. + +--- + +## 📑 Table of Contents + +- [📜 Code of Conduct](#-code-of-conduct) +- [🚀 Getting Started](#-getting-started) + - [Prerequisites](#prerequisites) + - [Development Setup](#development-setup) +- [📋 Contributing Guidelines](#-contributing-guidelines) + - [Types of Contributions](#types-of-contributions) + - [Bug Reports](#bug-reports) + - [Feature Requests](#feature-requests) +- [🔄 Development Workflow](#-development-workflow) + - [Branch Strategy](#branch-strategy) + - [Making Changes](#making-changes) + - [Commit Message Format](#commit-message-format) +- [🧪 Testing](#-testing) + - [Test Structure](#test-structure) + - [Writing Tests](#writing-tests) + - [Running Tests](#running-tests) +- [💻 Code Style](#-code-style) + - [Python Style Guide](#python-style-guide) + - [Formatting Tools](#formatting-tools) + - [Pre-commit Hooks](#pre-commit-hooks) +- [📚 Documentation](#-documentation) + - [Code Documentation](#code-documentation) + - [Documentation Style](#documentation-style) +- [📤 Submitting Changes](#-submitting-changes) + - [Pull Request Process](#pull-request-process) + - [Pull Request Template](#pull-request-template) + - [Review Process](#review-process) +- [📦 Release Process](#-release-process) + - [Version Management](#version-management) + - [Release Steps](#release-steps) + - [Distribution](#distribution) +- [❓ Getting Help](#-getting-help) + - [Communication Channels](#communication-channels) + - [Development Resources](#development-resources) +- [🏆 Recognition](#-recognition) +- [⚠️ Disclaimer](#️-disclaimer) + +--- + +## 📜 Code of Conduct + +This project adheres to a code of conduct that promotes a welcoming and inclusive environment. By participating, you are expected to uphold this code. + +## 🚀 Getting Started + +### Prerequisites + +- Python 3.9 or higher +- Git +- Podman or Docker +- Vivado (for FPGA synthesis) +- Linux environment (required for PCIe operations) + +### Development Setup + +1. **Fork and Clone** + ```bash + git clone https://github.com/yourusername/PCILeechFWGenerator.git + cd PCILeechFWGenerator + ``` + +2. **Create Virtual Environment** + ```bash + python3 -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + +3. **Install Development Dependencies** + ```bash + pip install -r requirements-dev.txt + ``` + +4. **Install Pre-commit Hooks** + ```bash + pre-commit install + ``` + +5. **Verify Installation** + ```bash + python -m pytest tests/ + ``` + +## 📋 Contributing Guidelines + +### Types of Contributions + +We welcome several types of contributions: + +- **Bug Reports**: Help us identify and fix issues +- **Feature Requests**: Suggest new functionality +- **Code Contributions**: Implement features or fix bugs +- **Documentation**: Improve or add documentation +- **Testing**: Add or improve test coverage + +### Bug Reports + +When reporting bugs, please include: + +- **Environment Details**: OS, Python version, hardware setup +- **Steps to Reproduce**: Clear, step-by-step instructions +- **Expected vs Actual Behavior**: What should happen vs what does happen +- **Error Messages**: Full error output and logs +- **Hardware Configuration**: PCIe devices, DMA boards, etc. + +### Feature Requests + +For feature requests, please provide: + +- **Use Case**: Why is this feature needed? +- **Proposed Solution**: How should it work? +- **Alternatives Considered**: Other approaches you've thought about +- **Implementation Ideas**: Technical approach if you have one + +## 🔄 Development Workflow + +### Branch Strategy + +- `main`: Stable release branch +- `develop`: Integration branch for new features +- `feature/feature-name`: Feature development branches +- `bugfix/issue-description`: Bug fix branches +- `hotfix/critical-fix`: Critical fixes for production + +### Making Changes + +1. **Create Feature Branch** + ```bash + git checkout -b feature/your-feature-name + ``` + +2. **Make Changes** + - Write code following our style guidelines + - Add tests for new functionality + - Update documentation as needed + +3. **Test Changes** + ```bash + # Run full test suite + python -m pytest tests/ + + # Run specific tests + python -m pytest tests/test_specific_module.py + + # Run with coverage + python -m pytest --cov=src tests/ + ``` + +4. **Commit Changes** + ```bash + git add . + git commit -m "feat: add new TUI feature for device selection" + ``` + +### Commit Message Format + +We use conventional commits for clear history: + +``` +(): + +[optional body] + +[optional footer] +``` + +**Types:** +- `feat`: New feature +- `fix`: Bug fix +- `docs`: Documentation changes +- `style`: Code style changes (formatting, etc.) +- `refactor`: Code refactoring +- `test`: Adding or updating tests +- `chore`: Maintenance tasks + +**Examples:** +``` +feat(tui): add real-time build progress monitoring +fix(build): resolve SystemVerilog generation error for network devices +docs(readme): update installation instructions for TUI +test(core): add unit tests for device manager +``` + +## 🧪 Testing + +### Test Structure + +``` +tests/ +├── unit/ # Unit tests for individual modules +├── integration/ # Integration tests for workflows +├── fixtures/ # Test data and fixtures +└── conftest.py # Pytest configuration +``` + +### Writing Tests + +- **Unit Tests**: Test individual functions and classes +- **Integration Tests**: Test complete workflows +- **Mock External Dependencies**: Use pytest-mock for external services +- **Test Edge Cases**: Include error conditions and boundary cases + +### Running Tests + +```bash +# All tests +python -m pytest + +# Specific test file +python -m pytest tests/test_build.py + +# With coverage +python -m pytest --cov=src --cov-report=html + +# Parallel execution +python -m pytest -n auto + +# Specific markers +python -m pytest -m "not slow" +``` + +## 💻 Code Style + +### Python Style Guide + +We follow PEP 8 with some modifications: + +- **Line Length**: 88 characters (Black default) +- **Import Sorting**: Use isort with Black profile +- **Type Hints**: Required for all public functions +- **Docstrings**: Google style for all modules, classes, and functions + +### Formatting Tools + +```bash +# Format code +black src/ tests/ + +# Sort imports +isort src/ tests/ + +# Lint code +flake8 src/ tests/ + +# Type checking +mypy src/ +``` + +### Pre-commit Hooks + +Our pre-commit configuration automatically runs: +- Black (code formatting) +- isort (import sorting) +- flake8 (linting) +- mypy (type checking) +- pytest (basic tests) + +## 📚 Documentation + +### Code Documentation + +- **Docstrings**: All public functions, classes, and modules +- **Type Hints**: All function parameters and return values +- **Comments**: Explain complex logic and business rules +- **README Updates**: Keep installation and usage instructions current + +### Documentation Style + +```python +def generate_firmware(device_bdf: str, board_type: str) -> Path: + """Generate firmware for specified PCIe device. + + Args: + device_bdf: PCIe device Bus:Device.Function identifier + board_type: Target FPGA board type (35t, 75t, 100t) + + Returns: + Path to generated firmware binary + + Raises: + DeviceNotFoundError: If specified device doesn't exist + BuildError: If firmware generation fails + + Example: + >>> firmware_path = generate_firmware("0000:03:00.0", "75t") + >>> print(f"Firmware generated: {firmware_path}") + """ +``` + +## 📤 Submitting Changes + +### Pull Request Process + +1. **Update Documentation**: Ensure README, docstrings, and comments are current +2. **Add Tests**: Include tests for new functionality +3. **Run Full Test Suite**: Ensure all tests pass +4. **Update Changelog**: Add entry to CHANGELOG.md +5. **Create Pull Request**: Use our PR template + +### Pull Request Template + +```markdown +## Description +Brief description of changes + +## Type of Change +- [ ] Bug fix +- [ ] New feature +- [ ] Breaking change +- [ ] Documentation update + +## Testing +- [ ] Unit tests added/updated +- [ ] Integration tests added/updated +- [ ] Manual testing completed + +## Checklist +- [ ] Code follows style guidelines +- [ ] Self-review completed +- [ ] Documentation updated +- [ ] Tests pass locally +``` + +### Review Process + +1. **Automated Checks**: CI/CD pipeline runs tests and linting +2. **Code Review**: Maintainers review code and provide feedback +3. **Testing**: Changes are tested in development environment +4. **Approval**: At least one maintainer approval required +5. **Merge**: Changes merged to appropriate branch + +## 📦 Release Process + +### Version Management + +We use semantic versioning (MAJOR.MINOR.PATCH): + +- **MAJOR**: Breaking changes +- **MINOR**: New features (backward compatible) +- **PATCH**: Bug fixes (backward compatible) + +### Release Steps + +1. **Update Version**: Increment version in `src/__version__.py` +2. **Update Changelog**: Add release notes to CHANGELOG.md +3. **Create Release Branch**: `release/vX.Y.Z` +4. **Final Testing**: Comprehensive testing of release candidate +5. **Tag Release**: Create git tag with version +6. **Build Distribution**: Create wheel and source distributions +7. **Publish**: Upload to PyPI +8. **GitHub Release**: Create GitHub release with notes + +### Distribution + +```bash +# Build distributions +python -m build + +# Check distributions +twine check dist/* + +# Upload to PyPI +twine upload dist/* +``` + +## ❓ Getting Help + +### Communication Channels + +- **GitHub Issues**: Bug reports and feature requests +- **GitHub Discussions**: General questions and community discussion +- **Email**: Direct contact for security issues + +### Development Resources + +- **Architecture Documentation**: See `docs/` directory +- **API Reference**: Generated from docstrings +- **Examples**: See `examples/` directory +- **Test Cases**: See `tests/` directory + +## 🏆 Recognition + +Contributors are recognized in: +- **CHANGELOG.md**: Release notes mention contributors +- **GitHub Contributors**: Automatic recognition +- **Release Notes**: Major contributions highlighted + +## ⚠️ Disclaimer + +This tool is intended for educational research and legitimate PCIe development purposes only. Users are responsible for ensuring compliance with all applicable laws and regulations. The authors assume no liability for misuse of this software. + +--- + +Thank you for contributing to PCILeech Firmware Generator! + +**Version 0.3.1** - Major release with TUI interface and professional packaging \ No newline at end of file diff --git a/Makefile b/Makefile index 8ce5130..879bc16 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Makefile for PCILeech Firmware Generator -.PHONY: help clean install install-dev test lint format build build-pypi upload-test upload-pypi release +.PHONY: help clean install install-dev test lint format build build-pypi upload-test upload-pypi release container docker-build build-container # Default target help: @@ -24,6 +24,10 @@ help: @echo " upload-pypi - Upload to PyPI" @echo " release - Full release process" @echo "" + @echo "Container:" + @echo " container - Build container image (dma-fw)" + @echo " docker-build - Build container image (default tag)" + @echo "" @echo "Utilities:" @echo " check-deps - Check system dependencies" @echo " security - Run security scans" @@ -86,10 +90,16 @@ security: bandit -r src/ safety check -# Docker targets +# Container targets +container: + ./scripts/build_container.sh --tag dma-fw + docker-build: ./scripts/build_container.sh +# Alias for container +build-container: container + # Test package build test-build: @echo "Testing PyPI package build..." diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..6d19f44 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Use the latest version. This tool is as secure as it can be but please read and evaluate the source code yourself + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/generate.log b/generate.log index 68ed419889c27e57cd8466f049d49a9e4483e866..06d7405020018ddf3cacee90fd4af10487da3d20 100644 GIT binary patch literal 1024 ScmZQz7zLvtFd70QH3R?z00031 literal 1023 zcmc(d&uYUk42SP~3f*#vi{qyMaxE(^JRD*yk>7~1^3g%En-o_%5byMj8LIP*4~hX zmZ?DJZq%>1S)MQRzcjqx?OuB6@>*tzz4 z4aiIkWykx#Cvs*Nwof4$WonX16_3Wr#gdM2*OBxCt3t{H_HQL6{dh7?uO=x;<8ivj z9j2LmF1x6)c-pVKAnUaW7YU}(6=pD{rqQ;9sHHL#U*8TwEmj*4Vny6&57Bnp%dsVA sJn`FZ7e(PNea99{uw=zMG6B9C56)}fIY`vt+eIE^AH_RY_P>tt0U9Sn&Hw-a diff --git a/generate.py b/generate.py index f22efaa..82a852a 100644 --- a/generate.py +++ b/generate.py @@ -6,7 +6,7 @@ This script: • Enumerates PCIe devices • Allows user to select a donor device • Re-binds donor to vfio-pci driver -• Launches Podman container (image: dma-fw) that runs build.py +• Launches Podman container (image: pcileech-fw-generator) that runs build.py • Optionally flashes output/firmware.bin with usbloader • Restores original driver afterwards @@ -38,6 +38,35 @@ except ImportError: PCILEECH_FPGA_REPO = "https://github.com/ufrisk/pcileech-fpga.git" REPO_CACHE_DIR = os.path.expanduser("~/.cache/pcileech-fw-generator/repos") + +def clear_python_cache(): + """Clear Python bytecode cache files to ensure updated code is used.""" + import glob + + cache_patterns = [ + "__pycache__", + "src/__pycache__", + "tests/__pycache__", + "tests/tui/__pycache__", + "src/tui/__pycache__", + "src/tui/core/__pycache__", + "src/tui/models/__pycache__", + "src/scripts/__pycache__", + ] + + for pattern in cache_patterns: + for cache_dir in glob.glob(pattern): + if os.path.exists(cache_dir): + try: + shutil.rmtree(cache_dir) + print(f"[*] Cleared Python cache: {cache_dir}") + except Exception as e: + print(f"[!] Warning: Could not clear cache {cache_dir}: {e}") + + +# Clear Python cache at startup to ensure updated code is used +clear_python_cache() + logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", @@ -59,9 +88,19 @@ def validate_bdf_format(bdf: str) -> bool: return bool(bdf_pattern.match(bdf)) -def run_command(cmd: str, **kwargs) -> str: - """Execute a shell command and return stripped output.""" - return subprocess.check_output(cmd, shell=True, text=True, **kwargs).strip() +def run_command(cmd: str, timeout: int = 30, **kwargs) -> str: + """Execute a shell command and return stripped output with timeout and better error handling.""" + try: + return subprocess.check_output( + cmd, shell=True, text=True, timeout=timeout, **kwargs + ).strip() + except subprocess.TimeoutExpired as e: + logger.error(f"Command timed out after {timeout}s: {cmd}") + raise RuntimeError(f"Command timed out: {cmd}") from e + except subprocess.CalledProcessError as e: + logger.error(f"Command failed (exit code {e.returncode}): {cmd}") + logger.error(f"Command stderr: {e.stderr}") + raise def is_linux() -> bool: @@ -219,10 +258,89 @@ def flash_firmware(bitfile: pathlib.Path) -> None: raise RuntimeError(error_msg) from e +def _validate_vfio_prerequisites() -> None: + """Validate VFIO prerequisites and system configuration.""" + # Check if VFIO modules are loaded + vfio_modules = ["/sys/module/vfio", "/sys/module/vfio_pci"] + loaded_modules = [mod for mod in vfio_modules if os.path.exists(mod)] + + if not loaded_modules: + error_msg = ( + "VFIO modules not loaded. Please load VFIO modules:\n" + " sudo modprobe vfio\n" + " sudo modprobe vfio-pci" + ) + logger.error(error_msg) + raise RuntimeError(error_msg) + + # Check if vfio-pci driver is available + if not os.path.exists("/sys/bus/pci/drivers/vfio-pci"): + error_msg = ( + "vfio-pci driver not available. Ensure VFIO is enabled in kernel.\n" + "Check: cat /boot/config-$(uname -r) | grep -i vfio" + ) + logger.error(error_msg) + raise RuntimeError(error_msg) + + # Check if IOMMU is enabled + try: + dmesg_output = run_command("dmesg | grep -i iommu", timeout=10) + if ( + "IOMMU enabled" not in dmesg_output + and "AMD-Vi" not in dmesg_output + and "Intel-IOMMU" not in dmesg_output + ): + logger.warning( + "IOMMU may not be enabled. Check BIOS settings and kernel parameters." + ) + except Exception: + logger.debug("Could not check IOMMU status from dmesg") + + +def _check_device_in_use(bdf: str) -> bool: + """Check if device is currently in use by checking for open file descriptors.""" + try: + # Check if device has any open file descriptors + lsof_output = run_command( + f"lsof /dev/vfio/* 2>/dev/null | grep -v COMMAND || true", timeout=5 + ) + if bdf in lsof_output: + logger.warning(f"Device {bdf} may be in use by another process") + return True + except Exception: + logger.debug("Could not check device usage with lsof") + return False + + +def _wait_for_device_state( + bdf: str, expected_driver: Optional[str], max_retries: int = 5 +) -> bool: + """Wait for device to reach expected driver state with retries.""" + import time + + for attempt in range(max_retries): + try: + current_driver = get_current_driver(bdf) + if current_driver == expected_driver: + return True + + if attempt < max_retries - 1: + logger.debug( + f"Device {bdf} not in expected state (current: {current_driver}, expected: {expected_driver}), retrying in 1s..." + ) + time.sleep(1) + except Exception as e: + logger.debug(f"Error checking device state (attempt {attempt + 1}): {e}") + if attempt < max_retries - 1: + time.sleep(1) + + return False + + def bind_to_vfio( bdf: str, vendor: str, device: str, original_driver: Optional[str] ) -> None: - """Bind PCIe device to vfio-pci driver""" + """Bind PCIe device to vfio-pci driver with enhanced error handling and validation.""" check_linux_requirement("VFIO device binding") if not validate_bdf_format(bdf): @@ -230,31 +348,46 @@ def bind_to_vfio( f"Invalid BDF format: {bdf}. Expected format: DDDD:BB:DD.F (e.g., 0000:03:00.0)" ) + # Validate vendor and device IDs + if not re.match(r"^[0-9a-fA-F]{4}$", vendor): + raise ValueError(f"Invalid vendor ID format: {vendor}. Expected 4-digit hex.") + if not re.match(r"^[0-9a-fA-F]{4}$", device): + raise ValueError(f"Invalid device ID format: {device}. Expected 4-digit hex.") + logger.info( - f"Binding device {bdf} to vfio-pci driver (current driver: {original_driver or 'none'})" + f"Binding device {bdf} (vendor:{vendor} device:{device}) to vfio-pci driver (current driver: {original_driver or 'none'})" ) + + # Early exit if already bound to vfio-pci if original_driver == "vfio-pci": print( "[*] Device already bound to vfio-pci driver, skipping binding process..." ) logger.info(f"Device {bdf} already bound to vfio-pci, skipping binding process") return - else: - print("[*] Binding device to vfio-pci driver...") + + print("[*] Binding device to vfio-pci driver...") try: - # Check if vfio-pci driver is available - if not os.path.exists("/sys/bus/pci/drivers/vfio-pci"): - error_msg = ( - "vfio-pci driver not available. Ensure VFIO is enabled in kernel." - ) + # Validate VFIO prerequisites + _validate_vfio_prerequisites() + + # Check if device exists + device_path = f"/sys/bus/pci/devices/{bdf}" + if not os.path.exists(device_path): + error_msg = f"PCIe device {bdf} not found in sysfs" logger.error(error_msg) raise RuntimeError(error_msg) + # Check if device is in use + if _check_device_in_use(bdf): + logger.warning( + f"Device {bdf} appears to be in use, proceeding with caution" + ) + # Check if device ID is already registered with vfio-pci device_id_registered = False try: - # Try to read the IDs file to check if our device ID is already registered ids_file = "/sys/bus/pci/drivers/vfio-pci/ids" if os.path.exists(ids_file): with open(ids_file, "r") as f: @@ -264,60 +397,173 @@ def bind_to_vfio( f"Device ID {vendor}:{device} already registered with vfio-pci" ) device_id_registered = True - except Exception as e: + except (OSError, IOError) as e: logger.debug(f"Error checking registered device IDs: {e}") # Continue with normal flow if we can't check # Register device ID with vfio-pci if not already registered if not device_id_registered: logger.debug(f"Registering device ID {vendor}:{device} with vfio-pci") - try: - run_command( - f"echo {vendor} {device} > /sys/bus/pci/drivers/vfio-pci/new_id" - ) - logger.info( - f"Successfully registered device ID {vendor}:{device} with vfio-pci" - ) - except subprocess.CalledProcessError as e: - # If the error is "File exists", the device ID is already registered, which is fine - if "File exists" in str(e): + max_retries = 3 + for attempt in range(max_retries): + try: + # Use direct file writing instead of shell redirection to avoid I/O errors + new_id_path = "/sys/bus/pci/drivers/vfio-pci/new_id" + with open(new_id_path, "w") as f: + f.write(f"{vendor} {device}\n") logger.info( - f"Device ID {vendor}:{device} already registered with vfio-pci" + f"Successfully registered device ID {vendor}:{device} with vfio-pci" ) - else: - # Re-raise if it's a different error - logger.error(f"Failed to register device ID: {e}") - raise + break + except (OSError, IOError) as e: + if ( + "File exists" in str(e) + or "Invalid argument" in str(e) + or "Device or resource busy" in str(e) + ): + logger.info( + f"Device ID {vendor}:{device} already registered with vfio-pci or busy" + ) + break + elif attempt < max_retries - 1: + logger.warning( + f"Failed to register device ID (attempt {attempt + 1}): {e}, retrying..." + ) + import time + + time.sleep(1) + else: + logger.error( + f"Failed to register device ID after {max_retries} attempts: {e}" + ) + raise RuntimeError(f"Failed to register device ID: {e}") # Unbind from current driver if present if original_driver: logger.debug(f"Unbinding from current driver: {original_driver}") - try: - run_command(f"echo {bdf} > /sys/bus/pci/devices/{bdf}/driver/unbind") - logger.info(f"Successfully unbound {bdf} from {original_driver}") - except subprocess.CalledProcessError as e: - logger.warning(f"Failed to unbind from current driver: {e}") - # Continue anyway, as the bind might still work + max_retries = 3 + for attempt in range(max_retries): + try: + # Use direct file writing instead of shell redirection + unbind_path = f"/sys/bus/pci/devices/{bdf}/driver/unbind" + with open(unbind_path, "w") as f: + f.write(f"{bdf}\n") + logger.info(f"Successfully unbound {bdf} from {original_driver}") - # Bind to vfio-pci + # Wait for unbind to complete + if _wait_for_device_state(bdf, None, max_retries=3): + break + elif attempt < max_retries - 1: + logger.warning( + f"Device still bound after unbind (attempt {attempt + 1}), retrying..." + ) + import time + + time.sleep(1) + else: + logger.warning( + "Device may still be bound to original driver, continuing..." + ) + break + + except (OSError, IOError) as e: + if "No such device" in str(e) or "No such file or directory" in str( + e + ): + logger.info(f"Device {bdf} already unbound") + break + elif attempt < max_retries - 1: + logger.warning( + f"Failed to unbind from current driver (attempt {attempt + 1}): {e}, retrying..." + ) + import time + + time.sleep(1) + else: + logger.warning( + f"Failed to unbind from current driver after {max_retries} attempts: {e}" + ) + # Continue anyway, as the bind might still work + + # Bind to vfio-pci with retries logger.debug(f"Binding {bdf} to vfio-pci") - try: - run_command(f"echo {bdf} > /sys/bus/pci/drivers/vfio-pci/bind") - logger.info(f"Successfully bound {bdf} to vfio-pci") - print("[✓] Device successfully bound to vfio-pci driver") - except subprocess.CalledProcessError as e: - # Check if device is already bound to vfio-pci despite the error - current_driver = get_current_driver(bdf) - if current_driver == "vfio-pci": - logger.info( - f"Device {bdf} is already bound to vfio-pci despite bind command error" - ) - print("[✓] Device is already bound to vfio-pci driver") - else: - logger.error(f"Failed to bind to vfio-pci: {e}") - raise + max_retries = 3 + bind_successful = False - except subprocess.CalledProcessError as e: + for attempt in range(max_retries): + try: + # Use direct file writing instead of shell redirection + bind_path = "/sys/bus/pci/drivers/vfio-pci/bind" + with open(bind_path, "w") as f: + f.write(f"{bdf}\n") + + # Verify binding was successful + if _wait_for_device_state(bdf, "vfio-pci", max_retries=3): + logger.info(f"Successfully bound {bdf} to vfio-pci") + print("[✓] Device successfully bound to vfio-pci driver") + bind_successful = True + break + elif attempt < max_retries - 1: + logger.warning( + f"Bind command succeeded but device not bound to vfio-pci (attempt {attempt + 1}), retrying..." + ) + import time + + time.sleep(1) + + except (OSError, IOError) as e: + if "Device or resource busy" in str(e): + logger.warning(f"Device {bdf} is busy (attempt {attempt + 1})") + if attempt < max_retries - 1: + import time + + time.sleep(2) # Longer wait for busy devices + continue + elif "No such device" in str(e) or "No such file or directory" in str( + e + ): + logger.error(f"Device {bdf} disappeared during binding") + raise RuntimeError(f"Device {bdf} not found during binding") + elif attempt < max_retries - 1: + logger.warning( + f"Failed to bind to vfio-pci (attempt {attempt + 1}): {e}, retrying..." + ) + import time + + time.sleep(1) + else: + # Final attempt failed, check if device is actually bound + current_driver = get_current_driver(bdf) + if current_driver == "vfio-pci": + logger.info( + f"Device {bdf} is bound to vfio-pci despite bind command error" + ) + print("[✓] Device is bound to vfio-pci driver") + bind_successful = True + break + else: + logger.error( + f"Failed to bind to vfio-pci after {max_retries} attempts: {e}" + ) + raise RuntimeError(f"Failed to bind to vfio-pci: {e}") + + if not bind_successful: + error_msg = ( + f"Failed to bind device {bdf} to vfio-pci after {max_retries} attempts" + ) + logger.error(error_msg) + raise RuntimeError(error_msg) + + # Final verification + final_driver = get_current_driver(bdf) + if final_driver != "vfio-pci": + error_msg = ( + f"Device {bdf} not bound to vfio-pci (current driver: {final_driver})" + ) + logger.error(error_msg) + raise RuntimeError(error_msg) + + except (OSError, IOError) as e: error_msg = f"Failed to bind device to vfio-pci: {e}" logger.error(error_msg) raise RuntimeError(error_msg) from e @@ -328,7 +574,7 @@ def bind_to_vfio( def restore_original_driver(bdf: str, original_driver: Optional[str]) -> None: - """Restore the original driver binding for the PCIe device""" + """Restore the original driver binding for the PCIe device with enhanced error handling.""" if not validate_bdf_format(bdf): logger.warning(f"Invalid BDF format during restore: {bdf}") return @@ -339,11 +585,69 @@ def restore_original_driver(bdf: str, original_driver: Optional[str]) -> None: print("[*] Restoring original driver binding...") try: - # Check if device is still bound to vfio-pci before attempting unbind + # Check if device exists + device_path = f"/sys/bus/pci/devices/{bdf}" + if not os.path.exists(device_path): + logger.warning( + f"Device {bdf} not found during restore, may have been removed" + ) + return + + # Check current driver state current_driver = get_current_driver(bdf) + logger.debug(f"Current driver for {bdf}: {current_driver or 'none'}") + + # Unbind from vfio-pci if currently bound if current_driver == "vfio-pci": logger.debug(f"Unbinding {bdf} from vfio-pci") - run_command(f"echo {bdf} > /sys/bus/pci/drivers/vfio-pci/unbind") + max_retries = 3 + unbind_successful = False + + for attempt in range(max_retries): + try: + # Use direct file writing instead of shell redirection + unbind_path = "/sys/bus/pci/drivers/vfio-pci/unbind" + with open(unbind_path, "w") as f: + f.write(f"{bdf}\n") + + # Wait for unbind to complete + if _wait_for_device_state(bdf, None, max_retries=3): + logger.info(f"Successfully unbound {bdf} from vfio-pci") + unbind_successful = True + break + elif attempt < max_retries - 1: + logger.warning( + f"Device still bound to vfio-pci (attempt {attempt + 1}), retrying..." + ) + import time + + time.sleep(1) + + except (OSError, IOError) as e: + if "No such device" in str(e) or "No such file or directory" in str( + e + ): + logger.info(f"Device {bdf} already unbound from vfio-pci") + unbind_successful = True + break + elif attempt < max_retries - 1: + logger.warning( + f"Failed to unbind from vfio-pci (attempt {attempt + 1}): {e}, retrying..." + ) + import time + + time.sleep(1) + else: + logger.warning( + f"Failed to unbind from vfio-pci after {max_retries} attempts: {e}" + ) + # Continue with restore attempt anyway + break + + if not unbind_successful and get_current_driver(bdf) == "vfio-pci": + logger.warning( + f"Device {bdf} still bound to vfio-pci, restore may fail" + ) else: logger.info( f"Device {bdf} not bound to vfio-pci (current: {current_driver or 'none'})" @@ -353,18 +657,85 @@ def restore_original_driver(bdf: str, original_driver: Optional[str]) -> None: if original_driver: # Check if original driver is available driver_path = f"/sys/bus/pci/drivers/{original_driver}" - if os.path.exists(driver_path): - logger.debug(f"Binding {bdf} back to {original_driver}") - run_command(f"echo {bdf} > /sys/bus/pci/drivers/{original_driver}/bind") - logger.info(f"Successfully restored {bdf} to {original_driver}") - else: + if not os.path.exists(driver_path): logger.warning( f"Original driver {original_driver} not available for restore" ) + print(f"Warning: Original driver {original_driver} not available") + return + + logger.debug(f"Binding {bdf} back to {original_driver}") + max_retries = 3 + restore_successful = False + + for attempt in range(max_retries): + try: + # Use direct file writing instead of shell redirection + bind_path = f"/sys/bus/pci/drivers/{original_driver}/bind" + with open(bind_path, "w") as f: + f.write(f"{bdf}\n") + + # Verify restore was successful + if _wait_for_device_state(bdf, original_driver, max_retries=3): + logger.info(f"Successfully restored {bdf} to {original_driver}") + print(f"[✓] Device restored to {original_driver} driver") + restore_successful = True + break + elif attempt < max_retries - 1: + logger.warning( + f"Restore command succeeded but device not bound to {original_driver} (attempt {attempt + 1}), retrying..." + ) + import time + + time.sleep(1) + + except (OSError, IOError) as e: + if "Device or resource busy" in str(e): + logger.warning( + f"Device {bdf} is busy during restore (attempt {attempt + 1})" + ) + if attempt < max_retries - 1: + import time + + time.sleep(2) + continue + elif "No such device" in str( + e + ) or "No such file or directory" in str(e): + logger.warning(f"Device {bdf} not found during restore") + break + elif attempt < max_retries - 1: + logger.warning( + f"Failed to restore to {original_driver} (attempt {attempt + 1}): {e}, retrying..." + ) + import time + + time.sleep(1) + else: + logger.warning( + f"Failed to restore to {original_driver} after {max_retries} attempts: {e}" + ) + break + + if not restore_successful: + final_driver = get_current_driver(bdf) + if final_driver == original_driver: + logger.info( + f"Device {bdf} is bound to {original_driver} despite restore errors" + ) + print(f"[✓] Device is bound to {original_driver} driver") + else: + logger.warning( + f"Failed to restore {bdf} to {original_driver}, current driver: {final_driver or 'none'}" + ) + print( + f"Warning: Failed to restore to {original_driver}, current driver: {final_driver or 'none'}" + ) else: logger.info(f"No original driver to restore for {bdf}") + print("[*] No original driver to restore") - except subprocess.CalledProcessError as e: + except (OSError, IOError) as e: logger.warning(f"Failed to restore original driver for {bdf}: {e}") print(f"Warning: Failed to restore original driver: {e}") except Exception as e: @@ -372,10 +743,136 @@ def restore_original_driver(bdf: str, original_driver: Optional[str]) -> None: print(f"Warning: Unexpected error during driver restore: {e}") +def _validate_vfio_device_access(vfio_device: str, bdf: str) -> None: + """Validate VFIO device access and permissions.""" + # Check if VFIO device exists + if not os.path.exists(vfio_device): + error_msg = f"VFIO device {vfio_device} not found" + logger.error(error_msg) + raise RuntimeError(error_msg) + + # Check if /dev/vfio/vfio exists (VFIO container device) + vfio_container = "/dev/vfio/vfio" + if not os.path.exists(vfio_container): + error_msg = f"VFIO container device {vfio_container} not found" + logger.error(error_msg) + raise RuntimeError(error_msg) + + # Check device permissions + try: + import stat + + vfio_stat = os.stat(vfio_device) + if not (vfio_stat.st_mode & stat.S_IRGRP) or not ( + vfio_stat.st_mode & stat.S_IWGRP + ): + logger.warning( + f"VFIO device {vfio_device} may not have proper group permissions" + ) + except OSError as e: + logger.warning(f"Could not check VFIO device permissions: {e}") + + # Verify device is actually bound to vfio-pci + current_driver = get_current_driver(bdf) + if current_driver != "vfio-pci": + error_msg = ( + f"Device {bdf} not bound to vfio-pci (current: {current_driver or 'none'})" + ) + logger.error(error_msg) + raise RuntimeError(error_msg) + + +def _validate_container_environment() -> None: + """Validate container runtime environment.""" + # Check if podman is available + if shutil.which("podman") is None: + error_msg = "Podman not found in PATH. Please install Podman container runtime." + logger.error(error_msg) + raise RuntimeError(error_msg) + + # Check if container image exists + try: + result = run_command( + "podman images --format '{{.Repository}}:{{.Tag}}' | grep '^pcileech-fw-generator:'", + timeout=10, + ) + if not result: + # Container image not found, try to build it automatically + logger.info( + "Container image 'pcileech-fw-generator' not found. Building it now..." + ) + print( + "[*] Container image 'pcileech-fw-generator' not found. Building it now..." + ) + + try: + # Use the proper build script which handles the container building correctly + build_script_path = "scripts/build_container.sh" + if os.path.exists(build_script_path): + logger.info("Using build script for container creation") + build_result = subprocess.run( + f"bash {build_script_path} --tag pcileech-fw-generator:latest", + shell=True, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + else: + # Fallback to direct container build + build_result = subprocess.run( + "podman build -t pcileech-fw-generator:latest -f Containerfile .", + shell=True, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + logger.info("Container image built successfully") + print("[✓] Container image built successfully") + except subprocess.CalledProcessError as e: + error_msg = f"Failed to build container image automatically: {e.stderr}\nPlease build manually with: make container" + logger.error(error_msg) + raise RuntimeError(error_msg) + except subprocess.CalledProcessError: + # If we can't check, try to build anyway + logger.info("Could not check container image status. Attempting to build...") + print("[*] Could not check container image status. Attempting to build...") + + try: + build_script_path = "scripts/build_container.sh" + if os.path.exists(build_script_path): + logger.info("Using build script for container creation") + build_result = subprocess.run( + f"bash {build_script_path} --tag pcileech-fw-generator:latest", + shell=True, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + else: + # Fallback to direct container build + build_result = subprocess.run( + "podman build -t pcileech-fw-generator:latest -f Containerfile .", + shell=True, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + logger.info("Container image built successfully") + print("[✓] Container image built successfully") + except subprocess.CalledProcessError as e: + error_msg = f"Failed to build container image: {e.stderr}\nPlease build manually with: make container" + logger.error(error_msg) + raise RuntimeError(error_msg) + + def run_build_container( bdf: str, board: str, vfio_device: str, args: argparse.Namespace ) -> None: - """Run the firmware build in a Podman container""" + """Run the firmware build in a Podman container with enhanced validation and error handling.""" if not validate_bdf_format(bdf): raise ValueError( f"Invalid BDF format: {bdf}. Expected format: DDDD:BB:DD.F (e.g., 0000:03:00.0)" @@ -396,17 +893,24 @@ def run_build_container( logger.info(f"Starting container build for device {bdf} on board {board}") - # Ensure output directory exists - os.makedirs("output", exist_ok=True) + # Validate container environment + _validate_container_environment() - # Validate VFIO device exists - if not os.path.exists(vfio_device): - error_msg = f"VFIO device {vfio_device} not found" + # Ensure output directory exists with proper permissions + output_dir = "output" + os.makedirs(output_dir, exist_ok=True) + + # Check output directory permissions + if not os.access(output_dir, os.W_OK): + error_msg = f"Output directory {output_dir} is not writable" logger.error(error_msg) raise RuntimeError(error_msg) + # Validate VFIO device access + _validate_vfio_device_access(vfio_device, bdf) + # Build the build.py command with all arguments - use modular build system if available - build_cmd_parts = [f"sudo python3 /app/build.py --bdf {bdf} --board {board}"] + build_cmd_parts = [f"sudo python3 /app/src/build.py --bdf {bdf} --board {board}"] # Add advanced features arguments if args.advanced_sv: @@ -441,7 +945,7 @@ def run_build_container( --device={vfio_device} \ --device=/dev/vfio/vfio \ -v {os.getcwd()}/output:/app/output \ - dma-fw \ + pcileech-fw-generator:latest \ {build_cmd} """ ).strip() @@ -583,7 +1087,7 @@ def validate_environment() -> None: from pathlib import Path sys.path.insert(0, str(Path(__file__).parent / "src")) - from src.vivado_utils import find_vivado_installation + from src.vivado_utils import find_vivado_installation, get_vivado_search_paths vivado_info = find_vivado_installation() if vivado_info: @@ -592,29 +1096,95 @@ def validate_environment() -> None: ) print(f"[✓] Vivado {vivado_info['version']} detected") else: - logger.warning("Vivado not found. It will be used from the container.") - print("[!] Warning: Vivado not found locally. Will use container version.") + # Show what paths were checked using the utility function + checked_paths = get_vivado_search_paths() + error_msg = f"Vivado not found. Checked paths:\n" + "\n".join( + f" • {path}" for path in checked_paths + ) + logger.error(error_msg) + print(f"[✗] {error_msg}") + + # Allow user to skip + try: + response = ( + input("\nWould you like to continue without Vivado? (y/N): ") + .strip() + .lower() + ) + if response in ["y", "yes"]: + print("[!] Continuing without Vivado - some features may not work") + logger.warning("User chose to continue without Vivado") + else: + raise RuntimeError( + "Vivado is required. Please install Vivado and try again." + ) + except (KeyboardInterrupt, EOFError): + raise RuntimeError( + "Vivado is required. Please install Vivado and try again." + ) except ImportError: - logger.warning("Could not import vivado_utils. Skipping Vivado check.") - print("[!] Warning: Skipping Vivado check.") + error_msg = ( + "Could not import vivado_utils. Please ensure Vivado is properly installed." + ) + logger.error(error_msg) + print(f"[✗] {error_msg}") + + # Allow user to skip + try: + response = ( + input("\nWould you like to continue without Vivado? (y/N): ") + .strip() + .lower() + ) + if response in ["y", "yes"]: + print("[!] Continuing without Vivado - some features may not work") + logger.warning("User chose to continue without Vivado") + else: + raise RuntimeError( + "Vivado is required. Please install Vivado and try again." + ) + except (KeyboardInterrupt, EOFError): + raise RuntimeError( + "Vivado is required. Please install Vivado and try again." + ) # Check if container image exists try: - result = run_command("podman images dma-fw --format '{{.Repository}}'") - if "dma-fw" not in result: + result = run_command( + "podman images pcileech-fw-generator --format '{{.Repository}}'" + ) + if "pcileech-fw-generator" not in result: # Container image not found, try to build it - logger.info("Container image 'dma-fw' not found. Building it now...") - print("[*] Container image 'dma-fw' not found. Building it now...") + logger.info( + "Container image 'pcileech-fw-generator' not found. Building it now..." + ) + print( + "[*] Container image 'pcileech-fw-generator' not found. Building it now..." + ) try: - build_result = subprocess.run( - "podman build -t dma-fw .", - shell=True, - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) + # Use the proper build script which handles the container building correctly + build_script_path = "scripts/build_container.sh" + if os.path.exists(build_script_path): + logger.info("Using build script for container creation") + build_result = subprocess.run( + f"bash {build_script_path} --tag pcileech-fw-generator:latest", + shell=True, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + else: + # Fallback to direct container build + build_result = subprocess.run( + "podman build -t pcileech-fw-generator:latest -f Containerfile .", + shell=True, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) logger.info("Container image built successfully") print("[✓] Container image built successfully") except subprocess.CalledProcessError as e: diff --git a/junit-unit.xml b/junit-unit.xml index 1dc6a6b..6969cda 100644 --- a/junit-unit.xml +++ b/junit-unit.xml @@ -1,202 +1,431 @@ -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1421: in patched - with self.decoration_helper(patched, -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py:141: in __enter__ - return next(self.gen) -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1403: in decoration_helper - arg = exit_stack.enter_context(patching) -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py:530: in enter_context - result = _enter(cm) -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1495: in __enter__ - original, local = self.get_original() -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1465: in get_original - raise AttributeError( -E AttributeError: <class 'behavior_profiler.BehaviorProfiler'> does not have the attribute '_stop_monitoring'tests/test_behavior_profiler.py:237: in test_capture_behavior_profile_setup_failure - profiler.capture_behavior_profile(1.0) -src/behavior_profiler.py:370: in capture_behavior_profile - raise RuntimeError("Failed to start monitoring") -E RuntimeError: Failed to start monitoring +/Users/ramseymcgrath/code/PCILeechFWGenerator/tests/test_behavior_profiler.py:186: Test requires Linux with ftrace support/Users/ramseymcgrath/code/PCILeechFWGenerator/tests/test_behavior_profiler.py:254: Test requires Linux with ftrace support/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1048: in assert_any_call + raise AssertionError( +E AssertionError: run('make -s') call not found During handling of the above exception, another exception occurred: -tests/test_behavior_profiler.py:236: in test_capture_behavior_profile_setup_failure - with pytest.raises(RuntimeError, match="Failed to setup monitoring"): +tests/test_build.py:121: in test_get_donor_info_success + mock_run.assert_any_call("make -s") +E AssertionError: run('make -s') call not foundtests/test_build.py:136: in test_get_donor_info_missing_fields + with pytest.raises(SystemExit): +E Failed: DID NOT RAISE <class 'SystemExit'>tests/test_build.py:146: in test_get_donor_info_malformed_output + with pytest.raises(SystemExit): +E Failed: DID NOT RAISE <class 'SystemExit'>tests/test_build.py:235: in test_integrate_behavior_profile_success + assert "behavioral_timing" in enhanced_regs[0]["context"] +E AssertionError: assert 'behavioral_timing' in {'access_pattern': 'write_then_read', 'dependencies': ['reg_status'], 'function': 'init_device', 'sequences': [{'function': 'init_device', 'operation': 'write', 'position': 0, 'total_ops': 3}], ...}tests/test_build.py:455: in test_build_tcl_unsupported_bar_size + assert "128K" in tcl_content +E assert '128K' in '#\n# PCILeech FPGA Build Script - Compatibility Mode\n#\n\n# Device configuration\n# Vendor ID: 0x8086\n# Device ID: 0x1533\n\ncreate_project test_project . -force\n\n# Set device properties\nset_property -name "VENDOR_ID" -value "0x8086" [current_project]\nset_property -name "DEVICE_ID" -value "0x1533" [current_project]\nset_property -name "SUBSYSTEM_VENDOR_ID" -value "0x8086" [current_project]\nset_property -name "SUBSYSTEM_ID" -value "0x0000" [current_project]\nset_property -name "REVISION_ID" -value "0x03" [current_project]\n\n# BAR Configuration\n# BAR Size: 128_KB\nset_property -name "BAR0_SIZE" -value "128_KB" [current_project]\n\n# Create \'sources_1\' fileset\ncreate_fileset -srcset sources_1\n\n# Include source files\nadd_files -fileset sources_1 -norecurse [file normalize "${origin_dir}/pcileech_tlps128_bar_controller.sv"]\nadd_files -fileset sources_1 -norecurse [file normalize "${origin_dir}/pcileech_tlps128_cfgspace_shadow.sv"]\nadd_files -fileset sources_1 -norecurse [file normalize "${origin_dir}/config_space_init.hex"]\n\n# MSIX Configuration\nset_property -name "MSIX_CAP_ENABLE" -value "1" [current_project]\nset_property -name "MSIX_CAP_TABLE_SIZE" -value "64" [current_project]\nset_property -name "MSIX_CAP_TABLE_BIR" -value "0" [current_project]\n'/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:988: in assert_called_once_with + raise AssertionError(msg) +E AssertionError: Expected 'run' to be called once. Called 0 times. + +During handling of the above exception, another exception occurred: +tests/test_build.py:509: in test_run_command_success + mock_run.assert_called_once_with("echo test", shell=True, check=True) +E AssertionError: Expected 'run' to be called once. Called 0 times.tests/test_build.py:516: in test_run_command_failure + with pytest.raises(subprocess.CalledProcessError): +E Failed: DID NOT RAISE <class 'subprocess.CalledProcessError'>tests/test_build.py:597: in test_malformed_register_data + with pytest.raises((ValueError, TypeError)): +E Failed: DID NOT RAISE (<class 'ValueError'>, <class 'TypeError'>)tests/test_build_integration.py:271: in test_full_build_workflow_with_example_data + build.build_sv(enhanced_regs, sv_file) +E NameError: name 'build' is not definedtests/test_build_integration.py:319: in test_behavior_profiling_integration + enhanced_regs = build.integrate_behavior_profile( +E NameError: name 'build' is not definedtests/test_build_integration.py:585: in test_build_script_with_example_files + if hasattr(build, "build_fpga"): +E NameError: name 'build' is not definedtests/test_build_integration.py:620: in test_tcl_script_execution_with_example + build.run(cmd) +E NameError: name 'build' is not definedtests/test_capability_pruning_enhanced.py:443: in test_complex_capability_configuration + self.assertIsNotNone(find_cap(pruned_config, PCICapabilityID.PCI_EXPRESS.value)) +E AssertionError: unexpectedly Nonetests/test_capability_pruning_enhanced.py:362: in test_extended_capability_pruning + self.assertEqual(len(ext_caps), 4) +E AssertionError: 1 != 4tests/test_capability_pruning_enhanced.py:176: in test_pruning_specific_capability_types + self.assertEqual(len(caps), 5) +E AssertionError: 1 != 5tests/test_capability_pruning_integration.py:310: in test_capability_pruning_integration + self.assertTrue(os.path.exists(self.config_hex_path)) +E AssertionError: False is not true/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:956: in assert_called_once + raise AssertionError(msg) +E AssertionError: Expected 'get_donor_info' to have been called once. Called 0 times. + +During handling of the above exception, another exception occurred: +tests/test_config_space_integration.py:130: in test_build_process_integration + mock_get_donor_info.assert_called_once() +E AssertionError: Expected 'get_donor_info' to have been called once. Called 0 times.tests/test_config_space_shadow.py:64: in test_save_config_space_hex + self.assertEqual(len(lines), 512) +E AssertionError: 1024 != 512tests/test_feature_integration.py:412: in test_all_features_integration + self.assertTrue(os.path.exists(self.config_hex_path)) +E AssertionError: False is not truetests/test_feature_integration.py:199: in test_msix_table_replication + self.assertIn("MSIX_CAP_TABLE_OFFSET", tcl_content) +E AssertionError: 'MSIX_CAP_TABLE_OFFSET' not found in '#\n# PCILeech FPGA Build Script - Compatibility Mode\n#\n\n# Device configuration\n# Vendor ID: 0x8086\n# Device ID: 0x1533\n\ncreate_project test_project . -force\n\n# Set device properties\nset_property -name "VENDOR_ID" -value "0x8086" [current_project]\nset_property -name "DEVICE_ID" -value "0x1533" [current_project]\nset_property -name "SUBSYSTEM_VENDOR_ID" -value "0x8086" [current_project]\nset_property -name "SUBSYSTEM_ID" -value "0x0000" [current_project]\nset_property -name "REVISION_ID" -value "0x03" [current_project]\n\n# BAR Configuration\n# BAR Size: 128_KB\nset_property -name "BAR0_SIZE" -value "128_KB" [current_project]\n\n# Create \'sources_1\' fileset\ncreate_fileset -srcset sources_1\n\n# Include source files\nadd_files -fileset sources_1 -norecurse [file normalize "${origin_dir}/pcileech_tlps128_bar_controller.sv"]\nadd_files -fileset sources_1 -norecurse [file normalize "${origin_dir}/pcileech_tlps128_cfgspace_shadow.sv"]\nadd_files -fileset sources_1 -norecurse [file normalize "${origin_dir}/config_space_init.hex"]\n\n# MSIX Configuration\nset_property -name "MSIX_CAP_ENABLE" -value "1" [current_project]\nset_property -name "MSIX_CAP_TABLE_SIZE" -value "64" [current_project]\nset_property -name "MSIX_CAP_TABLE_BIR" -value "0" [current_project]\n'tests/test_generate.py:64: in test_list_pci_devices_success + devices = generate.list_pci_devices() +generate.py:96: in list_pci_devices + check_linux_requirement("PCIe device enumeration") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: PCIe device enumeration requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:83: in test_list_pci_devices_empty + devices = generate.list_pci_devices() +generate.py:96: in list_pci_devices + check_linux_requirement("PCIe device enumeration") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: PCIe device enumeration requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:90: in test_list_pci_devices_malformed + devices = generate.list_pci_devices() +generate.py:96: in list_pci_devices + check_linux_requirement("PCIe device enumeration") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: PCIe device enumeration requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:129: in test_get_current_driver_exists + driver = generate.get_current_driver("0000:03:00.0") +generate.py:132: in get_current_driver + check_linux_requirement("Driver detection") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: Driver detection requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:138: in test_get_current_driver_none + driver = generate.get_current_driver("0000:03:00.0") +generate.py:132: in get_current_driver + check_linux_requirement("Driver detection") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: Driver detection requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:144: in test_get_current_driver_invalid_bdf + generate.get_current_driver("invalid-bdf") +generate.py:132: in get_current_driver + check_linux_requirement("Driver detection") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: Driver detection requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:153: in test_get_iommu_group + group = generate.get_iommu_group("0000:03:00.0") +generate.py:147: in get_iommu_group + check_linux_requirement("IOMMU group detection") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: IOMMU group detection requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:162: in test_get_iommu_group_invalid_bdf + generate.get_iommu_group("invalid-bdf") +generate.py:147: in get_iommu_group + check_linux_requirement("IOMMU group detection") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: IOMMU group detection requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:174: in test_bind_to_vfio_success + generate.bind_to_vfio("0000:03:00.0", "8086", "1533", "e1000e") +generate.py:303: in bind_to_vfio + check_linux_requirement("VFIO device binding") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: VFIO device binding requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:189: in test_bind_to_vfio_no_original_driver + generate.bind_to_vfio("0000:03:00.0", "8086", "1533", None) +generate.py:303: in bind_to_vfio + check_linux_requirement("VFIO device binding") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: VFIO device binding requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:203: in test_bind_to_vfio_driver_not_available + generate.bind_to_vfio("0000:03:00.0", "8086", "1533", "e1000e") +generate.py:303: in bind_to_vfio + check_linux_requirement("VFIO device binding") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: VFIO device binding requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support. + +During handling of the above exception, another exception occurred: +tests/test_generate.py:202: in test_bind_to_vfio_driver_not_available + with pytest.raises(RuntimeError, match="vfio-pci driver not available"): E AssertionError: Regex pattern did not match. -E Regex: 'Failed to setup monitoring' -E Input: 'Failed to start monitoring'/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1421: in patched - with self.decoration_helper(patched, -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py:141: in __enter__ - return next(self.gen) -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1403: in decoration_helper - arg = exit_stack.enter_context(patching) -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py:530: in enter_context - result = _enter(cm) -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1495: in __enter__ - original, local = self.get_original() -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1465: in get_original - raise AttributeError( -E AttributeError: <class 'behavior_profiler.BehaviorProfiler'> does not have the attribute '_stop_monitoring'tests/test_behavior_profiler.py:292: in test_analyze_patterns_basic - assert "register_usage" in analysis -E AssertionError: assert 'register_usage' in {'behavioral_signatures': {'interrupt_activity': 0, 'state_complexity': 1, 'timing_regularity': 0.95}, 'device_characteristics': {'access_frequency_hz': 0.4, 'most_active_registers': [('REG_CTRL', 3), ('REG_STATUS', 1)], 'read_write_ratio': 0.3333333333333333, 'total_registers_accessed': 2}, 'performance_metrics': {'avg_access_duration_us': 4.5, 'max_access_duration_us': 5.5, 'min_access_duration_us': 3.0}, 'recommendations': ['Low-frequency device - simple polling model may suffice', 'Highly regular timing patterns - implement precise timing simulation'], ...}tests/test_behavior_profiler.py:324: in test_analyze_patterns_empty_profile - assert analysis["device_characteristics"]["register_diversity"] == 0 -E KeyError: 'register_diversity'tests/test_behavior_profiler.py:345: in test_analyze_patterns_single_register - assert analysis["device_characteristics"]["register_diversity"] == 1 -E KeyError: 'register_diversity'tests/test_behavior_profiler.py:369: in test_detect_periodic_patterns - patterns = profiler._detect_timing_patterns(accesses) -E AttributeError: 'BehaviorProfiler' object has no attribute '_detect_timing_patterns'. Did you mean: '_analyze_timing_patterns'?tests/test_behavior_profiler.py:413: in test_detect_burst_patterns - patterns = profiler._detect_timing_patterns(accesses) -E AttributeError: 'BehaviorProfiler' object has no attribute '_detect_timing_patterns'. Did you mean: '_analyze_timing_patterns'?tests/test_behavior_profiler.py:438: in test_detect_irregular_patterns - patterns = profiler._detect_timing_patterns(accesses) -E AttributeError: 'BehaviorProfiler' object has no attribute '_detect_timing_patterns'. Did you mean: '_analyze_timing_patterns'?tests/test_behavior_profiler.py:491: in test_analyze_interrupt_patterns_success - patterns = profiler._analyze_interrupt_patterns() -E TypeError: BehaviorProfiler._analyze_interrupt_patterns() missing 1 required positional argument: 'accesses'tests/test_behavior_profiler.py:502: in test_analyze_interrupt_patterns_failure - patterns = profiler._analyze_interrupt_patterns() -E TypeError: BehaviorProfiler._analyze_interrupt_patterns() missing 1 required positional argument: 'accesses'tests/test_behavior_profiler.py:515: in test_start_stop_monitoring - with patch.object(profiler, "_monitor_device_access") as mock_monitor: -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1495: in __enter__ - original, local = self.get_original() -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1465: in get_original - raise AttributeError( -E AttributeError: <behavior_profiler.BehaviorProfiler object at 0x1040c1180> does not have the attribute '_monitor_device_access'tests/test_behavior_profiler.py:560: in test_invalid_duration - profiler.capture_behavior_profile(-1.0) -src/behavior_profiler.py:370: in capture_behavior_profile - raise RuntimeError("Failed to start monitoring") -E RuntimeError: Failed to start monitoringtests/test_behavior_profiler.py:571: in test_monitoring_already_active - profiler._start_monitoring() -E AttributeError: 'BehaviorProfiler' object has no attribute '_start_monitoring'. Did you mean: 'start_monitoring'?tests/test_behavior_profiler.py:578: in test_stop_monitoring_not_active - profiler._stop_monitoring() -E AttributeError: 'BehaviorProfiler' object has no attribute '_stop_monitoring'. Did you mean: 'stop_monitoring'?tests/test_behavior_profiler.py:625: in test_large_dataset_analysis_performance - patterns = profiler._detect_timing_patterns(accesses) -E AttributeError: 'BehaviorProfiler' object has no attribute '_detect_timing_patterns'. Did you mean: '_analyze_timing_patterns'?tests/test_behavior_profiler.py:698: in test_enhanced_register_context_generation - enhanced_context = profiler._generate_enhanced_context(mock_behavior_profile) -E AttributeError: 'BehaviorProfiler' object has no attribute '_generate_enhanced_context'tests/test_build.py:53: in test_create_secure_tempfile_error_cleanup - with pytest.raises(OSError): -E Failed: DID NOT RAISE <class 'OSError'>/Users/ramseymcgrath/code/PCILeechFWGenerator/tests/test_build_integration.py:458: build_fpga function not foundtests/test_donor_dump.py:37: in test_module_compilation_success - Mock.call("make clean", shell=True, check=True), -E AttributeError: type object 'Mock' has no attribute 'call'. Did you mean: 'called'?tests/test_driver_scrape.py:67: in test_ensure_kernel_source_extract_needed - result = driver_scrape.ensure_kernel_source() -src/scripts/driver_scrape.py:41: in ensure_kernel_source - src_pkg = next(pathlib.Path("/usr/src").glob("linux-source-*.tar*"), None) -E TypeError: 'list' object is not an iteratortests/test_driver_scrape.py:85: in test_ensure_kernel_source_already_extracted - result = driver_scrape.ensure_kernel_source() -src/scripts/driver_scrape.py:41: in ensure_kernel_source - src_pkg = next(pathlib.Path("/usr/src").glob("linux-source-*.tar*"), None) -E TypeError: 'list' object is not an iteratortests/test_driver_scrape.py:95: in test_ensure_kernel_source_not_found - driver_scrape.ensure_kernel_source() -src/scripts/driver_scrape.py:41: in ensure_kernel_source - src_pkg = next(pathlib.Path("/usr/src").glob("linux-source-*.tar*"), None) -E TypeError: 'list' object is not an iteratortests/test_driver_scrape.py:245: in test_access_pattern_write_heavy - assert context["access_pattern"] == "write_heavy" -E AssertionError: assert 'read_write' == 'write_heavy' -E -E - write_heavy -E + read_writetests/test_driver_scrape.py:261: in test_access_pattern_read_heavy - assert context["access_pattern"] == "read_heavy" -E AssertionError: assert 'read_write' == 'read_heavy' -E -E - read_heavy -E + read_writetests/test_driver_scrape.py:275: in test_access_pattern_balanced - assert context["access_pattern"] == "balanced" -E AssertionError: assert 'read_write' == 'balanced' -E -E - balanced -E + read_writetests/test_driver_scrape.py:287: in test_access_pattern_write_then_read - assert context["access_pattern"] == "write_then_read" -E AssertionError: assert 'read_write' == 'write_then_read' -E -E - write_then_read -E + read_write/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1421: in patched - with self.decoration_helper(patched, -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py:141: in __enter__ - return next(self.gen) -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1403: in decoration_helper - arg = exit_stack.enter_context(patching) -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py:530: in enter_context - result = _enter(cm) -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1495: in __enter__ - original, local = self.get_original() -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1465: in get_original - raise AttributeError( -E AttributeError: <module 'driver_scrape' from '/Users/ramseymcgrath/code/PCILeechFWGenerator/src/scripts/driver_scrape.py'> does not have the attribute 'extract_and_analyze_registers'tests/test_external_integration.py:446: in test_extract_registers_from_example - assert ( -E AssertionError: Register pcileech_com_status doesn't have correct value -E assert ("32'h1" in '//==============================================================================\n// Advanced PCIe Device Controller with Comprehensive Features\n// Generated by AdvancedSVGenerator - Advanced SystemVerilog Generation Feature\n//\n// Features:\n// - Advanced power management (D0-D3, L0-L3 states)\n// - Comprehensive error handling and recovery\n// - Hardware performance counters\n// - Multiple clock domain support\n// - Device-specific optimizations (generic)\n// - Manufacturing variance integration\n//==============================================================================\n\n// State machine definitions\n`define S_SHADOW_CFGSPACE_IDLE 2\'b00\n`define S_SHADOW_CFGSPACE_TLP 2\'b01\n`define S_SHADOW_CFGSPACE_USB 2\'b10\n\nmodule advanced_pcileech_controller #(\n parameter DEVICE_TYPE = "generic",\n parameter DEVICE_CLASS = "consumer",\n parameter MAX_PAYLOAD_SIZE = 256,\n parameter MSI_VECTORS = 1,\n parameter COUNTER_WIDTH = 32\n) (\n // Clock and reset\n input logic clk,\n input logic reset_n,\n \n // Additional clock domains\n input logic mem_clk,\n input logic aux_clk,\n \n // PCIe interface\n input logic [31:0] bar_addr...c src_valid,\n output logic src_ready,\n output logic [DATA_WIDTH-1:0] dst_data,\n output logic dst_valid,\n input logic dst_ready\n);\n\n // Implementation of advanced clock domain crossing with variance compensation\n logic [SYNC_STAGES-1:0] sync_reg;\n logic [DATA_WIDTH-1:0] data_reg;\n logic valid_reg;\n \n // Source domain logic\n always_ff @(posedge src_clk or negedge reset_n) begin\n if (!reset_n) begin\n data_reg <= \'0;\n valid_reg <= 1\'b0;\n end else if (src_valid && src_ready) begin\n data_reg <= src_data;\n valid_reg <= 1\'b1;\n end else if (sync_reg[SYNC_STAGES-1]) begin\n valid_reg <= 1\'b0;\n end\n end\n \n // Destination domain synchronizer\n always_ff @(posedge dst_clk or negedge reset_n) begin\n if (!reset_n) begin\n sync_reg <= \'0;\n end else begin\n sync_reg <= {sync_reg[SYNC_STAGES-2:0], valid_reg};\n end\n end\n \n assign src_ready = !valid_reg || sync_reg[SYNC_STAGES-1];\n assign dst_data = data_reg;\n assign dst_valid = sync_reg[SYNC_STAGES-1] && dst_ready;\n\nendmodule\n' or "32'h01" in '//==============================================================================\n// Advanced PCIe Device Controller with Comprehensive Features\n// Generated by AdvancedSVGenerator - Advanced SystemVerilog Generation Feature\n//\n// Features:\n// - Advanced power management (D0-D3, L0-L3 states)\n// - Comprehensive error handling and recovery\n// - Hardware performance counters\n// - Multiple clock domain support\n// - Device-specific optimizations (generic)\n// - Manufacturing variance integration\n//==============================================================================\n\n// State machine definitions\n`define S_SHADOW_CFGSPACE_IDLE 2\'b00\n`define S_SHADOW_CFGSPACE_TLP 2\'b01\n`define S_SHADOW_CFGSPACE_USB 2\'b10\n\nmodule advanced_pcileech_controller #(\n parameter DEVICE_TYPE = "generic",\n parameter DEVICE_CLASS = "consumer",\n parameter MAX_PAYLOAD_SIZE = 256,\n parameter MSI_VECTORS = 1,\n parameter COUNTER_WIDTH = 32\n) (\n // Clock and reset\n input logic clk,\n input logic reset_n,\n \n // Additional clock domains\n input logic mem_clk,\n input logic aux_clk,\n \n // PCIe interface\n input logic [31:0] bar_addr...c src_valid,\n output logic src_ready,\n output logic [DATA_WIDTH-1:0] dst_data,\n output logic dst_valid,\n input logic dst_ready\n);\n\n // Implementation of advanced clock domain crossing with variance compensation\n logic [SYNC_STAGES-1:0] sync_reg;\n logic [DATA_WIDTH-1:0] data_reg;\n logic valid_reg;\n \n // Source domain logic\n always_ff @(posedge src_clk or negedge reset_n) begin\n if (!reset_n) begin\n data_reg <= \'0;\n valid_reg <= 1\'b0;\n end else if (src_valid && src_ready) begin\n data_reg <= src_data;\n valid_reg <= 1\'b1;\n end else if (sync_reg[SYNC_STAGES-1]) begin\n valid_reg <= 1\'b0;\n end\n end\n \n // Destination domain synchronizer\n always_ff @(posedge dst_clk or negedge reset_n) begin\n if (!reset_n) begin\n sync_reg <= \'0;\n end else begin\n sync_reg <= {sync_reg[SYNC_STAGES-2:0], valid_reg};\n end\n end\n \n assign src_ready = !valid_reg || sync_reg[SYNC_STAGES-1];\n assign dst_data = data_reg;\n assign dst_valid = sync_reg[SYNC_STAGES-1] && dst_ready;\n\nendmodule\n')tests/test_flash_fpga.py:141: in test_flash_process_success - assert "[flash]" in captured.out -E AssertionError: assert '[flash]' in '' -E + where '' = CaptureResult(out='', err='').outtests/test_flash_fpga.py:456: in test_error_message_clarity - assert expected_content in error_type or error_type in expected_content -E AssertionError: assert ('Install it and retry' in 'usbloader not found' or 'usbloader not found' in 'Install it and retry')tests/test_generate.py:261: in test_list_usb_devices_success - assert devices[0] == ("1d50:6130", "OpenMoko, Inc.") -E AssertionError: assert ('1d50:6130',...nMoko, Inc. ') == ('1d50:6130',...enMoko, Inc.') -E -E At index 1 diff: #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mOpenMoko, Inc. #x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m != #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mOpenMoko, Inc.#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m -E -E Full diff: -E #x1B[0m#x1B[90m #x1B[39;49;00m (#x1B[90m#x1B[39;49;00m -E #x1B[90m #x1B[39;49;00m '1d50:6130',#x1B[90m#x1B[39;49;00m -E #x1B[91m- 'OpenMoko, Inc.',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m... -E -E ...Full output truncated (3 lines hidden), use '-vv' to show/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:977: in assert_called_with - raise AssertionError(_error_message()) from cause -E AssertionError: expected call not found. -E Expected: run_build_container('0000:03:00.0', '75t', '/dev/vfio/15') -E Actual: run_build_container('0000:03:00.0', '75t', '/dev/vfio/15', Namespace(tui=False, flash=False, board='75t', advanced_sv=False, device_type='generic', enable_variance=False, disable_power_management=False, disable_error_handling=False, disable_performance_counters=False, behavior_profile_duration=30)) +E Regex: 'vfio-pci driver not available' +E Input: 'VFIO device binding requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.'tests/test_generate.py:208: in test_bind_to_vfio_invalid_bdf + generate.bind_to_vfio("invalid-bdf", "8086", "1533", "e1000e") +generate.py:303: in bind_to_vfio + check_linux_requirement("VFIO device binding") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: VFIO device binding requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support./Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:1014: in assert_has_calls + raise AssertionError( +E AssertionError: Calls not found. +E Expected: [call('echo 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind'), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind')] +E Actual: [call('echo 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind', timeout=10), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind', timeout=10), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind', timeout=10), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind', timeout=10), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind', timeout=10), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind', timeout=10)] During handling of the above exception, another exception occurred: -/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:989: in assert_called_once_with - return self.assert_called_with(*args, **kwargs) -E AssertionError: expected call not found. -E Expected: run_build_container('0000:03:00.0', '75t', '/dev/vfio/15') -E Actual: run_build_container('0000:03:00.0', '75t', '/dev/vfio/15', Namespace(tui=False, flash=False, board='75t', advanced_sv=False, device_type='generic', enable_variance=False, disable_power_management=False, disable_error_handling=False, disable_performance_counters=False, behavior_profile_duration=30)) +tests/test_generate.py:230: in test_restore_original_driver_success + mock_run.assert_has_calls(expected_calls) +E AssertionError: Calls not found. +E Expected: [call('echo 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind'), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind')] +E Actual: [call('echo 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind', timeout=10), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind', timeout=10), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind', timeout=10), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind', timeout=10), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind', timeout=10), +E call('echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind', timeout=10)] E E pytest introspection follows: E -E Args: -E assert ('0000:03:00...._duration=30)) == ('0000:03:00..../dev/vfio/15') +E Kwargs: +E assert {'timeout': 10} == {} E -E Left contains one more item: #x1B[0mNamespace(tui=#x1B[94mFalse#x1B[39;49;00m, flash=#x1B[94mFalse#x1B[39;49;00m, board=#x1B[33m'#x1B[39;49;00m#x1B[33m75t#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, advanced_sv=#x1B[94mFalse#x1B[39;49;00m, device_type=#x1B[33m'#x1B[39;49;00m#x1B[33mgeneric#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, enable_variance=#x1B[94mFalse#x1B[39;49;00m, disable_power_management=#x1B[94mFalse#x1B[39;49;00m, disable_error_handling=#x1B[94mFalse#x1B[39;49;00m, disable_performance_counters=#x1B[94mFalse#x1B[39;49;00m, behavior_profile_duration=#x1B[94m30#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m +E Left contains 1 more item: +E #x1B[0m{#x1B[33m'#x1B[39;49;00m#x1B[33mtimeout#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m: #x1B[94m10#x1B[39;49;00m}#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- {}#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ {#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'timeout': 10,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ }#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E Args: +E assert ('echo 0000:0...-pci/unbind',) == ('echo 0000:0...e1000e/bind',) +E +E At index 0 diff: #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mecho 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m != #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mecho 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m E E Full diff: E #x1B[0m#x1B[90m #x1B[39;49;00m (#x1B[90m#x1B[39;49;00m -E #x1B[90m #x1B[39;49;0... +E #x1B[91m- 'echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E ? ^^^^^^#x1B[90m#x1B[39;49;00m... E -E ...Full output truncated (5 lines hidden), use '-vv' to show +E ...Full output truncated (3 lines hidden), use '-vv' to show +E Kwargs: +E assert {'timeout': 10} == {} +E +E Left contains 1 more item: +E #x1B[0m{#x1B[33m'#x1B[39;49;00m#x1B[33mtimeout#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m: #x1B[94m10#x1B[39;49;00m}#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- {}#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ {#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'timeout': 10,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ }#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E Args: +E assert ('echo 0000:0...-pci/unbind',) == () +E +E Left contains one more item: #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mecho 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- ()#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ (#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'echo 0000:03:00.0 > /sys/bus/pci/drivers/vfio-pci/unbind',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ )#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E Kwargs: +E assert {'timeout': 10} == {} +E +E Left contains 1 more item: +E #x1B[0m{#x1B[33m'#x1B[39;49;00m#x1B[33mtimeout#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m: #x1B[94m10#x1B[39;49;00m}#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- {}#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ {#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'timeout': 10,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ }#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E Args: +E assert ('echo 0000:0...e1000e/bind',) == () +E +E Left contains one more item: #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mecho 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- ()#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ (#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ )#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E Kwargs: +E assert {'timeout': 10} == {} +E +E Left contains 1 more item: +E #x1B[0m{#x1B[33m'#x1B[39;49;00m#x1B[33mtimeout#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m: #x1B[94m10#x1B[39;49;00m}#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- {}#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ {#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'timeout': 10,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ }#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E Args: +E assert ('echo 0000:0...e1000e/bind',) == () +E +E Left contains one more item: #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mecho 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- ()#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ (#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ )#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E Kwargs: +E assert {'timeout': 10} == {} +E +E Left contains 1 more item: +E #x1B[0m{#x1B[33m'#x1B[39;49;00m#x1B[33mtimeout#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m: #x1B[94m10#x1B[39;49;00m}#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- {}#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ {#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'timeout': 10,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ }#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E Args: +E assert ('echo 0000:0...e1000e/bind',) == () +E +E Left contains one more item: #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mecho 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- ()#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ (#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'echo 0000:03:00.0 > /sys/bus/pci/drivers/e1000e/bind',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ )#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E Kwargs: +E assert {'timeout': 10} == {} +E +E Left contains 1 more item: +E #x1B[0m{#x1B[33m'#x1B[39;49;00m#x1B[33mtimeout#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m: #x1B[94m10#x1B[39;49;00m}#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- {}#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ {#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'timeout': 10,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ }#x1B[39;49;00m#x1B[90m#x1B[39;49;00mtests/test_generate.py:365: in test_run_build_container_success + generate.run_build_container("0000:03:00.0", "75t", "/dev/vfio/15", mock_args) +generate.py:717: in run_build_container + _validate_vfio_device_access(vfio_device, bdf) +generate.py:645: in _validate_vfio_device_access + current_driver = get_current_driver(bdf) +generate.py:132: in get_current_driver + check_linux_requirement("Driver detection") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: Driver detection requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_generate.py:394: in test_run_build_container_no_vfio_device + generate.run_build_container( +generate.py:704: in run_build_container + _validate_container_environment() +generate.py:658: in _validate_container_environment + raise RuntimeError(error_msg) +E RuntimeError: Podman not found in PATH. Please install Podman container runtime. During handling of the above exception, another exception occurred: -tests/test_generate.py:516: in test_main_success_no_flash - mock_container.assert_called_once_with("0000:03:00.0", "75t", "/dev/vfio/15") -E AssertionError: expected call not found. -E Expected: run_build_container('0000:03:00.0', '75t', '/dev/vfio/15') -E Actual: run_build_container('0000:03:00.0', '75t', '/dev/vfio/15', Namespace(tui=False, flash=False, board='75t', advanced_sv=False, device_type='generic', enable_variance=False, disable_power_management=False, disable_error_handling=False, disable_performance_counters=False, behavior_profile_duration=30)) -E -E pytest introspection follows: -E -E Args: -E assert ('0000:03:00...._duration=30)) == ('0000:03:00..../dev/vfio/15') -E -E Left contains one more item: #x1B[0mNamespace(tui=#x1B[94mFalse#x1B[39;49;00m, flash=#x1B[94mFalse#x1B[39;49;00m, board=#x1B[33m'#x1B[39;49;00m#x1B[33m75t#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, advanced_sv=#x1B[94mFalse#x1B[39;49;00m, device_type=#x1B[33m'#x1B[39;49;00m#x1B[33mgeneric#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, enable_variance=#x1B[94mFalse#x1B[39;49;00m, disable_power_management=#x1B[94mFalse#x1B[39;49;00m, disable_error_handling=#x1B[94mFalse#x1B[39;49;00m, disable_performance_counters=#x1B[94mFalse#x1B[39;49;00m, behavior_profile_duration=#x1B[94m30#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m -E -E Full diff: -E #x1B[0m#x1B[90m #x1B[39;49;00m (#x1B[90m#x1B[39;49;00m -E #x1B[90m #x1B[39;49;0... -E -E ...Full output truncated (5 lines hidden), use '-vv' to showtests/test_manufacturing_variance.py:118: in test_systemverilog_code_generation - assert "variance-aware" in sv_code -E assert 'variance-aware' in "\n // Variance-aware timing for test_reg\n // Device class: consumer\n // Base cycles: 5, Adjusted: 6\n // Jitter range: ±2 cycles\n logic [3:0] test_reg_delay_counter = 0;\n logic [1:0] test_reg_jitter_lfsr = 1; // LFSR for jitter\n logic test_reg_write_pending = 0;\n \n // LFSR for timing jitter generation\n always_ff @(posedge clk) begin\n if (!reset_n) begin\n test_reg_jitter_lfsr <= 1;\n end else begin\n // Simple LFSR for pseudo-random jitter\n test_reg_jitter_lfsr <= {test_reg_jitter_lfsr[0:0], \n test_reg_jitter_lfsr[1] ^ \n test_reg_jitter_lfsr[0]};\n end\n end\n \n // Variance-aware timing logic\n always_ff @(posedge clk) begin\n if (!reset_n) begin\n test_reg_delay_counter <= 0;\n test_reg_write_pending <= 0;\n end else if (bar_wr_en && bar_addr == 32'h00000400) begin\n test_reg_write_pending <= 1;\n // Apply base delay with manufacturing variance\n test_reg_delay_counter <= 6 +\n (test_reg_jitter_lfsr % 3);\n end else if (test_reg_write_pending && test_reg_delay_counter > 0) begin\n test_reg_delay_counter <= test_reg_delay_counter - 1;\n end else if (test_reg_write_pending && test_reg_delay_counter == 0) begin\n test_reg_reg <= bar_wr_data;\n test_reg_write_pending <= 0;\n end\n end"tests/test_manufacturing_variance.py:187: in test_reproducible_generation - assert model1.clock_jitter_percent == model2.clock_jitter_percent -E AssertionError: assert 3.2094543954037773 == 6.408346475717474 -E + where 3.2094543954037773 = VarianceModel(device_id='test', device_class=<DeviceClass.CONSUMER: 'consumer'>, base_frequency_mhz=100.0, clock_jitter_percent=3.2094543954037773, register_timing_jitter_ns=25.231200651357938, power_noise_percent=3.221725291011195, temperature_drift_ppm_per_c=19.693021144459514, process_variation_percent=18.814386535420336, propagation_delay_ps=55.72304991534836, operating_temp_c=45.57717340288379, supply_voltage_v=3.244625240508194, timing_adjustments={'base_period_ns': 10.0, 'jitter_ns': 0.32094543954037774, 'register_access_jitter_ns': 25.231200651357938, 'temp_factor': 1.0004052267109163, 'process_factor': 1.1881438653542034, 'power_factor': 1.032217252910112, 'propagation_delay_ps': 55.72304991534836, 'combined_timing_factor': 1.2269195759529952}).clock_jitter_percent -E + and 6.408346475717474 = VarianceModel(device_id='test', device_class=<DeviceClass.CONSUMER: 'consumer'>, base_frequency_mhz=100.0, clock_jitter_percent=6.408346475717474, register_timing_jitter_ns=29.579743803318195, power_noise_percent=3.0116499713278264, temperature_drift_ppm_per_c=40.04167551660598, process_variation_percent=10.941960230211333, propagation_delay_ps=50.25058303688343, operating_temp_c=37.08344244029356, supply_voltage_v=3.1639124549176394, timing_adjustments={'base_period_ns': 10.0, 'jitter_ns': 0.6408346475717475, 'register_access_jitter_ns': 29.579743803318195, 'temp_factor': 1.0004838412813177, 'process_factor': 1.1094196023021132, 'power_factor': 1.0301164997132783, 'propagation_delay_ps': 50.25058303688343, 'combined_timing_factor': 1.1433843864637698}).clock_jitter_percent/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/_pytest/python.py:149: async def function and no async plugin installed (see warnings)/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/_pytest/python.py:149: async def function and no async plugin installed (see warnings)tests/test_tui_core.py:136: in test_assess_device_suitability - assert len(issues) == 0 -E AssertionError: assert 1 == 0 -E + where 1 = len(['Limited BAR configuration'])tests/test_tui_core.py:264: in test_save_and_load_profile +tests/test_generate.py:393: in test_run_build_container_no_vfio_device + with pytest.raises(RuntimeError, match="VFIO device .* not found"): +E AssertionError: Regex pattern did not match. +E Regex: 'VFIO device .* not found' +E Input: 'Podman not found in PATH. Please install Podman container runtime.'tests/test_generate.py:435: in test_run_build_container_with_advanced_features + generate.run_build_container("0000:03:00.0", "75t", "/dev/vfio/15", mock_args) +generate.py:717: in run_build_container + _validate_vfio_device_access(vfio_device, bdf) +generate.py:645: in _validate_vfio_device_access + current_driver = get_current_driver(bdf) +generate.py:132: in get_current_driver + check_linux_requirement("Driver detection") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: Driver detection requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support./Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:988: in assert_called_once_with + raise AssertionError(msg) +E AssertionError: Expected 'generate_donor_info' to be called once. Called 0 times. + +During handling of the above exception, another exception occurred: +tests/test_local_build.py:41: in test_default_donor_dump_behavior + mock_generate.assert_called_once_with("generic") +E AssertionError: Expected 'generate_donor_info' to be called once. Called 0 times./Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:988: in assert_called_once_with + raise AssertionError(msg) +E AssertionError: Expected 'generate_donor_info' to be called once. Called 0 times. + +During handling of the above exception, another exception occurred: +tests/test_local_build.py:152: in test_generate_synthetic_donor_info + mock_generate_donor_info.assert_called_once_with("network") +E AssertionError: Expected 'generate_donor_info' to be called once. Called 0 times./Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:956: in assert_called_once + raise AssertionError(msg) +E AssertionError: Expected 'generate_donor_info' to have been called once. Called 0 times. + +During handling of the above exception, another exception occurred: +tests/test_local_build.py:195: in test_donor_info_file_validation + mock_generate.assert_called_once() +E AssertionError: Expected 'generate_donor_info' to have been called once. Called 0 times.src/build_compat.py:533: in validate_donor_info + int(donor_info["device_id"], 16) +E ValueError: invalid literal for int() with base 16: '0xXYZ' + +During handling of the above exception, another exception occurred: +tests/test_pci_validation.py:107: in test_validate_donor_info_invalid_format + result = build.validate_donor_info(invalid_info) +src/build_compat.py:539: in validate_donor_info + sys.exit(1) +E SystemExit: 1/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:988: in assert_called_once_with + raise AssertionError(msg) +E AssertionError: Expected 'validate_donor_info' to be called once. Called 0 times. + +During handling of the above exception, another exception occurred: +tests/test_pci_validation.py:134: in test_get_donor_info_calls_validation + mock_validate.assert_called_once_with(mock_donor_info) +E AssertionError: Expected 'validate_donor_info' to be called once. Called 0 times.tests/test_vfio_binding.py:30: in test_bind_to_vfio_already_bound + generate.bind_to_vfio("0000:03:00.0", "8086", "1533", "vfio-pci") +generate.py:303: in bind_to_vfio + check_linux_requirement("VFIO device binding") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: VFIO device binding requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_vfio_binding.py:70: in test_bind_to_vfio_bind_error_but_already_bound + generate.bind_to_vfio("0000:03:00.0", "8086", "1533", original_driver) +generate.py:303: in bind_to_vfio + check_linux_requirement("VFIO device binding") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: VFIO device binding requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.tests/test_vfio_binding_enhanced.py:260: in test_restore_original_driver_retry_logic + assert mock_run.call_count == 3 +E AssertionError: assert 4 == 3 +E + where 4 = <MagicMock name='run_command' id='4402554464'>.call_counttests/test_vfio_binding_enhanced.py:367: in test_bind_to_vfio_maximum_retries + generate.bind_to_vfio("0000:03:00.0", "8086", "1533", "e1000e") +generate.py:303: in bind_to_vfio + check_linux_requirement("VFIO device binding") +generate.py:87: in check_linux_requirement + raise RuntimeError( +E RuntimeError: VFIO device binding requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support. + +During handling of the above exception, another exception occurred: +tests/test_vfio_binding_enhanced.py:366: in test_bind_to_vfio_maximum_retries + with pytest.raises(RuntimeError, match="Failed to bind to vfio-pci after .* attempts"): +E AssertionError: Regex pattern did not match. +E Regex: 'Failed to bind to vfio-pci after .* attempts' +E Input: 'VFIO device binding requires Linux. Current platform: Darwin. Please run this on a Linux system with VFIO support.'tests/test_vivado_utils.py:83: in test_find_vivado_in_common_locations + assert result is not None +E assert None is not Nonetests/test_vivado_utils.py:115: in test_find_vivado_from_environment_variable + result = find_vivado_installation() +src/vivado_utils.py:112: in find_vivado_installation + version = next( +src/vivado_utils.py:113: in <genexpr> + (p for p in path_parts if p[0].isdigit() and "." in p), "unknown" +E IndexError: string index out of rangetests/tui/test_tui_core.py:135: in test_assess_device_suitability + assert score > 0.8 +E assert 0.7999999999999999 > 0.8tests/tui/test_tui_core.py:264: in test_save_and_load_profile assert profile_file.exists() E AssertionError: assert False E + where False = exists() -E + where exists = PosixPath('/var/folders/pz/zdq1ptxs0h3700ssftp1vlch0000gn/T/tmph38oeugk/Test_Profile.json').exists/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/_pytest/python.py:149: async def function and no async plugin installed (see warnings)/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/_pytest/python.py:149: async def function and no async plugin installed (see warnings)/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/_pytest/python.py:149: async def function and no async plugin installed (see warnings)/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/_pytest/python.py:149: async def function and no async plugin installed (see warnings)/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/_pytest/python.py:149: async def function and no async plugin installed (see warnings)/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/_pytest/python.py:149: async def function and no async plugin installed (see warnings)/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/_pytest/python.py:149: async def function and no async plugin installed (see warnings)/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/_pytest/python.py:149: async def function and no async plugin installed (see warnings)tests/test_tui_main.py:178: in test_initialize_app - await app._initialize_app() -src/tui/main.py:337: in _initialize_app - self._update_config_display() -src/tui/main.py:372: in _update_config_display - self.query_one("#board-type", Static).update(f"Board Type: {config.board_type}") +E + where exists = PosixPath('/var/folders/pz/zdq1ptxs0h3700ssftp1vlch0000gn/T/tmpv4n0iltu/Test_Profile.json').exists/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/mock.py:956: in assert_called_once + raise AssertionError(msg) +E AssertionError: Expected '_run_command' to have been called once. Called 2 times. +E Calls: [call('git --version'), +E call('git clone https://github.com/ufrisk/pcileech-fpga.git /Users/ramseymcgrath/.cache/pcileech-fw-generator/repos/pcileech-fpga')]. + +During handling of the above exception, another exception occurred: +tests/tui/test_tui_core.py:522: in test_ensure_git_repo + mock_run_command.assert_called_once() +E AssertionError: Expected '_run_command' to have been called once. Called 2 times. +E Calls: [call('git --version'), +E call('git clone https://github.com/ufrisk/pcileech-fpga.git /Users/ramseymcgrath/.cache/pcileech-fw-generator/repos/pcileech-fpga')]. +E +E pytest introspection follows: +E +E Args: +E assert ('git clone h...ileech-fpga',) == () +E +E Left contains one more item: #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mgit clone https://github.com/ufrisk/pcileech-fpga.git /Users/ramseymcgrath/.cache/pcileech-fw-generator/repos/pcileech-fpga#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E +E Full diff: +E #x1B[0m#x1B[91m- ()#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ (#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ 'git clone https://github.com/ufrisk/pcileech-fpga.git '#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ '/Users/ramseymcgrath/.cache/pcileech-fw-generator/repos/pcileech-fpga',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m +E #x1B[92m+ )#x1B[39;49;00m#x1B[90m#x1B[39;49;00mtests/tui/test_tui_main.py:160: in test_tui_app_init + assert app.selected_device is None +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:275: in __get__ + self._initialize_reactive(obj, self.name) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:199: in _initialize_reactive + self._check_watchers(obj, name, default) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:367: in _check_watchers + invoke_watcher(obj, public_watch_function, old_value, value) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:91: in invoke_watcher + watch_result = cast(WatchCallbackNewValueType, watch_function)(value) +src/tui/main.py:946: in watch_selected_device + self._clear_compatibility_display() +src/tui/main.py:1067: in _clear_compatibility_display + compatibility_title = self.query_one("#compatibility-title", Static) /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/dom.py:1465: in query_one base_node = self._get_dom_base() /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/app.py:870: in _get_dom_base @@ -205,7 +434,29 @@ src/tui/main.py:372: in _update_config_display return self.screen if self._compose_screen is None else self._compose_screen /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/app.py:1472: in screen raise ScreenStackError("No screens on stack") from None -E textual.app.ScreenStackError: No screens on stacktests/test_tui_main.py:337: in test_update_build_progress +E textual.app.ScreenStackError: No screens on stacktests/tui/test_tui_main.py:175: in test_app_properties + assert hasattr(app, "selected_device") +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:275: in __get__ + self._initialize_reactive(obj, self.name) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:199: in _initialize_reactive + self._check_watchers(obj, name, default) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:367: in _check_watchers + invoke_watcher(obj, public_watch_function, old_value, value) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:91: in invoke_watcher + watch_result = cast(WatchCallbackNewValueType, watch_function)(value) +src/tui/main.py:946: in watch_selected_device + self._clear_compatibility_display() +src/tui/main.py:1067: in _clear_compatibility_display + compatibility_title = self.query_one("#compatibility-title", Static) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/dom.py:1465: in query_one + base_node = self._get_dom_base() +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/app.py:870: in _get_dom_base + return self.default_screen +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/app.py:883: in default_screen + return self.screen if self._compose_screen is None else self._compose_screen +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/app.py:1472: in screen + raise ScreenStackError("No screens on stack") from None +E textual.app.ScreenStackError: No screens on stacktests/tui/test_tui_main.py:353: in test_update_build_progress app.build_progress = BuildProgress( /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:345: in __set__ self._set(obj, value) @@ -215,9 +466,9 @@ E textual.app.ScreenStackError: No screens on stacktests/test_tui_main.py:478: in test_start_build - await app._start_build() -src/tui/main.py:537: in _start_build - self.query_one("#start-build", Button).disabled = False +E textual.app.ScreenStackError: No screens on stacktests/tui/test_tui_main.py:487: in test_start_build + app.selected_device = test_device +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:345: in __set__ + self._set(obj, value) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:299: in _set + self._initialize_reactive(obj, self.name) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:199: in _initialize_reactive + self._check_watchers(obj, name, default) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:367: in _check_watchers + invoke_watcher(obj, public_watch_function, old_value, value) +/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/reactive.py:91: in invoke_watcher + watch_result = cast(WatchCallbackNewValueType, watch_function)(value) +src/tui/main.py:946: in watch_selected_device + self._clear_compatibility_display() +src/tui/main.py:1067: in _clear_compatibility_display + compatibility_title = self.query_one("#compatibility-title", Static) /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/dom.py:1465: in query_one base_node = self._get_dom_base() /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/app.py:870: in _get_dom_base @@ -239,7 +502,9 @@ src/tui/main.py:537: in _start_build return self.screen if self._compose_screen is None else self._compose_screen /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages/textual/app.py:1472: in screen raise ScreenStackError("No screens on stack") from None -E textual.app.ScreenStackError: No screens on stacktests/test_tui_main.py:554: in test_reactive_watchers - assert mock_buttons["#start-build"].disabled is False -E AssertionError: assert <MagicMock name='mock.disabled' id='4363128176'> is False -E + where <MagicMock name='mock.disabled' id='4363128176'> = <MagicMock id='4363133216'>.disabled \ No newline at end of file +E textual.app.ScreenStackError: No screens on stacktests/tui/test_tui_models.py:91: in test_feature_summary + assert config.feature_summary == "Basic Configuration" +E AssertionError: assert 'Local Build' == 'Basic Configuration' +E +E - Basic Configuration +E + Local Build \ No newline at end of file diff --git a/pcileech-build-sudo b/pcileech-build-sudo index 4a0471c..7eb306a 100644 --- a/pcileech-build-sudo +++ b/pcileech-build-sudo @@ -1,11 +1,18 @@ #!/bin/bash -# Wrapper script to run pcileech-build with sudo while preserving Python path +# Wrapper script to run pcileech-generate with sudo while preserving Python path +# +# generate.py is the correct entry point that orchestrates: +# • Device enumeration and selection +# • Driver rebinding to vfio-pci +# • Container execution with build.py +# • Optional firmware flashing -# Get the path to the installed pcileech-build script -PCILEECH_BUILD_PATH=$(which pcileech-build) +# Get the path to the installed pcileech-generate script +PCILEECH_GENERATE_PATH=$(which pcileech-generate) -if [ -z "$PCILEECH_BUILD_PATH" ]; then - echo "Error: pcileech-build not found in PATH" +if [ -z "$PCILEECH_GENERATE_PATH" ]; then + echo "Error: pcileech-generate not found in PATH" + echo "Make sure pcileechfwgenerator is properly installed: pip install pcileechfwgenerator" exit 1 fi @@ -14,5 +21,5 @@ PYTHON_USER_SITE=$(python3 -m site --user-site) PYTHON_SITE_PACKAGES=$(python3 -c "import site; print(':'.join(site.getsitepackages()))") # Run with sudo, preserving the PYTHONPATH -echo "Running pcileech-build with sudo and preserved Python paths..." -sudo PYTHONPATH="$PYTHONPATH:$PYTHON_USER_SITE:$PYTHON_SITE_PACKAGES" "$PCILEECH_BUILD_PATH" "$@" \ No newline at end of file +echo "Running pcileech-generate with sudo and preserved Python paths..." +sudo PYTHONPATH="$PYTHONPATH:$PYTHON_USER_SITE:$PYTHON_SITE_PACKAGES" "$PCILEECH_GENERATE_PATH" "$@" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4a6f5b5..ad356c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,18 @@ pcileech-tui = "src.tui_cli:main" pcileech-build = "src.build_cli:main" [tool.setuptools] -packages = ["src", "src.tui", "src.tui.core", "src.tui.models", "src.scripts"] +packages = [ + "src", + "src.tui", + "src.tui.core", + "src.tui.models", + "src.scripts", + "src.build", + "src.build.analysis", + "src.build.config", + "src.build.orchestration", + "src.build.generators" +] [tool.setuptools.dynamic] version = {attr = "src.__version__.__version__"}