UTM Documentation

Virtual machines for iOS and macOS

34,911 stars Swift Swift 1 file · ~12,709 tokens #apple#emulation#ios#jailbreak#macos#qemu#utm#virtual-machines
RAW Doc

File: README.md

UTM

It is possible to invent a single machine which can be used to compute any computable sequence.

-- Alan Turing, 1936

UTM is a full featured system emulator and virtual machine host for iOS and macOS. It is based off of QEMU. In short, it allows you to run Windows, Linux, and more on your Mac, iPhone, and iPad. More information at https://getutm.app/ and https://mac.getutm.app/

Features

  • Full system emulation (MMU, devices, etc) using QEMU
  • 30+ processors supported including x86_64, ARM64, and RISC-V
  • VGA graphics mode using SPICE and QXL
  • Text terminal mode
  • USB devices
  • JIT based acceleration using QEMU TCG
  • Frontend designed from scratch for macOS 11 and iOS 11+ using the latest and greatest APIs
  • Create, manage, run VMs directly from your device

Additional macOS Features

  • Hardware accelerated virtualization using Hypervisor.framework and QEMU
  • Boot macOS guests with Virtualization.framework on macOS 12+

UTM SE

UTM/QEMU requires dynamic code generation (JIT) for maximum performance. JIT on iOS devices require either a jailbroken device, or one of the various workarounds found for specific versions of iOS (see "Install" for more details).

UTM SE ("slow edition") uses a threaded interpreter which performs better than a traditional interpreter but still slower than JIT. This technique is similar to what iSH does for dynamic execution. As a result, UTM SE does not require jailbreaking or any JIT workarounds and can be sideloaded as a regular app.

To optimize for size and build times, only the following architectures are included in UTM SE: ARM, PPC, RISC-V, and x86 (all with both 32-bit and 64-bit variants).

Install

UTM (SE) for iOS: https://getutm.app/install/

UTM is also available for macOS: https://mac.getutm.app/

Development

[macOS Development](Documentation/MacDevelopment.md)

[iOS Development](Documentation/iOSDevelopment.md)

  • iSH: emulates a usermode Linux terminal interface for running x86 Linux applications on iOS
  • a-shell: packages common Unix commands and utilities built natively for iOS and accessible through a terminal interface

License

UTM is distributed under the permissive Apache 2.0 license. However, it uses several (L)GPL components. Most are dynamically linked but the gstreamer plugins are statically linked and parts of the code are taken from qemu. Please be aware of this if you intend on redistributing this application.

Some icons made by Freepik from www.flaticon.com.

Additionally, UTM frontend depends on the following MIT/BSD License components:

Continuous integration hosting is provided by MacStadium


File: Documentation/Architecture.md

Architecture

UTM is built upon several pieces of technology, layered to provide compatibility across various host configurations. Below is a simplified diagram of key pieces of UTM with additional details provided below.

text
┌────────────────────┬──────────────────────┐
│   iOS VM Display   │   macOS VM Display   │
├────────────────────┴──────────────────────┤
│                  SwiftUI                  │
├───────────────────────────────────────────┤
│             UTMVirtualMachine             │
├────────────────┬──────────────────────────┤
│   CocoaSpice   │                          │
├────────────────┤ Virtualization.framework │
│ QEMU (TCG/HVF) │                          │
└────────────────┴──────────────────────────┘

QEMU

The backbone of UTM is QEMU, which provides the emulation and virtualization engine. We run a custom fork which includes several features such as:

  • Building QEMU as a shared library
  • APRR support for jailbroken iOS
  • ARM64 TCTI from @ktemkin (JIT-less iOS support)
  • SPICE ANGLE backend for hardware GL acceleration

These features, along with several others (which are undergoing code review and may not have made the latest stable release), allow our fork to be optimized for Darwin.

Hypervisor.framework

QEMU includes support for the hvf accelerator which provides same architecture virtualization (x86 -> x86 or ARM64 -> ARM64) on macOS. This framework is not available on iOS.

UTMQemu

QEMU is linked as a shared library. On iOS, there is no ability to use fork, XPC (directly), or any other way of launching a new process. As a result, we run the QEMU main loop in a pthread which for all intents and purposes work like normal (the key exception being that you cannot spawn multiple instances of QEMU and since QEMU does not properly clean up resources, you cannot re-launch QEMU). On macOS, XPC is used to launch QEMU in a new process. UTMQemu manages the pthread or XPC implementation.

XPC Helper

On macOS, spawning new processes is permitted but due to App Sandbox security requirements, we need some additional "bootstrapping" code to launch QEMU properly. The added benefit is that we only have to provide a single bundle identifier for the "launcher" executable rather than a different identifier for each QEMU executable (required for App Sandbox).

QEMUHelper is an XPC helper with its own App Sandbox separate from the UTM main application. This improves security by providing an additional layer of separation. However, due to this extra care has to be taken when passing file handles and other system resources from the main app. For example, the Unix socket file used to communicate with SPICE is stored in a shared App Group directory. For disk images and shared directories that cannot be stored in the App Group, we have to do a complicated sandbox dance to get the right access permissions.

  1. The main application opens a NSOpenPanel, allowing the user to select a file/directory outside the sandbox. This returns a NSURL with the right security scope attached. If we take a regular bookmark of NSURL and pass it through XPC, the XPC process should also have access once it reads the bookmark back into a NSURL.
  2. However, the access to that file is only valid while the app is open. As soon as you close it, the app loses those permissions and must either prompt the user to select the file again or store a "security scoped bookmark." The issue here is that a security scoped bookmark is only valid for the sandbox that created it. If we take a security scoped bookmark and pass it directly to the XPC process, it cannot get back a NSURL.
  3. So the solution is to take a standard bookmark, pass it to the XPC process, have the XPC process then create a security scoped bookmark and pass it back to the main process. The main process can now store this bookmark and have it work as intended when it passes it to the XPC process next launch.

