Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8e088ef
weird ass linking problems sort of fixed
0aids Jun 8, 2026
295d6d0
backtracing works
0aids Jun 8, 2026
49178b0
fix build for libunwind and child backtracing working
0aids Jun 9, 2026
5f893b0
wip meta.py functions and addr inserting
0aids Jun 10, 2026
d9d5676
wip meta.py for mapping functions of backtracing
0aids Jun 11, 2026
581cd0c
wip got multi-child fault and backtracing partially working
0aids Jun 11, 2026
bec9681
implemented multi-child PD fault backtracing
0aids Jun 11, 2026
66dcec7
fixed incorrect alignment and unnecessary channel ID incrementing
0aids Jun 14, 2026
496f5e9
cleaned up backtrace printing
0aids Jun 15, 2026
ae84197
feat: added backtrace component and works with backtrace example
0aids Jun 15, 2026
f86abeb
fix: backtraces not showing and cleaned up makefiles
0aids Jun 15, 2026
02c6ffb
chore: removed unnecessary files from backtrace_test example
0aids Jun 15, 2026
c64d3e0
feat: separate out backtracer related python functions into component
0aids Jun 15, 2026
ef889b4
fix: hardcoded paths in backtracing python tool and LDFLAGS manipulat…
0aids Jun 15, 2026
6133d9a
chore: some more docs
0aids Jun 16, 2026
3f45c1c
fix: llvm as submodule
0aids Jun 16, 2026
739aa5d
chore: remove llvm-project as a submodule in favour of wget
0aids Jun 17, 2026
23bb77d
backtracer/docs: more information on backtracer usage and deps
0aids Jun 17, 2026
7026586
chore: formatting
0aids Jun 17, 2026
8ed5aad
fix: missing include in monitor
0aids Jun 17, 2026
c21a6bb
chore: clean up makefiles with dependency tracking
0aids Jun 17, 2026
426f71e
chore: clean up python files
0aids Jun 17, 2026
694cfe1
chore: clean up c files and licensing
0aids Jun 17, 2026
31c8773
chore: remove unused vars and better names
0aids Jun 17, 2026
96e4629
chore: add relevant licensing
0aids Jun 17, 2026
c08a4a8
chore: use updated sddf for CXX var in make
0aids Jun 17, 2026
919a5d4
chore: clean up excess headers
0aids Jun 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions components/backtracer/LionsOS_Backtracer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Copyright 2026, UNSW
# SPDX-License-Identifier: BSD-2-Clause
import argparse
from dataclasses import dataclass
from typing import List
from sdfgen import SystemDescription, Sddf, DeviceTree, LionsOs
from importlib.metadata import version
from board import BOARDS
from subprocess import run
from copy import deepcopy

# assert version("sdfgen").split(".")[1] == "28", "Unexpected sdfgen version"

ProtectionDomain = SystemDescription.ProtectionDomain
ProtectionDomain.PRIORITY_MAX = 254

MemoryRegion = SystemDescription.MemoryRegion
Map = SystemDescription.Map
Channel = SystemDescription.Channel


def get_architecture_pointer_alignment(arch: SystemDescription.Arch):
match arch:
case (
SystemDescription.Arch.AARCH64
| SystemDescription.Arch.RISCV64
| SystemDescription.Arch.X86_64
):
return 8
case (
SystemDescription.Arch.AARCH32
| SystemDescription.Arch.RISCV32
| SystemDescription.Arch.X86
):
return 4
case _:
raise Exception(f"Alignment of architecture {arch} is unknown.")


def enable_backtracing(
sdf, arch, array_of_pds_or_single_pd, *, show_backtrace_func_list_addr=0xB00000, backtracer_stack_size=0x10000,
pd_callback_channel_id=61, endianness="little"
):
"""
Wrap an array or single pd as children into a backtracer parent,
capable of catching faults and then forcing prints of the backtrace
Remember to compile each of the children with "backtrace.o"
"""
backtracer = ProtectionDomain(
"backtracer",
"backtracer.elf",
priority=ProtectionDomain.PRIORITY_MAX,
stack_size=backtracer_stack_size,
)

pd_elf_paths = []
if not isinstance(array_of_pds_or_single_pd, list):
array_of_pds_or_single_pd = [array_of_pds_or_single_pd]

for i, child_pd in enumerate(array_of_pds_or_single_pd):
# Also add a channel for allowing a thread to pause completely
newChannel = Channel(
child_pd,
backtracer,
a_id=pd_callback_channel_id,
b_id=i,
pp_a=True,
pd_a_setvar_id="unwind_helper_channel_to_backtracer",
)
sdf.add_channel(newChannel)
backtracer.add_child_pd(child_pd)
pd_elf_paths.append(child_pd.program_image)

