test: Add userspace tests

This commit is contained in:
2026-10-27 23:11:15 +01:00
parent 196543c3d3
commit ba039a3e0a
13 changed files with 927 additions and 0 deletions
+1
View File
@@ -9,3 +9,4 @@ source "boot/Kconfig"
source "mem/Kconfig"
source "sched/Kconfig"
source "dev/Kconfig"
source "tests/Kconfig"
+5
View File
@@ -23,6 +23,7 @@ add_languages('c', native: false, required: true)
# Check if we're using a sane compiler.
c_compiler = meson.get_compiler('c')
python = import('python').find_installation('python3')
# We require a compiler that supports the GNU C dialect.
assert(c_compiler.has_define('__GNUC__'))
@@ -176,6 +177,10 @@ kernel = executable(
subdir('boot')
if kconfig.get('CONFIG_KTEST', 'n') == 'y'
subdir('tests')
endif
foreach name, v : modular_modules
shared_library(
name,
+7
View File
@@ -18,3 +18,10 @@ option(
value: '.config',
description: 'Relative path to the generated Kconfig file',
)
option(
'ovmf',
type: 'string',
value: '',
description: 'OVMF firmware image used to run the QEMU tests',
)
+10
View File
@@ -0,0 +1,10 @@
menu "Testing"
config KTEST
bool "Kernel test suite"
depends on BOOT_EFISTUB
default n
help
Builds the kernel unit tests. To run them, add "ktest=1" to the cmdline.
endmenu
+1
View File
@@ -0,0 +1 @@
@CMDLINE@
+121
View File
@@ -0,0 +1,121 @@
fs = import('fs')
assert(
kconfig.get('CONFIG_BOOT_EFISTUB', 'n') == 'y',
'CONFIG_KTEST depends on CONFIG_BOOT_EFISTUB',
)
assert(
target_machine.cpu_family() == 'x86_64',
'CONFIG_KTEST is only supported on x86_64',
)
# All userspace tests are a single executable, so they have to be idempotent.
utest_init = executable(
'init',
sources: files(
'user/lib/crt0.S',
'user/lib/utest.c',
'user/memory.c',
'user/system.c',
),
include_directories: include_directories('../include'),
c_args: [
'-ffreestanding',
'-nostdinc',
'-fno-pic',
'-fno-stack-protector',
'-masm=intel',
],
link_args: [
'-nostdlib',
'-static',
'-Wl,--no-dynamic-linker',
'-Wl,-z,max-page-size=0x1000',
'-Wl,-z,noexecstack',
'-Wl,--build-id=none',
],
pie: false,
)
test_initramfs = custom_target(
'initramfs.tar',
input: utest_init,
output: 'initramfs.tar',
command: [
find_program('tar'),
'--format=ustar',
'-cf', '@OUTPUT@',
'-C', '@OUTDIR@',
'init',
],
)
# Try to find an OVMF.
ovmf = get_option('ovmf')
if ovmf == ''
foreach path : [
'/usr/share/qemu/ovmf-x86_64-4m.bin',
'/usr/share/qemu/ovmf-x86_64.bin',
'/usr/share/ovmf/OVMF.fd',
'/usr/share/edk2/x64/OVMF.4m.fd',
'/usr/share/edk2/x64/OVMF.fd',
'/usr/share/edk2/ovmf/OVMF.fd',
]
if ovmf == '' and fs.is_file(path)
ovmf = path
endif
endforeach
endif
assert(ovmf != '', 'CONFIG_KTEST needs an OVMF image, set it with -Dovmf=')
qemu = [
find_program('qemu-system-x86_64'),
'-M', 'q35',
'-m', '512M',
'-smp', '4',
'-accel', 'kvm',
'-accel', 'tcg',
'-cpu', 'max',
'-nic', 'none',
'-display', 'none',
'-monitor', 'none',
'-serial', 'stdio',
'-no-reboot',
'-bios', ovmf,
]
test_images = {
'user': {
'cmdline': 'init=/init initrd=/initramfs.tar',
'files': [test_initramfs],
},
}
# Build an ESP with the cmdline file and an initramfs.
foreach name, image : test_images
conf = configure_file(
input: 'cmdline.in',
output: name + '.cmdline',
configuration: {'CMDLINE': image['cmdline']},
)
esp = [
zinnia_efi.full_path() + '=EFI/BOOT/BOOTX64.EFI',
conf.full_path() + '=EFI/zinnia/cmdline',
]
foreach file : image['files']
esp += file.full_path() + '=' + fs.name(file.full_path())
endforeach
test(
name,
python,
args: [files('run.py'), esp, '--', qemu],
depends: [zinnia_efi, image['files']],
protocol: 'tap',
suite: 'qemu',
timeout: 60,
)
endforeach
+37
View File
@@ -0,0 +1,37 @@
# SPDX-License-Identifier: ISC
import os
import re
import shutil
import subprocess
import sys
import tempfile
NOISE_RE = re.compile(r"\x1b\[[0-9;]*m|\r|^\[[ \d.]+\] (?:syslog\(\w+\): )?")
PLAN_RE = re.compile(r"^1\.\.\d+")
TAP_RE = re.compile(r"^(?:ok|not ok|#|1\.\.\d+|Bail out!)")
sep = sys.argv.index("--")
with tempfile.TemporaryDirectory(prefix="zinnia-esp-") as esp:
for spec in sys.argv[1:sep]:
src, dest = spec.split("=", 1)
path = os.path.join(esp, dest)
os.makedirs(os.path.dirname(path), exist_ok=True)
shutil.copyfile(src, path)
proc = subprocess.Popen(
sys.argv[sep + 1 :] + ["-drive", f"format=raw,file=fat:{esp},if=virtio,readonly=on"],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
text=True,
errors="replace",
)
for line in proc.stdout:
line = NOISE_RE.sub("", line).rstrip()
print(line if TAP_RE.match(line) else f"# {line}", flush=True)
if PLAN_RE.match(line):
proc.kill()
proc.wait()
sys.exit(0)
proc.wait()
sys.exit(1)
+18
View File
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: ISC
.intel_syntax noprefix
.text
.globl _start
.type _start, @function
_start:
xor ebp, ebp
and rsp, -16
call main
mov edi, eax
xor eax, eax
syscall
ud2
.size _start, . - _start
.section .note.GNU-stack, "", @progbits
+229
View File
@@ -0,0 +1,229 @@
// SPDX-License-Identifier: ISC
#pragma once
#include <stddef.h>
#include <stdint.h>
enum syscall_number : uintptr_t {
#define SYSCALL_DECLARE(number, name, num_args) SYS_##name = number,
#include <zinnia/syscall_list.inc>
#undef SYSCALL_DECLARE
};
enum syslog_kind : uintptr_t {
SYSLOG_ERR = 3,
SYSLOG_INFO = 6,
};
struct sys_ret {
uintptr_t value;
uintptr_t error;
};
static inline struct sys_ret syscall6(
enum syscall_number num,
uintptr_t a0,
uintptr_t a1,
uintptr_t a2,
uintptr_t a3,
uintptr_t a4,
uintptr_t a5
) {
register uintptr_t r10 asm("r10") = a3;
register uintptr_t r8 asm("r8") = a4;
register uintptr_t r9 asm("r9") = a5;
uintptr_t value;
uintptr_t error;
asm volatile("syscall"
: "=a"(value), "=d"(error)
: "a"(num), "D"(a0), "S"(a1), "d"(a2), "r"(r10), "r"(r8), "r"(r9)
: "rcx", "r11", "memory");
return (struct sys_ret){value, error};
}
static inline struct sys_ret syscall3(enum syscall_number num, uintptr_t a0, uintptr_t a1, uintptr_t a2) {
return syscall6(num, a0, a1, a2, 0, 0, 0);
}
static inline struct sys_ret syscall4(enum syscall_number num, uintptr_t a0, uintptr_t a1, uintptr_t a2, uintptr_t a3) {
return syscall6(num, a0, a1, a2, a3, 0, 0);
}
static inline struct sys_ret syscall5(
enum syscall_number num,
uintptr_t a0,
uintptr_t a1,
uintptr_t a2,
uintptr_t a3,
uintptr_t a4
) {
return syscall6(num, a0, a1, a2, a3, a4, 0);
}
[[noreturn]]
static inline void sys_exit(int status) {
syscall3(SYS_exit, (uintptr_t)status, 0, 0);
__builtin_unreachable();
}
static inline struct sys_ret sys_syslog(enum syslog_kind kind, const char* msg, size_t len) {
return syscall3(SYS_syslog, kind, (uintptr_t)msg, len);
}
static inline struct sys_ret sys_mmap(void* addr, size_t len, uint32_t prot, uint32_t flags, int fd, int64_t off) {
return syscall6(SYS_mmap, (uintptr_t)addr, len, prot, flags, (uintptr_t)fd, (uintptr_t)off);
}
static inline struct sys_ret sys_munmap(void* addr, size_t len) {
return syscall3(SYS_munmap, (uintptr_t)addr, len, 0);
}
static inline struct sys_ret sys_openat(int dirfd, const char* path, uint32_t flags, uint32_t mode) {
return syscall4(SYS_openat, (uintptr_t)dirfd, (uintptr_t)path, flags, mode);
}
static inline struct sys_ret sys_close(int fd) {
return syscall3(SYS_close, (uintptr_t)fd, 0, 0);
}
static inline struct sys_ret sys_pread(int fd, void* buf, size_t len, uint64_t off) {
return syscall4(SYS_pread, (uintptr_t)fd, (uintptr_t)buf, len, off);
}
static inline struct sys_ret sys_fstat(int fd, void* buf) {
return syscall3(SYS_fstat, (uintptr_t)fd, (uintptr_t)buf, 0);
}
static inline struct sys_ret sys_getuname(void* buf) {
return syscall3(SYS_getuname, (uintptr_t)buf, 0, 0);
}
static inline struct sys_ret sys_setuname(const void* buf) {
return syscall3(SYS_setuname, (uintptr_t)buf, 0, 0);
}
static inline struct sys_ret sys_mprotect(void* addr, size_t len, uint32_t prot) {
return syscall3(SYS_mprotect, (uintptr_t)addr, len, prot);
}
static inline struct sys_ret sys_madvise(void* addr, size_t len, int advice) {
return syscall3(SYS_madvise, (uintptr_t)addr, len, (uintptr_t)advice);
}
static inline struct sys_ret sys_msync(void* addr, size_t len, int flags) {
return syscall3(SYS_msync, (uintptr_t)addr, len, (uintptr_t)flags);
}
static inline struct sys_ret sys_pwrite(int fd, const void* buf, size_t len, uint64_t off) {
return syscall4(SYS_pwrite, (uintptr_t)fd, (uintptr_t)buf, len, off);
}
static inline struct sys_ret sys_readv(int fd, const void* iov, size_t count) {
return syscall3(SYS_readv, (uintptr_t)fd, (uintptr_t)iov, count);
}
static inline struct sys_ret sys_writev(int fd, const void* iov, size_t count) {
return syscall3(SYS_writev, (uintptr_t)fd, (uintptr_t)iov, count);
}
static inline struct sys_ret sys_seek(int fd, int64_t off, int whence) {
return syscall3(SYS_seek, (uintptr_t)fd, (uintptr_t)off, (uintptr_t)whence);
}
static inline struct sys_ret sys_fstatat(int dirfd, const char* path, void* buf, uint32_t flags) {
return syscall4(SYS_fstatat, (uintptr_t)dirfd, (uintptr_t)path, (uintptr_t)buf, flags);
}
static inline struct sys_ret sys_faccessat(int dirfd, const char* path, uint32_t mode, uint32_t flags) {
return syscall4(SYS_faccessat, (uintptr_t)dirfd, (uintptr_t)path, mode, flags);
}
static inline struct sys_ret sys_fcntl(int fd, int cmd, uintptr_t arg) {
return syscall3(SYS_fcntl, (uintptr_t)fd, (uintptr_t)cmd, arg);
}
static inline struct sys_ret sys_ftruncate(int fd, int64_t len) {
return syscall3(SYS_ftruncate, (uintptr_t)fd, (uintptr_t)len, 0);
}
static inline struct sys_ret sys_truncate(const char* path, int64_t len) {
return syscall3(SYS_truncate, (uintptr_t)path, (uintptr_t)len, 0);
}
static inline struct sys_ret sys_getcwd(char* buf, size_t len) {
return syscall3(SYS_getcwd, (uintptr_t)buf, len, 0);
}
static inline struct sys_ret sys_chdir(const char* path) {
return syscall3(SYS_chdir, (uintptr_t)path, 0, 0);
}
static inline struct sys_ret sys_fchdir(int fd) {
return syscall3(SYS_fchdir, (uintptr_t)fd, 0, 0);
}
static inline struct sys_ret sys_mkdirat(int dirfd, const char* path, uint32_t mode) {
return syscall3(SYS_mkdirat, (uintptr_t)dirfd, (uintptr_t)path, mode);
}
static inline struct sys_ret sys_rmdirat(int dirfd, const char* path) {
return syscall3(SYS_rmdirat, (uintptr_t)dirfd, (uintptr_t)path, 0);
}
static inline struct sys_ret sys_unlinkat(int dirfd, const char* path, uint32_t flags) {
return syscall3(SYS_unlinkat, (uintptr_t)dirfd, (uintptr_t)path, flags);
}
static inline struct sys_ret sys_getdents(int fd, void* buf, size_t len) {
return syscall3(SYS_getdents, (uintptr_t)fd, (uintptr_t)buf, len);
}
static inline struct sys_ret sys_renameat(int old_dirfd, const char* old_path, int new_dirfd, const char* new_path) {
return syscall4(SYS_renameat, (uintptr_t)old_dirfd, (uintptr_t)old_path, (uintptr_t)new_dirfd, (uintptr_t)new_path);
}
static inline struct sys_ret sys_linkat(
int old_dirfd,
const char* old_path,
int new_dirfd,
const char* new_path,
uint32_t flags
) {
return syscall5(
SYS_linkat,
(uintptr_t)old_dirfd,
(uintptr_t)old_path,
(uintptr_t)new_dirfd,
(uintptr_t)new_path,
flags
);
}
static inline struct sys_ret sys_symlinkat(const char* target, int dirfd, const char* path) {
return syscall3(SYS_symlinkat, (uintptr_t)target, (uintptr_t)dirfd, (uintptr_t)path);
}
static inline struct sys_ret sys_readlinkat(int dirfd, const char* path, char* buf, size_t len) {
return syscall4(SYS_readlinkat, (uintptr_t)dirfd, (uintptr_t)path, (uintptr_t)buf, len);
}
static inline struct sys_ret sys_dup(int fd) {
return syscall3(SYS_dup, (uintptr_t)fd, 0, 0);
}
static inline struct sys_ret sys_dup3(int old_fd, int new_fd, uint32_t flags) {
return syscall3(SYS_dup3, (uintptr_t)old_fd, (uintptr_t)new_fd, flags);
}
static inline struct sys_ret sys_umask(uint32_t mask) {
return syscall3(SYS_umask, mask, 0, 0);
}
static inline struct sys_ret sys_pipe(int* fds, uint32_t flags) {
return syscall3(SYS_pipe, (uintptr_t)fds, flags, 0);
}
static inline struct sys_ret sys_ppoll(void* fds, size_t nfds, const void* timeout, const void* sigmask) {
return syscall4(SYS_ppoll, (uintptr_t)fds, nfds, (uintptr_t)timeout, (uintptr_t)sigmask);
}
+210
View File
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: ISC
#include "utest.h"
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "syscall.h"
constexpr size_t LINE_MAX = 240;
extern const struct utest_case __start_utest_cases[];
extern const struct utest_case __stop_utest_cases[];
size_t strlen(const char* str) {
size_t len = 0;
while (str[len])
len++;
return len;
}
int memcmp(const void* s1, const void* s2, size_t size) {
const uint8_t* a = s1;
const uint8_t* b = s2;
for (size_t i = 0; i < size; i++) {
if (a[i] != b[i])
return a[i] < b[i] ? -1 : 1;
}
return 0;
}
void* memcpy(void* restrict dest, const void* restrict src, size_t n) {
uint8_t* d = dest;
const uint8_t* s = src;
for (size_t i = 0; i < n; i++)
d[i] = s[i];
return dest;
}
void* memset(void* dest, int value, size_t n) {
uint8_t* d = dest;
for (size_t i = 0; i < n; i++)
d[i] = (uint8_t)value;
return dest;
}
bool utest_streq(const char* a, const char* b) {
if (!a || !b)
return a == b;
while (*a && *a == *b) {
a++;
b++;
}
return *a == *b;
}
struct line {
char buf[LINE_MAX];
size_t len;
};
static void put(struct line* line, char c) {
if (line->len < sizeof(line->buf) - 1)
line->buf[line->len++] = c;
}
static void put_str(struct line* line, const char* str) {
for (; *str; str++)
put(line, *str);
}
static void put_num(struct line* line, unsigned long long value, unsigned base, bool negative, bool prefix) {
char digits[24];
size_t n = 0;
do {
digits[n++] = "0123456789abcdef"[value % base];
value /= base;
} while (value);
if (negative)
put(line, '-');
if (prefix)
put_str(line, "0x");
while (n)
put(line, digits[--n]);
}
static void format(struct line* line, const char* fmt, va_list args) {
for (; *fmt; fmt++) {
if (*fmt != '%') {
put(line, *fmt);
continue;
}
fmt++;
bool alt = false;
if (*fmt == '#') {
alt = true;
fmt++;
}
int longs = 0;
bool size = false;
for (;; fmt++) {
if (*fmt == 'l')
longs++;
else if (*fmt == 'z')
size = true;
else
break;
}
switch (*fmt) {
case 'd':
case 'i': {
long long v = size ? (long long)va_arg(args, ptrdiff_t)
: longs ? va_arg(args, long long)
: va_arg(args, int);
put_num(line, v < 0 ? -(unsigned long long)v : (unsigned long long)v, 10, v < 0, false);
break;
}
case 'u':
case 'x': {
unsigned long long v = size ? va_arg(args, size_t)
: longs ? va_arg(args, unsigned long long)
: va_arg(args, unsigned int);
put_num(line, v, *fmt == 'x' ? 16 : 10, false, alt && *fmt == 'x');
break;
}
case 'p':
put_num(line, (uintptr_t)va_arg(args, void*), 16, false, true);
break;
case 's': {
const char* s = va_arg(args, const char*);
put_str(line, s ? s : "(null)");
break;
}
case 'c':
put(line, (char)va_arg(args, int));
break;
case '%':
put(line, '%');
break;
default:
put(line, '?');
break;
}
}
}
static void emit(struct line* line) {
line->buf[line->len] = 0;
sys_syslog(SYSLOG_INFO, line->buf, line->len);
}
void utest_log(const char* fmt, ...) {
struct line line = {};
va_list args;
va_start(args, fmt);
format(&line, fmt, args);
va_end(args);
emit(&line);
}
void utest_fail(struct utest* test, const char* file, int line_nr, const char* fmt, ...) {
struct line line = {};
put_str(&line, "# ");
put_str(&line, test->suite);
put(&line, '.');
put_str(&line, test->name);
put_str(&line, ": ");
put_str(&line, file);
put(&line, ':');
put_num(&line, (unsigned)line_nr, 10, false, false);
put_str(&line, ": ");
va_list args;
va_start(args, fmt);
format(&line, fmt, args);
va_end(args);
emit(&line);
test->failed = true;
}
void utest_run_all() {
size_t index = 0;
for (const struct utest_case* c = __start_utest_cases; c < __stop_utest_cases; c++) {
struct utest test = {.suite = c->suite, .name = c->name};
c->func(&test);
index++;
if (test.skipped)
utest_log("ok %zu %s.%s # SKIP %s", index, c->suite, c->name, test.skip_reason ? test.skip_reason : "");
else if (c->xfail_reason)
utest_log(
"%s %zu %s.%s # TODO %s",
test.failed ? "not ok" : "ok",
index,
c->suite,
c->name,
c->xfail_reason
);
else
utest_log("%s %zu %s.%s", test.failed ? "not ok" : "ok", index, c->suite, c->name);
}
utest_log("1..%zu", index);
}
int main() {
utest_run_all();
for (;;)
;
}
+90
View File
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: ISC
#pragma once
#include <stddef.h> // IWYU pragma: keep
#include <stdint.h> // IWYU pragma: keep
struct utest {
const char* suite;
const char* name;
bool failed;
bool skipped;
const char* skip_reason;
};
struct utest_case {
const char* suite;
const char* name;
const char* xfail_reason;
void (*func)(struct utest* test);
};
#define _UTEST(suite_name, case_name, reason) \
static void suite_name##_##case_name(struct utest* test); \
[[gnu::used, gnu::retain, gnu::section("utest_cases"), gnu::aligned(8)]] \
static const struct utest_case suite_name##_##case_name##_case = { \
.suite = #suite_name, \
.name = #case_name, \
.xfail_reason = (reason), \
.func = suite_name##_##case_name, \
}; \
static void suite_name##_##case_name(struct utest* test)
// Expected success
#define UTEST(suite_name, case_name) _UTEST(suite_name, case_name, nullptr)
// Expected failure
#define UTEST_XFAIL(suite_name, case_name, reason) _UTEST(suite_name, case_name, reason)
[[gnu::format(printf, 1, 2)]]
void utest_log(const char* fmt, ...);
[[gnu::format(printf, 4, 5)]]
void utest_fail(struct utest* test, const char* file, int line, const char* fmt, ...);
void utest_run_all();
bool utest_streq(const char* a, const char* b);
#define _UTEST_CHECK(cond, on_fail, ...) \
do { \
if (!(cond)) { \
utest_fail(test, __FILE__, __LINE__, __VA_ARGS__); \
on_fail; \
} \
} while (0)
#define _UTEST_CMP(a, op, b, on_fail) \
do { \
const typeof(true ? (a) : (b)) _utest_a = (a); \
const typeof(true ? (a) : (b)) _utest_b = (b); \
_UTEST_CHECK( \
_utest_a op _utest_b, \
on_fail, \
"expected %s %s %s, got %#llx and %#llx", \
#a, \
#op, \
#b, \
(unsigned long long)_utest_a, \
(unsigned long long)_utest_b \
); \
} while (0)
#define EXPECT_TRUE(cond) _UTEST_CHECK(cond, (void)0, "expected %s", #cond)
#define EXPECT_FALSE(cond) _UTEST_CHECK(!(cond), (void)0, "expected !(%s)", #cond)
#define EXPECT_EQ(a, b) _UTEST_CMP(a, ==, b, (void)0)
#define EXPECT_NE(a, b) _UTEST_CMP(a, !=, b, (void)0)
#define EXPECT_GT(a, b) _UTEST_CMP(a, >, b, (void)0)
#define EXPECT_GE(a, b) _UTEST_CMP(a, >=, b, (void)0)
#define EXPECT_STREQ(a, b) _UTEST_CHECK(utest_streq((a), (b)), (void)0, "expected %s == \"%s\"", #a, (b))
#define ASSERT_TRUE(cond) _UTEST_CHECK(cond, return, "expected %s", #cond)
#define ASSERT_EQ(a, b) _UTEST_CMP(a, ==, b, return)
#define ASSERT_NE(a, b) _UTEST_CMP(a, !=, b, return)
#define SKIP(reason) \
do { \
test->skipped = true; \
test->skip_reason = (reason); \
return; \
} while (0)
+137
View File
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: ISC
#include <stdint.h>
#include <string.h>
#include <uapi/errno.h>
#include <uapi/fcntl.h>
#include <uapi/mman.h>
#include "lib/syscall.h"
#include "lib/utest.h"
constexpr size_t PAGE_SIZE = 0x1000;
constexpr uint32_t PROT_RW = __PROT_READ | __PROT_WRITE;
constexpr uint32_t MAP_ANON_PRIVATE = __MAP_PRIVATE | __MAP_ANON;
static uint8_t* map_anon(size_t len) {
struct sys_ret ret = sys_mmap(nullptr, len, PROT_RW, MAP_ANON_PRIVATE, -1, 0);
return ret.error ? nullptr : (uint8_t*)ret.value;
}
UTEST(mman, anonymous_mapping) {
const size_t len = 4 * PAGE_SIZE;
struct sys_ret ret = sys_mmap(nullptr, len, __PROT_READ | __PROT_WRITE, __MAP_PRIVATE | __MAP_ANON, -1, 0);
ASSERT_EQ(ret.error, 0);
uint8_t* mem = (uint8_t*)ret.value;
EXPECT_EQ((uintptr_t)mem % PAGE_SIZE, 0);
for (size_t i = 0; i < len; i += PAGE_SIZE)
EXPECT_EQ(mem[i], 0);
for (size_t i = 0; i < len; i++)
mem[i] = (uint8_t)i;
for (size_t i = 0; i < len; i++) {
if (mem[i] != (uint8_t)i) {
utest_fail(test, __FILE__, __LINE__, "byte %zu reads back as %u", i, mem[i]);
break;
}
}
EXPECT_EQ(sys_munmap(mem, len).error, 0);
}
UTEST(mman, rejects_zero_length) {
struct sys_ret ret = sys_mmap(nullptr, 0, __PROT_READ, __MAP_PRIVATE | __MAP_ANON, -1, 0);
EXPECT_EQ(ret.error, __EINVAL);
}
UTEST(mman, shared_anonymous_mapping) {
struct sys_ret ret = sys_mmap(nullptr, PAGE_SIZE, PROT_RW, __MAP_SHARED | __MAP_ANON, -1, 0);
ASSERT_EQ(ret.error, 0);
uint8_t* mem = (uint8_t*)ret.value;
EXPECT_EQ(mem[0], 0);
mem[0] = 0x5a;
mem[PAGE_SIZE - 1] = 0xa5;
EXPECT_EQ(mem[0], 0x5a);
EXPECT_EQ(mem[PAGE_SIZE - 1], 0xa5);
EXPECT_EQ(sys_munmap(mem, PAGE_SIZE).error, 0);
}
UTEST(mman, file_mapping) {
struct sys_ret ret = sys_openat(__AT_FDCWD, "/init", __O_RDONLY, 0);
ASSERT_EQ(ret.error, 0);
const int fd = (int)ret.value;
ret = sys_mmap(nullptr, PAGE_SIZE, __PROT_READ, __MAP_PRIVATE, fd, 0);
EXPECT_EQ(ret.error, 0);
if (!ret.error) {
const uint8_t elf_header[] = {0x7f, 'E', 'L', 'F'};
EXPECT_EQ(memcmp((const void*)ret.value, elf_header, sizeof(elf_header)), 0);
EXPECT_EQ(sys_munmap((void*)ret.value, PAGE_SIZE).error, 0);
}
EXPECT_EQ(sys_close(fd).error, 0);
}
UTEST(mman, fixed_mapping_replaces) {
uint8_t* mem = map_anon(3 * PAGE_SIZE);
ASSERT_TRUE(mem != nullptr);
memset(mem, 0xcc, 3 * PAGE_SIZE);
struct sys_ret ret = sys_mmap(mem + PAGE_SIZE, PAGE_SIZE, PROT_RW, MAP_ANON_PRIVATE | __MAP_FIXED, -1, 0);
EXPECT_EQ(ret.error, 0);
EXPECT_EQ(ret.value, (uintptr_t)(mem + PAGE_SIZE));
EXPECT_EQ(mem[0], 0xcc);
EXPECT_EQ(mem[PAGE_SIZE], 0);
EXPECT_EQ(mem[2 * PAGE_SIZE], 0xcc);
EXPECT_EQ(sys_munmap(mem, 3 * PAGE_SIZE).error, 0);
}
UTEST(mman, partial_unmap) {
uint8_t* mem = map_anon(3 * PAGE_SIZE);
ASSERT_TRUE(mem != nullptr);
EXPECT_EQ(sys_munmap(mem + PAGE_SIZE, PAGE_SIZE).error, 0);
mem[0] = 1;
mem[2 * PAGE_SIZE] = 2;
EXPECT_EQ(mem[0], 1);
EXPECT_EQ(mem[2 * PAGE_SIZE], 2);
EXPECT_EQ(sys_munmap(mem, 3 * PAGE_SIZE).error, 0);
}
UTEST(mman, rejects_invalid_arguments) {
EXPECT_EQ(sys_mmap(nullptr, PAGE_SIZE, 0x80, MAP_ANON_PRIVATE, -1, 0).error, __EINVAL);
EXPECT_EQ(sys_mmap(nullptr, PAGE_SIZE, PROT_RW, __MAP_ANON, -1, 0).error, __EINVAL);
EXPECT_EQ(sys_mmap(nullptr, PAGE_SIZE, PROT_RW, MAP_ANON_PRIVATE, -1, 1).error, __EINVAL);
EXPECT_EQ(sys_mmap((void*)0x1001, PAGE_SIZE, PROT_RW, MAP_ANON_PRIVATE | __MAP_FIXED, -1, 0).error, __EINVAL);
EXPECT_EQ(sys_mmap(nullptr, PAGE_SIZE, __PROT_READ, __MAP_PRIVATE, 1234, 0).error, __EBADF);
}
UTEST(mman, mprotect) {
uint8_t* mem = map_anon(2 * PAGE_SIZE);
ASSERT_TRUE(mem != nullptr);
mem[0] = 7;
EXPECT_EQ(sys_mprotect(mem, PAGE_SIZE, __PROT_READ).error, 0);
EXPECT_EQ(mem[0], 7);
EXPECT_EQ(sys_mprotect(mem, PAGE_SIZE, PROT_RW).error, 0);
mem[0] = 8;
EXPECT_EQ(mem[0], 8);
EXPECT_EQ(sys_mprotect(mem, PAGE_SIZE, 0x80).error, __EINVAL);
EXPECT_EQ(sys_mprotect(mem, 0, __PROT_READ).error, __EINVAL);
EXPECT_EQ(sys_munmap(mem, 2 * PAGE_SIZE).error, 0);
}
UTEST(mman, msync) {
uint8_t* mem = map_anon(PAGE_SIZE);
ASSERT_TRUE(mem != nullptr);
EXPECT_EQ(sys_msync(mem, PAGE_SIZE, __MS_SYNC).error, 0);
EXPECT_EQ(sys_msync(mem, PAGE_SIZE, __MS_ASYNC).error, 0);
EXPECT_EQ(sys_msync(mem, PAGE_SIZE, __MS_ASYNC | __MS_SYNC).error, __EINVAL);
EXPECT_EQ(sys_msync(mem + 1, PAGE_SIZE, __MS_SYNC).error, __EINVAL);
EXPECT_EQ(sys_munmap(mem, PAGE_SIZE).error, 0);
EXPECT_EQ(sys_msync(mem, PAGE_SIZE, __MS_ASYNC).error, __ENOMEM);
}
UTEST(mman, madvise) {
uint8_t* mem = map_anon(PAGE_SIZE);
ASSERT_TRUE(mem != nullptr);
EXPECT_EQ(sys_madvise(mem, PAGE_SIZE, 0).error, 0);
EXPECT_EQ(sys_madvise(mem + 1, PAGE_SIZE, 0).error, __EINVAL);
EXPECT_EQ(sys_munmap(mem, PAGE_SIZE).error, 0);
}
+61
View File
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: ISC
#include <string.h>
#include <uapi/errno.h>
#include <uapi/utsname.h>
#include "lib/syscall.h"
#include "lib/utest.h"
UTEST(syslog, writes_message) {
const char msg[] = "utest: syslog test";
struct sys_ret ret = sys_syslog(SYSLOG_INFO, msg, sizeof(msg) - 1);
EXPECT_EQ(ret.error, 0);
}
UTEST(syslog, rejects_bad_pointer) {
struct sys_ret ret = sys_syslog(SYSLOG_INFO, (const char*)0x10, 8);
EXPECT_EQ(ret.error, __EFAULT);
}
UTEST(system, uname) {
struct __utsname uts = {};
struct sys_ret ret = sys_getuname(&uts);
ASSERT_EQ(ret.error, 0);
EXPECT_STREQ(uts.sysname, "Zinnia");
EXPECT_STREQ(uts.machine, "x86_64");
}
UTEST(syslog, long_message) {
char msg[1024];
memset(msg, 'x', sizeof(msg));
EXPECT_EQ(sys_syslog(SYSLOG_INFO, msg, sizeof(msg)).error, 0);
}
UTEST(system, uname_rejects_null) {
EXPECT_EQ(sys_getuname(nullptr).error, __EINVAL);
EXPECT_EQ(sys_setuname(nullptr).error, __EINVAL);
}
UTEST(system, uname_rejects_bad_pointer) {
EXPECT_EQ(sys_getuname((void*)0x10).error, __EFAULT);
EXPECT_EQ(sys_setuname((const void*)0x10).error, __EFAULT);
}
UTEST(system, setuname) {
struct __utsname original = {};
ASSERT_EQ(sys_getuname(&original).error, 0);
struct __utsname changed = original;
memset(changed.nodename, 0, sizeof(changed.nodename));
memcpy(changed.nodename, "foobar", sizeof("foobar"));
ASSERT_EQ(sys_setuname(&changed).error, 0);
struct __utsname now = {};
EXPECT_EQ(sys_getuname(&now).error, 0);
EXPECT_STREQ(now.nodename, "foobar");
EXPECT_STREQ(now.sysname, original.sysname);
EXPECT_EQ(sys_setuname(&original).error, 0);
EXPECT_EQ(sys_getuname(&now).error, 0);
EXPECT_STREQ(now.nodename, original.nodename);
}