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
!