# Create a memory region at the predefined address, as an array
# Extract each of the addresses of the children's show_backtrace function
pd_show_backtrace_addrs = []
for elf_path in pd_elf_paths:
# Not very stable but good enough for now.
shell_output = run(
f'set -o pipefail && nm {elf_path} | grep "show_backtrace" | cut --delimiter=" " -f 1',
capture_output=True,
shell=True,
text=True,
)
if shell_output.returncode != 0:
raise Exception(
f"Failed to get addresses of 'show_backtrace' for file {elf_path}\n"
+ f"stderr: {shell_output.stderr}\n"
+ f"stdout: {shell_output.stdout}\n"
+ f"exit code: {shell_output.returncode}\n"
)
show_backtrace_addr = int(shell_output.stdout.strip(), 16)
print(f"'{elf_path}':show_backtrace @ {hex(show_backtrace_addr)}")
pd_show_backtrace_addrs.append(show_backtrace_addr)

# now write a .data file containing the files spaced out by architectures pointer size.
alignment = get_architecture_pointer_alignment(arch)
print(f"Alignment for architecture {arch.name}: {alignment}")
frame = b""
for backtrace_addr in pd_show_backtrace_addrs:
frame += bytes(backtrace_addr.to_bytes(alignment, endianness))
BACKTRACER_FUNCTION_DATA_PATH = "backtrace_functions.data"
with open(BACKTRACER_FUNCTION_DATA_PATH, "wb") as dataFile:
dataFile.write(frame)
func_list_mr = MemoryRegion(
sdf, "backtracerFunctions", prefill_path=BACKTRACER_FUNCTION_DATA_PATH
)
sdf.add_mr(func_list_mr)
func_list_map = Map(
func_list_mr, vaddr=0x20000, perms="r", setvar_vaddr="backtraceFunctions"
)
backtracer.add_map(func_list_map)
return backtracer
30 changes: 30 additions & 0 deletions components/backtracer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!--
Copyright 2024, UNSW
SPDX-License-Identifier: CC-BY-SA-4.0
-->
# Usage
1. Include `backtracer.mk` file.
2. For all targets to be backtraced:
1. Add `libunwind.a` and `unwind_helpers.o` as targets and add them to be compiled with the target.
3. Add `-funwind-tables` to `CFLAGS`
4. Add `--eh-frame-hdr -L{Directory of backtracer}` to `LDFLAGS`
5. Add `-Tunwind.ld -lunwind` to `LIBS`
6. Optionally add `--start-group` and `--end-group` to the beginning and end of `LIBS` respectively.
3. For `meta.py`:
1. Make sure to add the path to this directory in to `PYTHONPATH`
2. Import `LionsOS_Backtracer`
3. For all PDs to be traced, add them to a list and pass them to the `enable_backtracing` function.
This function returns a parent PD containing all of the children given.
The function prototype is `enable_backtracing(sdf, architecture, PD_list) -> PD`

See the `backtrace_test` example for more details.

# Dependencies
- `libc` for `libunwind`
- llvm-project's `libunwind`
- `sddf` or some implementation of `printf`
- patched `sdfgen` with Memory region prefilling capabilities (updated sdfgen)

# Cons
- Larger binary sizes
- Depending on `llvm-project` (sorry)
67 changes: 67 additions & 0 deletions components/backtracer/backtracer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2025, UNSW
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "monitor.h"
#include <microkit.h>
#include <sddf/util/printf.h>

void (**backtraceFunctions)() = NULL;

void init()
{
LOG("Backtracer initialised!\n");
LOG("Backtracer table starting address: %p\n", backtraceFunctions);
}

#if defined(__aarch64__)
static void callConvention_prologue(seL4_UserContext *ctxt, uintptr_t funcAddr)
{
// Set the link register to old PC
ctxt->x30 = ctxt->pc;
// Set the PC to the next function
ctxt->pc = funcAddr;
LOG("Old PC: %p, New PC: %p\n", (void *)ctxt->x30, (void *)funcAddr);
}
#elif defined(__riscv__)
#error "Unimplemented backtracer for riscv"
#elif defined(__x86_64__)
#error "Unimplemented backtracer for x86_64"
#else
#error "Unsupported architecture for backtracing"
#endif

seL4_Bool fault(microkit_child child, microkit_msginfo msginfo, microkit_msginfo *reply_msginfo)
{
LOG("Child '%d' Faulted!\n", child);
print_fault_error(child, msginfo);
seL4_UserContext ctxt = { 0 };
int readRegResult = seL4_TCB_ReadRegisters(BASE_TCB_CAP + child, seL4_True, 0,
sizeof(seL4_UserContext) / sizeof(seL4_Word), &ctxt);
if (readRegResult != 0) {
LOG("Failed to read registers for setting up backtrace jump! Got %d, "
"expected %d\n",
readRegResult, 0);
return seL4_False;
}
print_tcb_registers(&ctxt);
callConvention_prologue(&ctxt, (uintptr_t)(backtraceFunctions[child]));
int writeRegResult = seL4_TCB_WriteRegisters(BASE_TCB_CAP + child, seL4_True, 0,
sizeof(seL4_UserContext) / sizeof(seL4_Word), &ctxt);
if (writeRegResult != 0) {
LOG("Failed to write registers for setting up backtrace jump! Got error value %d, "
"expected %d\n",
writeRegResult, 0);
return seL4_False;
}
return seL4_True;
}

