linux/tools/testing/selftests/x86/single_step_syscall.c
<<
>>
Prefs
   1/*
   2 * single_step_syscall.c - single-steps various x86 syscalls
   3 * Copyright (c) 2014-2015 Andrew Lutomirski
   4 *
   5 * This program is free software; you can redistribute it and/or modify
   6 * it under the terms and conditions of the GNU General Public License,
   7 * version 2, as published by the Free Software Foundation.
   8 *
   9 * This program is distributed in the hope it will be useful, but
  10 * WITHOUT ANY WARRANTY; without even the implied warranty of
  11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12 * General Public License for more details.
  13 *
  14 * This is a very simple series of tests that makes system calls with
  15 * the TF flag set.  This exercises some nasty kernel code in the
  16 * SYSENTER case: SYSENTER does not clear TF, so SYSENTER with TF set
  17 * immediately issues #DB from CPL 0.  This requires special handling in
  18 * the kernel.
  19 */
  20
  21#define _GNU_SOURCE
  22
  23#include <sys/time.h>
  24#include <time.h>
  25#include <stdlib.h>
  26#include <sys/syscall.h>
  27#include <unistd.h>
  28#include <stdio.h>
  29#include <string.h>
  30#include <inttypes.h>
  31#include <sys/mman.h>
  32#include <sys/signal.h>
  33#include <sys/ucontext.h>
  34#include <asm/ldt.h>
  35#include <err.h>
  36#include <setjmp.h>
  37#include <stddef.h>
  38#include <stdbool.h>
  39#include <sys/ptrace.h>
  40#include <sys/user.h>
  41
  42static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
  43                       int flags)
  44{
  45        struct sigaction sa;
  46        memset(&sa, 0, sizeof(sa));
  47        sa.sa_sigaction = handler;
  48        sa.sa_flags = SA_SIGINFO | flags;
  49        sigemptyset(&sa.sa_mask);
  50        if (sigaction(sig, &sa, 0))
  51                err(1, "sigaction");
  52}
  53
  54static volatile sig_atomic_t sig_traps;
  55
  56#ifdef __x86_64__
  57# define REG_IP REG_RIP
  58# define WIDTH "q"
  59# define INT80_CLOBBERS "r8", "r9", "r10", "r11"
  60#else
  61# define REG_IP REG_EIP
  62# define WIDTH "l"
  63# define INT80_CLOBBERS
  64#endif
  65
  66static unsigned long get_eflags(void)
  67{
  68        unsigned long eflags;
  69        asm volatile ("pushf" WIDTH "\n\tpop" WIDTH " %0" : "=rm" (eflags));
  70        return eflags;
  71}
  72
  73static void set_eflags(unsigned long eflags)
  74{
  75        asm volatile ("push" WIDTH " %0\n\tpopf" WIDTH
  76                      : : "rm" (eflags) : "flags");
  77}
  78
  79#define X86_EFLAGS_TF (1UL << 8)
  80
  81static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
  82{
  83        ucontext_t *ctx = (ucontext_t*)ctx_void;
  84
  85        if (get_eflags() & X86_EFLAGS_TF) {
  86                set_eflags(get_eflags() & ~X86_EFLAGS_TF);
  87                printf("[WARN]\tSIGTRAP handler had TF set\n");
  88                _exit(1);
  89        }
  90
  91        sig_traps++;
  92
  93        if (sig_traps == 10000 || sig_traps == 10001) {
  94                printf("[WARN]\tHit %d SIGTRAPs with si_addr 0x%lx, ip 0x%lx\n",
  95                       (int)sig_traps,
  96                       (unsigned long)info->si_addr,
  97                       (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
  98        }
  99}
 100
 101static void check_result(void)
 102{
 103        unsigned long new_eflags = get_eflags();
 104        set_eflags(new_eflags & ~X86_EFLAGS_TF);
 105
 106        if (!sig_traps) {
 107                printf("[FAIL]\tNo SIGTRAP\n");
 108                exit(1);
 109        }
 110
 111        if (!(new_eflags & X86_EFLAGS_TF)) {
 112                printf("[FAIL]\tTF was cleared\n");
 113                exit(1);
 114        }
 115
 116        printf("[OK]\tSurvived with TF set and %d traps\n", (int)sig_traps);
 117        sig_traps = 0;
 118}
 119
 120int main()
 121{
 122        int tmp;
 123
 124        sethandler(SIGTRAP, sigtrap, 0);
 125
 126        printf("[RUN]\tSet TF and check nop\n");
 127        set_eflags(get_eflags() | X86_EFLAGS_TF);
 128        asm volatile ("nop");
 129        check_result();
 130
 131#ifdef __x86_64__
 132        printf("[RUN]\tSet TF and check syscall-less opportunistic sysret\n");
 133        set_eflags(get_eflags() | X86_EFLAGS_TF);
 134        extern unsigned char post_nop[];
 135        asm volatile ("pushf" WIDTH "\n\t"
 136                      "pop" WIDTH " %%r11\n\t"
 137                      "nop\n\t"
 138                      "post_nop:"
 139                      : : "c" (post_nop) : "r11");
 140        check_result();
 141#endif
 142
 143        printf("[RUN]\tSet TF and check int80\n");
 144        set_eflags(get_eflags() | X86_EFLAGS_TF);
 145        asm volatile ("int $0x80" : "=a" (tmp) : "a" (SYS_getpid)
 146                        : INT80_CLOBBERS);
 147        check_result();
 148
 149        /*
 150         * This test is particularly interesting if fast syscalls use
 151         * SYSENTER: it triggers a nasty design flaw in SYSENTER.
 152         * Specifically, SYSENTER does not clear TF, so either SYSENTER
 153         * or the next instruction traps at CPL0.  (Of course, Intel
 154         * mostly forgot to document exactly what happens here.)  So we
 155         * get a CPL0 fault with usergs (on 64-bit kernels) and possibly
 156         * no stack.  The only sane way the kernel can possibly handle
 157         * it is to clear TF on return from the #DB handler, but this
 158         * happens way too early to set TF in the saved pt_regs, so the
 159         * kernel has to do something clever to avoid losing track of
 160         * the TF bit.
 161         *
 162         * Needless to say, we've had bugs in this area.
 163         */
 164        syscall(SYS_getpid);  /* Force symbol binding without TF set. */
 165        printf("[RUN]\tSet TF and check a fast syscall\n");
 166        set_eflags(get_eflags() | X86_EFLAGS_TF);
 167        syscall(SYS_getpid);
 168        check_result();
 169
 170        /* Now make sure that another fast syscall doesn't set TF again. */
 171        printf("[RUN]\tFast syscall with TF cleared\n");
 172        fflush(stdout);  /* Force a syscall */
 173        if (get_eflags() & X86_EFLAGS_TF) {
 174                printf("[FAIL]\tTF is now set\n");
 175                exit(1);
 176        }
 177        if (sig_traps) {
 178                printf("[FAIL]\tGot SIGTRAP\n");
 179                exit(1);
 180        }
 181        printf("[OK]\tNothing unexpected happened\n");
 182
 183        return 0;
 184}
 185