A GDB stop leaves the other guest threads running instead of stopping them (all-stop)

Aperta
#530 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
1-2 giorni
Idoneità per principianti
52/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
c

Direzione di ricerca

Start by reading gdb_stub_handle_stop in src/debug/gdbstub.c and the canceled-exit handler in src/syscall/proc.c, then trace how stop requests are cleared on resume. Use the supplied two-thread reproducer and raw RSP client to check whether the counter stays unchanged after the T05 stop reply; done means sibling threads remain stopped until GDB resumes.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug

What happens

When one thread stops at a breakpoint under --gdb, the other guest threads keep running guest code while GDB inspects and writes state. GDB's all-stop mode, which the stub's comments describe ("Stop all other vCPUs (all-stop mode)"), expects every thread to be stopped until the next resume.

Evidence

On upstream main a20dfab, the probe below, a guest with one thread spinning on counter++ while the main thread calls bp_here(), driven by the raw RSP client below:

Z0: OK
stop reply: T05
counter while stopped: 232115109 -> 809825692 (changed)

The counter grew by 577,710,583 in the 0.5 s between two m reads after the T05 stop reply. No Linux comparison: the qemu reference VM has no gdbserver.

Cause

Checked by reading. In gdb_stub_handle_stop (src/debug/gdbstub.c) the first thread to stop sets all_stopped, clears stop_requested, and only then calls thread_interrupt_all. thread_interrupt_all only kicks every vCPU out of hv_vcpu_run. A kicked sibling's canceled-exit handler in src/syscall/proc.c enters the GDB stop only when gdb_stub_stop_requested() returns true, and that function reads stop_requested alone, which is already 0, so the sibling resumes guest code. Nothing else in the run loop checks all_stopped.

Reproduce

#include <pthread.h>
#include <stdint.h>
#include <unistd.h>

volatile uint64_t counter;

__attribute__((noinline)) void bp_here(void) { __asm__ volatile("" ::: "memory"); }

static void *spin(void *arg)
{
    (void) arg;
    for (;;)
        counter++;
    return NULL;
}

int main(void)
{
    pthread_t t;
    pthread_create(&t, NULL, spin, NULL);
    usleep(200 * 1000);
    bp_here();
    return 0;
}

Build it static, start it with build/elfuse --gdb 23947 --gdb-stop-on-entry ./gdb_allstop, and run this client with the addresses of bp_here and counter from nm:

import socket, sys, time
port, bp, ctr = int(sys.argv[1]), int(sys.argv[2], 16), int(sys.argv[3], 16)
s = None
for _ in range(100):
    try:
        s = socket.create_connection(("127.0.0.1", port), timeout=10)
        break
    except OSError:
        time.sleep(0.1)
buf = b""
def pkt(data):
    s.sendall(("$%s#%02x" % (data, sum(data.encode()) & 0xFF)).encode())
def reply():
    global buf
    while True:
        i = buf.find(b"$"); j = buf.find(b"#", i)
        if i >= 0 and j >= 0 and len(buf) >= j + 3:
            body = buf[i + 1:j].decode(); buf = buf[j + 3:]
            s.sendall(b"+")
            return body
        buf += s.recv(4096)
def cmd(data):
    pkt(data)
    return reply()
cmd("?")
print("Z0:", cmd("Z0,%x,4" % bp))
print("stop reply:", cmd("c")[:3])
read = lambda: int.from_bytes(bytes.fromhex(cmd("m%x,8" % ctr)), "little")
a = read(); time.sleep(0.5); b = read()
print("counter while stopped: %d -> %d (%s)" % (a, b, "changed" if b != a else "unchanged"))
pkt("k")

Possible fix

Keep the stop request raised until GDB resumes: clear stop_requested in the resume paths (c, s, vCont, detach) rather than in gdb_stub_handle_stop, or have gdb_stub_stop_requested also return true while all_stopped is set. Kicked siblings then park in gdb_stub_handle_stop until the resume.

Origin

Follow-up from #525.

Lingua principale
C
Stelle
282
Fork
32
Merge medio
1g 12h
PR unite (30g)
17

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di sysprog21/elfuse

Tutte le issue di sysprog21/elfuse

Issue simili

Altre issue su C

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.