Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the type of system call arguments on Linux?

I want to write a generic function that does a system call. Something like

long my_syscall2(long number, long arg1, long arg2);

I want it to be as portable as possible. The implementation is obviously different for all architectures. Does the signature of the function also need to be different? Can I use long or should I use something else?

Here are the possible solutions that I found:

  • The kernel uses some dark magic: (__SYSCALL_DEFINEx calls __SC_LONG to get the type, __SC_LONG contains magic). I heard somewhere that types in user space aren't always the same as in kernel space, so I don't know if I can use it.
  • musl-libc uses long for all architectures that it supports except x32: (defined in [arch]/syscall_arch.h).
  • I could find documentation for all processor architectures and compilers that I want to support, look up register sizes and integer types sizes and pick any integer type that has the same size as registers.

So I guess the question is "Is there some rule that says 'type of system call arguments are always long with some exceptions like x32' or do I need to look up documentation for every architecture and compiler?"

Edit: I know that some system calls take pointers and other types as parameters. I want to write generic functions that can call any system call, with generic parameter types. These generic parameter types should be big enough to hold any of the actual parameter types. I know it's possible because syscall() function exists.

Edit2: Here is another partial solution to this problem.

Implementations of these functions currently look like this:

static __inline long my_syscall2(long number, long arg1, long arg2)
{
    unsigned long ret;
    __asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(number), "D"(arg1), "S"(arg2)
                      : "rcx", "r11", "memory");
    return ret;
}

The interesting part is "=a"(ret), this means that syscall return value that is stored in register a should be saved into variable ret. Instead of writing a function that creates local variable, makes syscall, saves its return value into the variable and returns the variable I could write a macro that makes syscall and stores the result into a variable provided by caller. It would look like this:

#define my_syscall2(RET, NUMBER, ARG1, ARG2) \
  __asm__ __volatile__ ("syscall" : "=a"(RET) : "a"(NUMBER), "D"(ARG1), "S"(ARG2) \
                      : "rcx", "r11", "memory");

And it would be used like this:

long result;
void * arg1;
int arg2;
my_syscall2(result, <syscall number>, arg1, arg2);

This way I don't need to know register size and integer type that is big enough to hold a value of the register.

like image 210
alkedr Avatar asked Feb 25 '16 13:02

alkedr


People also ask

How many system calls are there in Linux?

Many modern operating systems have hundreds of system calls. For example, Linux and OpenBSD each have over 300 different calls, NetBSD has close to 500, FreeBSD has over 500, Windows has close to 2000, divided between win32k (graphical) and ntdll (core) system calls while Plan 9 has 51.

What is system call and its types?

In general, system calls are available as assembly language instructions. They are also included in the manuals used by the assembly level programmers. System calls are usually made when a process in user mode requires access to a resource. Then it requests the kernel to provide the resource via a system call.

What is system calls in Linux with examples?

System calls are how a program enters the kernel to perform some task. Programs use system calls to perform a variety of operations such as: creating processes, doing network and file IO, and much more.


3 Answers

System call arguments are passed in registers. There size is thus limited to the size of a CPU register. That is, 32 bit on a 32 bit architecture, 64 bit on a 64 bit architecture. Floating point numbers cannot be passed to the kernel this way. Traditionally the kernel does not use floating point instructions (and may not be able to as the FPU state is usually not saved on entry to the kernel) so try to avoid floating point numbers in your own system calls.

System calls that use arguments of smaller types zero or sign extend them. System calls that use larger argument types may split the argument into multiple registers.

System calls (like mmap()) that have to many parameters may be implemented by passing the parameters as a pointer to a structure, but this has a measurable performance overhead, so avoid designing system calls with more than five parameters.

At the end of the day, use the types appropriate for the value you want to send. Let the libc deal with putting the data in the right places.

like image 112
fuz Avatar answered Nov 12 '22 03:11

fuz


There's no general solution. If you want to make your code ultra-multiarch you can just do something like that:

#if ARCH_WITH_32BIT_REGS
typedef uint32_t reg_size_int_t;
#elif ARCH_WITH_64BIT_REGS
typedef uint64_t reg_size_int_t;
#elif ARCH_WITH_16BIT_REGS
typedef uint16_t reg_size_int_t;
....
#endif

reg_size_int_t syscall_1( reg_size_t nr, reg_size_t arg0);
...

But for most common-used architectures size of register is equal to long.

like image 43
Andrey Rogov Avatar answered Nov 12 '22 03:11

Andrey Rogov


I suggest that you use the existing syscall system call, rather than try to write one on your own. It seems to do exactly what you want. Look at the "Architecture-specific requirements" section of the manual page for a discussion of the valid questions you raised.

like image 26
Diomidis Spinellis Avatar answered Nov 12 '22 05:11

Diomidis Spinellis