A few weeks ago I wanted to confirm if errno was a libc
abstraction or a kernel feature. The glibc docs are deliberately vague
on the topic, so experimentation seemed like the best course.
static int use_wrapper(int cmd, union bpf_attr *attr, unsigned int size)
{
long ret;
/* Clear errno */
errno = 0;
ret = syscall(__NR_bpf, cmd, attr, size);
if (ret < 0)
printf("wrapped syscall failed, ret=%d, errno=%d\n", ret, errno);
else
printf("wrapped syscall succeeded\n");
}Here we make a syscall to bpf(2). Mostly b/c I’m quite
familiar with it.
static int use_raw(int cmd, union bpf_attr *attr, unsigned int size)
{
long ret;
/* Clear errno */
errno = 0;
__asm__(
"movq %1, %%rax\n" /* syscall number */
"movq %2, %%rdi\n" /* arg1 */
"movq %3, %%rsi\n" /* arg2 */
"movq %4, %%rdx\n" /* arg3 */
"syscall\n"
"movq %%rax, %0\n"
/* retval */
: "=r"(ret)
/* input operands */
: "r"((long)__NR_bpf), "r"((long)cmd), "r"((long)attr), "r"((long)size)
/* clobbers */
: "rax", "rdi", "rsi", "rdx"
);
/* Check return value */
if (ret < 0)
printf("raw syscall failed, ret=%d, errno=%d\n", ret, errno);
else
printf("raw syscall succeeded\n");
}use_raw() does the exact same thing as
use_wrapper(), except we use inline assembly. Even if you
don’t know assembly or inline assembler, it should be quite clear the
semantics from the comments.
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <linux/bpf.h>
[..]
int main()
{
int cmd = BPF_PROG_LOAD;
union bpf_attr *attr = NULL;
unsigned int size = 0;
use_raw(cmd, attr, size);
use_wrapper(cmd, attr, size);
}Finally, we put it together in main(). Consider this
output:
$ gcc main.c
$ ./a.out
raw syscall failed, ret=-1, errno=0
wrapped syscall failed, ret=-1, errno=1
$ sudo ./a.out
raw syscall failed, ret=-7, errno=0
wrapped syscall failed, ret=-1, errno=7
Notice how errno is always 0 when skiping libc. Further
notice how the return value of the raw syscall flipped to a positive
value and stored into errno by libc.
So that settles it – errno is a libc abstraction.