lkml.org 
[lkml]   [2012]   [Apr]   [11]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[ 25/42] kgdbts: (2 of 2) fix single step awareness to work correctly with SMP
    3.0-stable review patch.  If anyone has any objections, please let me know.

    ------------------

    From: Jason Wessel <jason.wessel@windriver.com>

    commit 23bbd8e346f1ef3fc1219c79cea53d8d52b207d8 upstream.

    The do_fork and sys_open tests have never worked properly on anything
    other than a UP configuration with the kgdb test suite. This is
    because the test suite did not fully implement the behavior of a real
    debugger. A real debugger tracks the state of what thread it asked to
    single step and can correctly continue other threads of execution or
    conditionally stop while waiting for the original thread single step
    request to return.

    Below is a simple method to cause a fatal kernel oops with the kgdb
    test suite on a 2 processor ARM system:

    while [ 1 ] ; do ls > /dev/null 2> /dev/null; done&
    while [ 1 ] ; do ls > /dev/null 2> /dev/null; done&
    echo V1I1F100 > /sys/module/kgdbts/parameters/kgdbts

    Very soon after starting the test the kernel will start warning with
    messages like:

    kgdbts: BP mismatch c002487c expected c0024878
    ------------[ cut here ]------------
    WARNING: at drivers/misc/kgdbts.c:317 check_and_rewind_pc+0x9c/0xc4()
    [<c01f6520>] (check_and_rewind_pc+0x9c/0xc4)
    [<c01f595c>] (validate_simple_test+0x3c/0xc4)
    [<c01f60d4>] (run_simple_test+0x1e8/0x274)

    The kernel will eventually recovers, but the test suite has completely
    failed to test anything useful.

    This patch implements behavior similar to a real debugger that does
    not rely on hardware single stepping by using only software planted
    breakpoints.

    In order to mimic a real debugger, the kgdb test suite now tracks the
    most recent thread that was continued (cont_thread_id), with the
    intent to single step just this thread. When the response to the
    single step request stops in a different thread that hit the original
    break point that thread will now get continued, while the debugger
    waits for the thread with the single step pending. Here is a high
    level description of the sequence of events.

    cont_instead_of_sstep = 0;

    1) set breakpoint at do_fork
    2) continue
    3) Save the thread id where we stop to cont_thread_id
    4) Remove breakpoint at do_fork
    5) Reset the PC if needed depending on kernel exception type
    6) soft single step
    7) Check where we stopped
    if current thread != cont_thread_id {
    if (here for more than 2 times for the same thead) {
    ### must be a really busy system, start test again ###
    goto step 1
    }
    goto step 5
    } else {
    cont_instead_of_sstep = 0;
    }
    8) clean up and run test again if needed
    9) Clear out any threads that were waiting on a break point at the
    point in time the test is ended with get_cont_catch(). This
    happens sometimes because breakpoints are used in place of single
    stepping and some threads could have been in the debugger exception
    handling queue because breakpoints were hit concurrently on
    different CPUs. This also means we wait at least one second before
    unplumbing the debugger connection at the very end, so as respond
    to any debug threads waiting to be serviced.

    Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
    Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

    ---
    drivers/misc/kgdbts.c | 73 ++++++++++++++++++++++++++++++++++++++++++--------
    1 file changed, 62 insertions(+), 11 deletions(-)

    --- a/drivers/misc/kgdbts.c
    +++ b/drivers/misc/kgdbts.c
    @@ -141,7 +141,9 @@ static int arch_needs_sstep_emulation =
    #else
    static int arch_needs_sstep_emulation;
    #endif
    +static unsigned long cont_addr;
    static unsigned long sstep_addr;
    +static int restart_from_top_after_write;
    static int sstep_state;

    /* Storage for the registers, in GDB format. */
    @@ -189,7 +191,8 @@ static int kgdbts_unreg_thread(void *ptr
    */
    while (!final_ack)
    msleep_interruptible(1500);
    -
    + /* Pause for any other threads to exit after final ack. */
    + msleep_interruptible(1000);
    if (configured)
    kgdb_unregister_io_module(&kgdbts_io_ops);
    configured = 0;
    @@ -311,13 +314,21 @@ static int check_and_rewind_pc(char *put
    if (addr + BREAK_INSTR_SIZE == ip)
    offset = -BREAK_INSTR_SIZE;
    #endif
    - if (strcmp(arg, "silent") && ip + offset != addr) {
    +
    + if (arch_needs_sstep_emulation && sstep_addr &&
    + ip + offset == sstep_addr &&
    + ((!strcmp(arg, "sys_open") || !strcmp(arg, "do_fork")))) {
    + /* This is special case for emulated single step */
    + v2printk("Emul: rewind hit single step bp\n");
    + restart_from_top_after_write = 1;
    + } else if (strcmp(arg, "silent") && ip + offset != addr) {
    eprintk("kgdbts: BP mismatch %lx expected %lx\n",
    ip + offset, addr);
    return 1;
    }
    /* Readjust the instruction pointer if needed */
    ip += offset;
    + cont_addr = ip;
    #ifdef GDB_ADJUSTS_BREAK_OFFSET
    instruction_pointer_set(&kgdbts_regs, ip);
    #endif
    @@ -327,6 +338,8 @@ static int check_and_rewind_pc(char *put
    static int check_single_step(char *put_str, char *arg)
    {
    unsigned long addr = lookup_addr(arg);
    + static int matched_id;
    +
    /*
    * From an arch indepent point of view the instruction pointer
    * should be on a different instruction
    @@ -337,17 +350,28 @@ static int check_single_step(char *put_s
    v2printk("Singlestep stopped at IP: %lx\n",
    instruction_pointer(&kgdbts_regs));

    - if (sstep_thread_id != cont_thread_id && !arch_needs_sstep_emulation) {
    + if (sstep_thread_id != cont_thread_id) {
    /*
    * Ensure we stopped in the same thread id as before, else the
    * debugger should continue until the original thread that was
    * single stepped is scheduled again, emulating gdb's behavior.
    */
    v2printk("ThrID does not match: %lx\n", cont_thread_id);
    + if (arch_needs_sstep_emulation) {
    + if (matched_id &&
    + instruction_pointer(&kgdbts_regs) != addr)
    + goto continue_test;
    + matched_id++;
    + ts.idx -= 2;
    + sstep_state = 0;
    + return 0;
    + }
    cont_instead_of_sstep = 1;
    ts.idx -= 4;
    return 0;
    }
    +continue_test:
    + matched_id = 0;
    if (instruction_pointer(&kgdbts_regs) == addr) {
    eprintk("kgdbts: SingleStep failed at %lx\n",
    instruction_pointer(&kgdbts_regs));
    @@ -389,6 +413,31 @@ static int got_break(char *put_str, char
    return 1;
    }

    +static void get_cont_catch(char *arg)
    +{
    + /* Always send detach because the test is completed at this point */
    + fill_get_buf("D");
    +}
    +
    +static int put_cont_catch(char *put_str, char *arg)
    +{
    + /* This is at the end of the test and we catch any and all input */
    + v2printk("kgdbts: cleanup task: %lx\n", sstep_thread_id);
    + ts.idx--;
    + return 0;
    +}
    +
    +static int emul_reset(char *put_str, char *arg)
    +{
    + if (strncmp(put_str, "$OK", 3))
    + return 1;
    + if (restart_from_top_after_write) {
    + restart_from_top_after_write = 0;
    + ts.idx = -1;
    + }
    + return 0;
    +}
    +
    static void emul_sstep_get(char *arg)
    {
    if (!arch_needs_sstep_emulation) {
    @@ -442,8 +491,7 @@ static int emul_sstep_put(char *put_str,
    v2printk("Stopped at IP: %lx\n",
    instruction_pointer(&kgdbts_regs));
    /* Want to stop at IP + break instruction size by default */
    - sstep_addr = instruction_pointer(&kgdbts_regs) +
    - BREAK_INSTR_SIZE;
    + sstep_addr = cont_addr + BREAK_INSTR_SIZE;
    break;
    case 2:
    if (strncmp(put_str, "$OK", 3)) {
    @@ -455,6 +503,9 @@ static int emul_sstep_put(char *put_str,
    if (strncmp(put_str, "$T0", 3)) {
    eprintk("kgdbts: failed continue sstep\n");
    return 1;
    + } else {
    + char *ptr = &put_str[11];
    + kgdb_hex2long(&ptr, &sstep_thread_id);
    }
    break;
    case 4:
    @@ -557,13 +608,13 @@ static struct test_struct do_fork_test[]
    { "c", "T0*", NULL, get_thread_id_continue }, /* Continue */
    { "do_fork", "OK", sw_rem_break }, /*remove breakpoint */
    { "g", "do_fork", NULL, check_and_rewind_pc }, /* check location */
    - { "write", "OK", write_regs }, /* Write registers */
    + { "write", "OK", write_regs, emul_reset }, /* Write registers */
    { "s", "T0*", emul_sstep_get, emul_sstep_put }, /* Single step */
    { "g", "do_fork", NULL, check_single_step },
    { "do_fork", "OK", sw_break, }, /* set sw breakpoint */
    { "7", "T0*", skip_back_repeat_test }, /* Loop based on repeat_test */
    { "D", "OK", NULL, final_ack_set }, /* detach and unregister I/O */
    - { "", "" },
    + { "", "", get_cont_catch, put_cont_catch },
    };

    /* Test for hitting a breakpoint at sys_open for what ever the number
    @@ -575,13 +626,13 @@ static struct test_struct sys_open_test[
    { "c", "T0*", NULL, get_thread_id_continue }, /* Continue */
    { "sys_open", "OK", sw_rem_break }, /*remove breakpoint */
    { "g", "sys_open", NULL, check_and_rewind_pc }, /* check location */
    - { "write", "OK", write_regs }, /* Write registers */
    + { "write", "OK", write_regs, emul_reset }, /* Write registers */
    { "s", "T0*", emul_sstep_get, emul_sstep_put }, /* Single step */
    { "g", "sys_open", NULL, check_single_step },
    { "sys_open", "OK", sw_break, }, /* set sw breakpoint */
    { "7", "T0*", skip_back_repeat_test }, /* Loop based on repeat_test */
    { "D", "OK", NULL, final_ack_set }, /* detach and unregister I/O */
    - { "", "" },
    + { "", "", get_cont_catch, put_cont_catch },
    };

    /*
    @@ -724,8 +775,8 @@ static int run_simple_test(int is_get_ch
    /* This callback is a put char which is when kgdb sends data to
    * this I/O module.
    */
    - if (ts.tst[ts.idx].get[0] == '\0' &&
    - ts.tst[ts.idx].put[0] == '\0') {
    + if (ts.tst[ts.idx].get[0] == '\0' && ts.tst[ts.idx].put[0] == '\0' &&
    + !ts.tst[ts.idx].get_handler) {
    eprintk("kgdbts: ERROR: beyond end of test on"
    " '%s' line %i\n", ts.name, ts.idx);
    return 0;



    \
     
     \ /
      Last update: 2012-04-12 01:37    [W:0.044 / U:33.820 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site