blob: 9aaaa0a414a900083c01a5923133ac8361b6e282 [file] [log] [blame] [edit]
# Copyright 2020 The Fuchsia Authors
#
# Use of this source code is governed by a MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT
import("//build/dist/distribution_manifest.gni")
import("//build/zircon/c_utils.gni")
import("//build/zircon/hermetic_code_blob.gni")
import("//build/zircon/zircon_cpu.gni")
## This file provides the templates needed to extract code-patching directives
## stored in a special ELF section, and needed to specify code patching
## alternatives and the functions to be later patched by those alternatives.
##
## Example usage:
## ```
## # Two possible patches for memset.
## code_patching_hermetic_alternative("memset_slow") {
## sources = ["memset_slow.c"]
## }
##
## code_patching_hermetic_alternative("memset_fast") {
## sources = ["memset_fast.S"]
## }
##
## # Generates an object file that defines a stub `memset` - requiring later
## # patching - filled with traps and just large enough to fit any of the
## # associated alternatives.
## code_patching_hermetic_stub("memset") {
## # Aggregates metadata on alternatives.
## deps = [":memset_slow", ":memset_fast"]
## }
## ```
##
# Extracts the content of the ".code-patches" sections from the linking
# target reached by in $deps.
#
# There is no particular output file this target produces as its API.
# Instead, having this target in $deps of a kernel_package() ensures the
# file "code-patches.bin" appears in the package file tree.
#
# Parameters
#
# * deps
# - Required: Must reach exactly one linking target as per metadata
# barriers used by link_output_rspfile(), which see. The code
# consuming the "code-patches.bin" file from the kernel package is
# expected to know what single ELF file the address constants in that
# file refer to, so it doesn't make sense to have more than one
# linkable target that might have ".code-patches" section data to
# extract.
# - Type: list(label)
#
# * data_deps
# - Optional: This can be used to reach other targets whose metadata
# should travel along with the output of this code_patches() target,
# such as code_patching_hermetic_alternative() targets. Any
# `distribution_entries` metadata (from resource() et al) in $deps
# is blocked from being reached via this target, but $data_deps is not.
# - Type: list(label)
#
# * metadata, testonly, visibility
# - See action().
#
template("code_patches") {
assert(defined(invoker.deps), "`deps` is a required parameter")
main_target = target_name
output_file = "$target_out_dir/$target_name.bin"
rspfile_target = "_code_patches.rsp.${target_name}"
rspfile = "${target_gen_dir}/${target_name}.code_patches.rsp"
link_output_rspfile(rspfile_target) {
visibility = [ ":$main_target" ]
outputs = [ rspfile ]
forward_variables_from(invoker,
[
"deps",
"testonly",
])
}
toolchain_utils_action(main_target) {
forward_variables_from(invoker,
[
"data_deps",
"visibility",
"testonly",
])
outputs = [ output_file ]
# Nothing prevents link_output_rspfile() from collecting multiple
# binary files here if $deps reaches more than one without barrier.
# This is verboten and will just make objcopy die from having too many
# positional arguments after @rspfile expansion, so there's no need to
# check for it otherwise (which would require a wrapper script).
utils = [ "objcopy" ]
script = true
args = [
"--dump-section=.code-patches=" + rebase_path(outputs[0], root_build_dir),
"@" + rebase_path(rspfile, root_build_dir),
"/dev/null",
]
sources = [ rspfile ]
deps = [ ":$rspfile_target" ]
metadata = {
# Behaves like a resource() target, for aggregation within a kernel
# package.
distribution_entries_barrier = []
distribution_entries = []
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
if (defined(invoker.data_deps)) {
distribution_entries_barrier += invoker.data_deps
}
distribution_entries += [
{
source = rebase_path(outputs[0], root_build_dir)
destination = "code-patches.bin"
label = get_label_info(":$main_target", "label_with_toolchain")
},
]
}
}
}
# Defines a "hermetic leaf" code patching alternative, which is defined by the
# following properties (enforced at link-time):
#
# * the named function (`entrypoint`) is first in the link order and all of the
# sources/deps together form a closed set that is collectively hermetic;
# * no non-code (rodata or writable data) sections;
# * no dynamic relocations.
#
# Used in conjunction with code_patching_hermetic_stub() (see below), several
# alternatives may be defined so that code patching logic can make a runtime
# decision of which one of these should be used.
#
# It produces a raw binary content of a position-indepdendent executable,
# output to `target_out_dir` with an extension of "bin".
#
# Parameters are as for hermetic_code_blob().
#
template("code_patching_hermetic_alternative") {
target_label = get_label_info(":$target_name", "label_with_toolchain")
if (defined(invoker.entrypoint)) {
blob_entrypoint = invoker.entrypoint
} else {
blob_entrypoint = target_name
}
blob_path =
rebase_path("$target_out_dir/$blob_entrypoint.bin", root_build_dir)
hermetic_code_blob(target_name) {
forward_variables_from(invoker,
"*",
[
"entrypoint",
"metadata",
])
entrypoint = blob_entrypoint
metadata = {
code_patching_hermetic_alternative_barrier = []
code_patching_hermetic_alternatives = []
distribution_entries_barrier = []
distribution_entries = []
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
code_patching_hermetic_alternatives += [
{
label = target_label
name = blob_entrypoint
path = blob_path
},
]
# Behaves like a resource() target, for aggregation within a kernel
# package.
distribution_entries += [
{
source = blob_path
destination = "code-patches/$entrypoint"
label = target_label
},
]
}
}
}
# Defines a function stub that is intended to be linked against and patched
# later. Specifically, it is indended that it will be patched by one of the
# code_patching_hermetic_alternative() targets in its dependency graph. The
# function will have a size equal to the largest of its associated alternatives
# and will initially be filled with trap instructions.
#
# This target should be regarded as an opaque source_set() that defines this
# function alone.
#
# Parameters
#
# * output_name
# - Optional: The name of the function stub to generate, which must be a
# valid C identifier.
# - Type: string
# - Default: $target_name.
#
# * deps
# - Required: Dependencies to reach all
# code_patching_hermetic_alternative() targets the stub may resolve to at
# runtime.
# - Type: list(label)
#
# * case_id_header
# - Optional: a header path that defines patch case IDs. These IDs are
# expected to be of the form "CASE_ID_${OUTPUT_NAME}", where
# "${OUTPUT_NAME}" is the upper-cased transformation of "${output_name}".
# This parameter is only expected to be explicitly set for tests that
# wish to supply their own case IDs.
# - Type: string
# - Default: "arch/code-patches/case-id.h"
#
# * aliases
# - Optional: A list of weak symbols to define as aliases to the associated
# function.
# - Type: list(string)
#
# Other parameters are as for source_set().
#
template("code_patching_hermetic_stub") {
main_target = target_name
gen_target = "_code_patching_hermetic_stub.gen.$main_target"
metadata_target = "_code_patching_hermetic_stub.json.$main_target"
gen_file = "$target_gen_dir/$main_target.S"
metadata_file = "$target_gen_dir/$main_target.json"
source_set(main_target) {
forward_variables_from(invoker,
"*",
[
"aliases",
"case_id_header",
"deps",
"output_name",
"testonly",
"visibility",
])
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
sources = [ gen_file ]
deps = [
":$gen_target",
"//zircon/kernel/lib/arch:headers",
"//zircon/kernel/lib/code-patching",
]
}
generated_file(metadata_target) {
visibility = [ ":$gen_target" ]
outputs = [ metadata_file ]
output_conversion = "json"
data_keys = [ "code_patching_hermetic_alternatives" ]
walk_keys = [ "code_patching_hermetic_alternative_barrier" ]
forward_variables_from(invoker,
[
"deps",
"testonly",
])
}
action(gen_target) {
visibility = [ ":$main_target" ]
forward_variables_from(invoker,
[
"aliases",
"case_id_header",
"output_name",
"testonly",
])
if (!defined(output_name)) {
output_name = main_target
}
if (!defined(case_id_header)) {
case_id_header = "arch/code-patches/case-id-asm.h"
# Public to propagate to :main_target.
public_deps = [ "//zircon/kernel/arch/$zircon_cpu/code-patches:headers" ]
}
outputs = [ gen_file ]
sources = [ metadata_file ]
deps = [ ":$metadata_target" ]
depfile = "$gen_file.d"
script = "//zircon/kernel/lib/code-patching/hermetic-stub.py"
args = [
"--name",
output_name,
"--header",
case_id_header,
"--metadata-file",
rebase_path(metadata_file, root_build_dir),
"--depfile",
rebase_path(depfile, root_build_dir),
"--outfile",
rebase_path(outputs[0], root_build_dir),
]
if (defined(aliases)) {
foreach(alias, aliases) {
args += [
"--aliases",
alias,
]
}
}
}
}