aboutsummaryrefslogtreecommitdiff
path: root/source/Plugins/Process/Linux/SingleStepCheck.cpp
blob: 8c557d4b6ff8699062c8ffbdafd0a5a87407102c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//===-- SingleStepCheck.cpp ----------------------------------- -*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "SingleStepCheck.h"

#include <sched.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>

#include "NativeProcessLinux.h"

#include "llvm/Support/Compiler.h"

#include "lldb/Core/Error.h"
#include "lldb/Core/Log.h"
#include "lldb/Host/linux/Ptrace.h"

using namespace lldb_private::process_linux;

#if defined(__arm64__) || defined(__aarch64__)
namespace
{

void LLVM_ATTRIBUTE_NORETURN
Child()
{
    if (ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) == -1)
        _exit(1);

    // We just do an endless loop SIGSTOPPING ourselves until killed. The tracer will fiddle with our cpu
    // affinities and monitor the behaviour.
    for (;;)
    {
        raise(SIGSTOP);

        // Generate a bunch of instructions here, so that a single-step does not land in the
        // raise() accidentally. If single-stepping works, we will be spinning in this loop. If
        // it doesn't, we'll land in the raise() call above.
        for (volatile unsigned i = 0; i < CPU_SETSIZE; ++i)
            ;
    }
}

struct ChildDeleter
{
    ::pid_t pid;

    ~ChildDeleter()
    {
        int status;
        kill(pid, SIGKILL);            // Kill the child.
        waitpid(pid, &status, __WALL); // Pick up the remains.
    }
};

} // end anonymous namespace

bool
impl::SingleStepWorkaroundNeeded()
{
    // We shall spawn a child, and use it to verify the debug capabilities of the cpu. We shall
    // iterate through the cpus, bind the child to each one in turn, and verify that
    // single-stepping works on that cpu. A workaround is needed if we find at least one broken
    // cpu.

    Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
    Error error;
    ::pid_t child_pid = fork();
    if (child_pid == -1)
    {
        if (log)
        {
            error.SetErrorToErrno();
            log->Printf("%s failed to fork(): %s", __FUNCTION__, error.AsCString());
        }
        return false;
    }
    if (child_pid == 0)
        Child();

    ChildDeleter child_deleter{child_pid};
    cpu_set_t available_cpus;
    if (sched_getaffinity(child_pid, sizeof available_cpus, &available_cpus) == -1)
    {
        if (log)
        {
            error.SetErrorToErrno();
            log->Printf("%s failed to get available cpus: %s", __FUNCTION__, error.AsCString());
        }
        return false;
    }

    int status;
    ::pid_t wpid = waitpid(child_pid, &status, __WALL);
    if (wpid != child_pid || !WIFSTOPPED(status))
    {
        if (log)
        {
            error.SetErrorToErrno();
            log->Printf("%s waitpid() failed (status = %x): %s", __FUNCTION__, status, error.AsCString());
        }
        return false;
    }

    unsigned cpu;
    for (cpu = 0; cpu < CPU_SETSIZE; ++cpu)
    {
        if (!CPU_ISSET(cpu, &available_cpus))
            continue;

        cpu_set_t cpus;
        CPU_ZERO(&cpus);
        CPU_SET(cpu, &cpus);
        if (sched_setaffinity(child_pid, sizeof cpus, &cpus) == -1)
        {
            if (log)
            {
                error.SetErrorToErrno();
                log->Printf("%s failed to switch to cpu %u: %s", __FUNCTION__, cpu, error.AsCString());
            }
            continue;
        }

        int status;
        error = NativeProcessLinux::PtraceWrapper(PTRACE_SINGLESTEP, child_pid);
        if (error.Fail())
        {
            if (log)
                log->Printf("%s single step failed: %s", __FUNCTION__, error.AsCString());
            break;
        }

        wpid = waitpid(child_pid, &status, __WALL);
        if (wpid != child_pid || !WIFSTOPPED(status))
        {
            if (log)
            {
                error.SetErrorToErrno();
                log->Printf("%s waitpid() failed (status = %x): %s", __FUNCTION__, status, error.AsCString());
            }
            break;
        }
        if (WSTOPSIG(status) != SIGTRAP)
        {
            if (log)
                log->Printf("%s single stepping on cpu %d failed with status %x", __FUNCTION__, cpu, status);
            break;
        }
    }

    // cpu is either the index of the first broken cpu, or CPU_SETSIZE.
    if (cpu == 0)
    {
        if (log)
            log->Printf("%s SINGLE STEPPING ON FIRST CPU IS NOT WORKING. DEBUGGING LIKELY TO BE UNRELIABLE.",
                        __FUNCTION__);
        // No point in trying to fiddle with the affinities, just give it our best shot and see how it goes.
        return false;
    }

    return cpu != CPU_SETSIZE;
}

#else // !arm64
bool
impl::SingleStepWorkaroundNeeded()
{
    return false;
}
#endif