Use macos-15-large (Intel) for DAQP test #65
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Test CasADi with memory debugging | |
| on: | |
| push: | |
| workflow_dispatch: | |
| jobs: | |
| test-python-macos-intel: | |
| runs-on: macos-15-large | |
| steps: | |
| - uses: actions/checkout@v4.1.1 | |
| - name: Download CasADi nightly (Intel) | |
| run: | | |
| wget https://github.com/casadi/casadi/releases/download/nightly-relwithdebinfo-debug/casadi-relwithdebinfo-debug-osx-x86_64-py312.tar.gz -O casadi.tar.gz | |
| - name: Unpack CasADi | |
| run: | | |
| mkdir -p casadi_py | |
| tar xzf casadi.tar.gz -C casadi_py | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| - name: Install numpy | |
| run: pip install numpy | |
| - name: Run DAQP bug test | |
| run: | | |
| export PYTHONPATH="${{ github.workspace }}/casadi_py:$PYTHONPATH" | |
| python test_daqp_bug.py | |
| test-matlab-macos-arm64: | |
| runs-on: macos-14 | |
| env: | |
| MW_CRASH_MODE: native | |
| steps: | |
| - uses: actions/checkout@v4.1.1 | |
| - name: Clone CasADi repo | |
| run: git clone --depth 1 https://github.com/casadi/casadi.git casadi_src | |
| # Download CasADi from debug branch (has dSYM files for symbolication) | |
| - name: Download CasADi nightly (debug branch with dSYMs) | |
| run: | | |
| wget https://github.com/casadi/casadi/releases/download/nightly-relwithdebinfo-debug/casadi-relwithdebinfo-debug-osx-arm64-matlab2018b.zip -O casadi.zip | |
| - name: Unpack CasADi | |
| run: unzip casadi.zip -d casadi | |
| - name: Set up MATLAB | |
| uses: matlab-actions/setup-matlab@v2 | |
| with: | |
| release: R2025a | |
| - name: Run sysid_gauss_newton_patched with dump enabled | |
| uses: matlab-actions/run-command@v2 | |
| continue-on-error: true | |
| env: | |
| MW_CRASH_MODE: native | |
| with: | |
| command: addpath('${{ github.workspace }}/casadi'); addpath('${{ github.workspace }}'); sysid_patched | |
| - name: List dump files | |
| if: always() | |
| run: | | |
| echo "=== Looking for dump files ===" | |
| find . -name "*.casadi" -o -name "solver.in*" 2>/dev/null | head -20 | |
| ls -la *.casadi 2>/dev/null || echo "No .casadi files in current dir" | |
| ls -la solver.in* 2>/dev/null || echo "No solver.in files in current dir" | |
| # Baseline: Run casadi-cli pure to verify crash reproduces | |
| - name: Run casadi-cli eval_dump (baseline - no debugging) | |
| if: always() | |
| continue-on-error: true | |
| run: | | |
| echo "=== Running casadi-cli eval_dump (baseline) ===" | |
| echo "This establishes whether the bug reproduces outside MATLAB" | |
| export DYLD_LIBRARY_PATH="${{ github.workspace }}/casadi:$DYLD_LIBRARY_PATH" | |
| # Find .casadi file and strip extension (eval_dump adds .casadi itself) | |
| DUMP_FILE=$(ls -t *.casadi 2>/dev/null | head -1) | |
| if [ -n "$DUMP_FILE" ]; then | |
| DUMP_BASE="${DUMP_FILE%.casadi}" | |
| echo "Found dump file: $DUMP_FILE, passing base name: $DUMP_BASE" | |
| "${{ github.workspace }}/casadi/casadi-cli" eval_dump "$DUMP_BASE" 2>&1 || echo "Baseline run exited with code $?" | |
| else | |
| echo "No dump file found" | |
| fi | |
| # Memory debugging approach 1: Guard Malloc | |
| - name: Run casadi-cli with Guard Malloc | |
| if: always() | |
| continue-on-error: true | |
| run: | | |
| echo "=== Running casadi-cli eval_dump with Guard Malloc ===" | |
| echo "Guard Malloc puts guard pages around allocations to catch buffer overruns" | |
| export DYLD_LIBRARY_PATH="${{ github.workspace }}/casadi:$DYLD_LIBRARY_PATH" | |
| # Find .casadi file and strip extension (eval_dump adds .casadi itself) | |
| DUMP_FILE=$(ls -t *.casadi 2>/dev/null | head -1) | |
| if [ -n "$DUMP_FILE" ]; then | |
| DUMP_BASE="${DUMP_FILE%.casadi}" | |
| echo "Found dump file: $DUMP_FILE, passing base name: $DUMP_BASE" | |
| DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib \ | |
| "${{ github.workspace }}/casadi/casadi-cli" eval_dump "$DUMP_BASE" 2>&1 || echo "Guard Malloc run exited with code $?" | |
| else | |
| echo "No dump file found" | |
| fi | |
| # Memory debugging approach 2: MallocScribble + MallocGuardEdges | |
| - name: Run casadi-cli with MallocScribble | |
| if: always() | |
| continue-on-error: true | |
| run: | | |
| echo "=== Running casadi-cli eval_dump with MallocScribble ===" | |
| echo "MallocScribble fills freed memory with 0x55, catches use-after-free" | |
| export DYLD_LIBRARY_PATH="${{ github.workspace }}/casadi:$DYLD_LIBRARY_PATH" | |
| # Find .casadi file and strip extension (eval_dump adds .casadi itself) | |
| DUMP_FILE=$(ls -t *.casadi 2>/dev/null | head -1) | |
| if [ -n "$DUMP_FILE" ]; then | |
| DUMP_BASE="${DUMP_FILE%.casadi}" | |
| echo "Found dump file: $DUMP_FILE, passing base name: $DUMP_BASE" | |
| MallocScribble=1 MallocGuardEdges=1 MallocErrorAbort=1 \ | |
| "${{ github.workspace }}/casadi/casadi-cli" eval_dump "$DUMP_BASE" 2>&1 || echo "MallocScribble run exited with code $?" | |
| else | |
| echo "No dump file found" | |
| fi | |
| # Memory debugging approach 3: Instruments/xctrace | |
| - name: Run casadi-cli with Instruments (Allocations) | |
| if: always() | |
| continue-on-error: true | |
| run: | | |
| echo "=== Running casadi-cli with Instruments Allocations template ===" | |
| export DYLD_LIBRARY_PATH="${{ github.workspace }}/casadi:$DYLD_LIBRARY_PATH" | |
| # Find .casadi file and strip extension | |
| DUMP_FILE=$(ls -t *.casadi 2>/dev/null | head -1) | |
| if [ -n "$DUMP_FILE" ]; then | |
| DUMP_BASE="${DUMP_FILE%.casadi}" | |
| echo "Found dump file: $DUMP_FILE, passing base name: $DUMP_BASE" | |
| # Use xctrace to record with Allocations template | |
| xcrun xctrace record \ | |
| --template "Allocations" \ | |
| --output allocations.trace \ | |
| --launch -- "${{ github.workspace }}/casadi/casadi-cli" eval_dump "$DUMP_BASE" 2>&1 || echo "xctrace exited with code $?" | |
| # Try to export summary | |
| echo "=== Trace file created ===" | |
| ls -la *.trace 2>/dev/null || echo "No trace files" | |
| else | |
| echo "No dump file found" | |
| fi | |
| # Memory debugging approach 4: leaks tool | |
| - name: Run casadi-cli with leaks detection | |
| if: always() | |
| continue-on-error: true | |
| run: | | |
| echo "=== Running casadi-cli eval_dump with leaks detection ===" | |
| export DYLD_LIBRARY_PATH="${{ github.workspace }}/casadi:$DYLD_LIBRARY_PATH" | |
| # Find .casadi file and strip extension (eval_dump adds .casadi itself) | |
| DUMP_FILE=$(ls -t *.casadi 2>/dev/null | head -1) | |
| if [ -n "$DUMP_FILE" ]; then | |
| DUMP_BASE="${DUMP_FILE%.casadi}" | |
| echo "Found dump file: $DUMP_FILE, passing base name: $DUMP_BASE" | |
| leaks --atExit -- "${{ github.workspace }}/casadi/casadi-cli" eval_dump "$DUMP_BASE" 2>&1 || echo "leaks run exited with code $?" | |
| else | |
| echo "No dump file found" | |
| fi | |
| - name: Wait for crash report generation | |
| if: always() | |
| run: | | |
| echo "Waiting for macOS to generate crash report..." | |
| sleep 15 | |
| - name: Collect crash info | |
| if: always() | |
| run: | | |
| echo "=== Core dumps ===" | |
| ls -la /cores/ 2>/dev/null || echo "No /cores directory or empty" | |
| find /cores -name "core.*" 2>/dev/null | head -5 | while read f; do | |
| echo "=== Core dump: $f ===" | |
| ls -la "$f" | |
| done | |
| echo "" | |
| echo "=== MATLAB crash dump in home directory ===" | |
| find ~ -maxdepth 1 -name "matlab_crash_dump*" -mmin -30 2>/dev/null | while read f; do | |
| echo "=== $f ===" | |
| cat "$f" 2>/dev/null | head -1000 | |
| done | |
| echo "" | |
| echo "=== System crash reports - .ips files ===" | |
| find /Library/Logs/DiagnosticReports ~/Library/Logs/DiagnosticReports -name "*.ips" -mmin -30 2>/dev/null | while read f; do | |
| echo "=== $f ===" | |
| cat "$f" 2>/dev/null | |
| done | |
| echo "" | |
| echo "=== System crash reports - .crash files ===" | |
| find /Library/Logs/DiagnosticReports ~/Library/Logs/DiagnosticReports -name "*.crash" -mmin -30 2>/dev/null | while read f; do | |
| echo "=== $f ===" | |
| cat "$f" 2>/dev/null | |
| done | |
| echo "" | |
| echo "=== List all diagnostic reports ===" | |
| ls -la /Library/Logs/DiagnosticReports/ 2>/dev/null | tail -30 | |
| ls -la ~/Library/Logs/DiagnosticReports/ 2>/dev/null | tail -30 | |
| - name: Symbolicate crash dump | |
| if: always() | |
| run: | | |
| python3 << 'EOF' | |
| import re | |
| import subprocess | |
| import os | |
| from pathlib import Path | |
| # Find the crash dump | |
| crash_files = list(Path.home().glob("matlab_crash_dump*")) | |
| if not crash_files: | |
| print("No crash dump found") | |
| exit(0) | |
| crash_file = max(crash_files, key=lambda f: f.stat().st_mtime) | |
| print(f"=== Symbolicating {crash_file} ===\n") | |
| content = crash_file.read_text() | |
| # Pattern for stack frames like: | |
| # [ 12] 0x000000012b17d4d0 /path/to/lib.dylib+00201936 __func+00000108 | |
| pattern = r'\[\s*(\d+)\]\s+(0x[0-9a-fA-F]+)\s+(/[^\s]+\.dylib)\+(\d+)\s+(\S+)' | |
| casadi_dir = Path("${{ github.workspace }}/casadi") | |
| print("=== Symbolicated Stack Trace ===\n") | |
| for match in re.finditer(pattern, content): | |
| frame_num = match.group(1) | |
| runtime_addr = match.group(2) | |
| lib_path = match.group(3) | |
| offset = int(match.group(4)) | |
| func_name = match.group(5) | |
| # Only symbolicate our libraries | |
| if "/casadi/" not in lib_path: | |
| print(f"[{frame_num:>3}] {func_name} (system library)") | |
| continue | |
| # Find dSYM - try versioned name first, then unversioned | |
| lib_name = Path(lib_path).name | |
| dsym_path = casadi_dir / f"{lib_name}.dSYM" / "Contents" / "Resources" / "DWARF" / lib_name | |
| if not dsym_path.exists(): | |
| # Try stripping version number: libfoo.2.dylib -> libfoo.dylib | |
| unversioned = re.sub(r'\.\d+\.dylib$', '.dylib', lib_name) | |
| if unversioned != lib_name: | |
| dsym_path = casadi_dir / f"{unversioned}.dSYM" / "Contents" / "Resources" / "DWARF" / unversioned | |
| if not dsym_path.exists(): | |
| print(f"[{frame_num:>3}] {func_name} (no dSYM for {lib_name})") | |
| continue | |
| # Calculate load address | |
| runtime_int = int(runtime_addr, 16) | |
| load_addr = hex(runtime_int - offset) | |
| # Use atos to symbolicate | |
| try: | |
| result = subprocess.run( | |
| ["atos", "-o", str(dsym_path), "-l", load_addr, runtime_addr], | |
| capture_output=True, text=True, timeout=10 | |
| ) | |
| symbolicated = result.stdout.strip() | |
| if symbolicated and "atos cannot" not in symbolicated: | |
| print(f"[{frame_num:>3}] {symbolicated}") | |
| else: | |
| print(f"[{frame_num:>3}] {func_name} @ {lib_name}+{offset}") | |
| except Exception as e: | |
| print(f"[{frame_num:>3}] {func_name} @ {lib_name}+{offset} (atos error: {e})") | |
| print("\n=== End Symbolicated Stack Trace ===") | |
| EOF |