void notified(microkit_channel ch)
{
}
microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo)
{
microkit_pd_stop(ch);
return msginfo;
}
79 changes: 79 additions & 0 deletions components/backtracer/backtracer.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#
# Copyright 2026, UNSW
#
# SPDX-License-Identifier: BSD-2-Clause
#
TAR ?= tar
Comment thread
0aids marked this conversation as resolved.

BACKTRACER_DIR := $(LIONSOS)/components/backtracer
LLVM := llvm-project-22.1.8.src
LLVM_TAR := llvm-project-22.1.8.src.tar.xz
LLVM_URL := https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz
LIBUNWIND := $(LLVM)/libunwind
LIBUNWIND_BUILD_DIR := ./libunwind

CFLAGS_backtracer := \
$(CFLAGS)\
-I$(LIONSOS)/include \
-I$(SDDF)/include \
-I$(SDDF)/include/microkit \
-I$(LIBUNWIND)/include \
-I$(BOARD_DIR)/include \
-I$(LIONS_LIBC)/include -funwind-tables

LDFLAGS_backtracer := -L$(BOARD_DIR)/lib -L$(LIONS_LIBC)/lib
LIBS_backtracer := -lmicrokit -Tmicrokit.ld libsddf_util_debug.a -lc

LLVM_CMAKE_FLAGS := \
-DLLVM_ENABLE_RUNTIMES=libunwind\
-DCMAKE_SYSTEM_NAME=Generic\
-DCMAKE_C_COMPILER_TARGET=$(TARGET)\
-DCMAKE_CXX_COMPILER_TARGET=$(TARGET)\
-DCMAKE_ASM_COMPILER_TARGET=$(TARGET)\
-DLIBUNWIND_IS_BAREMETAL=ON\
-DLIBUNWIND_ENABLE_SHARED=OFF\
-DLIBUNWIND_ENABLE_THREADS=OFF\
-DLIBUNWIND_USE_COMPILER_RT=ON\
-DLIBUNWIND_ENABLE_PEDANTIC=OFF\
-DLIBUNWIND_ENABLE_ASSERTIONS=OFF\
-DCMAKE_BUILD_TYPE=Debug\
-DLIBUNWIND_ENABLE_STATIC=ON\
-DCMAKE_C_COMPILER=$(CC)\
-DCMAKE_CXX_COMPILER=$(CXX)\
-DCMAKE_ASM_COMPILER=$(CC)\
-DCMAKE_C_FLAGS="-I$(LIONS_LIBC)/include"\
-DCMAKE_CXX_FLAGS="-I$(LIONS_LIBC)/include -fno-exceptions"\
-DCMAKE_C_COMPILER_WORKS=ON\
-DCMAKE_CXX_COMPILER_WORKS=ON\
-DCMAKE_ASM_COMPILER_WORKS=ON

backtracer:
mkdir -p $@

unwind_helpers.o: $(BACKTRACER_DIR)/unwind_helpers.c | $(LIONS_LIBC)/include backtracer $(LLVM)
${CC} ${CFLAGS_backtracer} -c -o $@ $<

backtracer/backtracer.o: $(BACKTRACER_DIR)/backtracer.c | $(LIONS_LIBC)/include backtracer
${CC} ${CFLAGS_backtracer} -c -o $@ $<

backtracer.elf: backtracer/backtracer.o libunwind.a | backtracer
${LD} ${LDFLAGS_backtracer} -o $@ $^ ${LIBS_backtracer}

$(LLVM_TAR):
wget $(LLVM_URL)

$(LLVM): $(LLVM_TAR)
${TAR} xvf $< $@/{libunwind,runtimes,cmake,utils,third-party} $@/llvm/{cmake,utils}

$(LIBUNWIND_BUILD_DIR): | $(LLVM) $(LIONS_LIBC)/include
cmake -B $(LIBUNWIND_BUILD_DIR) -S $(LLVM)/runtimes \
$(LLVM_CMAKE_FLAGS)

libunwind.a: | $(LIONS_LIBC)/include $(LIBUNWIND_BUILD_DIR)
${MAKE} -C $(LIBUNWIND_BUILD_DIR)
cp $(LIBUNWIND_BUILD_DIR)/lib/libunwind.a $@

clean::
${RM} -rf backtracer backtracer.elf unwind_helpers.o libunwind.a libunwind

-include unwind_helpers.d backtrace/backtracer.d
Loading
Loading