blob: 91bcedc5ce17e7f0ce75d5ecbfcae7ea8979e73e [file] [view]
# Userboot library
The kernel itself directly launches one program in one user process at boot.
That first process, and the program running in it, are called "userboot".
This library facilitates writing userboot programs. It supports writing
specific userboot programs to do whatever you want them to do. The library is
not much involved in _what_ userboot _does_. It takes care of _how_ it can do
_anything_ in the special circumstances of _what_ and _where_ userboot _is_.
## Kernel storage packing
The userboot program is a single ELF file. Unlike all other user-space
programs stored in a bootable ZBI, userboot is packed into the `STORAGE_KERNEL`
ZBI item along with kernel binaries as part of the kernel ZBI. Normal userland
is instead packed into the `STORAGE_BOOTFS` ZBI item that's combined with the
kernel ZBI during the product assembly phase. (It's userboot itself that's
responsible for unpacking the `BOOTFS` item, so it can't be inside there!)
The file is found inside the "kernel package" selected by the `kernel.select`
boot option. In the build system, this means its `executable()` should be in
`deps` of a [`kernel_package()`](/zircon/kernel/kernel_package.gni). Then that
goes into `deps` of a [`kernel_image()`](/zircon/kernel/kernel_image.gni).
The `kernel.select.userboot` boot option sets the file name to be found
_inside_ the selected kernel package; by default it's just `userboot`, but it
can be any relative path matching the `distribution_entries` metadata used in a
target that went into `deps` of `kernel_package()`.
## Kernel program loading
The kernel loads userboot as a mostly-normal ELF file, with some restrictions.
* It must be a self-contained, statically linked executable (a static PIE): it
cannot use a `PT_INTERP` (separate dynamic linker), nor have any `DT_NEEDED`
dependencies other than the vDSO.
* It must not have `PT_LOAD` segments that overlap in the file. This is a
constraint on the link-time layout that is ordinarily met by binaries linked
for Fuchsia, but is required by neither ELF nor the system program loader.
## Kernel bootstrap protocol
Like any Zircon process, userboot is started with a single handle from which it
bootstraps all other capabilities and information it needs. The kernel passes
a channel handle on which it sends two messages (not necessarily queued yet
when the process starts). Each message contains only handles. There is no
additional data about the handles, and no specified order or number of handles
in each message. Each handle's purpose can be identified from its object type
and the details available from queries via that handle (`zx_object_get_info`,
etc.). The distinction between these two messages is reflected in the
structure of the library.
### Process capability message
The first message contains the few essential handles describing the userboot
process itself. The userboot library provides custom startup code for libc
that uses this message. The message has already been read to initialize
library state before any C++ static constructors or the userboot program's
`main` function run.
This message is kept separate precisely because of this topical separation:
it's just about bootstrapping the _userboot program_. The userboot library
handles this part completely.
### System capability message
The second and final message contains all the handles that constitute
system-wide privilege:
* The root job handle.
* Resource handles, identified by resource kind.
* This includes the root resource and some others.
* All other resource objects are made from these in userland.
* VMO handles, identified by name.
* `zbi`: The ZBI, where `CMDLINE`, `STORAGE_BOOTFS`, etc. are found.
* `vdso/*`: The various vDSO images as ELF in VMOs blessed to make syscalls.
* Various others to be made available to userland as `/kernel/...` files.
The library does not consume this message. Instead, the library's [startup
support API](#startup-support-api) hands the channel off to the program after
reading the process capability. It's entirely up to the program to read and
decode the system capability message and bootstrap _the whole system_. The
library doesn't impose any data structures to represent this, nor do any
allocation to hold the message or its representation. If the details of this
message change in the future, that will be between the kernel and userboot
programs, without changes to the userboot library or its APIs.
## Library features
This library facilitates writing a userboot program. In the build system,
`deps` on the [userboot](BUILD.gn) library automatically propagate the use of
pure static linking via the [`//sdk/lib/c:static`](/sdk/lib/c/BUILD.gn) target.
(Note the `userboot` library target must be in the _direct_ `deps` of the
`executable()` GN target, or treated as such via `public_deps` propagation.
Otherwise, it may be necessary to add a direct dependency on `...:static`.)
### Process startup support
The userboot library and the C library together take care of process startup so
that things look somewhat "normal" to the userboot program. The usual `main`
function is called as in any other program with the canonical signatures
available. However, there are never any arguments or environment (such that
`argc == 0`). So there's no reason to use a `main` signature that takes any
arguments.
When `main` returns it will do the normal things just as when `exit` is called:
run any destructors or `atexit` hooks, etc; then call `zx_process_exit` with
the exit code (return value from `main` or argument to `exit`). However,
nothing ever notices the exit code. If userboot crashes rather than exiting,
the kernel will print exception details with a register dump, etc. But if
userboot exits intentionally, the kernel considers this "normal":
* If any other processes are running, then great! Userboot's job is done.
* If the root job ever becomes empty because no process is running anymore,
the system always just reboots or shuts it down.
In short, the main "startup" support is just that the C library pretty much all
works normally and is available to be used in normal ways. (The main caveats
are just the context: there are no other processes on the system to provide any
services; and only libraries that support full static linking can be used, e.g.
there is no fdio---as well as nothing for it to talk to.) `main` should return
(or lead to calling `exit` or `_exit`, etc.) either when it's done and other
things are running now; or with no other process running when in a bad panic or
minimal testing scenario without proper drivers to manage full-system shutdown.
### Startup support API
[`<lib/userboot/startup.h>`](include/lib/userboot/startup.h) defines what
little API there is for startup support per se: The single function
`TakeBootstrapChannel()` returns the startup channel handle, transferring
ownership to the caller. That channel is expected to get the system capability
message from the kernel, and no further messages (the kernel closes its end
after sending the message). There is no guarantee that the message has already
been sent or the peer (kernel) side closed by the time the channel is handed
over. The channel signals must be waited for as usual.
This simple API uses C linkage and a trivial signature so it can be used easily
from C, C++, or Rust or other languages with even minimal C interoperation.
### Additional library utilities
So far the library has no additional utility APIs. If there are common pieces
that should be reused across different userboot programs but are very specific
to the niche of userboot work, this is a natural place to add them.
Other libraries not specific to the userboot context support ZBI decoding,
decompression, BOOTFS format, process management, `fuchsia.ldsvc` protocol
implementation, ELF loading, etc.
## Testing support library
One big feature of userboot programs being "somewhat normal" is that they can
be tested and debugged somewhat normally as well. Aside from the special
properties and privileges of particular resource objects, a userboot program
running in a normal sandbox without special privileges works much the same as
if it had been launched by the kernel at boot time. It's not alone on the
system. But it only knows about the task hierarchy under the job it's given as
"the root job". It only knows what resource and VMO handles it's given and
what queries on those handles report about them, not what kernel or hardware
privilege they truly confer.
The [userboot-testing](testing) library provides support for writing Fuchsia
test components with [gtest](/third_party/googletest) that exercise userboot
programs in sandbox environments.
The [API](testing/include/lib/userboot/testing/launcher.h) provides for:
* Fetching a userboot ELF file under test from the test package.
* Creating an empty job to stand in for the root job for the life of a test.
* Launching a userboot test process in a sandbox, including:
* Automatic generation of the process capability message.
* Passing through a vector of handles as the system capability message.
* Waiting for the process to terminate.
This allows the test logic to concentrate on the userboot "business logic":
what it expects in the system capability message; and what it does with that.