Loading repository data…
Loading repository data…
GrapheneOS / repository
Hardened Android standard C library. Some of the past hardening has not yet been ported from Marshmallow, Nougat and Oreo to this Android Pie repository. Most is available via archived tags in https://github.com/AndroidHardeningArchive/platform_bionic (check both the most recent Oreo and Nougat tags).
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
bionic is Android's C library, math library, and dynamic linker.
This document is a high-level overview of making changes to bionic itself. If you're trying to use bionic, or want more in-depth information about some part of the implementation, see all the bionic documentation.
The C library. Stuff like fopen(3) and kill(2).
The math library. Traditionally Unix systems kept stuff like sin(3) and
cos(3) in a separate library to save space in the days before shared
libraries.
The dynamic linker interface library. This is actually just a bunch of stubs
that the dynamic linker replaces with pointers to its own implementation at
runtime. This is where stuff like dlopen(3) lives.
The C++ ABI support functions. The C++ compiler doesn't know how to implement
thread-safe static initialization and the like, so it just calls functions that
are supplied by the system. Stuff like __cxa_guard_acquire and
__cxa_pure_virtual live here.
The dynamic linker. When you run a dynamically-linked executable, its ELF file
has a DT_INTERP entry that says "use the following program to start me". On
Android, that's either linker or linker64 (depending on whether it's a
32-bit or 64-bit executable). It's responsible for loading the ELF executable
into memory and resolving references to symbols (so that when your code tries to
jump to fopen(3), say, it lands in the right place).
The tests/ directory contains unit tests. Roughly arranged as one file per
publicly-exported header file. tests/headers/ contains compile-only tests
that just check that things are in the headers, whereas the "real" tests
check actual behavior.
The benchmarks/ directory contains benchmarks, with its own documentation.
libc/
arch-arm/
arch-arm64/
arch-common/
arch-x86/
arch-x86_64/
# Each architecture has its own subdirectory for stuff that isn't shared
# because it's architecture-specific. There will be a .mk file in here that
# drags in all the architecture-specific files.
bionic/
# Every architecture needs a handful of machine-specific assembler files.
# They live here.
string/
# Most architectures have a handful of optional assembler files
# implementing optimized versions of various routines. The <string.h>
# functions are particular favorites.
syscalls/
# The syscalls directories contain script-generated assembler files.
# See 'Adding system calls' later.
include/
kernel/
android/
uapi/
# The public header files on everyone's include path.
# These are a mixture of files written by us, files taken from BSD,
# and Linux kernel header files.
# See "Kernel header files" below for more on the last of those.
private/
# These are private header files meant for use within bionic itself.
dns/
# Contains the DNS resolver (originates from NetBSD code).
upstream-freebsd/
upstream-netbsd/
upstream-openbsd/
# These directories contain upstream source with no local changes.
# Any time we can just use a BSD implementation of something unmodified,
# we should. Ideally these should probably have been three separate git
# projects in external/, but they're here instead mostly by historical
# accident (because it wouldn't have been easy to import just the tiny
# subset of these operating systems that -- unlike Android -- just have
# one huge repository rather than lots of little ones and a mechanism
# like our `repo` tool).
# The structure under these directories mimics the relevant upstream tree,
# but in order to actually be able to compile this code in our tree
# _without_ making modifications to the source files directly, we also
# have the following subdirectories in each one that aren't upstream:
android/
include/
# This is where we keep the hacks necessary to build BSD source
# in our world. The *-compat.h files are automatically included
# using -include, but we also provide equivalents for missing
# header/source files needed by the BSD implementation.
bionic/
# This is where code we wrote lives. The C++ files are files we own,
# typically because the Linux kernel interface is sufficiently different
# that we can't use any of the BSD implementations. The C files are
# legacy mess that needs to be sorted out, either by replacing it with
# current upstream source in one of the upstream directories or by
# switching the file to C++ and cleaning it up.
memory/
# Glue code and utilities related to the native heap. Note that the actual
# heap implementation is in external/scudo/.
stdio/
# These are legacy files of dubious provenance. We're working to clean
# this mess up, and this directory should disappear when the stdio
# implementation moves into bionic/libc/bionic/.
tools/
# Various tools used to maintain bionic.
tzcode/
# A modified superset of the IANA tzcode. Most of the modifications relate
# to Android's use of a single file (with corresponding index) to contain
# timezone data.
zoneinfo/
# Android-format timezone data.
# See 'Updating tzdata' later.
System call stubs (explained in more detail below) are generated from SYSCALLS.TXT at build time, and not checked in.
The heap implementation is in external/scudo/.
Some optimized assembler string/memory and math routines for arm/arm64 are in external/arm-optimized-routines/ (though many -- especially for 32-bit arm -- are in bionic).
Some code also comes from external/llvm-libc/.
The first question you should ask is "should I add this function?". The answer is usually "no".
The answer is "yes" if the function is part of ISO C or POSIX.
The answer is "probably" if the function is in glibc and musl and the BSDs (including iOS/macOS).
The answer is "probably not" if the function differs in API or behavior between them (ABI doesn't matter).
The answer is "maybe" if the function has three/four distinct users in different unrelated projects, and there isn't a more specific higher-level library that would make more sense as the place to add the function.
Adding a function usually involves:
Find the right header file to work in by looking up the official specification. The POSIX documentation on opengroup.org is the canonical source (though this may lag behind the most recent ISO C). This documentation will also be useful in later steps such as writing tests.
Add constants (and perhaps types) to the appropriate header file.
Note that you should check to see whether the constants are already in
kernel uapi header files, in which case you just need to make sure that
the appropriate header file in libc/include/ #includes the relevant
linux/ file or files.
Add function declarations to the appropriate header file. Don't forget
to include the appropriate __INTRODUCED_IN(), with the right API level
for the first release your system call wrapper will be in. See
libc/include/android/api_level.h for the API levels.
If the header file doesn't exist, copy all of libc/include/sys/sysinfo.h
into your new file --- it's a good short example to start from.
Note also our style for naming arguments: always use two leading
underscores (so developers are free to use any of the unadorned names as
macros without breaking things), avoid abbreviations, and ideally try to
use the same name as an existing system call (to reduce the amount of
English vocabulary required by people who just want to use the function
signatures). If there's a similar function already in the C library,
check what names it already used. Finally, prefer the void* orthography we
use over the void * you'll see on man7.org, and don't forget to include
nullability annotations for any pointers.
Add basic documentation to the header file. Again, the existing libc/include/sys/sysinfo.h is a good short example that shows the expected style.
Most of the detail should actually be left to the man7.org page, with only a brief one-sentence explanation (usually based on the description in the NAME section of the man page) in our documentation. Always include the return value/error reporting details (you can find out what the system call returns from the RETURN VALUE of the man page), but try to match the wording and style wording from our existing documentation; we're trying to minimize the number of English words that readers need to understand by using the exact same wording where possible).
Explicitly say which version of Android the function was added to in
the documentation because the documentation generation tool doesn't yet
understand __INTRODUCED_IN().
Explicitly call out any Android-specific changes/additions/limitations because they won't be on the man7.org page.
Add the function name to the correct section in libc/libc.map.txt (or the corresponding map file for libm or libdl). If you don't add your function to the map, it won't be exported. Adding the function to the map adds an implicit build-time check that your function is defined for all supported architectures.
Any new function should include an # introduced= matching the
__INTRODUCED_IN() from the header. For several versions of Android we
created separate sections (such as LIBC_Q) to make it a little more
obvious in places like nm(1) output which version of Android is required.
Theoretically this also gave us the option of having multiple versions of
the same symbol with different behavior. Other OSes use this for backward
compatibility but we tend to use API level checks within the implementation
instead. When marketing moved back to numbers, and then when the alphabet
wrapped, this started to look like less of a good idea. The move to "2025Q4"
style releases made this even less convincing, so we've gone back to just
adding new symbols unversioned (but with the # introduced= annotation).
Add a basic test. Don't try to test everything; concentrate on just testing the code that's actually in bionic, not all the functionality that's implemented in the kernel. At one end of the spectrum, for string routines that's everything; at the other, for simple syscalls, that's just the auto-generated argument and return value marshalling.
Add a test in the right file in tests/. We have one file per header, so if your system call is exposed in <unistd.h>, for example, your test would go in tests/unistd_test.cpp.
A trivial test that deliberately supplies an invalid argument helps check that we're generating the right symbol and have the right declaration in the header file, and that the change to libc.map.txt from step 5 is correct. (For system calls, you can use strace(1) manually to confirm that the correct system call is being made.)
For testing the kernel side of things, we should prefer to rely on https://github.com/linux-test-project/ltp for kernel testing, but you'll want to check that external/ltp/ does contain tests for the syscall you're adding. Also check that external/ltp/ is using the libc wrapper for the syscall rather than calling it "directly" via syscall(