Kernel Development Learning Pipeline
P1 - Add a new syscall 🤫
Add a new system call to the kernel
Outcomes:
- Get comfortable with the concept of a syscall from both kernelspace and userspace
- Learn how userspace programs make system calls
- Learn how those calls are handled by the kernel
- Understand how C relates to assembly language
- Write a C program to call your system call
- Write the same program in the assembly language of your platform
What to submit:
- A patch which adds a directory named
firstname_lastname
to the assignment repo.
- The first patch should add your kernel patch as a .patch file to our repo.
- Generate a single patch from your commit to the Linux kernel using the command
git format-patch -1
within your linux repo
- Take the
.patch
file outputted, and put it in your named directory (e.g. cp 0001*.patch ~/ILKD_Assignments/p1/your_name/
- Add the email patch itself to the staged git files and commit this.
- The second patch adds your C program to the named directory
- Include a Makefile that can compile it
- Make sure to have compiler warnings enabled (at least
-Wall
but ideally -Wextra
and -Wpedantic
too, or even -Weverything
if you use clang)
- Make sure that your code doesn’t have any warnings or errors.
- The third patch adds your assembly program to your named directory.
- Edit the Makefile to add rules for assembling and linking your assembly program using
as
and ld
- The fourth patch adds the output of running
strace
on your assembly program as it uses the new syscall.
- Don’t forget a cover letter.
- Submit your patches to programming1@kdlp.underground.software
Procedure:
- Start by adding a new syscall to your copy of the linux kernel.
- Create a new
.c
file in the kernel
directory within the linux repo named kdlp.c
- Using the appropriately numbered
SYSCALL_DEFINE
macro, define a syscall entry function for a new kdlp
syscall in the kdlp.c
file that behaves as follows:
- Takes two arguments, an appropriately marked pointer to a buffer from userspace of type
char *
, an integer specifying the size of the buffer of type size_t
.
- Formats a message into a local buffer that includes at least the student’s name and the name of the current task that is running on the CPU (see the
get_task_comm
macro).
- Determines the amount of data to send - equal to the lesser of the size of the kernel message and how much space the user provided (see
min
macro).
- Copies that many bytes from the local message buffer into the user pointer (see the
copy_to_user
function).
- Returns a negative error code of
-EFAULT
(errno 14, bad address) if the copy is not successful.
- Otherwise, returns the amount of data that was copied.
- The syscall implementation should take care to prevent any possibility of a buffer overflow.
- Modify the Makefile for the kernel subdirectory in
kernel/Makefile
to add kdlp.o
to the list of objects that are always compiled (add it to the end of the obj-y
variable).
- Add an
asmlinkage
declaration of sys_kdlp
to the bottom of include/linux/syscalls.h
(take inspiration from the numerous examples of other syscalls in that file).
- Add an entry to the syscall table for your architecture.
- On x86-64, this is in a special table format located at
arch/x86/entry/syscalls/syscall_64.tbl
for historical reasons.
- You need to pick a number for it, put it in order with the next highest number after the last
common
entry in the table
- Your syscall will be
common
(i.e. shared between 32 and 64 bit userspace programs transparently by the kernel)
- The name is
kdlp
and the entry point is sys_kdlp
- Take inspiration from the other nearby syscalls
- On aarch64 the list comes from the modern shared table located in
include/uapi/asm-generic/unistd.h
.
- Find where
__NR_syscalls
is defined and increment the value.
- Just above that line, add a
#define
for __NR_kdlp
with the next syscall number that is free
- add an invocation of the
__SYSCALL
macro with the __NR_kdlp
number and and the sys_kdlp
entry point.
- Take inspiration from the other nearby syscalls.
- Update the extra version tag in the main kernel Makefile so you can tell this kernel that has your syscall apart from the other kernel you compiled.
- Update the config using
make oldconfig
(this should only take a few seconds, and shouldn’t require you to answer any questions).
- Compile your new kernel with
make -j $(nproc)
- If the command finishes, make sure it was successful:
echo $?
should output 0.
- If it does not, re run
make
without -j $(nproc)
to get a better error message.
- Fix whatever issue you see, and re run
make -j $(nproc)
.
- Repeat this process of checking the result and fixing any errors until it compiles successfully.
- Install your new kernel and its modules
sudo make -j $(nproc) modules_install install
- Reboot your vm and pick the new kernel.
- If all is well it should boot successfully.
- Congrats, you just wrote your first kernel code!
- Make a patch out of your kernel code.
- Find all untracked files, and files you modified by looking at the output of
git status
within your linux kernel repository.
- Add them to the staging area with
git add
and then run git commit -s
and provide a message.
- Format the resulting patch as a
.patch
file with git format-patch -1
.
- You don’t need
--rfc
or -v1
or --cover-letter
because this .patch
is not getting emailed directly
- Copy the
.patch
file into your named folder within the p1
folder in the ILKD Assignments Repo.
- You can make your first commit for the assignment at this time.
- Write a C program that invokes the new syscall.
- Use the
syscall(2)
function from the C standard library to invoke your syscall.
- Pass the number you gave it in the table as the first argument and then pass the rest of the arguments.
- Check for any errors and print an appropriate message if they occur.
- Otherwise, print the message you received from the kernel using the
write(2)
syscall.
- You can now make your second commit for the assignment at this time.
- Write an assembly language program that behaves exactly the same as your C program with the following minor differences because you are not using the C standard library:
- Your program starts at a label named
_start
instead of a function named main
- you need to explicitly call the
exit
system call to terminate your program (trying to return from _start
will segfault)
- You explicitly invoke the system calls by following the conventions of the linux syscall ABI (application binary interface)
- For x86-64:
- Arguments go into specific registers in order (
%rdi
, %rsi
, %rdx
, %r10
, %r8
, %r9
)
- The syscall number goes into
%rax
- The syscall is invoked using the
syscall
instruction
- The return value is in
%rax
and is either a non-negative return value, or a negative error number (errno variable is a C library concept)
- For aarch64:
- Arguments go into specific registers in order (
x0
, x1
, x2
, x3
, x4
, x5
)
- The syscall number goes into
x8
- The syscall is invoked using the
svc #0
instruction
- The return value is in
x0
and is either a non-negative return value, or or negative error number (errno variable is a C library concept)
- Make sure to update Makefile to assemble and link your program
- You can now make your third commit for the assignment at this time.
- Test your C program and assembly program.
- They should both print a message including your name and the name of executable file when you try them on the kernel you just compiled.
- Try running
strace
on your assembly program. Capture the output into a file using 2> strace_log
on the end of the command and add it to the repo as your fourth patch.
- You can also test the error case by booting into a kernel without your syscall (i.e. the regular upstream kernel you compiled or the one packaged by fedora)
- the kernel will return
-ENOSYS
to indicate that the syscall is not implemented.
- both programs should print some sort of error message and exit early with a nonzero code.
The following pages will be of interest to a student:
Conventions:
-
Assembly files that must be pre-processed use the extension .S
-
Assembly files that are ready to be assembled use the extension .s
Refer to the Linux kernel documentation about adding syscalls for further guidance.
Submission Guidelines