#!/bin/bash
# build_dext.sh -- Compile the CybMemDriver DriverKit extension
#
# DriverKit DEXTs are compiled as Mach-O executables (not kernel extensions).
# They link against the DriverKit runtime libraries, not the kernel.
#
# The .iig file must first be processed by the IIG (IOKit Interface Generator)
# tool to produce a C++ header. Then the .cpp is compiled against the
# DriverKit SDK headers and linked with the DriverKit runtime.
#
# Output: CybMemDriver.dext/ bundle ready for loading.
#
# Prerequisites:
#   - Xcode or Command Line Tools with DriverKit SDK
#   - Developer mode enabled (csrutil disable OR SIP configured)
#
# NOTE: Building a real DEXT requires Xcode's iig tool and the DriverKit
#       runtime libraries. This script documents the manual process, but
#       in practice you would use an Xcode project with the
#       "DriverKit Extension" target template.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DEXT_SRC="${SCRIPT_DIR}/dext"
BUILD_DIR="${SCRIPT_DIR}/build"
BUNDLE_DIR="${BUILD_DIR}/CybMemDriver.dext"

# ---- Find SDK ----
# Try to locate the DriverKit SDK. DriverKit headers ship with the macOS SDK
# in newer Xcode versions and Command Line Tools.
SDK_PATH=""

# Check Command Line Tools
for sdk in /Library/Developer/CommandLineTools/SDKs/MacOSX*.sdk; do
    if [ -d "${sdk}/System/Library/Frameworks/DriverKit.framework" ]; then
        SDK_PATH="${sdk}"
        break
    fi
done

# Check Xcode if not found
if [ -z "${SDK_PATH}" ]; then
    XCODE_SDK="$(xcrun --sdk macosx --show-sdk-path 2>/dev/null || true)"
    if [ -d "${XCODE_SDK}/System/Library/Frameworks/DriverKit.framework" ]; then
        SDK_PATH="${XCODE_SDK}"
    fi
fi

# Also look for dedicated DriverKit SDK (Xcode 11+)
DRIVERKIT_SDK=""
if command -v xcrun &>/dev/null; then
    DRIVERKIT_SDK="$(xcrun --sdk driverkit --show-sdk-path 2>/dev/null || true)"
fi

if [ -z "${SDK_PATH}" ] && [ -z "${DRIVERKIT_SDK}" ]; then
    echo "ERROR: Cannot find macOS SDK with DriverKit framework."
    echo "Install Xcode or Command Line Tools with DriverKit support."
    exit 1
fi

DK_FRAMEWORK="${SDK_PATH}/System/Library/Frameworks/DriverKit.framework"
DK_HEADERS="${DK_FRAMEWORK}/Headers"

echo "=== CybMemDriver DEXT Build ==="
echo "SDK:        ${SDK_PATH}"
echo "DriverKit:  ${DK_FRAMEWORK}"
if [ -n "${DRIVERKIT_SDK}" ]; then
    echo "DK SDK:     ${DRIVERKIT_SDK}"
fi
echo ""

# ---- Create build directory and bundle structure ----
mkdir -p "${BUNDLE_DIR}/Contents/MacOS"

# ---- Step 1: Process .iig file with IIG (IOKit Interface Generator) ----
# The iig tool generates a C++ header from the .iig interface definition.
# It's typically located in Xcode's toolchain.
IIG_TOOL=""
if command -v xcrun &>/dev/null; then
    IIG_TOOL="$(xcrun --find iig 2>/dev/null || true)"
fi

if [ -n "${IIG_TOOL}" ] && [ -x "${IIG_TOOL}" ]; then
    echo "[*] Running IIG on CybMemDriver.iig..."

    # IIG generates the C++ header that the implementation includes.
    # The generated header contains the vtable layout, IPC stubs, etc.
    "${IIG_TOOL}" \
        --def "${DEXT_SRC}/CybMemDriver.iig" \
        --header "${BUILD_DIR}/CybMemDriver.h" \
        --impl "${BUILD_DIR}/CybMemDriver.iig.cpp" \
        -- \
        -isystem "${DK_HEADERS}" \
        -D__IIG=1 \
        -std=gnu++20

    echo "[+] IIG generated: CybMemDriver.h, CybMemDriver.iig.cpp"