The XPC helper spawns QEMULauncher with an inherited sandbox. This means it can access files that are accessible to the helper XPC. The launcher process is what runs QEMU. When a new file is opened (for example a new disk image is mounted), the main application will pass a bookmark to the helper XPC where it will call -startAccessingSecurityScopedResource which also applies to the child process (QEMULauncher). This way, QEMU does not have to have any knowledge of the App Sandbox.

UTMConfiguration

VM configuration is stored in a PLIST format. This PLIST maps to either a UTMQemuConfiguration or UTMAppleConfiguration structure which stores the underlying configuration data in a Codable interface for easy serialization.

UTMQemuSystem

UTMQemuSystem maps UTMQemuConfiguration to command line arguments used to launch QEMU.

UTMQemuManager

After a VM is launched, UTMQemuManager provides run-time services though the QMP protocol. These services include stopping/pausing/resuming the VM, taking snapshots, switching between mouse and tablet, mounting removable disk images, and more. The underlying transport for QMP is JSON (over a socket), so UTMJSONStream marshals the data to and from NSDictionary objects.

QAPI

QMP protocol is defined by the QAPI schema which is provided as a set of JSON files in QEMU. QEMU uses these JSON files to generate wrapper C functions for internal usage. UTM includes a modified function generator derived from QEMU's own script (qapi-gen.py) along with modified QAPI C visitors (for NSDictionary). This allows UTM to use the same commands, structures, and events that QEMU uses in a transparent way.

For example, when UTM makes a call to some QAPI command such as qmp_blockdev_change_medium, the generated C functions will automatically marshal the function arguments into a NSDictionary object which UTMJSONStream converts into JSON and sends it over the QMP socket. When a response is received, UTMJSONStream converts the JSON into a NSDictionary object, which goes through the generated C functions to unmarshal into some C structure which is returned from qmp_blockdev_change_medium. This is all transparent to the caller as long as they understand that the function call will block until the response is received so it must not be called from the main thread.

Virtualization.framework

On Apple Silicon Macs running macOS 12 or later, Virtualization.framework is provided by Apple to run macOS 12 guests.

UTMAppleConfiguration

As the backend is different, the configuration format UTM uses is also different and is represented in UTMAppleConfiguration. As this configuration is handed in Swift, the Codable protocol is used for serialization instead of a NSDictionary backing used in UTMQemuConfiguration. The Codable backing is more extendible as it allows more complex data to be represented without a lot of boilerplate.

CocoaSpice

UTM uses the SPICE front-end with QEMU because it has more versatility than VNC to handle things like USB forwarding, multiple displays, and the ability to use a SPICE agent running on the guest to share clipboard and change the resolution. CocoaSpice is provided as a Swift package and acts as Cocoa/Objective-C bindings for SPICE GTK. CocoaSpice also provides a bridge between the Pixman framebuffer that SPICE uses and Metal textures that is used by MetalKit to render to screen.

UTMSpiceIO

UTMSpiceIO connects CocoaSpice to UTM and is used by UTMQemuVirtualMachine to control the SPICE client and respond to client events.

UTMVirtualMachine

UTMVirtualMachine provides file I/O operations for creating and saving .utm VM bundles as well as controls for the platform-specific layer above to do high level tasks like starting and stopping the VM. It is highest level of the "backend", providing a platform independent view of UTM virtual machines.

UTMQemuVirtualMachine

This subclass manages QEMU+CocoaSpice backend VMs.

UTMAppleVirtualMachine

This subclass manages Apple Virtualization.framework backend VMs.

Frontend

SwiftUI

The frontend for UTM is designed mostly in SwiftUI 2.0. That means the minimum supported operating system is iOS 14 and macOS 11 and is the main reason there are no plans to back-port UTM to earlier versions. Most views are designed to work on both macOS and iOS.

UTMData

UTMData is a SwiftUI ObservableObject that contains the "state" and is the "single source of truth" for the home view. It stores the list of VMs and functions to create, modify, move, etc VMs on that list. UTMConfigurable provides the state for VM configuration views.

iOS VM Display

The VM display uses UIKit as SwiftUI is not mature enough to do everything UTM needs. This includes the custom keyboard accessory view implemented in a NIB for emulating keys that are not available on the standard iOS keyboard.

macOS VM Display

On macOS, the VM display uses AppKit for similar reasons above.


File: Documentation/Dependencies.md

Dependencies

UTM is built upon QEMU, SPICE, and various libraries that those projects depend on. To support building as an Xcode project, we designed a custom build system that creates Xcode compatible frameworks from autoconf and meson projects.

`build_dependencies.sh`

The build script sets up a build environment for the target platform and architecture.

