Why clock_gettime() may not show up in strace: the Linux vDSO

Your program calls clock_gettime(), yet strace shows no such call. A look at the vDSO the Linux kernel maps into each process, and how to read a syscall trace with it in mind.

linuxvdsostracesyscallclock_gettime

Trace a program that reads the time a lot with strace, and you may not find a single clock_gettime line. The code clearly calls clock_gettime(), and the output looks right. The trace didn’t miss anything; more likely, those calls finished without a system call. On Linux, the mechanism that makes this possible is the vDSO.

This post uses the Linux kernel documentation and the Linux man-pages to explain what the vDSO is and how to read an strace trace with it in mind. What each clock ID measures is covered separately in real, user and sys in time output, so it isn’t repeated here.

The vDSO is a small shared library the kernel maps in

The vDSO (virtual dynamic shared object) is a small shared library that the kernel automatically maps into the address space of user-space programs. The kernel maps this fully formed ELF image itself when it loads the program. The kernel’s ABI document describes this as something that happens “on some architectures”, and the vdso(7) manual page says the base address is passed “if one exists”. Don’t assume every Linux environment has one.

The name varies by architecture too. According to the table in vdso(7), it is linux-vdso.so.1 on x86-64 and aarch64, linux-gate.so.1 on i386, and linux-vdso64.so.1 on ppc/64. You will often see this name in glibc’s ldd output, but the manual says code should not depend on the exact name.

Why a system call can be skipped

A system call costs a transition: leaving user space and entering the kernel. vdso(7) explains that some system calls are made so often that this cost can dominate overall performance. Asking for the current time is the classic case. Timestamps, timing loops and polling all keep asking what time it is right now.

The current time isn’t secret either. Root and unprivileged users get the same answer. So the kernel places the information needed to answer that question in memory the process can read, and a function inside the vDSO reads that memory to build the answer. In the words of vdso(7), a call to gettimeofday(2) “changes from a system call to a normal function call and a few memory accesses”.

This path is only used when the architecture and kernel provide that function in the vDSO. The sources give no figures for how much faster it is, and neither does this post.

libc uses it; you don’t call it directly

Ordinary application code doesn’t need to care about the vDSO. Call clock_gettime() as usual, and the C library checks what the vDSO offers and uses it. vdso(7) goes as far as saying that if you’re trying to call the vDSO from your own application rather than through the C library, “you’re most likely doing it wrong”. The kernel ABI document says programs that dynamically link to glibc use the vDSO automatically. It makes no such statement about static linking or other C libraries, so in those environments check that libc’s own documentation.

The documents give several reasons not to call it directly.

vDSO functions are called according to the platform ABI. The kernel ABI document says that on many platforms they are called like ordinary C functions, but on some, such as powerpc, they use the system call convention instead. It also warns that passing a bad pointer may get you SIGSEGV instead of -EFAULT.

Symbol names and the set of symbols differ by architecture. In the vdso(7) tables, the function is __vdso_clock_gettime on x86-64 but __kernel_clock_gettime on aarch64. Some architectures have no time-related symbol in their table at all.

vDSO symbols are versioned (GNU symbol versioning). For example, __vdso_clock_gettime on x86-64 is LINUX_2.6, and __kernel_clock_gettime on aarch64 is LINUX_2.6.39. When you look up a symbol you must specify the version you expect. The kernel ABI document says the set of symbols for a given version and their ABI are considered stable, adds that this may vary across architectures, and notes that the document had been confirmed only for x86_64.

How a process finds the vDSO

When the kernel starts a program, it puts the vDSO’s base address in the auxiliary vector under the AT_SYSINFO_EHDR entry. glibc’s getauxval() can read it. The address is usually randomized at each execve, so don’t assume it sits at any particular location.

getauxval() is a GNU extension, not a POSIX function, and arrived in glibc 2.16. It returns 0 when the requested entry is missing, and since glibc 2.19 it also sets errno to ENOENT in that case. Before 2.19 there was no way to tell “entry not present” apart from “the value really is 0”. So to check, set errno to 0 before the call and look at errno whenever the result is 0. A result of 0 with errno set to ENOENT means the entry is missing. If the result is 0 and errno is unchanged, glibc before 2.19 can’t tell you whether the entry is missing or the value is 0, so the example below reports that case separately.

