1. Overview
When we dump the memory of a process in Linux, we observe two memory regions in the output, namely vdso and vsyscall.
In this tutorial, we’ll discuss what vdso and vsyscall are. Firstly, we’ll examine the address space of an example application, and see the vdso and vsyscall regions. Then, after a brief discussion of system calls, we’ll learn the importance of vdso and vsyscall in speeding up system calls for user space applications.
2. An Example
We’ll use the following C program, wait_enter.c:
#include <stdio.h>
void main()
{
printf("Press enter to exit\n");
getchar();
}
The program waits until we press Enter. Let’s build it using gcc to generate the executable wait_enter:
$ gcc -o wait_enter wait_enter.c
Having built the executable, let’s run it:
$ ./wait_enter
Press enter to exit
The running process waits for us to press Enter as expected. Now, let’s find the PID of the running process using ps and grep:
$ ps -ef | grep wait_enter | grep -v grep
centos 3095 2659 0 13:07 pts/0 00:00:00 ./wait_enter
We used grep wait_enter and grep -v grep to filter the output of ps -ef. The PID of the process is 3095.
Now, let’s list the memory regions of the process using the /proc filesystem:
$ cat /proc/3095/maps | grep -E 'vdso|vsyscall'
7fff5c1a8000-7fff5c1aa000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
The /proc/
As is apparent from the output, there are two memory regions with the names vdso and vsyscall. Additionally, after running wait_enter once more, let’s check its memory regions:
$ ps -ef | grep wait_enter | grep -v grep
centos 3095 2659 0 13:07 pts/0 00:00:00 ./wait_enter
centos 3213 2706 0 13:08 pts/1 00:00:00 ./wait_enter
$ cat /proc/3213/maps | grep -E 'vdso|vsyscall'
ffcaa7fe000-7ffcaa800000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
The process with PID 3213 is the second process spawned by running wait_enter. The address ranges of the vdso memory regions are different for the two processes. However, the address ranges of the vsyscall memory regions are the same in the two processes, which is ffffffffff600000-ffffffffff601000.
3. Understanding vdso and vsyscall
Before discussing vdso and vsyscall in this section, we’ll shortly see what system calls are.
3.1. System Calls
System calls are service requests of applications from the kernel. They can be slow because of the context switches between the user and kernel spaces. If there are frequently used system calls, the performance of an application might get even worse.
An example of a frequently used system call is gettimeofday(). An application might call gettimeofday() directly, but it might also be called indirectly by the C standard library.
3.2. vdso
Since system calls like gettimeofday() might be frequently called, the kernel automatically maps the most frequently used system calls into the address space of an application using the memory region vdso, which is the abbreviation of virtual dynamic shared object. A call to gettimeofday() becomes like a normal library call in this case.
Let’s extract the vdso region of the already running process with PID 3095 using the dd command:
$ dd if=/proc/3095/mem of=out.vdso bs=1 count=$((0x7fff5c1aa000-0x7fff5c1a8000)) skip=$((0x7fff5c1aa000))
8192+0 records in
8192+0 records out
8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00695626 s, 1.2MB/s
The if option of dd specifies the input file, which is /proc/3095/mem in our case. Having extracted vdso to out.vdso, let’s check its type using the file command:
$ file out.vdso
out.vdso: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=4caa2879ca7056c7706fe028473d304d44c0febd, stripped
As we see, the extracted memory region is a shared library in the ELF format. Let’s list the symbols in the ELF file using readelf:
$ readelf -Ws out.vdso
Symbol table '.dynsym' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000c20 5 FUNC WEAK DEFAULT 11 clock_gettime@@LINUX_2.6
2: 0000000000000be0 5 FUNC GLOBAL DEFAULT 11 __vdso_gettimeofday@@LINUX_2.6
3: 0000000000000c30 96 FUNC WEAK DEFAULT 11 clock_getres@@LINUX_2.6
4: 0000000000000c30 96 FUNC GLOBAL DEFAULT 11 __vdso_clock_getres@@LINUX_2.6
5: 0000000000000be0 5 FUNC WEAK DEFAULT 11 gettimeofday@@LINUX_2.6
6: 0000000000000bf0 41 FUNC GLOBAL DEFAULT 11 __vdso_time@@LINUX_2.6
7: 0000000000000cc0 156 FUNC GLOBAL DEFAULT 11 __vdso_sgx_enter_enclave@@LINUX_2.6
8: 0000000000000bf0 41 FUNC WEAK DEFAULT 11 time@@LINUX_2.6
9: 0000000000000c20 5 FUNC GLOBAL DEFAULT 11 __vdso_clock_gettime@@LINUX_2.6
10: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS LINUX_2.6
11: 0000000000000c90 37 FUNC GLOBAL DEFAULT 11 __vdso_getcpu@@LINUX_2.6
12: 0000000000000c90 37 FUNC WEAK DEFAULT 11 getcpu@@LINUX_2.6
gettimeofday() exists within the symbols. Additionally, clock_gettime(), clock_getres(), getcpu() and time() are other system calls in vdso.
Let’s check the soname in the extracted vdso region again using readelf:
$ readelf -s out.vdso | grep SONAME
0x000000000000000e (SONAME) Library soname: [linux-vdso.so.1]
The soname is linux-vdso.so.1. This is listed in the shared library dependencies of wait_enter:
$ ldd wait_enter
linux-vdso.so.1 (0x00007ffc7d955000)
libc.so.6 => /lib64/libc.so.6 (0x00007fc723af3000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc723d0a000)
As the output of ldd wait_enter shows, the first line in the output contains the vdso region, namely, linux-vdso.so.1. But it isn’t a physical file on the file system. The other dependencies, libc.so.6 and ld-linux-x86-64.so.2, are the C standard library and the run-time linker that runs the program, respectively.
3.3. vsyscall
vsyscall is like vdso. It’s the oldest mechanism in the kernel to accelerate the execution of some system calls. It speeds up the calls to gettimeofday(), time() and getcpu() system calls.
vsyscall has been replaced by vdso over time. The main difference between them is that vsyscall is static in memory, i.e., it has always the same address.
Having always the same address might have security risks, so the address of vdso is random. vdso also maps memory pages into processes in a shared object form as we’ve already seen.
vsyscall still exists in memory mappings just as a fallback for vdso.
4. Conclusion
In this article, we discussed what vdso and vsycall are. Firstly, we saw that these are memory regions belonging to the address space of every process in the user space.
Then, we learned what system calls are. Finally, we saw that vdso and vsyscall are mechanisms for speeding up the most frequently used system calls.