glibc Source Internals and System-Call Wrappers

Last update: 2026-05-07 03:47:31 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

glibc exposes C functions such as read(2) and write(2), while Linux exposes architecture-specific system-call entry conventions. A wrapper bridges those layers: it accepts the public C ABI, follows the selected architecture’s system-call ABI, translates kernel errors, and may implement POSIX thread-cancellation behavior.

Examples below describe the current upstream source tree. A released glibc can select different files or macro definitions, so source inspection must begin with the release and target triple being built.

sysdeps/ Selection #

The sysdeps/ tree holds implementations selected by operating system, CPU, ABI, word size, and features. Generic code is a fallback; a more specific directory can replace a file with the same relative name. For a Linux build, relevant paths include sysdeps/unix/sysv/linux/; an architecture may further supply Linux-specific entry code or an ABI-specific variant.

glibc’s configure and build machinery constructs an ordered list of applicable system-dependency directories. Build rules search that list so that the selected implementation wins over a generic fallback. The layout is consequently not a taxonomy in which each file has exactly one dimension: an OS directory can depend on architecture headers, and an architecture directory can contain OS-independent code. Inspect config.make, the configured build directory, and the selected source paths rather than inferring behavior from one directory name.

Generated Thin Wrappers #

Some thin assembly wrappers are generated from syscalls.list. Each record names an output, a Linux syscall, an argument signature, a strong symbol, and optional weak or versioned names. The build invokes make-syscalls.sh, which:

  1. removes entries overridden by higher-priority sysdeps directories;
  2. resolves the SYS_ number through the target’s sysdep.h;
  3. derives arity and special argument handling from the signature; and
  4. emits a rule using syscall-template.S, including aliases and compatibility symbols where the list requests them.

syscalls.list does not define every public function. A wrapper with validation, fallback logic, cancellation handling, time-width conversion, or another policy is often hand-written. write is one such hand-written C wrapper in current glibc write.c; it is therefore a useful contrast with the generated path.

write and Cancellation-Aware Dispatch #

Conceptually, the Linux write wrapper does the following:

c
ssize_t
write(int fd, const void *buffer, size_t count)
{
    return cancellation_aware_syscall(SYS_write, fd, buffer, count);
}

The actual source calls SYSCALL_CANCEL (write, fd, buf, nbytes). That macro selects an inline system-call path when cancellation checking is unnecessary. Otherwise it temporarily changes the thread’s cancellation type, issues the call, and restores the prior type. write is a POSIX cancellation point, so this boundary lets a pending cancellation be acted on without treating every internal system call as a cancellation point.

The macro dispatches by argument count before reaching the architecture’s inline system-call primitive. On Linux, that primitive places the syscall number and arguments in registers prescribed by the target ABI, performs the architecture’s system-call instruction, and receives the kernel return value. Neither the C function ABI nor the register sequence is universal: x86-64, i386, AArch64, and other ports use different registers and clobber rules.

The kernel returns an error in its low-level convention. The selected glibc support code recognizes the error range, stores a positive error number in thread-local errno, and makes a normal public wrapper return -1. Interfaces that intentionally expose a raw error value use distinct internal conventions. The glibc manual’s system-call section documents the application-level contract; the source macros implement the architecture-specific boundary.

Public Names, Hidden Definitions, and Aliases #

The write.c definition is named __libc_write, then its source declares internal visibility and aliases including __write and the public write name. This shape separates implementation identity from public ABI identity:

  • libc_hidden_def and libc_hidden_weak allow calls made inside libc to bind to a hidden definition when appropriate, avoiding an interposable public-symbol lookup.
  • A strong alias gives another internal name the same implementation.
  • A weak public alias permits a different definition to override it when the build or ABI requires that behavior.
  • Symbol-version machinery records which ABI version exports a public name and can retain compatibility entry points for old binaries.

hidden affects references within glibc; it does not erase the documented public symbol from the shared-library ABI. Examine the architecture’s libc.so with readelf --dyn-syms --version-info and compare it with the source and release version scripts when diagnosing an unresolved symbol. An alias declaration alone is not sufficient evidence of the version a distributed library exports.

Comments

Read or comment on GitHub (sign-in required to comment)