blob: 0f26d6e145f349cd13b3442a9cb2da3e638a1d0c [file] [log] [blame]
-- Copyright Microsoft and CHERIoT Contributors.
-- SPDX-License-Identifier: MIT
-- xmake has started refusing to pass flags that it doesn't recognise, so tell
-- it to stop doing that for now.
set_policy("check.auto_ignore_flags", false)
add_rules("mode.release", "mode.debug")
-- Disallow any modes other than release and debug. The only difference is the
-- value of the `NDEBUG` macro: We always enable debug info and optimise for
-- size in both modes, most things should use the --debug-{option}= flags for
-- finer-grained control.
set_allowedmodes("release", "debug")
set_allowedarchs("cheriot")
-- More work arounds for xmake's buggy flag detection.
if is_mode("release") then
add_defines("NDEBUG", {force = true})
end
option("scheduler-accounting")
set_default(false)
set_description("Track per-thread cycle counts in the scheduler");
set_showmenu(true)
function debugOption(name)
option("debug-" .. name)
set_default(false)
set_description("Enable verbose output and assertions in the " .. name)
set_showmenu(true)
set_category("Debugging")
option_end()
end
debugOption("loader")
debugOption("scheduler")
debugOption("allocator")
debugOption("token_library")
function stackCheckOption(name)
option("stack-usage-check-" .. name)
set_default(false)
set_description("Enable dynamic stack usage checks in " .. name .. ". Do not enable this in debug builds!")
set_showmenu(true)
set_category("Debugging")
option_end()
end
stackCheckOption("allocator")
stackCheckOption("scheduler")
-- Force -Oz irrespective of build config. At -O0, we blow out our stack and
-- require much stronger alignment.
set_optimize("Oz")
--Capture the directory containing this script for later use. We need this to
--find the location of the linker scripts and so on.
local scriptdir = os.scriptdir()
-- The directory where we will find the core components
local coredir = path.join(scriptdir, "core")
-- Set up our llvm configuration.
toolchain("cheriot-clang")
set_kind("standalone")
set_toolset("cc", "clang")
set_toolset("cxx", "clang++")
set_toolset("ld", "ld.lld")
set_toolset("objdump", "llvm-objdump")
set_toolset("strip", "llvm-strip")
set_toolset("as", "clang")
--Set up the flags that we need.
on_load(function (toolchain)
local core_directory = scriptdir
local include_directory = path.join(core_directory, "include")
-- Flags used for C/C++ and assembly
local default_flags = {
"-target",
"riscv32-unknown-unknown",
"-mcpu=cheriot",
"-mabi=cheriot",
"-mxcheri-rvc",
"-mrelax",
"-fshort-wchar",
"-nostdinc",
"-Oz",
"-g",
"-ffunction-sections",
"-fdata-sections",
"-fomit-frame-pointer",
"-fno-builtin",
"-fno-exceptions",
"-fno-asynchronous-unwind-tables",
"-fno-c++-static-destructors",
"-fno-rtti",
"-Werror",
"-I" .. path.join(include_directory, "c++-config"),
"-I" .. path.join(include_directory, "libc++"),
"-I" .. include_directory,
}
-- C/C++ flags
toolchain:add("cxflags", default_flags, {force = true})
toolchain:add("cflags", default_flags)
toolchain:add("cxxflags", "-std=c++20")
toolchain:add("cflags", "-std=c2x", {force = true})
-- Assembly flags
toolchain:add("asflags", default_flags)
end)
toolchain_end()
set_defaultarchs("cheriot")
set_defaultplat("cheriot")
set_languages("c2x", "cxx20")
-- Common rules for any CHERI MCU component (library or compartment)
rule("cheriot.component")
-- Set some default config values for all cheriot components.
on_load(function (target)
-- Treat this as a static library, though we will replace the default linking steps.
target:set("kind", "static")
-- We don't want a lib prefix or equivalent.
target:set("prefixname", "")
end)
before_build(function (target)
if not target:get("cheriot.board_file") then
raise("target " .. target:name() .. " is being built but does not " ..
"appear to be connected to a firmware image. Please either use " ..
"add_deps(\"" .. target:name() .. "\" to add it or use set_default(false) " ..
"prevent it from being built when not linked")
end
end)
-- Custom link step, link this as a compartment, with the linker script
-- that will be provided in the specialisation of this rule.
on_linkcmd(function (target, batchcmds, opt)
-- Get a specified linker script
local linkerscript_name = target:get("cheriot.ldscript")
local linkerscript = path.join(scriptdir, linkerscript_name)
-- Link using the compartment's linker script.
batchcmds:show_progress(opt.progress, "linking " .. target:get("cheriot.type") .. ' ' .. target:filename())
batchcmds:mkdir(target:targetdir())
batchcmds:vrunv(target:tool("ld"), table.join({"--script=" .. linkerscript, "--compartment", "--gc-sections", "--relax", "-o", target:targetfile()}, target:objectfiles()), opt)
-- This depends on all of the object files and the linker script.
batchcmds:add_depfiles(linkerscript)
batchcmds:add_depfiles(target:objectfiles())
end)
-- CHERI MCU libraries are currently built as compartments, without a
-- `-cheri-compartment` flag. They should gain that flag once the compiler
-- supports more than one library.
rule("cheriot.library")
add_deps("cheriot.component")
on_load(function (target)
-- Mark this target as a CHERI MCU library.
target:set("cheriot.type", "library")
-- Libraries have a .library extension
target:set("extension", ".library")
-- Link with the library linker script, which drops .data* sections.
target:set("cheriot.ldscript", "library.ldscript")
end)
-- CHERI MCU compartments have an explicit compartment name passed to the
-- compiler.
rule("cheriot.compartment")
add_deps("cheriot.component")
on_load(function (target)
-- Mark this target as a CHERI MCU compartment.
target:set("cheriot.type", "compartment")
target:set("cheriot.ldscript", "compartment.ldscript")
target:set("extension", ".compartment")
end)
-- Add the compartment name flag. This defaults to the target's name but
-- can be overridden by setting the cheriot.compartment property.
after_load(function (target)
local compartment = target:get("cheriot.compartment") or target:name()
target:add("cxflags", "-cheri-compartment=" .. compartment, {force=true})
end)
-- Privileged compartments are built as compartments, but with a slightly
-- different linker script.
rule("cheriot.privileged-compartment")
add_deps("cheriot.compartment")
on_load(function (target)
target:set("cheriot.ldscript", "privileged-compartment.ldscript")
target:set("cheriot.type", "privileged compartment")
target:add("defines", "CHERIOT_AVOID_CAPRELOCS")
end)
rule("cheriot.privileged-library")
add_deps("cheriot.library")
on_load(function (target)
target:set("cheriot.type", "privileged library")
target:set("cheriot.ldscript", "privileged-compartment.ldscript")
end)
-- Build the switcher as an object file that we can import into the final
-- linker script. The switcher is independent of the firmware image
-- configuration and so can be built as a single target.
target("cheriot.switcher")
set_kind("object")
add_files(path.join(coredir, "switcher/entry.S"))
-- Build the allocator as a privileged compartment. The allocator is
-- independent of the firmware image configuration and so can be built as a
-- single target.
-- TODO: We should provide a mechanism for firmware images to either opt out of
-- having an allocator (or into providing a different allocator for a
-- particular application)
target("cheriot.allocator")
add_rules("cheriot.privileged-compartment", "cheriot.component-debug", "cheriot.component-stack-checks")
add_files(path.join(coredir, "allocator/main.cc"))
add_deps("locks")
add_deps("compartment_helpers")
on_load(function (target)
target:set("cheriot.compartment", "alloc")
target:set('cheriot.debug-name', "allocator")
end)
target("cheriot.token_library")
add_rules("cheriot.privileged-library", "cheriot.component-debug")
add_files(path.join(coredir, "token_library/token_unseal.S"))
on_load(function (target)
target:set('cheriot.debug-name', "token_library")
end)
target("cheriot.software_revoker")
set_default(false)
add_files(path.join(coredir, "software_revoker/revoker.cc"))
add_rules("cheriot.privileged-compartment")
on_load(function (target)
target:set("cheriot.compartment", "software_revoker")
target:set("cheriot.ldscript", "software_revoker.ldscript")
end)
-- Helper to get the board file for a given target
local board_file = function(target)
local boardfile = target:values("board")
if not boardfile then
raise("target " .. target:name() .. " does not define a board name")
end
-- The directory containing the board file.
local boarddir = path.directory(boardfile);
if path.basename(boardfile) == boardfile then
boarddir = path.join(scriptdir, "boards")
boardfile = path.join(boarddir, boardfile .. '.json')
end
return boarddir, boardfile
end
-- Helper to visit all dependencies of a specified target exactly once and call
-- a callback.
local function visit_all_dependencies_of(target, callback)
local visited = {}
local function visit(target)
if not visited[target:name()] then
visited[target:name()] = true
callback(target)
for _, d in table.orderpairs(target:deps()) do
visit(d)
end
end
end
visit(target)
end
-- Rule for defining a firmware image.
rule("firmware")
on_run(function (target)
import("core.base.json")
import("core.project.config")
local boarddir, boardfile = board_file(target)
local board = json.loadfile(boardfile)
if not board.simulator then
raise("board description " .. boardfile .. " does not define a run command")
end
local simulator = board.simulator
simulator = string.gsub(simulator, "${(%w*)}", { sdk=scriptdir, board=boarddir })
local firmware = target:targetfile()
local directory = path.directory(firmware)
firmware = path.filename(firmware)
local run = function(simulator)
os.execv(simulator, { firmware }, { curdir = directory })
end
-- Try executing the simulator from the sdk directory, if it's there.
local tools_directory = config.get("sdk")
local simpath = path.join(tools_directory, simulator)
if os.isexec(simpath) then
run(simpath)
return
end
simpath = path.join(path.join(tools_directory, "bin"), simulator)
if os.isexec(simpath) then
run(simpath)
return
end
-- Otherwise, hope that it's in the path
run(simulator)
end)
-- Set up the thread defines and the information for the linker script.
-- This must be after load so that dependencies are resolved.
after_load(function (target)
import("core.base.json")
local function visit_all_dependencies(callback)
visit_all_dependencies_of(target, callback)
end
local boarddir, boardfile = board_file(target);
local board = json.loadfile(boardfile)
-- Add defines to all dependencies.
local add_defines = function (defines)
visit_all_dependencies(function (target)
target:add('defines', defines)
end)
end
local software_revoker = false
if board.revoker then
local temporal_defines = { "TEMPORAL_SAFETY" }
if board.revoker == "software" then
temporal_defines[#temporal_defines+1] = "SOFTWARE_REVOKER"
software_revoker = true
target:add('deps', "cheriot.software_revoker")
end
add_defines(temporal_defines)
end
-- Check that all dependences have a single board that they're targeting.
visit_all_dependencies(function (target)
local targetBoardFile = target:get("cheriot.board_file")
local targetBoardDir = target:get("cheriot.board_dir")
if not targetBoard then
target:set("cheriot.board_file", boardfile)
target:set("cheriot.board_dir", boarddir)
else
if targetBoardFile ~= boardfile or targetBoardDir ~= boarddir then
raise("target " .. target:name() .. " is used in two or more firmware targets with different boards")
end
end
end)
if board.driver_includes then
for _, include_path in ipairs(board.driver_includes) do
-- Allow ${sdk} to refer to the SDK directory, so that external
-- board includes can include generic platform bits.
include_path = string.gsub(include_path, "${(%w*)}", { sdk=scriptdir })
if not path.is_absolute(include_path) then
include_path = path.join(boarddir, include_path);
end
visit_all_dependencies(function (target)
target:add('includedirs', include_path)
end)
end
end
-- If this board defines any macros, add them to all targets
if board.defines then
add_defines(board.defines)
end
add_defines("CPU_TIMER_HZ=" .. math.floor(board.timer_hz))
add_defines("TICK_RATE_HZ=" .. math.floor(board.tickrate_hz))
if board.simulation then
add_defines("SIMULATION")
end
local loader = target:deps()['cheriot.loader'];
if board.stack_high_water_mark then
add_defines("CONFIG_MSHWM")
else
-- If we don't have the stack high watermark, the trusted stack is smaller.
loader:set('loader_trusted_stack_size', 176)
end
-- Build the MMIO space for the board
local mmio = ""
local mmio_start = 0xffffffff
local mmio_end = 0
-- Add start and end markers for all MMIO devices.
for name, range in table.orderpairs(board.devices) do
local start = range.start
local stop = range["end"]
if not stop then
if not range.length then
raise("Device " .. name .. " does not specify a length or an end)")
end
stop = start + range.length
end
add_defines("DEVICE_EXISTS_" .. name)
mmio_start = math.min(mmio_start, start)
mmio_end = math.max(mmio_end, stop)
mmio = format("%s__export_mem_%s = 0x%x;\n__export_mem_%s_end = 0x%x;\n",
mmio, name, start, name, stop);
end
-- Provide the range of the MMIO space and the heap.
mmio = format("__mmio_region_start = 0x%x;\n%s__mmio_region_end = 0x%x;\n__export_mem_heap_end = 0x%x;\n",
mmio_start, mmio, mmio_end, board.heap["end"])
local code_start = format("0x%x", board.instruction_memory.start)
-- Set the start of memory that can be revoked.
-- By default, this is the start of code memory but it can be
-- explicitly overwritten.
local revokable_memory_start = code_start;
if board.revokable_memory_start then
revokable_memory_start = format("0x%x", board.revokable_memory_start);
end
add_defines("REVOKABLE_MEMORY_START=" .. revokable_memory_start);
local heap_start = '.'
if board.heap.start then
heap_start = format("0x%x", board.heap.start)
end
if board.interrupts then
-- The macro used to provide the interrupt enumeration in the public header
local interruptNames = "CHERIOT_INTERRUPT_NAMES="
-- Define the macro that's used to initialise the scheduler's interrupt configuration.
local interruptConfiguration = "CHERIOT_INTERRUPT_CONFIGURATION="
for _, interrupt in ipairs(board.interrupts) do
interruptNames = interruptNames .. interrupt.name .. "=" .. math.floor(interrupt.number) .. ", "
interruptConfiguration = interruptConfiguration .. "{"
.. math.floor(interrupt.number) .. ","
.. math.floor(interrupt.priority) .. ","
.. (interrupt.edge_triggered and "true" or "false")
.. "},"
end
add_defines(interruptNames)
target:deps()[target:name() .. ".scheduler"]:add('defines', interruptConfiguration)
end
local loader_stack_size = loader:get('loader_stack_size')
local loader_trusted_stack_size = loader:get('loader_trusted_stack_size')
loader:add('defines', "CHERIOT_LOADER_TRUSTED_STACK_SIZE=" .. loader_trusted_stack_size)
-- Get the threads config and prepare the predefined macros that describe them
local threads = target:values("threads")
-- Declare space and start and end symbols for a thread's C stack
local thread_stack_template =
"\n\t. = ALIGN(16);" ..
"\n\t.thread_stack_${thread_id} : CAPALIGN" ..
"\n\t{" ..
"\n\t\t.thread_${thread_id}_stack_start = .;" ..
"\n\t\t. += ${stack_size};" ..
"\n\t\t.thread_${thread_id}_stack_end = .;" ..
"\n\t}\n"
-- Declare space and start and end symbols for a thread's trusted stack
local thread_trusted_stack_template =
"\n\t. = ALIGN(8);" ..
"\n\t.thread_trusted_stack_${thread_id} : CAPALIGN" ..
"\n\t{" ..
"\n\t\t.thread_${thread_id}_trusted_stack_start = .;" ..
"\n\t\t. += ${trusted_stack_size};" ..
"\n\t\t.thread_${thread_id}_trusted_stack_end = .;" ..
"\n\t}\n"
-- Build a `class ThreadConfig` for a thread
local thread_template =
"\n\t\tSHORT(${priority});" ..
"\n\t\tLONG(${mangled_entry_point});" ..
"\n\t\tLONG(.thread_${thread_id}_stack_start);" ..
"\n\t\tSHORT(.thread_${thread_id}_stack_end - .thread_${thread_id}_stack_start);" ..
"\n\t\tLONG(.thread_${thread_id}_trusted_stack_start);" ..
"\n\t\tSHORT(.thread_${thread_id}_trusted_stack_end - .thread_${thread_id}_trusted_stack_start);" ..
"\n\n"
--Pass the declared threads as macros when building the loader and the
--scheduler.
local thread_headers = ""
local thread_trusted_stacks =
"\n\t. = ALIGN(8);" ..
"\n\t.loader_trusted_stack : CAPALIGN" ..
"\n\t{" ..
"\n\t\tbootTStack = .;" ..
"\n\t\t. += " .. loader_trusted_stack_size .. ";" ..
"\n\t}\n"
local thread_stacks =
"\n\t. = ALIGN(16);" ..
"\n\t.loader_stack : CAPALIGN" ..
"\n\t{" ..
"\n\t\tbootStack = .;" ..
"\n\t\t. += " .. loader_stack_size .. ";" ..
"\n\t}\n"
-- Stacks must be less than this size or truncating them in compartment
-- switch may encounter precision errors.
local stack_size_limit = 8176
for i, thread in ipairs(threads) do
thread.mangled_entry_point = string.format("__export_%s__Z%d%sv", thread.compartment, string.len(thread.entry_point), thread.entry_point)
thread.thread_id = i
-- Trusted stack frame is 24 bytes. If this size is too small, the
-- loader will fail. If it is too big, we waste space.
thread.trusted_stack_size = loader_trusted_stack_size + (24 * thread.trusted_stack_frames)
if thread.stack_size > stack_size_limit then
raise("thread " .. i .. " requested a " .. thread.stack_size ..
" stack. Stacks over " .. stack_size_limit ..
" are not yet supported in the compartment switcher.")
end
thread_stacks = thread_stacks .. string.gsub(thread_stack_template, "${([_%w]*)}", thread)
thread_trusted_stacks = thread_trusted_stacks .. string.gsub(thread_trusted_stack_template, "${([_%w]*)}", thread)
thread_headers = thread_headers .. string.gsub(thread_template, "${([_%w]*)}", thread)
end
local add_defines = function(compartment, option_name)
target:deps()[compartment]:add('defines', "CONFIG_THREADS_NUM=" .. #(threads))
end
add_defines(target:name() .. ".scheduler", "scheduler")
-- Next set up the substitutions for the linker scripts.
-- Templates for parts of the linker script that are instantiated per compartment
local compartment_templates = {
compartment_headers =
"\n\t\tLONG(.${compartment}_code_start);" ..
"\n\t\tSHORT((SIZEOF(.${compartment}_code) + 7) / 8);" ..
"\n\t\tSHORT(.${compartment}_imports_end - .${compartment}_code_start);" ..
"\n\t\tLONG(.${compartment}_export_table);" ..
"\n\t\tSHORT(.${compartment}_export_table_end - .${compartment}_export_table);" ..
"\n\t\tLONG(.${compartment}_globals);" ..
"\n\t\tSHORT(SIZEOF(.${compartment}_globals));" ..
"\n\t\tSHORT(.${compartment}_bss_start - .${compartment}_globals);" ..
"\n\t\tLONG(.${compartment}_cap_relocs_start);" ..
"\n\t\tSHORT(.${compartment}_cap_relocs_end - .${compartment}_cap_relocs_start);" ..
"\n\t\tLONG(.${compartment}_sealed_objects_start);" ..
"\n\t\tSHORT(.${compartment}_sealed_objects_end - .${compartment}_sealed_objects_start);\n",
pcc_ld =
"\n\t.${compartment}_code : CAPALIGN" ..
"\n\t{" ..
"\n\t\t.${compartment}_code_start = .;" ..
"\n\t\t${obj}(.compartment_import_table);" ..
"\n\t\t.${compartment}_imports_end = .;" ..
"\n\t\t${obj}(.text);" ..
"\n\t\t${obj}(.init_array);" ..
"\n\t\t${obj}(.rodata);" ..
"\n\t\t. = ALIGN(8);" ..
"\n\t}\n",
gdc_ld =
"\n\t.${compartment}_globals : CAPALIGN" ..
"\n\t{" ..
"\n\t\t.${compartment}_globals = .;" ..
"\n\t\t${obj}(.data);" ..
"\n\t\t.${compartment}_bss_start = .;" ..
"\n\t\t${obj}(.bss)" ..
"\n\t}\n",
compartment_exports =
"\n\t\t.${compartment}_export_table = ALIGN(8);" ..
"\n\t\t${obj}(.compartment_export_table);" ..
"\n\t\t.${compartment}_export_table_end = .;\n",
cap_relocs =
"\n\t\t.${compartment}_cap_relocs_start = .;" ..
"\n\t\t${obj}(__cap_relocs);\n\t\t.${compartment}_cap_relocs_end = .;",
sealed_objects =
"\n\t\t.${compartment}_sealed_objects_start = .;" ..
"\n\t\t${obj}(.sealed_objects);\n\t\t.${compartment}_sealed_objects_end = .;"
}
--Library headers are almost identical to compartment headers, except
--that they don't have any globals.
local library_templates = {
compartment_headers =
"\n\t\tLONG(.${compartment}_code_start);" ..
"\n\t\tSHORT((SIZEOF(.${compartment}_code) + 7) / 8);" ..
"\n\t\tSHORT(.${compartment}_imports_end - .${compartment}_code_start);" ..
"\n\t\tLONG(.${compartment}_export_table);" ..
"\n\t\tSHORT(.${compartment}_export_table_end - .${compartment}_export_table);" ..
"\n\t\tLONG(0);" ..
"\n\t\tSHORT(0);" ..
"\n\t\tSHORT(0);" ..
"\n\t\tLONG(.${compartment}_cap_relocs_start);" ..
"\n\t\tSHORT(.${compartment}_cap_relocs_end - .${compartment}_cap_relocs_start);" ..
"\n\t\tLONG(.${compartment}_sealed_objects_start);" ..
"\n\t\tSHORT(.${compartment}_sealed_objects_end - .${compartment}_sealed_objects_start);\n",
pcc_ld = compartment_templates.pcc_ld,
gdc_ld = "",
library_exports = compartment_templates.compartment_exports,
cap_relocs = compartment_templates.cap_relocs,
sealed_objects = compartment_templates.sealed_objects
}
-- The substitutions that we're going to have in the final linker
-- script. Initialised as empty strings.
local ldscript_substitutions = {
compartment_exports="",
library_exports="",
cap_relocs="",
compartment_headers="",
pcc_ld="",
gdc_ld="",
software_revoker_code="",
software_revoker_globals="",
software_revoker_header="",
sealed_objects="",
mmio=mmio,
code_start=code_start,
heap_start=heap_start,
thread_count=#(threads),
thread_headers=thread_headers,
thread_trusted_stacks=thread_trusted_stacks,
thread_stacks=thread_stacks,
loader_stack_size=loader:get('loader_stack_size'),
loader_trusted_stack_size=loader:get('loader_trusted_stack_size')
}
-- Helper function to add a dependency to the linker script
local add_dependency = function (name, dep, templates)
local obj = path.relative(dep:targetfile(), "$(projdir)")
local obj = dep:targetfile()
-- Helper to substitute the current compartment name and object file into a template.
local substitute = function (str)
return string.gsub(str, "${(%w*)}", { obj=obj, compartment=name })
end
for key, template in table.orderpairs(templates) do
ldscript_substitutions[key] = ldscript_substitutions[key] .. substitute(template)
end
end
-- If this board requires the software revoker, add it as a dependency
-- and add the relevant bits to the linker script.
if software_revoker then
ldscript_substitutions.software_revoker_code =
"\tsoftware_revoker_code : CAPALIGN\n" ..
"\t{\n" ..
"\t\t.software_revoker_start = .;\n" ..
"\t\t.software_revoker_import_end = .;\n" ..
"\t\tsoftware_revoker.compartment(.text .text.* .rodata .rodata.* .data.rel.ro);\n" ..
"\t\t*/cheriot.software_revoker.compartment(.text .text.* .rodata .rodata.* .data.rel.ro);\n" ..
"\t}\n" ..
"\t.software_revoker_end = .;\n\n"
ldscript_substitutions.software_revoker_globals =
"\n\t.software_revoker_globals : CAPALIGN" ..
"\n\t{" ..
"\n\t\t.software_revoker_globals = .;" ..
"\n\t\t*/cheriot.software_revoker.compartment(.data .data.* .sdata .sdata.*);" ..
"\n\t\t.software_revoker_bss_start = .;" ..
"\n\t\t*/cheriot.software_revoker.compartment(.sbss .sbss.* .bss .bss.*)" ..
"\n\t}" ..
"\n\t.software_revoker_globals_end = .;\n"
ldscript_substitutions.compartment_exports =
"\n\t\t.software_revoker_export_table = ALIGN(8);" ..
"\n\t\t*/cheriot.software_revoker.compartment(.compartment_export_table);" ..
"\n\t\t.software_revoker_export_table_end = .;\n" ..
ldscript_substitutions.compartment_exports
ldscript_substitutions.software_revoker_header =
"\n\t\tLONG(.software_revoker_start);" ..
"\n\t\tSHORT(.software_revoker_end - .software_revoker_start);" ..
"\n\t\tLONG(.software_revoker_globals);" ..
"\n\t\tSHORT(SIZEOF(.software_revoker_globals));" ..
-- The software revoker has no import table.
"\n\t\tLONG(0)" ..
"\n\t\tSHORT(0)" ..
"\n\t\tLONG(.software_revoker_export_table);" ..
"\n\t\tSHORT(.software_revoker_export_table_end - .software_revoker_export_table);\n" ..
"\n\t\tLONG(0);" ..
"\n\t\tSHORT(0);\n"
end
-- Process all of the library dependencies.
local library_count = 0
visit_all_dependencies(function (target)
if target:get("cheriot.type") == "library" then
library_count = library_count + 1
add_dependency(target:name(), target, library_templates)
end
end)
-- Process all of the compartment dependencies.
local compartment_count = 0
visit_all_dependencies(function (target)
if target:get("cheriot.type") == "compartment" then
compartment_count = compartment_count + 1
add_dependency(target:name(), target, compartment_templates)
end
end)
local shared_objects = {
-- 32-bit counter for the hazard-pointer epoch.
allocator_epoch = 4,
-- Two hazard pointers per thread.
allocator_hazard_pointers = #(threads) * 8 * 2
}
visit_all_dependencies(function (target)
local globals = target:values("shared_objects")
if globals then
for name, size in pairs(globals) do
if not (name == "__wrap_locked__") then
if shared_objects[global] and (not (shared_objects[global] == size)) then
raise("Global " .. global .. " is declared with different sizes.")
end
shared_objects[name] = size
end
end
end
end)
-- TODO: We should sort pre-shared globals by size to minimise padding.
-- Each global is emitted as a separate section so that we can use
-- CAPALIGN and let the linker insert the required padding.
local shared_objects_template =
"\n\t\t. = ALIGN(MIN(${size}, 8));" ..
"\n\t\t__cheriot_shared_object_section_${global} : CAPALIGN" ..
"\n\t\t{" ..
"\n\t\t\t__cheriot_shared_object_${global} = .;" ..
"\n\t\t\t. += ${size};" ..
"\n\t\t\t__cheriot_shared_object_${global}_end = .;" ..
"\n\t\t}\n"
local shared_objects_section = ""
for global, size in table.orderpairs(shared_objects) do
shared_objects_section = shared_objects_section .. string.gsub(shared_objects_template, "${([_%w]*)}", {global=global, size=size})
end
ldscript_substitutions.shared_objects = shared_objects_section
-- Add the counts of libraries and compartments to the substitution list.
ldscript_substitutions.compartment_count = compartment_count
ldscript_substitutions.library_count = library_count
-- Set the each of the substitutions.
for key, value in pairs(ldscript_substitutions) do
target:set("configvar", key, value)
end
end)
-- Perform the final link step for a firmware image.
on_linkcmd(function (target, batchcmds, opt)
import("core.project.config")
-- Get a specified linker script, or set the default to the compartment
-- linker script.
local linkerscript = path.join(config.buildir(), target:name() .. "-firmware.ldscript")
-- Link using the firmware's linker script.
batchcmds:show_progress(opt.progress, "linking firmware " .. target:targetfile())
batchcmds:mkdir(target:targetdir())
local objects = target:objectfiles()
visit_all_dependencies_of(target, function (dep)
if (dep:get("cheriot.type") == "library") or
(dep:get("cheriot.type") == "compartment") or
(dep:get("cheriot.type") == "privileged compartment") or
(dep:get("cheriot.type") == "privileged library") then
table.insert(objects, dep:targetfile())
end
end)
batchcmds:vrunv(target:tool("ld"), table.join({"-n", "--script=" .. linkerscript, "--relax", "-o", target:targetfile(), "--compartment-report=" .. target:targetfile() .. ".json" }, objects), opt)
batchcmds:show_progress(opt.progress, "Creating firmware report " .. target:targetfile() .. ".json")
batchcmds:show_progress(opt.progress, "Creating firmware dump " .. target:targetfile() .. ".dump")
batchcmds:vexecv(target:tool("objdump"), {"-glxsdrS", "--demangle", target:targetfile()}, table.join(opt, {stdout = target:targetfile() .. ".dump"}))
batchcmds:add_depfiles(linkerscript)
batchcmds:add_depfiles(objects)
end)
-- Rule for conditionally enabling debug for a component.
rule("cheriot.component-debug")
after_load(function (target)
local name = target:get("cheriot.debug-name") or target:name()
target:add('options', "debug-" .. name)
target:add('defines', "DEBUG_" .. name:upper() .. "=" .. tostring(get_config("debug-"..name)))
end)
-- Rule for conditionally enabling stack checks for a component.
rule("cheriot.component-stack-checks")
after_load(function (target)
local name = target:get("cheriot.debug-name") or target:name()
target:add('options', "stack-usage-check-" .. name)
target:add('defines', "CHERIOT_STACK_CHECKS_" .. name:upper() .. "=" .. tostring(get_config("stack-usage-check-"..name)))
end)
-- Build the loader. The firmware rule will set the flags required for
-- this to create threads.
target("cheriot.loader")
add_rules("cheriot.component-debug")
set_kind("object")
-- FIXME: We should be setting this based on a board config file.
add_files(path.join(coredir, "loader/boot.S"), path.join(coredir, "loader/boot.cc"), {force = {cxflags = "-O1"}})
add_defines("CHERIOT_AVOID_CAPRELOCS")
on_load(function (target)
target:set('cheriot.debug-name', "loader")
local config = {
-- Size in bytes of the trusted stack.
loader_trusted_stack_size = 192,
-- Size in bytes of the loader's stack.
loader_stack_size = 1024
}
target:add('defines', "CHERIOT_LOADER_STACK_SIZE=" .. config.loader_stack_size)
target:set('cheriot_loader_config', config)
for k, v in pairs(config) do
target:set(k, v)
end
end)
-- Helper function to define firmware. Used as `target`.
function firmware(name)
-- Build the scheduler. The firmware rule will set the flags required for
-- this to create threads.
target(name .. ".scheduler")
add_rules("cheriot.privileged-compartment", "cheriot.component-debug")
add_deps("locks", "crt", "atomic1")
add_deps("compartment_helpers")
on_load(function (target)
target:set("cheriot.compartment", "sched")
target:set('cheriot.debug-name', "scheduler")
target:add('defines', "SCHEDULER_ACCOUNTING=" .. tostring(get_config("scheduler-accounting")))
end)
add_files(path.join(coredir, "scheduler/main.cc"))
-- Create the firmware target. This target remains open on return and so
-- the caller can add more rules to it.
target(name)
set_kind("binary")
add_rules("firmware")
-- TODO: Make linking the allocator optional.
add_deps(name .. ".scheduler", "cheriot.loader", "cheriot.switcher", "cheriot.allocator")
add_deps("cheriot.token_library")
-- The firmware linker script will be populated based on the set of
-- compartments.
add_configfiles(path.join(scriptdir, "firmware.ldscript.in"), {pattern = "@(.-)@", filename = name .. "-firmware.ldscript"})
end
-- Helper to create a library.
function library(name)
target(name)
add_rules("cheriot.library")
end
-- Helper to create a compartment.
function compartment(name)
target(name)
add_rules("cheriot.compartment")
end
includes("lib/")