Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a system call happen in a C program? [duplicate]

Can a system call happen in a C program? Consider this:

int main()
{
    int f = open("/tmp/test.txt", O_CREAT | O_RDWR, 0666);
    write(f, "hello world", 11);
    close(f);

    return 0;
}

In this sample code, open, write, and close are library functions. During my searches I conclude that they are functions not system calls. Each of these functions (open, write, and close) make a system call.

Questions

  • Are my conclusions above all correct?
  • Can system calls happen in C programs?
  • If system calls can happen in C programs, when do they happen? Please give an example.
  • Can the use of a library function versus directly making a system call be controlled by compile options? For example, is it possible that can we compile the above program with some options so that the write and read system calls are made directly, and if we compile it with different options, it calls library functions instead?
like image 226
taranom Avatar asked Mar 01 '23 19:03

taranom


1 Answers

System call background

A system call, according to Wikipedia, is a "programmatic way in which a computer program requests a service from the kernel of the operating system on which it is executed".

Another way of understanding a system call is as a user space program making a request to the operating system kernel to perform some task on behalf of the user space program. The full set of system calls provided by the kernel is analogous (in some ways) to an API provided by the kernel to user space.

As system calls are a low level interface to the kernel, correctly providing their arguments can be error prone or even dangerous. For these reasons, C library authors provide simpler and safer wrapper functions for a significant portion of a kernel's set of system calls.

These wrapper functions take a simplified argument set and then derive the appropriate values to pass on to the kernel so the system call can be executed.

Example

Note: This example is based on compiling and running a C program with gcc on Linux. The system calls, library functions, and output may differ on other POSIX or non-POSIX operating systems.

I will attempt to show how to see when system calls are being made with a simple example.

#include <stdio.h>

int main() {
    write(1, "Hello world!\n", 13);
}

Above we have a very simple C program that writes the string Hello world!\n to stdout. If we compile and then execute this program with strace, we see the following (note the output may look different on other computers):

$ strace ./hello > /dev/null
execve("./hello", ["./hello"], 0x7fff083a0630 /* 58 vars */) = 0
<a bunch of output we aren't interested in>
write(1, "Hello world!\n", 13)          = 13
exit_group(0)                           = ?
+++ exited with 0 +++

strace is a Linux program that intercepts and displays all system calls made by a program, as well as the arguments provided to the system calls and their return values.

We can see here that, as expected, the write system call was made with the expected arguments. Nothing strange yet.

Another Linux tracing program is ltrace, which intercepts dynamic library calls made by a program, and displays their arguments and return values.

If we run the same program with ltrace, we see this:

$ ltrace ./hello > /dev/null
write(1, "Hello world!\n", 13)                                = 13
+++ exited (status 0) +++

This tells us that the write library function was executed. This means that the C code first called the write library function, which then in turn called the write system call.

Suppose now that we want to explicitly make a write system call without calling the write library function. (This is inadvisable in normal use, but useful for illustration.)

Here is the new code:

#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>

int main() {
    syscall(SYS_write, 1, "Hello world!\n", 13);
}

Here we directly call the syscall library function, telling it we want to execute the write system call.

After recompiling, here is the output of strace:

$ strace ./hello > /dev/null 
execve("./hello", ["./hello"], 0x7ffe3790a660 /* 58 vars */) = 0
<a bunch of output we aren't interested in>
write(1, "Hello world!\n", 13)          = 13
exit_group(0)                           = ?
+++ exited with 0 +++

We can see the write system call is made as before as expected.

If we run ltrace we see the following:

$ ltrace ./hello > /dev/null 
syscall(1, 1, 0x560b30e4d704, 13)                             = 13
+++ exited (status 0) +++

So the write library function is no longer being called, but we are still making a library function call. Now we are making a call to the syscall library function instead of the write library function.

There may be a way to directly make a system call from a user space C program without calling any library functions, and if there is a way I believe it would be very advanced.

Detecting when a C program makes system calls

In general, nearly every non-trivial C program makes at least one system call. This is because user space does not have direct access to kernel memory or to the computer's hardware. User space programs have indirect access to kernel memory and the hardware through system calls.

To identify if a compiled C program (or any other program on Linux) makes a system call, and to identify which system calls it makes, simply use strace.

Are there compiler options to prevent calling the library wrapper functions for system calls?

You can compile your C program (assuming you are using gcc) with the -nostdlib option. This will prevent linking the C standard library as part of producing your executable. However, then you would need to write your own code to make system calls.

like image 181
Shane Bishop Avatar answered Mar 05 '23 17:03

Shane Bishop