A neat trick I found in glibc

While digging in and doing research for my previous post, I came across a really cool trick that glibc uses to provide a single macro for invoking system calls, no matter how many arguments they take. I thought I'd share the trick here, along with an explanation on how it works for those unfamiliar with the C preprocessor.

Here's a link to the source: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysdep.h;h=a19e84165b637d5832e6d02ef169ec810c7107b1;hb=ef321e23c20eebc6d6fb4044425c00e6df27b05f#l31

So, the macro in question is INTERNAL_SYSCALL_CALL(syscall_name, args...), and like I just mentioned, you can call it with any number of arguments (eg. INTERNAL_SYSCALL_CALL(getpid), INTERNAL_SYSCALL(write, fd, buffer, length), etc). Here's its definition, along with the definition of a helper macro __INTERNAL_SYSCALL_DISP:

#define __INTERNAL_SYSCALL_DISP(b,...) \
  __SYSCALL_CONCAT (b,__INTERNAL_SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__)

#define INTERNAL_SYSCALL_CALL(...) \
  __INTERNAL_SYSCALL_DISP (__INTERNAL_SYSCALL, __VA_ARGS__)

Here, all INTERNAL_SYSCALL_CALL is doing is calling that __INTERNAL_SYSCALL_DISP helper macro, which takes a prefix (__INTERNAL_SYSCALL in this case) and the arguments (__VA_ARGS__ is how the preprocessor passes variadic arguments around). __INTERNAL_SYSCALL_DISP then tacks the number of arguments given onto the prefix, using __INTERNAL_SYSCALL_NARGS (more about that coming up) to calculate the argument count - so in the case of INTERNAL_SYSCALL(write, fd, buffer, length) above, it concatenates __INTERNAL_SYSCALL and 3 to give us __INTERNAL_SYSCALL3. There are definitions for __INTERNAL_SYSCALL0 through __INTERNAL_SYSCALL7, each of which just invokes a syscall with the proper number of arguments in whatever way the processor architecture requires it.

__INTERNAL_SYSCALL_DISP then takes this generated name and calls it with the original arguments.

Now comes what I feel is the really cool part of the trick - __INTERNAL_SYSCALL_NARGS! This macro calculates the number of arguments it's been given, and the way it does so is rather clever and elegant - here's its definition:

#define __INTERNAL_SYSCALL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n
#define __INTERNAL_SYSCALL_NARGS(...) \
  __INTERNAL_SYSCALL_NARGS_X (__VA_ARGS__,7,6,5,4,3,2,1,0,)

All __INTERNAL_SYSCALL_NARGS does is invoke another helper macro __INTERNAL_SYSCALL_NARGS_X with its arguments via __VA_ARGS__ followed by the numbers from the maximum number of syscall arguments down to zero - it relies on __VA_ARGS__ "pushing" enough of those extra numbers out of the way so that the answer ends up getting assigned to n within __INTERNAL_SYSCALL_NARGS_X!

Published on 2024-07-24