else
    echo "[!] iig tool not found -- creating stub header for compilation test"
    echo ""
    echo "    To get the iig tool, install Xcode (not just Command Line Tools)."
    echo "    The iig tool is required for building real DEXTs."
    echo ""

    # Create a minimal stub header so we can at least check syntax
    cat > "${BUILD_DIR}/CybMemDriver.h" << 'STUB_EOF'
/*
 * CybMemDriver.h -- STUB header (iig tool not available)
 *
 * In a real build, this is generated by the iig tool from CybMemDriver.iig.
 * It contains the C++ class declaration with DriverKit IPC method stubs,
 * vtable layout, and serialization code.
 *
 * This stub allows compilation syntax checking but the result will NOT
 * link or load as a real DEXT.
 */
#ifndef CybMemDriver_h_generated
#define CybMemDriver_h_generated

#include <DriverKit/IOUserClient.h>
#include <DriverKit/IODMACommand.h>
#include <DriverKit/IOBufferMemoryDescriptor.h>

/* Selectors */
enum {
    kCybMemDriverMethodGetPhysAddrs = 0,
    kCybMemDriverMethodCount        = 1,
};

/* Wire format structs */
#define CYBMEM_MAX_SEGMENTS 32

struct CybMemInput {
    uint32_t surface_id;
    uint32_t _pad0;
    uint64_t byte_length;
};

struct CybMemPhysSegment {
    uint64_t phys_addr;
    uint64_t length;
};

struct CybMemOutput {
    uint32_t num_segments;
    uint32_t _pad0;
    uint64_t total_length;
    struct CybMemPhysSegment segments[CYBMEM_MAX_SEGMENTS];
};

/*
 * Stub class declaration (normally generated by iig tool).
 * This provides enough structure for compile-checking the .cpp.
 * The real iig output includes IPC stubs, vtable layout, and
 * serialization code that this stub omits.
 *
 * In the real iig output, Start/Stop/ExternalMethod/CopyClientMemoryForType
 * come from IOUserClient (which inherits from IOService). The iig tool
 * generates them as overridable. Our stub removes 'override' since the
 * headers from CLT don't expose them the same way as the full iig output.
 */

struct CybMemDriver_IVars;

/* DriverKit metaclass stub -- normally generated by iig */
extern OSMetaClass * gCybMemDriverMetaClass;

class CybMemDriver : public IOUserClient
{
public:
    CybMemDriver_IVars * ivars;

    virtual bool init();
    virtual void free();
    virtual kern_return_t Start(IOService * provider);
    virtual kern_return_t Stop(IOService * provider);

    virtual kern_return_t ExternalMethod(
        uint64_t                            selector,
        IOUserClientMethodArguments       * arguments,
        const IOUserClientMethodDispatch  * dispatch,
        OSObject                          * target,
        void                              * reference);

    virtual kern_return_t CopyClientMemoryForType(
        uint64_t                type,
        uint64_t              * options,
        IOMemoryDescriptor   ** memory);

    /* Provide GetProvider stub since we call it in the implementation */
    IOService * GetProvider() const { return nullptr; }
};

/* Metaclass symbol stub */
OSMetaClass * gCybMemDriverMetaClass = nullptr;

#endif /* CybMemDriver_h_generated */
STUB_EOF
    echo "[*] Created stub CybMemDriver.h"
fi

# ---- Step 2: Compile the DEXT ----
echo ""
echo "[*] Compiling CybMemDriver.cpp..."

