diff --git a/server-guides/Sources/ServerGuides.docc/Documentation.md b/server-guides/Sources/ServerGuides.docc/Documentation.md index 2b997d7e2..c20108e42 100644 --- a/server-guides/Sources/ServerGuides.docc/Documentation.md +++ b/server-guides/Sources/ServerGuides.docc/Documentation.md @@ -6,6 +6,13 @@ ## Topics +### Guides + - - - +- + +## See Also + +- [Backtracing reference (swiftlang/swift)](https://github.com/swiftlang/swift/blob/main/docs/Backtracing.rst) diff --git a/server-guides/Sources/ServerGuides.docc/building.md b/server-guides/Sources/ServerGuides.docc/building.md index a4ebdd4a1..b30dbb653 100644 --- a/server-guides/Sources/ServerGuides.docc/building.md +++ b/server-guides/Sources/ServerGuides.docc/building.md @@ -1,15 +1,17 @@ -# Building Swift Server Applications +# Building Swift server applications Assemble your server applications using Swift Package Manager. -Use [Swift Package Manager](/documentation/package-manager/) to build server applications. +## Overview + +Use [Swift Package Manager](https://www.swift.org/documentation/package-manager/) to build server applications. It provides a cross-platform foundation for building Swift code. -You can build using the command line or through an integrated development environment (IDE) such as Xcode or Visual Studio Code. +You can build using the command line or through an integrated development environment such as Xcode or Visual Studio Code. ## Choose a build configuration Swift Package Manager supports two distinct build configurations, each optimized for different stages of your development workflow. -The configurations are `debug`, frequently used during development, and `release`, which you use when profiling or creating production artifacts. +The configurations are `debug`, which you use during active development, and `release`, which you use when profiling or creating production artifacts. ### Use debug builds during development @@ -21,7 +23,7 @@ swift build Debug builds include full debugging symbols and runtime safety checks, which are essential during active development. The compiler skips most optimizations to keep compilation times fast and preserve maximum debugging information. -This lets you quickly test changes and debug your app using lldb and breakpoints. +This lets you quickly test changes and debug your app using `lldb` and breakpoints. However, skipping optimizations can come at a significant cost to runtime performance. Debug builds typically run more slowly than their release counterparts. @@ -70,23 +72,76 @@ For code that frequently calls small functions across module boundaries, this ca However, results vary by project because optimizations are specific to your code. Always benchmark your specific workload with and without this flag before deploying to production. -## Review your build artifacts +### Choose static or dynamic linking for the standard library -After compiling, locate your build artifacts. -Swift Package Manager places them in directories that vary by platform and architecture: +By default, Swift build artifacts link the standard library dynamically. +This keeps individual build artifacts smaller, and multiple programs can share a single copy of the Swift runtime. +However, dynamic linking requires the Swift runtime to be installed on your deployment target. + +For deployment scenarios where you want more self-contained build artifacts, statically link the Swift standard library: ```bash -# Show where debug build artifacts are located -swift build --show-bin-path +swift build -c release --static-swift-stdlib +``` -# Show where release build artifacts are located -swift build --show-bin-path -c release +The resulting build artifacts still dynamically link to `glibc`, but have fewer other dependencies on the target system. +These executables bundle the Swift runtime directly: + +| Aspect | Dynamic linking | Static linking | +|--------|----------------|----------------| +| Build artifact size | Smaller (runtime not included) | Larger (runtime included in binary) | +| Deployment complexity | Requires Swift runtime on target system | Self-contained, no runtime needed | +| Version management | Must match runtime version on system | Each artifact includes its own runtime version | + +For deploying to VMs or bare metal where you don't control the system configuration, static linking removes the dependency on a pre-installed Swift runtime. + +> Note: This technique doesn't apply to Apple platforms. + +## Preserve debug information for symbolication + +Release builds optimize for performance and don't embed DWARF debug information by default. +Without DWARF, a crashing binary's stack trace reports raw addresses instead of function names, files, and line numbers. +You have two ways to keep symbolication working: embed the debug information in the binary, or split it into a sidecar file you ship separately. + +### Split debug information into a sidecar file + +For a smaller deployable artifact, separate the debug information into its own file and strip the original binary. +This is the layout Linux distributions use for `-debuginfo` and `-dbgsym` packages. + +```bash +cd .build/release + +# Copy debug sections into a sidecar file. +objcopy --only-keep-debug MyServer MyServer.debug + +# Remove debug information from the deployed binary. +objcopy --strip-debug --strip-unneeded MyServer + +# Record a link from the stripped binary to its sidecar. +objcopy --add-gnu-debuglink=MyServer.debug MyServer ``` -The build products are written to the scratch path, which defaults to `.build`, but the specific location can vary based on platform or Swift compiler. -Use the `--show-bin-path` flag in deployment scripts to locate the build product without hardcoding platform-specific paths. +You now have two artifacts: a small, stripped `MyServer` to ship in your container or distribution package, and a `MyServer.debug` to publish to a symbol server or ship in a companion debug-info package. +For how symbolicators consume the sidecar at crash time, see . + +For additional examples of this kind of process, review the [backtracing scripts](https://github.com/swiftlang/swift/blob/main/test/Backtracing/Inputs/) in the Swift GitHub repository. + +### Verify build IDs match + +A sidecar is only useful if it shares a build ID with the binary it describes. +The linker writes the build ID into a `.note.gnu.build-id` section that survives stripping, and symbolicators use it to pair the two files. + +Read the build ID from each file: -### Build services for other platforms +```bash +readelf -n MyServer | grep 'Build ID' +readelf -n MyServer.debug | grep 'Build ID' +``` + +Both commands print the same hexadecimal value when the files match. +A mismatch means the binary and sidecar came from different builds; in that case, the sidecar can't symbolicate the binary, and you need to rebuild both together. + +## Build for another platform Swift build artifacts are both platform- and architecture-specific. Artifacts you create on macOS run only on macOS; those you create on Linux run only on Linux. @@ -95,15 +150,15 @@ This creates a challenge when you work on macOS and deploy to Linux servers. You can use Xcode for development, but it can't produce Linux artifacts for deployment. Swift provides two main approaches for cross-platform building. -#### Build with Linux containers +### Build with Linux containers -On macOS, you can use [Container](https://github.com/apple/container) or the Docker CLI to verify your project builds under Linux. +On macOS, you can use [Apple's container tool](https://github.com/apple/container) or the Docker CLI to verify your project builds under Linux. Apple publishes official Swift Docker images to [Docker Hub](https://hub.docker.com/_/swift), which provide complete Linux build environments. To build your application using the latest Swift release image: ```bash -# Build with Container +# Build with container container run -c 2 -m 8g --rm -it \ -v "$PWD:/code" -w /code \ swift:latest swift build @@ -115,12 +170,12 @@ docker run --rm -it \ ``` These commands mount your current directory as `/code` in the container, set it as the working directory, and run `swift build` inside the Linux environment. -The `swift:latest` container image provides this environment and `swift build` produces Linux-compatible build artifacts. +The `swift:latest` container image provides this environment, and `swift build` produces Linux-compatible build artifacts. -If you're on Apple silicon and need to target x86_64 Linux servers, you need to specify the target platform with the `--platform` option: +If you're on Apple silicon and need to target `x86_64` Linux servers, you need to specify the target platform with the `--platform` option: ```bash -# Build with Container +# Build with container container run -c 2 -m 8g --rm -it \ -v "$PWD:/code" -w /code \ --platform linux/amd64 \ @@ -140,39 +195,20 @@ The `-e QEMU_CPU=max` environment variable enables the maximum set of CPU featur To build your code into a container, you typically use a container declaration — a Dockerfile or Containerfile — that specifies all the steps to assemble the container image holding your build artifacts. Container-based builds work well in CI/CD pipelines and for validating that your code builds cleanly on Linux. -However, Docker container builds can be slower than native builds, especially on Apple silicon where x86_64 containers run through emulation. - -For more information on packaging your application or service, read [Packaging Swift Server Applications](./packaging.md). - -#### Choose static or dynamic linking for the standard library - -By default, Swift build artifacts link the standard library dynamically. -This keeps individual build artifacts smaller, and multiple programs can share a single copy of the Swift runtime. -However, dynamic linking requires the Swift runtime to be installed on your deployment target. +However, Docker container builds can be slower than native builds, especially on Apple silicon, where `x86_64` containers run through emulation. -For deployment scenarios where you want more self-contained build artifacts, statically link the Swift standard library: - -```bash -swift build -c release --static-swift-stdlib -``` - -The resulting build artifacts still dynamically link to glibc, but have fewer other dependencies on the target system. -These executables bundle the Swift runtime directly: - -| Aspect | Dynamic linking | Static linking | -|--------|----------------|----------------| -| Build artifact size | Smaller (runtime not included) | Larger (runtime included in binary) | -| Deployment complexity | Requires Swift runtime on target system | Self-contained, no runtime needed | -| Version management | Must match runtime version on system | Each artifact includes its own runtime version | +For more information on packaging your application or service, see . -For deploying to VMs or bare metal where you don't control the system configuration, static linking removes the dependency on a pre-installed Swift runtime. +### Build with a VS Code Dev Container -> Note: This technique doesn't apply on Apple platforms. +Visual Studio Code supports the Dev Container feature, which lets you open, build, and debug your project within a container running on your local machine. +The Dev Container builds your code in the Linux environment that the container provides. +For more information on using a Dev Container, read [Visual Studio Code Dev Containers](https://docs.swift.org/vscode/documentation/userdocs/remote-dev/). -#### Cross-compile with the Static Linux SDK +### Cross-compile with the Static Linux SDK If the performance overhead of Docker-based builds affects your workflow, Swift 5.9 and later provide Static Linux SDKs. -Find the link to install the static Linux SDK on the [install page at Swift.org](https://www.swift.org/install/), and detailed instructions in the article [Getting Started with the Static Linux SDK](https://www.swift.org/documentation/articles/static-linux-getting-started.html). +Find the link to install the static Linux SDK on the [install page at Swift.org](https://www.swift.org/install/); for detailed instructions, see [Getting Started with the Static Linux SDK](https://www.swift.org/documentation/articles/static-linux-getting-started.html). The SDK enables cross-compilation directly from macOS to Linux without using a container: ```bash @@ -195,16 +231,23 @@ your code can't use `dlopen` or similar mechanisms to dynamically load libraries For most projects, this distinction doesn't matter. However, packages with complex C dependencies can behave differently when built natively on Linux versus cross-compiled. +## Locate your build artifacts -### Build with VS Code using a Dev Container +After compiling, locate your build artifacts. +Swift Package Manager places them in directories that vary by platform and architecture: -Visual Studio Code supports the Dev Container feature that lets you open, build, and debug your project within a container running on your local machine. -The Dev Container builds your code in the Linux environment that the container provides. -For more information on using a Dev Container, read [Visual Studio Code Dev Containers](https://docs.swift.org/vscode/documentation/userdocs/remote-dev/). +```bash +# Show where debug build artifacts are located +swift build --show-bin-path -### Inspect a binary +# Show where release build artifacts are located +swift build --show-bin-path -c release +``` + +Swift Package Manager writes the build products to the scratch path, which defaults to `.build`, but the specific location can vary by platform or Swift compiler version. +Use the `--show-bin-path` flag in deployment scripts to locate the build product without hardcoding platform-specific paths. -If you're uncertain what platform a binary was built for, use the `file` command to inspect it: +If you're uncertain which platform a binary targets, use the `file` command to inspect it: ``` file .build/debug/MyServer @@ -226,7 +269,7 @@ Output from a debug build on Linux, built inside a container on Apple silicon: with debug_info, not stripped ``` -Output from a debug build on macOS, built using the Container tool with x86_64 emulation: +Output from a debug build on macOS, built using the container tool with `x86_64` emulation: ``` .build/debug/MyServer: ELF 64-bit LSB pie executable, diff --git a/server-guides/Sources/ServerGuides.docc/debugging-a-service-using-a-backtrace.md b/server-guides/Sources/ServerGuides.docc/debugging-a-service-using-a-backtrace.md new file mode 100644 index 000000000..62189c398 --- /dev/null +++ b/server-guides/Sources/ServerGuides.docc/debugging-a-service-using-a-backtrace.md @@ -0,0 +1,214 @@ +# Debugging a service using a backtrace + +Interpret a Swift service's crash trace to find the failing code on Linux. + +## Overview + +When your Swift service crashes on Linux, the runtime spawns a helper process, +`swift-backtrace`, that prints a stack trace that describes what was running and where it failed. +This article covers how to persist a trace from a container, read its structure, +map frames back to your source, recognize common crash patterns, and reproduce the crash locally. + +For the configuration options the trace responds to, see the [Swift backtracing reference](https://github.com/swiftlang/swift/blob/main/docs/Backtracing.rst#how-do-i-configure-backtracing). +To install the backtracer in a container image, +see . + +### Persist backtraces from a container + +When a container exits, its filesystem is gone, +so a crash that writes to a file inside the container loses the trace. +For durable, addressable crash files, write the trace to a volume that you mount when you run the container. For example, if you mounted a volume at `/var/crash-logs`, use the configuration: + +```Dockerfile +ENV SWIFT_BACKTRACE=enable=yes,interactive=no,symbolicate=off,output-to=/var/crash-logs/ +``` + +With `docker run` or Apple container, use the `-v` option to mount a volume; +with Kubernetes, mount a `PersistentVolume` or an `emptyDir`-backed volume at that path. + +When `output-to` resolves to a directory, the backtracer writes each crash +to a uniquely named file inside it, so a burst of crashes won't overwrite +earlier traces. +The `symbolicate=off` setting keeps crash handling fast in production; +see for how to resolve symbols afterward. + +For the format of the captured file (text or JSON) and other output options, +see the [Swift backtracing reference](https://github.com/swiftlang/swift/blob/main/docs/Backtracing.rst#how-do-i-configure-backtracing). + +### Read the trace structure + +A captured trace has four parts: a header naming the signal, +a thread block for each thread the backtracer captures, +a frame list inside each thread, and an optional images list. +Here's a representative trace, abbreviated: + +``` +*** Program crashed: Bad pointer dereference at 0x0000000000000010 *** + +Platform: Linux x86_64 + +Thread 0 "myservice" crashed: + +0 0x000055a9c4001234 MyService.handleRequest(_:) + 132 in myservice at Handler.swift:84 +1 0x000055a9c4002345 closure #1 in MyService.run() + 66 in myservice at Service.swift:42 +2 0x000055a9c4003456 MyService.run() + 280 in myservice at Service.swift:28 +3 0x00007f1234567890 swift_runJob + 48 in libswift_Concurrency.so +``` + +The header line names the **signal** and the **fault address**. +On Linux the common signals are `SIGSEGV` (bad memory access), +`SIGILL` and `SIGTRAP` (Swift runtime traps — see the table below), +`SIGABRT` (a deliberate abort, raised by `abort()` and by force-cast (`as!`) failures), +`SIGBUS` (misaligned access or access past the end of a memory-mapped file), +and `SIGFPE` (arithmetic exception, including integer division by zero). + +Swift runtime traps (force-unwrap of `nil`, index-out-of-range, `try!` on a throwing call, +arithmetic overflow, `precondition`, `fatalError`) lower to a CPU trap instruction. +The kernel reports this as `SIGILL` on x86_64 (where the trap lowers to `ud2`) +and as `SIGTRAP` on aarch64 (where it lowers to `brk #1`). + +The **thread line** identifies which thread crashed. +The backtracer captures only the crashed thread by default. +Use `SWIFT_BACKTRACE=threads=all` to see every thread which helps when diagnosing deadlocks. +For more information, see the [Swift backtracing reference](https://github.com/swiftlang/swift/blob/main/docs/Backtracing.rst#how-do-i-configure-backtracing). + +Each **frame line** has the form: + +``` +
+ in at : +``` + +> Note: In the plaintext format, Swift backtracing may also include the source code in the output if it's accessible when we're generating the crash report. + +The backtracer demangles the symbol by default. +The `at :` portion appears only when the binary carries DWARF debug information +and you run with `symbolicate=full` (the default). +Stripped production binaries may produce frames with the address and image name only. +Swift backtracing tryies to find the closest visible symbol. + +By default the trace includes registers and a list of loaded images for the crashed thread. +You can tune or suppress both; see the [Swift backtracing reference](https://github.com/swiftlang/swift/blob/main/docs/Backtracing.rst#how-do-i-configure-backtracing). + +### Map a frame to source + +Symbolication — turning addresses into function names, files, and lines — has a real cost. +The default, `symbolicate=full`, walks DWARF for every captured frame, +which extends crash-handling time noticeably on a large binary. +Production services typically capture lightweight traces at crash time +and resolve symbols offline. + +**At crash time, in production.** + +Build production binaries with debug info stripped and run with `SWIFT_BACKTRACE=symbolicate=off`. +Each captured frame has its address and the image (binary or shared library) it belongs to — +enough to resolve to source later. +See the [Swift backtracing reference](https://github.com/swiftlang/swift/blob/main/docs/Backtracing.rst#how-do-i-configure-backtracing) for the full set of `symbolicate` values +and the trade-offs between `off`, `fast`, and `full`. + +**Post-mortem, on a developer or build machine.** + +Keep the unstripped binary, or a separate DWARF debug-info package, that came from the same build as the trace. +With that artifact in hand: + +```bash +# Resolve an address to a file:line and (mangled) symbol name. +llvm-symbolizer --obj=path/to/unstripped/myservice 0x000055a9c4001234 + +# Demangle the resulting Swift symbol names if needed. +swift demangle "$1MyService13handleRequestyAA8ResponseVAA7RequestVF" +``` + +**With a separate debug-info file.** + +Ship debug information as a sidecar file rather than embedding it in the binary +(see ). +Place the sidecar where symbolicators look for it: first at the path that +`objcopy --add-gnu-debuglink` records (resolved relative to the binary, +then under `/usr/lib/debug//`), +then at the build-ID index at `/usr/lib/debug/.build-id//.debug`, +where the hex digits come from the binary's build ID. +The `-debuginfo` and `-dbgsym` packages populate the build-ID index when you install them, +so a developer machine with the matching debug-info package installed resolves symbols transparently: + +``` +/usr/bin/MyServer # stripped, build ID ab12cd34... +/usr/lib/debug/.build-id/ab/12cd34....debug # sidecar +``` + +With the sidecar in place, the same `llvm-symbolizer` invocation resolves to file and line: + +```bash +llvm-symbolizer --obj=/usr/bin/MyServer 0x000055a9c4001234 +``` + +`llvm-symbolizer` reads the build ID from the stripped binary, finds the matching sidecar on the index path, and pulls source locations from its DWARF. +If the build IDs don't match, the symbolicator falls back to address-only output; +confirm the pairing with `readelf -n` against both files (see ). + +Time isn't critical at this stage, so spending CPU on full demangling and inline-frame resolution is fine. +Tools like `addr2line` from Linux binutils work as well; pick whichever your build environment already has. + +### Recognize the crash pattern + +The signal and the message that the runtime prints just before the trace are usually enough +to identify the kind of bug. + +| Signal and message | Typical cause | +|---|---| +| **trap** + `Fatal error: Index out of range` | Out-of-bounds collection access | +| **trap** + `Fatal error: Unexpectedly found nil while unwrapping an Optional value` | Force-unwrap (`!`) of a `nil` optional | +| **trap** + `Fatal error: 'try!' expression unexpectedly raised an error` | `try!` on a call that threw | +| **trap** + `Fatal error: Arithmetic operation ... overflow` | Trapping integer arithmetic | +| **trap** + `Precondition failed: ` or `Fatal error: ` | `precondition()` or `fatalError()` call | +| `SIGABRT` + `Could not cast value of type ...` | Force-cast (`as!`) failure | +| `SIGABRT` | Direct `abort()` call from Swift or a C dependency | +| `SIGSEGV` with frames in your unsafe-pointer or C-interop code | Memory-safety violation in that code | +| `SIGSEGV` with frames only in libc or runtime | Likely heap corruption from earlier code | +| `SIGSEGV` with a deeply uniform recursive frame pattern | Stack overflow | + +For runtime-trap crashes (the **trap** rows in the previous table), the relevant fix is almost always +in the topmost frame of *your* code in the trace — +the standard library trap is a few frames up from there. +For `SIGSEGV` in unsafe code or dependencies, the topmost frame names the call that touched bad memory, +but the actual cause is often earlier code that produces a bad pointer or frees memory still in use. + +### Reproduce locally + +Once you've identified the suspect call site, capture the input that triggered it — +request body, environment variables, mounted file, and configured client state — +and rerun the service locally. +Two paths are useful: + +- **Run under `lldb`.** + Launch the service with `lldb` and let it stop at the crash for live inspection of variables and threads. +- **Rerun with the interactive backtracer.** + Set `SWIFT_BACKTRACE=interactive=yes` so the backtracer drops into a debugger-like prompt at crash time + instead of printing and exiting. + See the [Swift backtracing reference](https://github.com/swiftlang/swift/blob/main/docs/Backtracing.rst#how-do-i-configure-backtracing) for the option. + Interactive mode requires a TTY, so it's a developer-machine tool, not a production setting. + +### Diagnose missing backtraces + +If a crash produced no trace at all, or one that ends after a few frames, +the cause is almost always one of the following: + +- term The helper binary isn't in the runtime image: + Slim and distroless base images don't ship `swift-backtrace`. + Copy `swift-backtrace-static` into the image — see . + +- term The runtime can't find the helper: + The runtime searches a fixed set of locations relative to a Swift root + directory; it doesn't consult `PATH`. + In a statically linked binary that doesn't follow the toolchain layout, + set `SWIFT_BACKTRACE=swift-backtrace=` or `SWIFT_ROOT=`. + See the [Swift backtracing reference](https://github.com/swiftlang/swift/blob/main/docs/Backtracing.rst#how-do-i-configure-backtracing) for the search order. + +- term `/proc` isn't mounted: + The backtracer enumerates threads and locates loaded images through `/proc//`. + Containers running with a stripped-down filesystem don't always have it mounted; re-enable `/proc`. + +- term Frame pointers are missing: + The fast unwinder follows frame pointers, so a binary built with `-fomit-frame-pointer` + (or a C/C++ dependency built that way) produces traces that stop at the first frame without one. + Current Swift toolchains preserve frame pointers in release builds on Linux server architectures. + Rebuild affected dependencies with `-fno-omit-frame-pointer`. diff --git a/server-guides/Sources/ServerGuides.docc/deploying-static-binaries.md b/server-guides/Sources/ServerGuides.docc/deploying-static-binaries.md index 58dd83235..52c2a3a97 100644 --- a/server-guides/Sources/ServerGuides.docc/deploying-static-binaries.md +++ b/server-guides/Sources/ServerGuides.docc/deploying-static-binaries.md @@ -120,6 +120,33 @@ container build -t :latest . > If you need a shell for troubleshooting, > use a slim base image during development and switch to `scratch` for production. +### Include swift-backtrace for crash diagnostics + +A static binary in a `scratch` container has no toolchain layout for the runtime to fall back on. +As a result, the runtime's default path to find `swift-backtrace` doesn't exist in the image. +To get backtraces from crashes, copy the static helper from a Swift container image +into your final image and explicitly pass its path to the runtime: + +```Dockerfile +FROM swift:6.2-noble AS toolchain + +FROM scratch +COPY --from=toolchain /usr/libexec/swift/linux/swift-backtrace-static /swift-backtrace +COPY .build/release/ / + +ENV SWIFT_BACKTRACE=enable=yes,interactive=no,symbolicate=off,swift-backtrace=/swift-backtrace + +EXPOSE 8080 +ENTRYPOINT ["/"] +``` + +The `swift-backtrace=/swift-backtrace` setting overrides the runtime's default search +and points it to the correct location. +The `symbolicate=off` setting keeps crash handling fast; you resolve addresses against the original binary later. + +For the full set of `SWIFT_BACKTRACE` options, see the [Swift backtracing reference](https://github.com/swiftlang/swift/blob/main/docs/Backtracing.rst#how-do-i-configure-backtracing). +To debug your service using a captured trace, see . + ### Build and publish with Swift Container Plugin The [Swift Container Plugin](https://github.com/apple/swift-container-plugin) diff --git a/server-guides/Sources/ServerGuides.docc/packaging.md b/server-guides/Sources/ServerGuides.docc/packaging.md index de223ff27..2bfd67799 100644 --- a/server-guides/Sources/ServerGuides.docc/packaging.md +++ b/server-guides/Sources/ServerGuides.docc/packaging.md @@ -117,7 +117,7 @@ CMD ["/"] The `--static-swift-stdlib` flag links the Swift standard library into your executable, so the final image doesn't need the Swift runtime installed. -If your service uses `FoundationNetworking` or `FoundationXML`, use `swift:6.2-noble-slim` instead of `ubuntu:noble` for the runtime image. +If your service uses `FoundationNetworking` or `FoundationXML`, use `swift:6.2-noble-slim` instead of `ubuntu:noble` for a smaller runtime image. This image includes system libraries that those frameworks require: `libcurl` and `libxml2`. Build and run the image the same way: @@ -131,6 +131,37 @@ container build -t :latest . > Without it, `COPY . /workspace` sends everything to the build daemon, > which slows builds and can bloat image layers. +### Include the backtracer in the runtime image + +When a Swift service crashes, the runtime invokes a helper binary, `swift-backtrace`, +to capture a stack trace and write it to stderr. +Slim runtime images don't include this helper, +so crashes terminate silently without a trace. +The toolchain ships a statically linked backtracer in the builder image +that you can copy into the runtime stage as is: + +```Dockerfile +COPY --from=builder /usr/libexec/swift/linux/swift-backtrace-static \ + /usr/bin/swift-backtrace +``` + +The runtime looks for a binary named `swift-backtrace`, so the COPY renames the static helper as it copies it into `/usr/bin`. + +For containers without a TTY, set `SWIFT_BACKTRACE` so the backtracer +runs non-interactively and writes machine-readable output: + +```Dockerfile +ENV SWIFT_BACKTRACE=enable=yes,interactive=no +``` + +Current Swift toolchains preserve frame pointers in release builds on Linux server architectures, +so typical builds don't need extra flags. +The fast unwinder relies on frame pointers; without them, the runtime falls back to DWARF-based unwinding, +which is slower but still produces complete traces when `.eh_frame` information is present. + +For the full set of `SWIFT_BACKTRACE` options, see the [Swift backtracing reference](https://github.com/swiftlang/swift/blob/main/docs/Backtracing.rst#how-do-i-configure-backtracing). +To debug your service using a captured trace, see . + ### Cache build artifacts to speed up rebuilds The Dockerfiles above copy all source files into the image and build from scratch every time. @@ -193,7 +224,7 @@ before the second stage picks it up. ### Build for a different platform If your development machine and deployment target use different CPU architectures — -for example, building on Apple Silicon (arm64) and deploying to x86_64 cloud infrastructure — +for example, building on Apple silicon (arm64) and deploying to x86_64 cloud infrastructure — you need to specify the target platform when building the image. With `docker`, use the `--platform` flag: