162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * unwind_vdso.c - tests unwind info for AT_SYSINFO in the vDSO
462306a36Sopenharmony_ci * Copyright (c) 2014-2015 Andrew Lutomirski
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This tests __kernel_vsyscall's unwind info.
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#define _GNU_SOURCE
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <features.h>
1262306a36Sopenharmony_ci#include <stdio.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include "helpers.h"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ciint main()
1962306a36Sopenharmony_ci{
2062306a36Sopenharmony_ci	/* We need getauxval(). */
2162306a36Sopenharmony_ci	printf("[SKIP]\tGLIBC before 2.16 cannot compile this test\n");
2262306a36Sopenharmony_ci	return 0;
2362306a36Sopenharmony_ci}
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#else
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include <sys/time.h>
2862306a36Sopenharmony_ci#include <stdlib.h>
2962306a36Sopenharmony_ci#include <syscall.h>
3062306a36Sopenharmony_ci#include <unistd.h>
3162306a36Sopenharmony_ci#include <string.h>
3262306a36Sopenharmony_ci#include <inttypes.h>
3362306a36Sopenharmony_ci#include <sys/mman.h>
3462306a36Sopenharmony_ci#include <signal.h>
3562306a36Sopenharmony_ci#include <sys/ucontext.h>
3662306a36Sopenharmony_ci#include <err.h>
3762306a36Sopenharmony_ci#include <stddef.h>
3862306a36Sopenharmony_ci#include <stdbool.h>
3962306a36Sopenharmony_ci#include <sys/ptrace.h>
4062306a36Sopenharmony_ci#include <sys/user.h>
4162306a36Sopenharmony_ci#include <link.h>
4262306a36Sopenharmony_ci#include <sys/auxv.h>
4362306a36Sopenharmony_ci#include <dlfcn.h>
4462306a36Sopenharmony_ci#include <unwind.h>
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
4762306a36Sopenharmony_ci		       int flags)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	struct sigaction sa;
5062306a36Sopenharmony_ci	memset(&sa, 0, sizeof(sa));
5162306a36Sopenharmony_ci	sa.sa_sigaction = handler;
5262306a36Sopenharmony_ci	sa.sa_flags = SA_SIGINFO | flags;
5362306a36Sopenharmony_ci	sigemptyset(&sa.sa_mask);
5462306a36Sopenharmony_ci	if (sigaction(sig, &sa, 0))
5562306a36Sopenharmony_ci		err(1, "sigaction");
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic volatile sig_atomic_t nerrs;
5962306a36Sopenharmony_cistatic unsigned long sysinfo;
6062306a36Sopenharmony_cistatic bool got_sysinfo = false;
6162306a36Sopenharmony_cistatic unsigned long return_address;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistruct unwind_state {
6462306a36Sopenharmony_ci	unsigned long ip;	/* trap source */
6562306a36Sopenharmony_ci	int depth;		/* -1 until we hit the trap source */
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci_Unwind_Reason_Code trace_fn(struct _Unwind_Context * ctx, void *opaque)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	struct unwind_state *state = opaque;
7162306a36Sopenharmony_ci	unsigned long ip = _Unwind_GetIP(ctx);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (state->depth == -1) {
7462306a36Sopenharmony_ci		if (ip == state->ip)
7562306a36Sopenharmony_ci			state->depth = 0;
7662306a36Sopenharmony_ci		else
7762306a36Sopenharmony_ci			return _URC_NO_REASON;	/* Not there yet */
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci	printf("\t  0x%lx\n", ip);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	if (ip == return_address) {
8262306a36Sopenharmony_ci		/* Here we are. */
8362306a36Sopenharmony_ci		unsigned long eax = _Unwind_GetGR(ctx, 0);
8462306a36Sopenharmony_ci		unsigned long ecx = _Unwind_GetGR(ctx, 1);
8562306a36Sopenharmony_ci		unsigned long edx = _Unwind_GetGR(ctx, 2);
8662306a36Sopenharmony_ci		unsigned long ebx = _Unwind_GetGR(ctx, 3);
8762306a36Sopenharmony_ci		unsigned long ebp = _Unwind_GetGR(ctx, 5);
8862306a36Sopenharmony_ci		unsigned long esi = _Unwind_GetGR(ctx, 6);
8962306a36Sopenharmony_ci		unsigned long edi = _Unwind_GetGR(ctx, 7);
9062306a36Sopenharmony_ci		bool ok = (eax == SYS_getpid || eax == getpid()) &&
9162306a36Sopenharmony_ci			ebx == 1 && ecx == 2 && edx == 3 &&
9262306a36Sopenharmony_ci			esi == 4 && edi == 5 && ebp == 6;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci		if (!ok)
9562306a36Sopenharmony_ci			nerrs++;
9662306a36Sopenharmony_ci		printf("[%s]\t  NR = %ld, args = %ld, %ld, %ld, %ld, %ld, %ld\n",
9762306a36Sopenharmony_ci		       (ok ? "OK" : "FAIL"),
9862306a36Sopenharmony_ci		       eax, ebx, ecx, edx, esi, edi, ebp);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci		return _URC_NORMAL_STOP;
10162306a36Sopenharmony_ci	} else {
10262306a36Sopenharmony_ci		state->depth++;
10362306a36Sopenharmony_ci		return _URC_NO_REASON;
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic void sigtrap(int sig, siginfo_t *info, void *ctx_void)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	ucontext_t *ctx = (ucontext_t *)ctx_void;
11062306a36Sopenharmony_ci	struct unwind_state state;
11162306a36Sopenharmony_ci	unsigned long ip = ctx->uc_mcontext.gregs[REG_EIP];
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	if (!got_sysinfo && ip == sysinfo) {
11462306a36Sopenharmony_ci		got_sysinfo = true;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci		/* Find the return address. */
11762306a36Sopenharmony_ci		return_address = *(unsigned long *)(unsigned long)ctx->uc_mcontext.gregs[REG_ESP];
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci		printf("\tIn vsyscall at 0x%lx, returning to 0x%lx\n",
12062306a36Sopenharmony_ci		       ip, return_address);
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	if (!got_sysinfo)
12462306a36Sopenharmony_ci		return;		/* Not there yet */
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if (ip == return_address) {
12762306a36Sopenharmony_ci		ctx->uc_mcontext.gregs[REG_EFL] &= ~X86_EFLAGS_TF;
12862306a36Sopenharmony_ci		printf("\tVsyscall is done\n");
12962306a36Sopenharmony_ci		return;
13062306a36Sopenharmony_ci	}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	printf("\tSIGTRAP at 0x%lx\n", ip);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	state.ip = ip;
13562306a36Sopenharmony_ci	state.depth = -1;
13662306a36Sopenharmony_ci	_Unwind_Backtrace(trace_fn, &state);
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ciint main()
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	sysinfo = getauxval(AT_SYSINFO);
14262306a36Sopenharmony_ci	printf("\tAT_SYSINFO is 0x%lx\n", sysinfo);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	Dl_info info;
14562306a36Sopenharmony_ci	if (!dladdr((void *)sysinfo, &info)) {
14662306a36Sopenharmony_ci		printf("[WARN]\tdladdr failed on AT_SYSINFO\n");
14762306a36Sopenharmony_ci	} else {
14862306a36Sopenharmony_ci		printf("[OK]\tAT_SYSINFO maps to %s, loaded at 0x%p\n",
14962306a36Sopenharmony_ci		       info.dli_fname, info.dli_fbase);
15062306a36Sopenharmony_ci	}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	sethandler(SIGTRAP, sigtrap, 0);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	syscall(SYS_getpid);  /* Force symbol binding without TF set. */
15562306a36Sopenharmony_ci	printf("[RUN]\tSet TF and check a fast syscall\n");
15662306a36Sopenharmony_ci	set_eflags(get_eflags() | X86_EFLAGS_TF);
15762306a36Sopenharmony_ci	syscall(SYS_getpid, 1, 2, 3, 4, 5, 6);
15862306a36Sopenharmony_ci	if (!got_sysinfo) {
15962306a36Sopenharmony_ci		set_eflags(get_eflags() & ~X86_EFLAGS_TF);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci		/*
16262306a36Sopenharmony_ci		 * The most likely cause of this is that you're on Debian or
16362306a36Sopenharmony_ci		 * a Debian-based distro, you're missing libc6-i686, and you're
16462306a36Sopenharmony_ci		 * affected by libc/19006 (https://sourceware.org/PR19006).
16562306a36Sopenharmony_ci		 */
16662306a36Sopenharmony_ci		printf("[WARN]\tsyscall(2) didn't enter AT_SYSINFO\n");
16762306a36Sopenharmony_ci	}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	if (get_eflags() & X86_EFLAGS_TF) {
17062306a36Sopenharmony_ci		printf("[FAIL]\tTF is still set\n");
17162306a36Sopenharmony_ci		nerrs++;
17262306a36Sopenharmony_ci	}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	if (nerrs) {
17562306a36Sopenharmony_ci		printf("[FAIL]\tThere were errors\n");
17662306a36Sopenharmony_ci		return 1;
17762306a36Sopenharmony_ci	} else {
17862306a36Sopenharmony_ci		printf("[OK]\tAll is well\n");
17962306a36Sopenharmony_ci		return 0;
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci#endif	/* New enough libc */
184