text
Usage: [VARIABLE...] build_dependencies.sh [-p platform] [-a architecture] [-q qemu_path] [-d] [-r]

  -p platform      Target platform. Default ios. [ios|ios_simulator|ios-tci|ios_simulator-tci|macos]
  -a architecture  Target architecture. Default arm64. [armv7|armv7s|arm64|i386|x86_64]
  -q qemu_path     Do not download QEMU, use qemu_path instead.
  -d, --download   Force re-download of source even if already downloaded.
  -r, --rebuild    Avoid cleaning build directory.

  VARIABLEs are:
    SDKVERSION     Target a specific SDK version.
    CHOST          Configure host, set if not deducable by ARCH.

    CFLAGS CPPFLAGS CXXFLAGS LDFLAGS

The build steps are summarized below:

  1. It will attempt to detect if your build system has all the required tools before starting.
  2. All source archives in patches/sources are downloaded if not found. If the download is a Git repository, it is cloned and the right commit is checked out.
  3. If found, the .patch file in patches with the corresponding name is applied to each download.
  4. Each dependency is configured, built, and installed to the sysroot directory.
  5. Once all libraries are built, they are converted to a .framework. The id and library dependencies are patched to be relative to @rpath (using install_name_tool).
  6. The QAPI sources are generated using scripts/qapi-gen.py.

Updating dependencies

QEMU

The steps for updating QEMU is the most involved. UTM maintains a fork of QEMU which the updated QEMU version must be merged into. This will be the most time consuming part as the fork needs to build and run correctly outside of UTM.

Next, the QAPI generator for UTM needs to be updated with the changes from QEMU. The UTM QAPI script is derived from QEMU's scripts/qapi/*. Many of the files are unchanged and copied directly from QEMU. However, the key files (commands.py, events.py, types.py) are heavily modified. The best way to approach this is to do a 3-way diff with the UTM files, the version of QEMU where those files are derived from, and the new version of the scripts. Then take the changes from the old version of QEMU to the new version of QEMU and merge it into UTM. For files where there are no changes between UTM and QEMU, the new version from QEMU can be copied to directly. For files where there are changes, some work is required to integrate the changes. From experience, it may be easiest to do incremental changes from QEMU's commits. Also, QAPI does not change often so it is not required to update the scripts after each QEMU update.

