1/*
2 * Copyright 2017, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12#include <autoconf.h>
13
14#ifdef CONFIG_HARDWARE_DEBUG_API
15
16#include <sel4/sel4.h>
17#include <utils/attribute.h>
18
19#include <arch/debug.h>
20#include "../../../helpers.h"
21#include "../../../test.h"
22#include "../../../tests/breakpoints.h"
23
24/* Single-step test is currently only for x86. ARM requires some extra
25 * components before it can be usable.
26 */
27static volatile int counter;
28
29NO_INLINE static void stop_point(void)
30{
31    /* Counter is volatile so the compiler cannot optimize this away */
32    counter = counter;
33}
34
35NO_INLINE static void single_step_guinea_pig(void)
36{
37    for (; counter < SINGLESTEP_TEST_MAX_LOOP_ITERATIONS; counter++) {
38        /* This syscall is inserted here to ensure that syscalls are being
39         * stepped over successfully on x86. On ARM, we can just disable single-
40         * stepping in PL1 and PL2 altogether, so the problem doesn't arise.
41         */
42        seL4_Yield();
43    }
44    stop_point();
45}
46
47int debugger_main(seL4_Word a0, seL4_Word reply, seL4_Word a2, seL4_Word a3)
48{
49    seL4_CPtr debuggee_tcb_cap = a0;
50    seL4_Word badge;
51    seL4_MessageInfo_t tag;
52    seL4_TCB_ConfigureSingleStepping_t result;
53
54    /* The main thread sets a breakpoint in the debuggee thread, and this thread
55     * will get a fault on the fault EP. At that point we can enable
56     * single-stepping on the debuggee thread.
57     */
58    tag = api_wait(fault_ep_cspath.capPtr, &badge);
59
60    if (seL4_MessageInfo_get_label(tag) != seL4_Fault_DebugException) {
61        ZF_LOGE("debugger: Got unexpected fault %zd.\n",
62                seL4_MessageInfo_get_label(tag));
63        return -1;
64    }
65
66    fault_data.vaddr = seL4_GetMR(seL4_DebugException_FaultIP);
67    fault_data.reason = seL4_GetMR(seL4_DebugException_ExceptionReason);
68    fault_data.bp_num = seL4_GetMR(seL4_DebugException_BreakpointNumber);
69    if (fault_data.reason != seL4_InstructionBreakpoint
70        || fault_data.vaddr != (seL4_Word)&single_step_guinea_pig) {
71        ZF_LOGE("debugger: debug exception not triggered by expected conditions.\n");
72        return -1;
73    }
74
75    /* So now we can set the single stepping going. */
76    ZF_LOGV("debugger: Instruction BP triggered: beginning single-stepping.\n");
77    seL4_TCB_UnsetBreakpoint(debuggee_tcb_cap, TEST_FIRST_INSTR_BP);
78    result = seL4_TCB_ConfigureSingleStepping(debuggee_tcb_cap, TEST_FIRST_INSTR_BP + 1, 1);
79
80    test_eq((int)result.bp_was_consumed, SINGLESTEP_EXPECTED_BP_CONSUMPTION_VALUE);
81    test_eq(result.error, seL4_NoError);
82
83    /* Resume() the debuggee thread, and keep stepping until it reaches the
84     * stop point (when it calls stop_point()).
85     *
86     * Set the counter to 0. The debuggee thread will be incrementing this.
87     * We will check its value afterward to verify that the debuggee ran as
88     * expected.
89     */
90    counter = 0;
91    seL4_TCB_Resume(debuggee_tcb_cap);
92
93    for (;;) {
94        tag = api_recv(fault_ep_cspath.capPtr, &badge, reply);
95
96        if (seL4_MessageInfo_get_label(tag) != seL4_Fault_DebugException) {
97            ZF_LOGE("Debugger: while single stepping, got unexpected fault.\n");
98            return -1;
99        }
100
101        fault_data.vaddr = seL4_GetMR(seL4_DebugException_FaultIP);
102        fault_data.reason = seL4_GetMR(seL4_DebugException_ExceptionReason);
103        fault_data.bp_num = seL4_GetMR(seL4_DebugException_TriggerAddress);
104        if (fault_data.reason != seL4_SingleStep) {
105            ZF_LOGE("Debugger: while single stepping, got debug exception, but "
106                    "for the wrong reason (reason %zd).\n",
107                    fault_data.reason);
108            return -1;
109        }
110
111        if (fault_data.vaddr == (seL4_Word)&stop_point) {
112            /* We're done: disable single-stepping */
113            ZF_LOGV("About to disable stepping and resume debuggee.\n");
114            tag = seL4_MessageInfo_set_label(tag, 0);
115            seL4_SetMR(0, 0);
116            api_reply(reply, tag);
117            break;
118        }
119
120        tag = seL4_MessageInfo_set_label(tag, 0);
121        seL4_SetMR(0, 1);
122        api_reply(reply, tag);
123    }
124
125    if (fault_data.vaddr != (seL4_Word)&stop_point) {
126        ZF_LOGE("Exited loop, but the debuggee thread did not get where we "
127                "expected it to.\n");
128        return -1;
129    }
130
131    /* Test the value of "counter", which the debuggee thread was incrementing. */
132    if (counter <= 0 || counter != SINGLESTEP_TEST_MAX_LOOP_ITERATIONS) {
133        return -1;
134    }
135
136    return 0;
137}
138
139int debuggee_main(seL4_Word a0, seL4_Word a1, seL4_Word a2, seL4_Word a3)
140{
141    ZF_LOGV("Debuggee: about to execute guinea_pig.\n");
142    single_step_guinea_pig();
143    return 0;
144}
145
146static int test_debug_api_single_step(struct env *env)
147{
148    helper_thread_t debugger, debuggee;
149    int error;
150
151    test_eq(setup_caps_for_test(env), 0);
152
153    create_helper_thread(env, &debugger);
154    set_helper_priority(env, &debugger, BREAKPOINT_TEST_HANDLER_PRIO);
155
156    error = setup_faulter_thread_for_test(env, &debuggee);
157    test_eq(error, seL4_NoError);
158    error = seL4_TCB_SetBreakpoint(get_helper_tcb(&debuggee),
159                                   TEST_FIRST_INSTR_BP, (seL4_Word)&single_step_guinea_pig,
160                                   seL4_InstructionBreakpoint, 0, seL4_BreakOnRead);
161    test_eq(error, seL4_NoError);
162
163    start_helper(env, &debugger, &debugger_main,
164                 get_helper_tcb(&debuggee),
165                 get_helper_reply(&debuggee), 0, 0);
166    start_helper(env, &debuggee, &debuggee_main, 0, 0, 0, 0);
167
168    wait_for_helper(&debugger);
169    wait_for_helper(&debuggee);
170
171    cleanup_helper(env, &debuggee);
172    cleanup_helper(env, &debugger);
173    return sel4test_get_result();
174}
175DEFINE_TEST(SINGLESTEP_001, "Attempt to step through a function",
176            test_debug_api_single_step, config_set(CONFIG_HARDWARE_DEBUG_API));
177
178#endif /* #ifdef CONFIG_HARDWARE_DEBUG_API */
179