- Sys syscall h linux
- ОПИСАНИЕ
- ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
- ЗАМЕЧАНИЯ
- Требования, зависящие от архитектуры
- Архитектурные соглашения по вызовам
- ПРИМЕР
- Implementing a Linux Syscall
- Clone the kernel code
- Preparing the development environment
- Implementing the syscall
- Extend the system call table
- Extend the syscall header file
- Build the kernel
- Installing the kernel in the VM
- Testing the syscall
- Conclusion
- Where do you find the syscall table for Linux?
- 5 Answers 5
Sys syscall h linux
#define _GNU_SOURCE /* см. feature_test_macros(7) */
#include
#include /* для определений SYS_xxx */
long syscall(long number, . );
ОПИСАНИЕ
syscall() — это маленькая библиотечная функция, которая делает системный вызов, чей интерфейс ассемблерного языка указывается в number, с дополнительными аргументами. Выполнение syscall() нужно, например, для запуска системного вызова, у которого нет обёрточной функции в библиотеке C. При вызове syscall() сохраняет регистры ЦП до выполнения системного вызова, восстанавливает регистры при возврате из системного вызова и если возникла ошибка, то сохраняет любой код, полученный от системного вызова, в errno(3). Символьные константы для системных вызовов можно найти в заголовочном файле .
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
Возвращаемое значение определяется вызываемым системным вызовом. При успешном выполнении обычно возвращается 0. При ошибке возвращается -1, при этом код ошибки сохраняется в errno.
ЗАМЕЧАНИЯ
Требования, зависящие от архитектуры
Каждый ABI архитектуры имеет свои собственные требования по передаче аргументов системного вызова в ядро. Для системных вызовов, имеющих обёртку в glibc (большинство системных вызовов), копирование аргументов в правильные регистры с учётом архитектуры выполняется в самой glibc. Однако при выполнении системного вызова через syscall(), вызывающий сам должен учитывать особенности архитектуры; чаще всего это относится к 32-битным архитектурам. Например, на архитектуре ARM Embedded ABI (EABI) 64-битное значение (long long) должно быть выровнено по чётной паре регистров. То есть, при использовании syscall() вместо обёрточной функции glibc системный вызов readahead() на ARM вызывался бы с учётом EABI следующим образом:
syscall(SYS_readahead, fd, 0, (unsigned int) (offset >> 32), (unsigned int) (offset & 0xFFFFFFFF), count);
Так как смещение аргумента 64 бита, и первый аргумент (fd) передаётся в регистре r0, вызывающий должен разделить и выровнять 64-битное значение так, чтобы оно передавалось в паре регистров r2/r3. Это выполняется вставкой пустого значения в r1 (второго аргумент 0). Подобные сложности можно видеть на MIPS с O32 ABI, на PowerPC с 32-битным ABI и на Xtensa. Это относится к системным вызовам fadvise64_64(2), ftruncate64(2), posix_fadvise(2), pread64(2), pwrite64(2), readahead(2), sync_file_range(2) и truncate64(2).
Архитектурные соглашения по вызовам
В каждой архитектуре есть собственный способ передачи аргументов вызову ядра. Особенности различных архитектур перечислены в двух таблицах ниже. Поля первой таблицы: инструкция для перехода в режим ядра (может быть не быстрым или лучшим способом перехода в ядро, лучше использовать vdso(7)), регистр для указания номера системного вызова, регистр возврата результата работы системного вызова и регистр сигнализации ошибки. Во второй таблице показаны регистры, которые используются для передачи аргументов в системный вызов. Заметим, что эти таблицы не описывают полное соглашение о вызове — некоторые архитектуры могут затирать другие регистры и это здесь не описано.
ПРИМЕР
#define _GNU_SOURCE #include #include #include #include int main(int argc, char *argv[])
Implementing a Linux Syscall
Sometimes, issues can only be fixed in a layer below the abstractions you rely on usually. Understanding the whole stack as much as possible is a great advantage when a bug pops up. For this reason, I decided to implement a simple Linux syscall to become familiar with the Linux kernel’s code organization.
That said, I don’t aspire to become a kernel hacker. The new syscall printk simply prints a given null-terminated message into the kernel log. All code changes referenced in this post can be found in this Pull Request.
Clone the kernel code
git clone --branch v5.8 --single-branch --no-tags https://github.com/torvalds/linux.git git checkout -b add-printk-syscall
Note the branch option allows tags, too.
Preparing the development environment
We will test our custom kernel on a VM to protect our host machine’s stability. In my case, the host runs Ubuntu 20.04. The modified Kernel will be tested in a VM running Ubuntu 18.04.
Install the packages needed to compile the kernel. More information about the packages required for compilation can be found on kernel.org/.
sudo apt-get install git build-essential kernel-package fakeroot libncurses5-dev libssl-dev ccache libncurses-dev bison flex gcc make git vim
Install virtualization software for the VM:
sudo apt-get install -y qemu-kvm uvtool-libvirt
Fetch an Ubuntu cloud image, a compact pre-installed disk images. With this, we don’t have to run the OS install:
uvt-simplestreams-libvirt sync --source https://cloud-images.ubuntu.com/daily release=focal arch=amd64
uvt-kvm create kerneltest arch=amd64 release=focal --memory 4096 --cpu 2 --disk 15 --unsafe-caching
Install ccache to heavily reduce build times after the first build. Making changes iteratively and playing around with the code is a lot faster with ccache.
You can check the number of cache misses and hits with “ccache -s”.
Implementing the syscall
This is the code of the syscall itself (kernel/sys_printk.c):
1 2 3 4 5 6 7 8 9 10 11 12 13
#include #include SYSCALL_DEFINE1(printk, char *, msg) < char buf[256]; long copied = strncpy_from_user(buf, msg, sizeof(buf)); if (copied 0 || copied == sizeof(buf)) < return -EFAULT; > printk(KERN_INFO "Msg: \"%s\"\n", buf); return 0L; >
The syscall is defined with the SYSCALL_DEFINE1 macro because it takes one argument. The macro’s first argument is the syscall name. The second and third macro arguments describe the type and name of the syscall’s first argument.
Accessing user space memory from the kernel is a bad idea. Therefore, we copy the content of the message passed as argument to the syscall into kernel space with “strncpy_from_user”.
Next, we hook the new file into the build system in kernel/Makefile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# SPDX-License-Identifier: GPL-2.0 # # Makefile for the linux kernel. # obj-y = fork.o exec_domain.o panic.o \ cpu.o exit.o softirq.o resource.o \ sysctl.o sysctl_binary.o capability.o ptrace.o user.o \ signal.o sys.o umh.o workqueue.o pid.o task_work.o \ extable.o params.o \ kthread.o sys_ni.o nsproxy.o \ notifier.o ksysfs.o cred.o reboot.o \ async.o range.o smpboot.o ucount.o sys_printk.o .
Extend the system call table
Add an entry for the printk syscall to vim arch/x86/entry/syscalls/syscall_64.tbl :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
. 434 common pidfd_open sys_pidfd_open 435 common clone3 sys_clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 64 printk sys_printk # # x32-specific system call numbers start at 512 to avoid cache impact # for native 64-bit operation. The __x32_compat_sys stubs are created # on-the-fly for compat_sys_*() compatibility system calls if X86_X32 # is defined. # 512 x32 rt_sigaction compat_sys_rt_sigaction 513 x32 rt_sigreturn compat_sys_x32_rt_sigreturn 514 x32 ioctl compat_sys_ioctl .
Extend the syscall header file
Add the following line to the syscall header file include/linux/syscalls.h
/* kernel/sys_printk.c */ asmlinkage long sys_printk(const char __user *msg);
Build the kernel
Create the kernel configuration with the following command. You can pick drivers, algorithms and customize the kernel with a huge number of options.
Next, we build the kernel and create a debian package containing the kernel. We can easily install the debian package in the VM.
# Disable debug info, reduces compile time required space scripts/config --disable DEBUG_INFO make -j `$nproc` bindeb-pkg CC="ccache gcc"
The -j or –jobs option specifies how many makefile recipes can be executed in parallel. A sensible value reduces the compile time a lot. I set the number of jobs to $nproc, the number of CPU cores in my laptop (including hyper-threads). In my case, $nproc is 12.
The CC variable stands for “c compiler”. We use ccache because it caches compilation artifacts which speeds up subsequent builds.
Installing the kernel in the VM
Move the Debian packages into the VM:
scp ../linux-*.deb ubuntu@$(uvt-kvm ip kerneltest):~
Install the Debian packages in the VM:
uvt-kvm ssh kerneltest -- sudo dpkg -i /home/ubuntu/*.deb
uvt-kvm ssh kerneltest -- sudo reboot
Check the new kernel is running with:
# Wait until VM boot is ready after the reboot uvt-kvm ssh wait kerneltest # Returns 5.8.0 uvt-kvm ssh kerneltest -- uname -r
Testing the syscall
For testing purposes, we will create a small test program that uses our new syscall. There is no libc wrapper function for the new syscall printk. To call our syscall printk, we use the “syscall” function. It’s arguments are the syscall number specified in the syscall table and the arguments for the syscall.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#include #include #include #include #define __NR_printk 440 int main(int argc, char *argv[]) < if (argc != 2) < fprintf(stdout, "Please provide a msg (with quotes if multiple words)\n"); return 1; > char* msg = argv[1]; int result = syscall(__NR_printk, msg); if (result == -1) < fprintf(stderr, "syscall failed, errno = %d\n", errno); > else < fprintf(stdout, "syscall succeeded, run \"dmesg\" to see the message in the kernel logs\n"); > return 0; >
Comspile the test program “syscall-printk-test” with “make”. We run the program on our development host to verify it fails as our kernel doesn’t have the new syscall.
make ./syscall-printk-test "hello world" syscall failed, errno = 38
Let’s check the meaning of errno 38.
sudo apt-get install errno errno 38 ENOSYS 38 Function not implemented
As expected, the test program fails because our kernel doesn’t have a syscall with number 440.
Next, we run the test program in our VM with the custom kernel.
1 2 3 4 5 6 7 8 9 10 11 12 13
# Move binary into VM scp syscall-printk-test ubuntu@$(uvt-kvm ip kerneltest):~ # Run the test program uvt-kvm ssh kerneltest -- "~/syscall-printk-test 'hello kernel'" syscall succeeded, run "dmesg" to see the message in the kernel logs # Check the kernel log in the VM uvt-kvm ssh kerneltest -- dmesg . [ 11.495005] audit: type=1400 audit(1599501627.676:39): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap.lxd.hook.remove" pid=838 comm="apparmor_parser" [ 55.784973] Msg: "hello kernel"
The message was written to the kernel log successfully.
Conclusion
As usual, taking a look under the hood of a technology demystifies it — there is no magic involved. Building and installing the Linux kernel was a lot simpler than I expected. Modifying the kernel was slightly trickier because of all the historical cruft. Surely, having a basic understanding of the kernel’s source code organization will come in handy in the future.
Where do you find the syscall table for Linux?
Could someone tell me the difference between these unistd files. Explain how unistd.h works? And what the best method for finding the syscall table?
5 Answers 5
When I’m investigating this kind of thing, I find it useful to ask the compiler directly (see Printing out standard C/GCC predefined macros in terminal for details):
printf SYS_read | gcc -include sys/syscall.h -E -
This shows that the headers involved (on Debian) are /usr/include/x86_64-linux-gnu/sys/syscall.h , /usr/include/x86_64-linux-gnu/asm/unistd.h , /usr/include/x86_64-linux-gnu/asm/unistd_64.h , and /usr/include/x86_64-linux-gnu/bits/syscall.h , and prints the system call number for read , which is 0 on x86-64.
You can find the system call numbers for other architectures if you have the appropriate system headers installed (in a cross-compiler environment). For 32-bit x86 it’s quite easy:
printf SYS_read | gcc -include sys/syscall.h -m32 -E -
which involves /usr/include/asm/unistd_32.h among other header files, and prints the number 3.
So from the userspace perspective, 32-bit x86 system calls are defined in asm/unistd_32.h , 64-bit x86 system calls in asm/unistd_64.h . asm/unistd_x32.h is used for the x32 ABI.
uapi/asm-generic/unistd.h lists the default system calls, which are used on architectures which don’t have an architecture-specific system call table.
In the kernel the references are slightly different, and are architecture-specific (again, for architectures which don’t use the generic system call table). This is where files such as arch/x86/entry/syscalls/syscall_64.tbl come in (and they ultimately end up producing the header files which are used in user space, unistd_64.h etc.). You’ll find a lot more detail about system calls in the pair of LWN articles on the topic, Anatomy of a system call part 1 and Anatomy of a system call part 2.