Skip to content

Use macos-15-large (Intel) for DAQP test #65

Use macos-15-large (Intel) for DAQP test

Use macos-15-large (Intel) for DAQP test #65

Workflow file for this run

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