Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sysdeps,book: add demo sysdeps and porting mdbook #1211

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions book/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
book
6 changes: 6 additions & 0 deletions book/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[book]
authors = ["Matt Staveley-Taylor"]
language = "en"
multilingual = false
src = "src"
title = "mlibc User Guide"
5 changes: 5 additions & 0 deletions book/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Introduction

Welcome to the [`mlibc`](https://github.com/managarm/mlibc) User Guide.

[`mlibc`](https://github.com/managarm/mlibc) is a fully featured C standard library designed with portability in mind. It provides a clean syscall abstraction layer for new operating system ports to plug into. This guide will explain how to integrate a new OS / syscall backend and get your first program up and running.
11 changes: 11 additions & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Summary

[Introduction](README.md)

- [Adding a new OS port](porting/intro.md)
- [Kernel prerequisites](porting/kernel_prerequisites.md)
- [Choosing a compiler](porting/choosing_a_compiler.md)
- [Implementing sysdeps](porting/implementing_sysdeps.md)
- [Upstreaming your port](porting/upstreaming.md)
- [Next steps](porting/next_steps.md)
- [Adding a new ISA port]()
26 changes: 26 additions & 0 deletions book/src/porting/choosing_a_compiler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Choosing a compiler

To compile [`mlibc`](https://github.com/managarm/mlibc) and any userspace programs which link against it, you'll need a suitable compiler. Roughly speaking you have two choices:

1. **Build a full [OS Specific Toolchain](https://wiki.osdev.org/OS_Specific_Toolchain).**

With this, you'll have a compiler that implicitly links against (m)libc, so we recommend doing this _after_ you have a basic port working.

1. **Use a generic ELF toolchain.**

If compiling your kernel with a generic toolchain like `x86_64-unknown-elf-gcc` or `riscv64-elf-gcc`, you may re-use the same compiler for this.

Note that when compiling userspace programs, you must manually provide a number of command line arguments (such as the location of your compiled libc and any headers).


For [`mlibc-demo-os`](https://github.com/64/mlibc-demo-os), we'll rely on a generic `riscv64-elf-gcc` toolchain provided by our distro.

## Creating a Meson cross file

[`mlibc`](https://github.com/managarm/mlibc) uses a build system called [Meson](https://mesonbuild.com/). When cross-compiling, you must tell meson about your compiler and target via a [_cross file_](https://mesonbuild.com/Cross-compilation.html).

For [`mlibc-demo-os`](https://github.com/64/mlibc-demo-os), our cross file looks like this:

```toml
{{#include ../../../ci/demo.cross-file}}
```
148 changes: 148 additions & 0 deletions book/src/porting/implementing_sysdeps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Implementing your sysdeps

## Telling mlibc about your sysdeps

In mlibc's top level `meson.build` file, you'll see a large `if`/`else` chain that selects the right sysdeps subdirectory based on the system name. We'll add ourselves here:
```meson
{{#include ../../../meson.build:demo-sysdeps}}
# ...
```

Now, create the `sysdeps/demo` folder. At minimum it should contain a `meson.build` file where we'll declare all the `.cpp` files and select header files to install:

```meson
{{#include ../../../sysdeps/demo/meson.build:sources-and-includes}}
```

The `sysdep_supported_options` tells mlibc which 'options' your sysdeps supports. For example, the `unistd.h` header is only available when the POSIX option is enabled.

## Preparing to build mlibc

Now, from a fresh clone of `mlibc` you can run:

```
$ meson setup --cross-file=path/to/your.cross-file -Ddefault_library=static -Dbuildtype=debug build
```

The `-Ddefault_library=static` tells meson to only produce a statically linked library (`libc.a`). We *strongly* recommend getting statically linked binaries to work before dynamically linked ones.

Now run `ninja -C build` to start the build. You'll likely get a large number of compilation errors at this point, and we'll work on fixing these in the next sections.

## Selecting ABI headers

Many of the compile errors are because we need to tell mlibc about the ABI between our sysdeps and kernel. For example, we must define the layout of `struct stat`.

We _strongly_ recommend re-using an existing ABI where possible rather than creating your own, because many programs assume a particular ABI and will fail to compile or subtly break if you choose a different one.

For the demo OS, we'll symlink each ABI header to Linux's definition in `sysdeps/linux/include/abi-bits`. Then, we'll add the following to our `meson.build`:

```meson
{{#include ../../../sysdeps/demo/meson.build:abi-includes}}
```

This is not an exhaustive list of ABI headers but should be enough to get started.

## Implementing (S)crt1

Next, we must provide a definition of the ELF entrypoint (traditionally named `_start`). Each ISA tends to require its own definition written in assembly; for example RISC-V targets must initialise the `gp` register before jumping to C++.

Traditionally the file that defines `_start` is called `crt1.S` (or `Scrt1.S` for position independent executables). This produces an object file which has to be linked into each application.

Part of configuring an [OS Specific Toolchain](https://wiki.osdev.org/OS_Specific_Toolchain) is [specifying the location of `(S)crt1.o`](https://wiki.osdev.org/OS_Specific_Toolchain#Start_Files_Directory) so that it linked automatically, but generic ELF targets must link it explicitly.

We recommend copying `(S)crt1.S` from an existing target like Linux. Then, we'll compile it by adding the following to our `meson.build`:

```
{{#include ../../../sysdeps/demo/meson.build:crt1}}
```

## Implementing a C++ entry point

Now, we'll implement the C++ entry point that we call from `crt1.S`.

```cpp
{{#include ../../../sysdeps/demo/entry.cpp}}
```

The call to `__dlapi_enter` is used to perform initialisation in statically linked executables (but is a no-op in dynamically linked ones). For example, any global constructors in the program will be called from here.

The `LibraryGuard` is used to perform initialisation of `libc` itself like parsing the environment and arguments from the kernel. Putting this code in a global constructor ensures that it's called at the right time relative to other pieces of initialisation.

## Performing system calls

The final piece of infrastructure we require is the ability to invoke system calls.

For example, on RISC-V a system call is invoked via the `ecall` instruction and requires putting arguments in specific registers, which requires a bit of (inline) assembly. We recommend copying this glue from an existing target.

For the demo OS, this is provided by `syscall.cpp` and `include/bits/syscall.h`.

## Implementing sysdeps

Finally we're ready to implement the actual sysdep functions. For a basic statically-linked hello world program, you'll need to provide definitions of the following sysdep functions:

- `mlibc::sys_libc_panic`
- `mlibc::sys_libc_log`
- `mlibc::sys_isatty`
- `mlibc::sys_write`
- `mlibc::sys_tcb_set`
- `mlibc::sys_anon_allocate`
- `mlibc::sys_anon_free`
- `mlibc::sys_seek`
- `mlibc::sys_exit`
- `mlibc::sys_close`
- `mlibc::sys_futex_wake`
- `mlibc::sys_futex_wait`
- `mlibc::sys_read`
- `mlibc::sys_open`
- `mlibc::sys_vm_map`

Note that many of these functions are declared as weak symbols. You _must_ include the relevant headers (e.g `<mlibc/all-sysdeps.hpp>`) before providing definitions otherwise these won't be emitted as weak symbols and you'll see strange errors.

Most sysdep functions return an integer error code (0 for success, -E for errors) and return data via out parameters. Note that sysdeps shouldn't set errno directly - mlibc will set it from the error code you return.

As a general strategy, it's a good idea to stub whatever's required to make things compile, and then add proper implementations later. For example:

```cpp
{{#include ../../../sysdeps/demo/sysdeps.cpp:stub}}

namespace mlibc {
int sys_close(int fd) { STUB(); }
}
```

## Compiling a test program

At this point, you should be able to compile and link mlibc itself. Congratulations!

Now we'll compile a simple hello world program that we can run on our kernel:

```bash
# Install mlibc to a temporary directory
DESTDIR=/tmp/mlibc-install ninja -C build install

# Some targets require an implementation of libgcc (if you see linker errors below).
# We recommend using cc-runtime: https://github.com/osdev0/cc-runtime

riscv64-elf-gcc \
-static -nostdinc -nostdlib -g \
-I /tmp/mlibc-install/include \
/tmp/mlibc-install/lib/crt1.o \
hello_world_program.c \
/tmp/mlibc-install/lib/libc.a \
/path/to/cc-runtime.a \
-o hello_world_program
```

## Troubleshooting

Something not working? Here are some common issues to look out for:

- Your kernel isn't loading the ELF file correctly. Double check that you're loading the contents from file to the right addresses and zeroing the uninitialised portions.
- Your `sys_anon_allocate` function is broken. Try commenting out `sys_anon_free` and see if that helps. Try printing the addresses to see if you're getting unique allocations.
- You're pushing the arguments/environment/auxiliary vectors to the stack wrong. Double check you're pushing in the right order and everything is properly aligned.
- You're not saving and restoring the userspace state properly when a syscall happens.
- Your `syscall` glue is passing arguments in the wrong registers. Double check that the kernel and userspace agree on the order of arguments.

When stuck, we recommend using GDB to step through the program.

If all else fails, feel free to hop in [the Managarm Discord server](https://discord.gg/7WB6Ur3) and ask for help in `#mlibc-dev`.
1 change: 1 addition & 0 deletions book/src/porting/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Adding a new OS port
11 changes: 11 additions & 0 deletions book/src/porting/kernel_prerequisites.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Kernel prerequisites

Before you attempt to port [`mlibc`](https://github.com/managarm/mlibc), ensure your kernel supports these things:
- Writing text to the screen or a serial port
- Basic paging and virtual memory operations
- Loading an ELF program, mapping a stack and jumping to the entrypoint
- Handling syscalls from userspace

For this port, we'll use [`mlibc-demo-os`](https://github.com/64/mlibc-demo-os), a small RISC-V kernel which supports the minimum functionality required for a mlibc 'hello world' program. Refer to this if you get stuck!

Note that a filesystem and storage drivers are not strictly necessary. In [`mlibc-demo-os`](https://github.com/64/mlibc-demo-os), we `include_bytes!` the contents of the user-space program into the kernel's executable. To support multiple files, you could include a trivial read-only filesystem like `tar` instead.
3 changes: 3 additions & 0 deletions book/src/porting/next_steps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Next steps

- implement more sysdeps, enable more options
9 changes: 9 additions & 0 deletions book/src/porting/upstreaming.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Upstreaming your port

Once your port is reasonably stable, feel free to submit it to us upstream. This ensures that your sysdeps won't be broken by any internal refactorings.

Though we won't be able to test your kernel on our CI, we require you add your port to the 'Compile sysdeps' GitHub action which checks that compilation succeeds.

It's a good idea to include a `.clang-format` file so that any changes we make to your code will be formatted to your liking.

See the [pull request adding Astral sysdeps](https://github.com/managarm/mlibc/pull/1136) for an example to follow.
11 changes: 11 additions & 0 deletions ci/demo.cross-file
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[binaries]
c = ['riscv64-elf-gcc']
cpp = ['riscv64-elf-g++']
ar = ['riscv64-elf-ar']
ranlib = ['riscv64-elf-ranlib']

[host_machine]
system = 'demo'
cpu_family = 'riscv64'
cpu = 'unknown'
endian = 'little'
4 changes: 4 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,11 @@ elif host_machine.system() == 'astral'
internal_conf.set10('MLIBC_MAP_DSO_SEGMENTS', true)
internal_conf.set10('MLIBC_MAP_FILE_WINDOWS', true)
subdir('sysdeps/astral')
# ANCHOR: demo-sysdeps
elif host_machine.system() == 'demo'
subdir('sysdeps/demo')
else
# ANCHOR_END: demo-sysdeps
error('No sysdeps defined for OS: ' + host_machine.system())
endif

Expand Down
9 changes: 9 additions & 0 deletions notes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
setup: make cross file pointing to compiler

meson setup --cross-file=ci/demo.cross-file build

-Ddefault_library=static

add sysdeps directory


14 changes: 0 additions & 14 deletions options/internal/include/bits/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -347,18 +347,4 @@ __MLIBC_CHECK_TYPE(__mlibc_uint16, __UINT_LEAST16_TYPE__);
__MLIBC_CHECK_TYPE(__mlibc_uint32, __UINT_LEAST32_TYPE__);
__MLIBC_CHECK_TYPE(__mlibc_uint64, __UINT_LEAST64_TYPE__);

/* Fast-width. */
/* Unfortunately, GCC and Clang disagree about fast types. */
#ifndef __clang__
__MLIBC_CHECK_TYPE(__mlibc_int_fast8, __INT_FAST8_TYPE__);
__MLIBC_CHECK_TYPE(__mlibc_int_fast16, __INT_FAST16_TYPE__);
__MLIBC_CHECK_TYPE(__mlibc_int_fast32, __INT_FAST32_TYPE__);
__MLIBC_CHECK_TYPE(__mlibc_int_fast64, __INT_FAST64_TYPE__);

__MLIBC_CHECK_TYPE(__mlibc_uint_fast8, __UINT_FAST8_TYPE__);
__MLIBC_CHECK_TYPE(__mlibc_uint_fast16, __UINT_FAST16_TYPE__);
__MLIBC_CHECK_TYPE(__mlibc_uint_fast32, __UINT_FAST32_TYPE__);
__MLIBC_CHECK_TYPE(__mlibc_uint_fast64, __UINT_FAST64_TYPE__);
#endif

#endif /* _MLIBC_INTERNAL_TYPES_H */
3 changes: 2 additions & 1 deletion options/rtld/generic/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "elf.hpp"
#include "linker.hpp"
#include "mlibc/ansi-sysdeps.hpp"

#if __MLIBC_POSIX_OPTION
#include <dlfcn.h>
Expand All @@ -24,7 +25,7 @@
#define HIDDEN __attribute__((__visibility__("hidden")))
#define EXPORT __attribute__((__visibility__("default")))

static constexpr bool logEntryExit = false;
static constexpr bool logEntryExit = true;
static constexpr bool logStartup = false;
static constexpr bool logDlCalls = false;

Expand Down
31 changes: 31 additions & 0 deletions sysdeps/demo/crt1.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.weak __global_pointer$
.hidden __global_pointer$

.section .text
.global _start
_start:
# Load gp.
.option push
.option norelax
lla gp, __global_pointer$
.option pop

mv a0, sp
la a1, main
call __mlibc_entry
unimp

# Load gp from .preinit_array since it may be used by the executable's .init_array.
# We still load it in _start to account for static binaries. This matches glibc's behavior.
load_gp:
.option push
.option norelax
lla gp, __global_pointer$
.option pop
ret

.section .preinit_array,"aw"
.dword load_gp

.section .note.GNU-stack,"",%progbits

37 changes: 37 additions & 0 deletions sysdeps/demo/entry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <bits/ensure.h>
#include <mlibc/elf/startup.h>
#include <stdint.h>
#include <stdlib.h>

// defined by the POSIX library
void __mlibc_initLocale();

extern "C" uintptr_t *__dlapi_entrystack();
extern "C" void __dlapi_enter(uintptr_t *);

extern char **environ;
static mlibc::exec_stack_data __mlibc_stack_data;

struct LibraryGuard {
LibraryGuard();
};

static LibraryGuard guard;

LibraryGuard::LibraryGuard() {
__mlibc_initLocale();

// Parse the exec() stack.
mlibc::parse_exec_stack(__dlapi_entrystack(), &__mlibc_stack_data);
mlibc::set_startup_data(__mlibc_stack_data.argc, __mlibc_stack_data.argv,
__mlibc_stack_data.envp);
}

extern "C" void __mlibc_entry(uintptr_t *entry_stack,
int (*main_fn)(int argc, char *argv[],
char *env[])) {
__dlapi_enter(entry_stack);
auto result =
main_fn(__mlibc_stack_data.argc, __mlibc_stack_data.argv, environ);
exit(result);
}
1 change: 1 addition & 0 deletions sysdeps/demo/include/abi-bits/auxv.h
1 change: 1 addition & 0 deletions sysdeps/demo/include/abi-bits/blkcnt_t.h
1 change: 1 addition & 0 deletions sysdeps/demo/include/abi-bits/blksize_t.h
1 change: 1 addition & 0 deletions sysdeps/demo/include/abi-bits/clockid_t.h
1 change: 1 addition & 0 deletions sysdeps/demo/include/abi-bits/dev_t.h
1 change: 1 addition & 0 deletions sysdeps/demo/include/abi-bits/errno.h
1 change: 1 addition & 0 deletions sysdeps/demo/include/abi-bits/fcntl.h
1 change: 1 addition & 0 deletions sysdeps/demo/include/abi-bits/gid_t.h
1 change: 1 addition & 0 deletions sysdeps/demo/include/abi-bits/ino_t.h
Loading
Loading