As a result of the above, the QAPI support files may also need to be updated. Check with QEMU qapi/*.c's commit history to see if there's any changes needed there. It will usually correspond to changes in the Python generator code.

UTMQemuConfiguration+ConstantsGenerated.m needs to be updated by running const-gen.py. You need to build the UTM fork of QEMU for macOS and pass the build directory as an argument to const-gen. It will then run each QEMU executable in order to parse the help text to find changes in device support. If QEMU adds or removes a supported architecture, this must be manually changed in the const-gen script.

Finally, make sure to rename the binary patches directory to the new QEMU version. The code patch for the previous QEMU version can be deleted if all the changes have been integrated into the UTM fork of QEMU.

Others

The other dependencies are more straightforward. Take the latest release tarball and re-integrate the UTM patches if needed. The UTM GitHub will usually keep forks of projects it depends on with required patches. The UTM changes should be rebased off of the commit corresponding to the latest release of the project. Then git format-patch can be used to generate the patch file which will be applied to the release tarball.


File: Documentation/Graphics.md

Graphics

The graphics architecture of UTM involves many separate translation layers.

GPU Acceleration

text
┌────────────────────────────────────────────────┐
                             │  Host                                          │
                             │ ┌────────────────────────────────┬───────────┐ │
                          ┌──┼─► virglrenderer       │ +Venus†  │ gfxstream†│ │
                          │  │ │                     │          │           │ │
                          │  │ ├──────────┬──────────┬──────────┴───────────┤ │
┌─────────────────────┐   │  │ │ ANGLE    │ ANGLE    │ MoltenVK†            │ │
│ Guest               │   Q  │ │ Metal    │ OpenGL   │                      │ │
│ ┌─────────────────┐ │   E  │ ├──────────┴──────────┴──────────────────────┤ │
│ │ Userland 3D API │ │   M  │ │ CocoaSpice Metal Renderer                  │ │
│ │ (e.g. Mesa)     │ │   U  │ │                                            │ │
│ ├─────────────────┤ │   │  │ ├────────────────────────────────────────────┤ │
│ │ Kernel Driver   │ │   │  │ │ Metal Device                               │ │
│ │ (virtio-gpu)    ├─┼───┘  │ │                                            │ │
│ └─────────────────┘ │      │ └────────────────────────────────────────────┘ │
│                     │      │                                                │
└─────────────────────┘      └────────────────────────────────────────────────┘

†: Future work that is not currently in UTM.

No GPU Acceleration

text
┌───────────────────────────────┐
                             │  Host                         │
                             │ ┌─────────────┬─────────────┐ │
┌─────────────────────┐   ┌──┼─► pixman      │ pixman      │ │
│ Guest               │   │  │ │ EGL Canvas  │ Pixel Buffer│ │
│                     │   Q  │ ├─────────────┴─────────────┤ │
│                     │   E  │ │ CocoaSpice Metal Renderer │ │
│                     │   M  │ │                           │ │
│ ┌─────────────────┐ │   U  │ ├───────────────────────────┤ │
│ │ Framebuffer     │ │   │  │ │ Metal Device              │ │
│ │                 ├─┼───┘  │ │                           │ │
│ └─────────────────┘ │      │ └───────────────────────────┘ │
│                     │      │                               │
└─────────────────────┘      └───────────────────────────────┘

Guest Side

When GPU acceleration is available, the guest userland will translate the graphics API (OpenGL, Vulkan, etc) to kernel driver commands. The commands goes through QEMU through the VirtIO interface to be decoded by the host.

If GPU acceleration is not available, it is typically due to one or more of the following:

  • The VirtIO GPU device is not used or is not available on the guest architecture
  • The guest drivers for VirtIO GPU are not available or are incomplete (as is the case with Windows)
  • The guest userland component (e.g. Mesa) is incompatible with the host side libraries (due to a bug or a missing feature)
  • The guest application attempts to use a graphics API feature that is not supported by the host side libraries

When GPU acceleration is missing, if a -gl display hardware is used, then QEMU will handle the blit operations directly to a EGL canvas. This will be slight faster and have lower latency than the alternative, which is for CocoaSpice to do the blit operation AND render to screen. This is why it is worth selecting a -gl device even if there is no guest driver support.

virglrenderer

virglrenderer is the host side library that decodes the draw commands from the guest and calls into OpenGL (on the host side). Venus is a newer addition to virglrenderer that allows the guest to pass Vulkan commands to the host.

gfxstream

gfxstream is an alternative library that allows the guest to serialize OpenGL and Vulkan commands, pass them through a communication channel ("pipe") to the host, and the host will deserialize and evaluate the calls. It differs from virglrenderer in that there is no intermediate translation (guest Mesa -> virgl commands -> host OpenGL). Currently this technology is used for Google's Android emulator and not by mainline QEMU so it will take some time for UTM to adopt the code.

ANGLE

ANGLE is an implementation of OpenGL ES on top of other graphics APIs. UTM uses three ANGLE backends:

  1. On macOS, the cgl (Core OpenGL) backend is provided
  2. On iOS, the eagl backend is provided
  3. For both macOS and iOS, the metal backend is provided

The three backends have differing compatibility and there is no "best" backend. QEMU uses ANGLE to draw into an IOSurface instead of directly to screen.

MoltenVK

MoltenVK is used to translate Vulkan to Metal because Apple devices do not support Vulkan natively. MoltenVK is currently not used in UTM.

CocoaSpice

CocoaSpice renders the IOSurface as a texture directly to screen with Metal APIs. It also controls the frame time by synchronizing to the display's vblank signal to reduce tearing. As an optimization, CocoaSpice renderer will only draw the last update before a vblank which means that if the guest is drawing multiple times per monitor refresh, the host will consolidate the draws to a single Metal call. On macOS, the IOSurface is passed from QEMULauncher (rendered in a separate process) through a global IOSurfaceID. On iOS, because there is no process separation, the IOSurface reference is passed directly from QEMU to CocoaSpice.

UTM uses SPICE as a QEMU frontend, which means that all input/output goes through SPICE. SPICE was designed to work remotely over the network but when operating remotely, GPU acceleration is not supported. Instead, all pixel buffer updates must be sent from QEMU which is why it is slower than the EGL canvas rendering.

Debugging Tips

ANGLE has an option to enable tracing all GL calls. You can modify scripts/build_dependencies.sh to add the following arguments to the ANGLE build:

text
--args angle_enable_trace=true angle_enable_trace_events=true

By default, the trace logs are set to the device syslog. If you want it to show up in stderr (especially to sync with log items from other components), modify src/common/debug.cpp and change the line #elif defined(ANGLE_PLATFORM_APPLE) to #elif defined(ANGLE_PLATFORM_APPLE_NOT_DEFINED) as well as the fprintf after it to fprintf(stderr, "%s: %s\n", LogSeverityName(severity), (in order to force stderr to always be used).

virglrenderer also can print debug output. In order to enable it, modify scripts/build_dependencies.sh to include --buildtype debug to the Meson call. Then you have to add the environment variable VREND_DEBUG=all to the process. The easiest way to do that is to modify UTMQemuSystem.m and add it to the setRendererBackend: method.

To enable MoltenVK debug output, modify scripts/build_dependencies.sh and change the call to make $platform in build_moltenvk() to make ${platform}-debug.


File: Documentation/iOSDevelopment.md

iOS Development

This document describes the steps to build and debug UTM on iOS and simulator devices.

Getting the Source

Make sure you perform a recursive clone to get all the submodules:

text
git clone --recursive https://github.com/utmapp/UTM.git

Alternatively, run git submodule update --init --recursive after cloning if you did not do a recursive clone.

Dependencies

The easy way is to get the prebuilt dependences from GitHub Actions. Pick the latest release and download the Sysroot-* artifact for the targets you wish to develop on. You need to be logged in to GitHub to download artifacts.

Intel Apple Silicon
iOS N/A ios-arm64
iOS SE N/A ios-tci-arm64
iOS Simulator ios_simulator-x86_64 ios_simulator-arm64
iOS Simulator SE ios_simulator-tci-x86_64 ios_simulator-tci-arm64
visionOS N/A visionos-arm64
visionOS SE N/A visionos-tci-arm64
visionOS Simulator N/A visionos_simulator-arm64
visionOS Simulator SE N/A visionos_simulator-tci-arm64

After downloading the prebuilt artifacts of your choice, extract them to the root directory where you cloned the repository.

To build UTM, make sure you have the latest version of Xcode installed.

Building Dependencies (Advanced)

If you want to build the dependencies yourself, it is highly recommended that you start with a fresh macOS VM. This is because some of the dependencies attempt to use /usr/local/lib even though the architecture does not match. Certain installed packages like libusb, gawk, and cmake will break the build.

  1. Install Xcode command line and Homebrew
  2. Install the following build prerequisites
    brew install bison pkg-config gettext glib libgpg-error nasm meson
    pip3 install six pyparsing
    Make sure to add bison to your $PATH environment variable!
    export PATH=/usr/local/opt/bison/bin:/opt/homebrew/opt/bison/bin:$PATH
  3. Run ./scripts/build_dependencies.sh -p PLATFORM -a ARCHITECTURE where ARCHITECTURE is the last part of the table above (e.g. x86_64) and PLATFORM is the first part (e.g. ios_simulator-tci).
  4. Repeat the above for any other platforms and architectures you wish to target.

Building UTM

Command Line

You can build UTM for iOS with the script (run ./scripts/build_utm.sh for all options):

text
./scripts/build_utm.sh -k iphoneos -s iOS -a arm64 -o /path/to/output/directory

The built artifact is an unsigned .xcarchive which you can use with the package tool (see below). Replace iOS with iOS-SE to build UTM SE. Replace iphoneos with xros to build for visionOS.

Packaging

Artifacts built with build_utm.sh (includes GitHub Actions artifacts) must be re-signed before it can be used. For stock iOS devices, you can sign with either a free developer account or a paid developer account. Free accounts have a 7 day expire time and must be re-signed every 7 days. For jailbroken iOS devices, you can generate a DEB which is fake-signed.

Stock signed IPA

For a user friendly option, you can use iOS App Signer to re-sign the .xcarchive. Advanced users can use the package.sh script:

text
./scripts/package.sh signedipa /path/to/UTM.xcarchive /path/to/output TEAM_ID PROFILE_UUID

This builds UTM.ipa in /path/to/output which can be installed by Xcode, iTunes, or AirDrop. Note that you need a "Development" signing certificate and NOT a "Distribution" certificate. This is because UTM requires a provisioning profile with the get-task-allow entitlement which Apple only grants for Development signing.

Unsigned IPA

text
./scripts/package.sh ipa /path/to/UTM.xcarchive /path/to/output

This builds UTM.ipa in /path/to/output which can be installed by AltStore or a jailbroken device with AppSync Unified installed.

DEB Package

text
./scripts/package.sh deb /path/to/UTM.xcarchive /path/to/output

This builds UTM.deb which is a wrapper for an unsigned UTM.ipa which can be installed by Cydia or Sileo along with AppSync Unified.

Xcode Development

Copy CodeSigning.xcconfig.sample to CodeSigning.xcconfig and modify the file replacing DEVELOPMENT_TEAM with your Team ID and PRODUCT_BUNDLE_PREFIX with a bundle identifier that is registered to you.

If you have a paid Apple Developer account, you can find your Team ID at https://developer.apple.com/account/#/membership

If you have a free Apple Developer account, you need to generate a new signing certificate. To do so, follow the steps in iOS App Signer to create a new Xcode project and generate a provisioning profile. After saving the project, open project.pbxproj inside your newly created .xcproj and look for DEVELOPMENT_TEAM. Copy this value to CodeSigning.xcconfig and your unique identifier to PRODUCT_BUNDLE_PREFIX.

Set DEVELOPER_ACCOUNT_PAID = YES if you used a paid Apple Developer account in order to automatically request the increased memory limit entitlement from Apple.

Tethered Launch

For JIT to work on the latest version of iOS, it must be launched through the debugger. You can do it from Xcode (and detach the debugger after launching) or you can follow these instructions for an easier way.


File: Documentation/MacDevelopment.md

macOS Development

Because UTM is a sand-boxed Mac app, there are a few extra steps needed for a proper development environment.

Getting the Source

Make sure you perform a recursive clone to get all the submodules:

sh
git clone --recursive https://github.com/utmapp/UTM.git

Alternatively, run the following after cloning if you did not do a recursive clone.

sh
git submodule update --init --recursive

Dependencies

The easy way is to get the prebuilt dependencies from GitHub Actions. Pick the latest release and download all of the Sysroot-macos-* artifacts. You need to be logged in to GitHub to download artifacts. If you only intend to run locally, it is alright to just download the sysroot for your architecture. After downloading the prebuilt artifacts of your choice, extract them to the root directory where you cloned the repository.

To build UTM, make sure you have the latest version of Xcode installed.

Building Dependencies (Advanced)

If you want to build the dependencies yourself, it is highly recommended that you start with a fresh macOS VM. This is because some of the dependencies attempt to use /usr/local/lib even though the architecture does not match. Certain installed packages like libusb, gawk, and cmake will break the build.

  1. Install Xcode command line and Homebrew

  2. Install the following build prerequisites

    sh
    brew install bison pkg-config gettext glib-utils libgpg-error nasm meson
    sh
    pip3 install six pyparsing

    Make sure to add bison to your $PATH environment variable!

    sh
    export PATH=/usr/local/opt/bison/bin:/opt/homebrew/opt/bison/bin:$PATH
  3. Run

    sh
    ./scripts/build_dependencies.sh -p macos -a ARCH

    where ARCH is either arm64 or x86_64.

If you want to build universal binaries, you need to run build_dependencies.sh for both arm64 and x86_64 and then run

sh
./scripts/pack_dependencies.sh . macos arm64 x86_64

If you are developing QEMU and wish to pass in a custom path to QEMU, you can use the -q PATH_TO_QEMU_SOURCE option to build_dependencies.sh. Note that you need to use a UTM compatible fork of QEMU.

Building UTM

Command Line

You can build UTM with the script:

sh
./scripts/build_utm.sh -t TEAMID -k macosx -s macOS -a ARCH -o /path/to/output/directory

ARCH can be x86_64 or arm64 or "arm64 x86_64" (quotes are required) for a universal binary. The built artifact is an unsigned .xcarchive which you can use with the package tool (see below).

TEAMID is optional and only used if you are going to sign it.

Packaging

Artifacts built with build_utm.sh (includes GitHub Actions artifacts) must be re-signed before it can be used. To properly use all features, you must be a paid Apple Developer with access to a provisioning profile with the Hypervisor entitlements. However, non-registered developers can build "unsigned" packages which lack certain features (such as USB and network bridging support).

Unsigned packages

sh
./scripts/package_mac.sh unsigned /path/to/UTM.xcarchive /path/to/output

where /path/to/output is an existing directory.

This builds UTM.dmg in /path/to/output which can be installed to /Applications.

Signed packages

sh
./scripts/package_mac.sh developer-id /path/to/UTM.xcarchive /path/to/output TEAM_ID PROFILE_UUID HELPER_PROFILE_UUID LAUNCHER_PROFILE_UUID

where /path/to/output is an existing directory.

To build a signed package, you need to be a registered Apple Developer. From the developer portal, create a certificate for "Developer ID Application" (and install it into your Keychain). Also create three provisioning profiles with that certificate with Hypervisor entitlements (you need to manually request these entitlements and be approved by Apple) for UTM, QEMUHelper, and QEMULauncher. TEAM_ID should be the same as in the certificate, PROFILE_UUID should be the UUID of the profile installed by Xcode (open the profile in Xcode), and HELPER_PROFILE_UUID is the UUID of a separate profile for the XPC helper. LAUNCHER_PROFILE_UUID is the UUID of a profile for the launcher.

Once properly signed, you can ask Apple to notarize the DMG.

Mac App Store

sh
./scripts/package_mac.sh app-store /path/to/UTM.xcarchive /path/to/output TEAM_ID PROFILE_UUID HELPER_PROFILE_UUID LAUNCHER_PROFILE_UUID

Similar to the above but builds a UTM.pkg for submission to the Mac App Store. You need a certificate for "Apple Distribution" and a certificate for "Mac App Distribution" as well as a provisioning profile with the right entitlements.

Xcode Development

By default, Xcode will build UTM unsigned (lacking USB and bridged networking features).

If you have a registered developer account with access to Hypervisor entitlements, you should create a CodeSigning.xcconfig file with the proper values (see CodeSigning.xcconfig.sample). Make sure to set DEVELOPER_ACCOUNT_VM_ACCESS = YES.

Note that due to a macOS bug, you may get a crash when launching a VM with the debugger attached. The workaround is to start UTM with the debugger detached and attach the debugger with Debug -> Attach to Process after launching a VM. Once you do that, you can start additional VMs without any issues with the debugger.


File: Documentation/Release.md

Release Guide

This document details the release procedure for UTM team members. The release procedure is mostly automated by GitHub Actions. In short, when you submit a new "release" on the GitHub repository, the following happens:

  1. If UTM dependencies are not cached from a previous run, build it for {iOS,macOS,iOS-TCI,iOS Simulator} X {arm64,x86_64}.
  2. Build UTM for all configurations and architectures.
  3. Take the {iOS-arm64,iOS-TCI-arm64} build and package it into a fakesigned IPA and post it as a release asset.
  4. Take the iOS-arm64 build and package it into a Cydia DEB and post it as a release asset.
  5. Update the Cydia repository.
  6. Update the AltStore repository.
  7. Combine dependencies for macOS-arm64 and macOS-x86_64 into universal binaries.
  8. Build UTM as a macOS universal binary.
  9. Take the universal binary and sign and package it into a DMG.
  10. Notarize the DMG with App Store Connect and post it as a release asset.
  11. Take the universal binary and sign and package it for the Mac App Store.
  12. Submit the signed package to App Store Connect.

For more details see the build.yml file.

Making a release

Once you are ready to make a new release:

  1. Modify Build.xcconfig and bump MARKETING_VERSION following semantic versioning and CURRENT_PROJECT_VERSION by 1.
  2. Commit the change with the commit message project: bumped version. Tag the release with git tag vx.y.z corresponding to MARKETING_VERSION.
  3. Push the commit and tag.
  4. In GitHub, draft a new release with the template below. Use the title vx.y.z or vx.y.z (Beta). Select the tag you just created. Check "This is a pre-release" if it is a beta. Check "Create a discussion for this release" and the "Release" category.
  5. When the release is published, the pipeline will run and do everything else.

Release notes template

Make sure to copy all the changes for all beta releases after the last non-beta release.

text
## Installation
Visit [https://getutm.app/install/][1] for the most up to date installation instructions.

## Highlights
* Key features
* Keep these bullets short

## Notes
* List any important changes here
* Include anything that deviates significantly from previously defined behaviour

## Changes (vx.y.1)
* Third change
* (iOS) iOS only change
* (iOS) Another iOS only change
* (macOS) A macOS only change

## Changes (vx.y.0)
* One change
* Another change

## Issues
Please check the full list on [Github][2] and help report any bug you find that is not listed.

### Known Issues

* Optional section. List any known major issues here in bullet points.

[1]: https://getutm.app/install/
[2]: https://github.com/utmapp/UTM/issues

Beta release

Beta releases will not show up as the "latest version" in the GitHub home page. It also will not be posted to AltStore and Cydia and will not be distributed to the App Store (exception: TestFlight).

Re-release

In case of issues in post release that warrants a re-release, follow the same steps but do not change MARKETING_VERSION (CURRENT_PROJECT_VERSION must still be incremented by 1 or App Store Connect rejects the build). The tag should be named vx.y.z-t where t starts at 2 and increments by 1 for every re-release. Then copy-paste the release notes from the previous release and follow the same steps above. Finally, delete the old release if desired.

Actions Details

Secrets

Below is a summary of all the variables and secrets used by GitHub Actions in the release process.

Secret Description
PERSONAL_ACCESS_TOKEN GitHub personal token with permission for repository_dispatch
SIGNING_CERTIFICATE_P12_DATA Base64 encoded PKCS#12 format containing certificates and private keys for signing
SIGNING_CERTIFICATE_PASSWORD Password of the PKCS#12 file
CONNECT_KEY App Store Connect API key for notarizing and submission (base64 encoded .p8)
Variable Description
DISPATCH_ALTSTORE_REPO_NAME username/repo path to a altstore-github repository
DISPATCH_CYDIA_REPO_NAME username/repo path to a silica-package-github repository
SIGNING_TEAM_ID Team ID associated with signing certificates
CONNECT_ISSUER_ID App Store Connect API issuer id
CONNECT_KEY_ID App Store Connect API key id
PROFILE_DATA Base64 encoded provisioning profile of main application
PROFILE_UUID UUID of provisioning profile above
HELPER_PROFILE_DATA Base64 encoded provisioning profile of QEMUHelper
HELPER_PROFILE_UUID UUID of provisioning profile above
LAUNCHER_PROFILE_DATA Base64 encoded provisioning profile of QEMULauncher
LAUNCHER_PROFILE_UUID UUID of provisioning profile above
APP_STORE_PROFILE_DATA Base64 encoded provisioning profile of main application for App Store submission
APP_STORE_PROFILE_UUID UUID of provisioning profile above
APP_STORE_HELPER_PROFILE_DATA Base64 encoded provisioning profile of QEMUHelper for App Store submission
APP_STORE_HELPER_PROFILE_UUID UUID of provisioning profile above
APP_STORE_LAUNCHER_PROFILE_DATA Base64 encoded provisioning profile of QEMULauncher for App Store submission
APP_STORE_LAUNCHER_PROFILE_UUID UUID of provisioning profile above
IOS_REMOTE_PROFILE_DATA Base64 encoded provisioning profile of iOS Remote for App Store submission
IOS_REMOTE_PROFILE_UUID UUID of provisioning profile above
IOS_SE_PROFILE_DATA Base64 encoded provisioning profile of iOS SE for App Store submission
IOS_SE_PROFILE_UUID UUID of provisioning profile above
IS_SELF_HOSTED_RUNNER Set to true to use a self hosted macOS runner set up by the owner

Signing for release

The following certificates (and associated private keys) must be exported from Keychain as a PKCS#12 file (Cmd+click to select multiple and right click to export).

  • Developer ID Application
  • 3rd Party Mac Developer Application (Mac App Store) or Apple Distribution
  • 3rd Party Mac Developer Installer (Mac App Store)

Give a password when prompted and save it to the repository secret SIGNING_CERTIFICATE_PASSWORD. Then, in Terminal, convert the PKCS#12 file to Base64 and copy it: cat Certificates.p12 | base64 | pbcopy and paste it to SIGNING_CERTIFICATE_P12_DATA.

Next you need to get each provisioning profile {3 profiles for macOS} X {1 for Developer ID, 1 for Mac App Store}. Save each UUID of the profile as *_PROFILE_UUID and the Base64 encoded data from cat name.provisionprofile | base64 | pbcopy as *_PROFILE_DATA.

AltStore Repository

The AltStore repository is generated by altstore-github. The repository utmapp/altstore-repo is created which contains its own GitHub Actions that is triggered on a repository_dispatch event. When the event is dispatched by the main repository's release Actions, the other repository will use altstore-github to generate an AltStore compatible JSON repository file from GitHub releases containing the release notes and download links to all the recent releases. The resulting repository file is hosted on GitHub Pages.

Cydia Repository

The Cydia repository is generated by silica-package-github. The repository utmapp/cydia-repo has its own GitHub Actions triggered by a repository_dispatch event sent from the main repository during the release GitHub Actions. It generates the repository index and HTML pages and uses GitHub Pages to host everything.

Debugging release pipeline

Go to the Build workflow and click the "Run workflow" button. Type "true" for "Test release?" and you can test out changes to the release pipeline without making a release. The built assets will be provided as artifacts instead of as release assets.


File: Documentation/TetheredLaunch.md

Tethered Launch

On iOS 14, Apple patched the trick we used to get JIT working. As a result, the next best workaround is significantly more involved. This only applies to non-jailbroken devices. If you are jailbroken, you do not need to do this.

Prerequisites

Signing

Install and follow the instructions for iOS App Signer. Make sure your signing certificate and provisioning profiles matches. Select the UTM.ipa release as the input file and press start.

Save the signed IPA as UTM-signed.ipa. Once the process is completed, rename UTM-signed.ipa to UTM-signed.zip and open the ZIP file. macOS should extract the files to a new directory named Payload/.

Deploying

To deploy UTM, connect your device and run in Terminal:

sh
ios-deploy --bundle /path/to/Payload/UTM.app

(Hint: you can drag Payload/UTM.app into Terminal to auto-fill in the path.)

Launching

You need to run the following each subsequent time you wish to launch UTM. (You cannot launch UTM from the home screen in iOS 14 or it will not work properly!)

sh
ios-deploy --justlaunch --noinstall --bundle /path/to/Payload/UTM.app

(Hint: if you open Xcode and go to Window -> Devices and Simulators and find your device, you can check "Connect via network" in order to deploy/launch without a USB cable. You just need the device unlocked and near your computer.)

Troubleshooting

Trust issue

If you see the message The operation couldn’t be completed. Unable to launch xxx because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user., you need to open Settings -> General -> Device Management, select the developer profile, and press Trust.

Failed to register bundle identifier

Xcode might show this message when trying to create a signing profile. You need to change the Bundle Identifier and try again.


File: Documentation/TetheredLaunch.zh-Hans.md

捆绑启动

在 iOS 14 中,Apple 修补了我们用来让 JIT 工作的“把戏”。因此,下一个最佳变通方案所涉及的范围更广。这一操作只适用于未经越狱(Jailbreak)过的设备。若你已经越狱,就不需要这样做了。

前置条件

签名

安装并按照 iOS App Signer 的说明进行操作。确保你的签名证书和配置文件相匹配。选择 UTM.ipa 版本作为输入的文件,然后点击“开始(Start)”。

将已签名的 IPA 保存为 UTM-signed.ipa,完成操作后将 UTM-signed.ipa 重命名为 UTM-signed.zip,打开 ZIP 文件。macOS 会将文件提取到名为Payload/的新目录中。

部署

若要部署 UTM,请连接你的设备,然后在终端中运行:

sh
ios-deploy --bundle /path/to/Payload/UTM.app

(提示:你可以把 Payload/UTM.app 拖放进终端来自动填充目录。)

启动

当你每次希望启动 UTM 时,都需要运行如下命令。(在 iOS 14 中,不要从主屏幕启动 UTM,否则它将无法正常工作!)

sh
ios-deploy --justlaunch --noinstall --bundle /path/to/Payload/UTM.app

(提示:如果你打开了 Xcode 并转到窗口(Window)> 设备和模拟器(Devices and Simulators)找到你的设备,可以勾选“通过网络连接”,以便在没有 USB 电缆的情况下部署/启动。只需要解锁设备并靠近你的电脑即可。)

疑难解答

信任问题

如果你看到了消息 The operation couldn’t be completed. Unable to launch xxx because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user.(无法完成操作。无法启动 xxx,因为它的代码签名无效,授权不足,或者其配置文件尚未被用户明确信任。),你需要打开设置 > 通用 > 设备管理,选择开发者描述文件,然后选择信任。

注册捆绑包标识符失败(Failed to register bundle identifier)

Xcode 可能在尝试创建签名配置文件时显示此消息,你需要更改绑定标识符并重试。


File: Documentation/TetheredLaunch.zh-Hant.md

不完美啟動

在iOS14中,蘋果修補了我們用來讓JIT工作的“把戲”。 因此,下一個最佳的解決方案所涉及的範圍更廣。 這只適用於非越獄設備。 如果你越獄了,你不需要這樣做。

前置條件

簽名

安裝並按照iOS App Signer的說明進行操作。 請確保您的簽名證書和配置文件匹配。 選擇UTM.ipa正式版作為輸入文件並且按下開始。

將已簽名的IPA保存為UTM-signed.ipa,過程完成後將UTM-signed.ipa重命名為UTM-signed.zip並且打開ZIP文件。 macOS會將文件提取至名為Payload/的新目錄。

部署

要部署UTM,連接你的設備然後在終端中運行:

sh
ios-deploy --bundle /path/to/Payload/UTM.app

(提示:你可以把 Payload/UTM.app 拖放進終端來自動填充目錄。)

啟動

當你每次希望啟動UTM時,都需要運行以下命令。 (你無法在iOS14中從主屏幕正常啟動UTM否則它無法正常運行!)

sh
ios-deploy --justlaunch --noinstall --bundle /path/to/Payload/UTM.app

(提示:如果您打開Xcode並轉到Window->Devices and Simulators並找到您的設備,那麼您可以選中“Connect via network”(通過網絡連接)以便在沒有USB電纜的情況下部署/啟動。你只 需要解鎖設備並靠近你的電腦。)

疑難解答

信任問題

如果你看見了消息:The operation couldn't be completed. Unable to launch xxx because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user.(無法完成操作。無法啟動xxx, 因為它的代碼簽名無效、授權不足或其配置文件未被用戶明確信任。 ),你需要打開設置-> 通用-> 設備管理,選擇開發者描述文件,然後選擇信任。

註冊捆綁標識符失敗

Xcode 可能在嘗試創建簽名配置文件時顯示此消息,您需要更改綁定標識符並重試。