# Determine compiler flags
COMPILE_FLAGS=(
    -std=gnu++20
    -fno-rtti            # DriverKit uses its own RTTI system
    -fno-exceptions      # no C++ exceptions in DriverKit
    -target arm64-apple-macos13.0
)

INCLUDE_FLAGS=(
    -isystem "${DK_HEADERS}"
    -I "${BUILD_DIR}"     # for generated CybMemDriver.h
    -I "${DEXT_SRC}"
)

# If we have the dedicated DriverKit SDK, use it
if [ -n "${DRIVERKIT_SDK}" ]; then
    COMPILE_FLAGS+=(-isysroot "${DRIVERKIT_SDK}")
    INCLUDE_FLAGS=(-I "${BUILD_DIR}" -I "${DEXT_SRC}")
fi

# Compile .cpp to .o
clang++ "${COMPILE_FLAGS[@]}" "${INCLUDE_FLAGS[@]}" \
    -c "${DEXT_SRC}/CybMemDriver.cpp" \
    -o "${BUILD_DIR}/CybMemDriver.o" \
    2>&1 || {
        echo "[!] Compilation failed (expected without full DriverKit SDK/iig)."
        echo "    This is normal for CLT-only setups."
        echo "    Install Xcode for full DEXT build capability."
        echo ""
        echo "    The source code has been syntax-checked as much as possible."
        exit 0
    }

echo "[+] Compiled CybMemDriver.o"

# ---- Step 3: Link the DEXT ----
# Link with DriverKit runtime. The DEXT is a regular Mach-O executable.
echo "[*] Linking..."

LINK_FLAGS=(
    -target arm64-apple-macos13.0
    -L "${DK_FRAMEWORK}"
    -framework DriverKit
)

if [ -n "${DRIVERKIT_SDK}" ]; then
    LINK_FLAGS+=(-isysroot "${DRIVERKIT_SDK}")
fi

# Also compile the iig-generated implementation if it exists
if [ -f "${BUILD_DIR}/CybMemDriver.iig.cpp" ]; then
    clang++ "${COMPILE_FLAGS[@]}" "${INCLUDE_FLAGS[@]}" \
        -c "${BUILD_DIR}/CybMemDriver.iig.cpp" \
        -o "${BUILD_DIR}/CybMemDriver.iig.o"
    LINK_FLAGS+=("${BUILD_DIR}/CybMemDriver.iig.o")
fi

clang++ "${LINK_FLAGS[@]}" \
    "${BUILD_DIR}/CybMemDriver.o" \
    -o "${BUNDLE_DIR}/Contents/MacOS/CybMemDriver" \
    2>&1 || {
        echo "[!] Link failed (expected without full DriverKit runtime libraries)."
        exit 0
    }

echo "[+] Linked: ${BUNDLE_DIR}/Contents/MacOS/CybMemDriver"

# ---- Step 4: Copy Info.plist and create bundle ----
cp "${DEXT_SRC}/Info.plist" "${BUNDLE_DIR}/Contents/Info.plist"
echo "[+] Copied Info.plist"

# ---- Step 5: Codesign ----
echo "[*] Code signing (ad-hoc)..."
codesign --sign - \
    --entitlements "${DEXT_SRC}/CybMemDriver.entitlements" \
    --force \
    "${BUNDLE_DIR}" \
    2>&1 || {
        echo "[!] Code signing failed."
        echo "    For DEXT distribution, you need a Developer ID certificate"
        echo "    with DriverKit entitlements provisioned by Apple."
    }

echo "[+] Signed: ${BUNDLE_DIR}"
echo ""
echo "=== Build complete ==="
echo "DEXT bundle: ${BUNDLE_DIR}"
echo ""
echo "To load (requires SIP disabled or proper provisioning):"
echo "  sudo kmutil load -p ${BUNDLE_DIR}"
echo "  -- or --"
echo "  Use systemextensionsctl for proper system extension activation."

Synonyms

honeycrisp/unimem/experiments/dext_contiguous_alloc/build_dext.sh

Neighbours