What is POSIX and what is the Linux implementation

clock_gettime() itself is a POSIX interface. Its manual page lists POSIX.1-2024 under STANDARDS, and the function goes back to POSIX.1-1996. The vDSO, on the other hand, is how Linux implements it. The clock_gettime(2) manual page mentions the vDSO not in its standards text but under “C library/kernel differences”, saying only that “on some architectures, an implementation of clock_gettime() is provided in the vdso(7)”.

So what a program can rely on is that clock_gettime() behaves as specified. Whether a given call goes through a system call or ends inside the vDSO is an implementation detail that depends on the combination of architecture, kernel and C library.

clock_gettime() does not always finish in the vDSO

Having clock_gettime in the vDSO doesn’t mean every call avoids a system call. vdso(7) documents cases where it didn’t. On ppc/64 before Linux 4.16, and on ppc/32 before Linux 5.6, CLOCK_REALTIME_COARSE and CLOCK_MONOTONIC_COARSE were not supported by the vDSO’s __kernel_clock_gettime, and those calls fell back to the real system call. The clock_gettime(2) manual page also says these two COARSE clocks require per-architecture support, and probably also vDSO support.

These documents don’t let you generalize about which clocks are handled in the vDSO on the system you’re using. This post doesn’t promise that any particular clock ID is fast on every system.

Reading strace output

strace traces system calls. vdso(7) states plainly that symbols (system calls) exported by the vDSO will not appear in strace output. For the same reason they are not visible to seccomp(2) filters.

That leads to the following reading.

  • A trace with no clock_gettime line is not evidence that the program never asked for the time. The vDSO may have handled it.
  • A clock_gettime line in the trace means that call really entered the kernel. The vDSO may not have handled that case, or the C library may not have used the vDSO, but the trace alone can’t tell you which.
  • The same applies if you use seccomp filters to block or log time-related system calls.

Checking it yourself

Below is a small C program that prints the vDSO base address and calls clock_gettime() three times. This code was not compiled or run while writing this post, so no sample output is shown.

#include <errno.h>
#include <stdio.h>
#include <sys/auxv.h>
#include <time.h>

int main(void)
{
    errno = 0;
    unsigned long base = getauxval(AT_SYSINFO_EHDR);
    if (base != 0)
        printf("AT_SYSINFO_EHDR = %#lx\n", base);
    else if (errno == ENOENT)
        printf("AT_SYSINFO_EHDR not found\n");
    else
        printf("AT_SYSINFO_EHDR: 0 or not found (ambiguous before glibc 2.19)\n");

    for (int i = 0; i < 3; i++) {
        struct timespec ts;
        if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) {
            perror("clock_gettime");
            return 1;
        }
        printf("%lld.%09ld\n", (long long) ts.tv_sec, ts.tv_nsec);
    }
    return 0;
}

With glibc 2.17 or later, build it with cc -o vdso-demo vdso-demo.c. On older glibc, clock_gettime() lives in librt, so you need -lrt.

strace ./vdso-demo

Count the clock_gettime lines in the output. You may see other system calls, such as the write that printf uses for output. If three time values were printed and there is no clock_gettime line, you can read that as those calls finishing without a system call. If you see three such lines, those calls took the system call path in this environment.

You can also look at the auxiliary vector without changing the program. The getauxval(3) manual page says you can view the auxiliary vector a program receives by setting the LD_SHOW_AUXV environment variable when running it, and gives this example.

LD_SHOW_AUXV=1 sleep 1

If an AT_SYSINFO_EHDR entry is there, the kernel passed in a vDSO. That alone doesn’t confirm that any particular clock passed to clock_gettime() is handled in the vDSO. For another running process, the auxiliary vector can be read from /proc/<pid>/auxv, subject to permissions.

Sources

Checked on 2026-10-18. Both the man-pages git sources and the man7.org HTML (based on man-pages 6.19) were consulted.

Back to the blog