From patchwork Tue May 30 20:41:53 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 70335 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 1D03D385660E for ; Tue, 30 May 2023 20:42:37 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 1D03D385660E DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1685479357; bh=t/NF6bCnrtWyICMtYucKo9mV6l+cuD6Hg7MuFrRFvA0=; h=To:Cc:Subject:Date:In-Reply-To:References:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=sw8JmjIdC46ceWwZWgVa+BMaVsgs22unbfv+dFK0biOEnvPS7ArzyHNz9HVJIhXIk 19u+obyXzotQKcRsCh3a/3mLp9yTXo072nAyzLabLdpbF1FLTga4IQsSP6aQLCZ5sl YwvFrO8wxxphETpuCmqUjVJ4zbXC1tudbubD4mzE= X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 5AEB2385702F for ; Tue, 30 May 2023 20:42:02 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 5AEB2385702F Received: from mail-wm1-f72.google.com (mail-wm1-f72.google.com [209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-16-SZHwdMSxPeC5KHIl-tC6SQ-1; Tue, 30 May 2023 16:42:00 -0400 X-MC-Unique: SZHwdMSxPeC5KHIl-tC6SQ-1 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-3f611d31577so809545e9.0 for ; Tue, 30 May 2023 13:42:00 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685479319; x=1688071319; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=t/NF6bCnrtWyICMtYucKo9mV6l+cuD6Hg7MuFrRFvA0=; b=OoHpE/7vVfVZNlqYuImluFemWsToRpiu7gLSSA4uuDkpObXro1zKJCMRuwT6JSGXJC X31PY+5oirn57f5qN9Drvz0r7jy6yxXvkAViC9YOuNWQ1Ly2Ql6ZB06zCZ+JKkWXSSHX b0UaGSbAoEq8R/RBBO6oyXxNrCqUQ9xPCa2E2D71R1pHX0k9ZVfDSI5P4gPpNK9viqxH YdDjeMvEdgLE7TyR6lnrV4qTordDshlUFh3luV/5Fd58jx+aho+u+ptV6SKRUCQSXiFw n+bWGlp+PQgKYkae5C6EhwBhAYL8gFr8Rffz3CNyqhcw6Tj2OXEacI3DqvywXcHYy1M5 XulQ== X-Gm-Message-State: AC+VfDw1MRNQxtxOlAoJINLSB4mSXH6X4drNtSIXZucYwDf3l5rcNH9F 6mGn4GI9dZpsW1sBE6SbQSjopWgooEmGvr3///9pe8opmzTQv032A3TbtCTnWItpSL+aipyBBr2 bPXM3jfjKoF5FpeGGFkGK9JUuVoNWKm64W2KaktAFGUGsB3JqPtqbG4spnOTHbPxcMWGoEPMOFX UuwG1mbA== X-Received: by 2002:a7b:c04a:0:b0:3f5:e29:bcde with SMTP id u10-20020a7bc04a000000b003f50e29bcdemr2563436wmc.17.1685479318225; Tue, 30 May 2023 13:41:58 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ5o6+NQB7Qj8j2vIjei9aAzgQ4BfASvKuxnKkNRSVM91ShvTy8NUqePAPolaRpaA/MMolqhjw== X-Received: by 2002:a7b:c04a:0:b0:3f5:e29:bcde with SMTP id u10-20020a7bc04a000000b003f50e29bcdemr2563408wmc.17.1685479316839; Tue, 30 May 2023 13:41:56 -0700 (PDT) Received: from localhost (11.72.115.87.dyn.plus.net. [87.115.72.11]) by smtp.gmail.com with ESMTPSA id o11-20020a05600c378b00b003f195d540d9sm22040479wmr.14.2023.05.30.13.41.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 30 May 2023 13:41:56 -0700 (PDT) To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCHv6] gdb: add inferior-specific breakpoints Date: Tue, 30 May 2023 21:41:53 +0100 Message-Id: X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE, URI_LONG_REPEAT autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Andrew Burgess via Gdb-patches From: Andrew Burgess Reply-To: Andrew Burgess Errors-To: gdb-patches-bounces+patchwork=sourceware.org@sourceware.org Sender: "Gdb-patches" Doc changes have already been approved here: https://sourceware.org/pipermail/gdb-patches/2023-January/195986.html In v6: - Rebased to current upstream master, this includes all Simon's recent breakpoint changes. Retested with no regressions seen. In v5: - Rebased to current upstream master and retested, - No changes to code or docs. In v4: - Merged patch #1 from the previous series as this felt like a pretty obvious cleanup. - Rebased to HEAD of master. - No changes to code or docs. --- This commit extends the breakpoint mechanism to allow for inferior specific breakpoints (but not watchpoints in this commit). As GDB gains better support for multiple connections, and so for running multiple (possibly unrelated) inferiors, then it is not hard to imagine that a user might wish to create breakpoints that apply to any thread in a single inferior. To achieve this currently, the user would need to create a condition possibly making use of the $_inferior convenience variable, which, though functional, isn't the most user friendly. This commit adds a new 'inferior' keyword that allows for the creation of inferior specific breakpoints. Inferior specific breakpoints are automatically deleted when the associated inferior is removed from GDB, this is similar to how thread-specific breakpoints when the associated thread is deleted. Watchpoints are already per-program-space, which in most cases mean watchpoints are already inferior specific. There is a small window where inferior-specific watchpoints might make sense, which is after a vfork, when two processes are sharing the same address space. However, I'm leaving that as an exercise for another day. For now, attempting to use the inferior keyword with a watchpoint will give an error, like this: (gdb) watch a8 inferior 1 Cannot use 'inferior' keyword with watchpoints A final note on the implementation. Currently, inferior specific breakpoints, like thread-specific breakpoints, are inserted into every inferior, GDB then checks once the inferior stops if we are in the correct thread or inferior, and resumes automatically if we stopped in the wrong thread/inferior. An obvious optimisation here is to only insert breakpoint locations into the specific program space (which mostly means inferior) that contains either the inferior or thread we are interested in. This would reduce the number times GDB has to stop and then resume again in a multi-inferior setup. I have started investigating implementing this. And though the initial implementation is trivial to get going, like so many things, the devil is in the detail. While my hope is that I will bring this optimisation to the list at some point in the near future I can't guarantee it. That said, we already have the issue of placing breakpoints into inferiors that will never be hit (with thread specific breakpoints), so I don't believe I'm adding anything worse than we already have. Personally, I'd be happy to see this new feature merged even if the optimisation never happens. --- gdb/NEWS | 15 ++ gdb/breakpoint.c | 237 ++++++++++++++---- gdb/breakpoint.h | 14 +- gdb/doc/gdb.texinfo | 72 +++++- gdb/doc/python.texi | 26 +- gdb/guile/scm-breakpoint.c | 7 +- gdb/infcmd.c | 15 +- gdb/inferior.h | 11 + gdb/linespec.c | 4 +- gdb/mi/mi-cmd-break.c | 11 +- gdb/mi/mi-main.c | 18 +- gdb/mi/mi-main.h | 6 + gdb/python/py-breakpoint.c | 96 ++++++- gdb/python/py-finishbreakpoint.c | 2 +- gdb/testsuite/gdb.ada/tasks.exp | 2 + gdb/testsuite/gdb.linespec/cpcompletion.exp | 4 +- gdb/testsuite/gdb.linespec/explicit.exp | 1 + gdb/testsuite/gdb.mi/new-ui-bp-deleted.c | 29 +++ gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp | 108 ++++++++ .../gdb.multi/inferior-specific-bp-1.c | 52 ++++ .../gdb.multi/inferior-specific-bp-2.c | 52 ++++ .../gdb.multi/inferior-specific-bp.exp | 179 +++++++++++++ gdb/testsuite/gdb.python/py-breakpoint.exp | 43 ++++ gdb/testsuite/lib/completion-support.exp | 2 +- gdb/testsuite/lib/mi-support.exp | 20 +- 25 files changed, 938 insertions(+), 88 deletions(-) create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp base-commit: 2bd766d6245bf9db77c42da3537c949ffb814bfc diff --git a/gdb/NEWS b/gdb/NEWS index d97e3c15a87..e16e93cb6cd 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -67,6 +67,13 @@ break foo thread 1 task 1 watch var thread 2 task 3 +* Breakpoints can now be inferior-specific. This is similar to the + existing thread-specific breakpoint support. Breakpoint conditions + can include the 'inferior' keyword followed by an inferior id (as + displayed in the 'info inferiors' output). It is invalid to use the + 'inferior' keyword with either the 'thread' or 'task' keywords when + creating a breakpoint. + * New commands maintenance print record-instruction [ N ] @@ -127,6 +134,14 @@ info main considered simple.) Support for this feature can be verified by using the '-list-features' command, which should contain "simple-values-ref-types". +** The -break-insert command now accepts a '-g thread-group-id' option + to allow for the creation of inferior-specific breakpoints. + +** The bkpt tuple, which appears in breakpoint-created notifications, + and in the result of the -break-insert command can now include an + optional 'inferior' field for both the main breakpoint, and each + location, when the breakpoint is inferior-specific. + * Python API ** The gdb.unwinder.Unwinder.name attribute is now read-only. diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index a94277f0c6c..a838706eba4 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -98,7 +98,7 @@ static void create_breakpoints_sal (struct gdbarch *, gdb::unique_xmalloc_ptr, gdb::unique_xmalloc_ptr, enum bptype, - enum bpdisp, int, int, + enum bpdisp, int, int, int, int, int, int, int, unsigned); @@ -322,6 +322,9 @@ struct momentary_breakpoint : public code_breakpoint disposition = disp_donttouch; frame_id = frame_id_; thread = thread_; + + /* The inferior should have been set by the parent constructor. */ + gdb_assert (inferior == -1); } void re_set () override; @@ -1453,13 +1456,15 @@ breakpoint_set_silent (struct breakpoint *b, int silent) void breakpoint_set_thread (struct breakpoint *b, int thread) { - /* It is invalid to set the thread field to anything other than -1 (which - means no thread restriction) if a task restriction is already in - place. */ - gdb_assert (thread == -1 || b->task == -1); + /* THREAD should be -1, meaning no thread restriction, or it should be a + valid global thread-id, which are greater than zero. */ + gdb_assert (thread == -1 || thread > 0); - int old_thread = b->thread; + /* It is not valid to set a thread restriction for a breakpoint that + already has task or inferior restriction. */ + gdb_assert (thread == -1 || (b->task == -1 && b->inferior == -1)); + int old_thread = b->thread; b->thread = thread; if (old_thread != thread) gdb::observers::breakpoint_modified.notify (b); @@ -1467,16 +1472,37 @@ breakpoint_set_thread (struct breakpoint *b, int thread) /* See breakpoint.h. */ +void +breakpoint_set_inferior (struct breakpoint *b, int inferior) +{ + /* INFERIOR should be -1, meaning no inferior restriction, or it should + be a valid inferior number, which are greater than zero. */ + gdb_assert (inferior == -1 || inferior > 0); + + /* It is not valid to set an inferior restriction for a breakpoint that + already has a task or thread restriction. */ + gdb_assert (inferior == -1 || (b->task == -1 && b->thread == -1)); + + int old_inferior = b->inferior; + b->inferior = inferior; + if (old_inferior != inferior) + gdb::observers::breakpoint_modified.notify (b); +} + +/* See breakpoint.h. */ + void breakpoint_set_task (struct breakpoint *b, int task) { - /* It is invalid to set the task field to anything other than -1 (which - means no task restriction) if a thread restriction is already in - place. */ - gdb_assert (task == -1 || b->thread == -1); + /* TASK should be -1, meaning no task restriction, or it should be a + valid task-id, which are greater than zero. */ + gdb_assert (task == -1 || task > 0); - int old_task = b->task; + /* It is not valid to set a task restriction for a breakpoint that + already has a thread or inferior restriction. */ + gdb_assert (task == -1 || (b->thread == -1 && b->inferior == -1)); + int old_task = b->task; b->task = task; if (old_task != task) gdb::observers::breakpoint_modified.notify (b); @@ -3154,6 +3180,12 @@ insert_breakpoint_locations (void) && !valid_global_thread_id (bl->owner->thread)) continue; + /* Or inferior specific breakpoints if the inferior no longer + exists. */ + if (bl->owner->inferior != -1 + && !valid_global_inferior_id (bl->owner->inferior)) + continue; + switch_to_program_space_and_thread (bl->pspace); /* For targets that support global breakpoints, there's no need @@ -3254,6 +3286,29 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\ } } +/* Called when inferior INF has been removed from GDB. Remove associated + per-inferior breakpoints. */ + +static void +remove_inferior_breakpoints (struct inferior *inf) +{ + for (breakpoint &b : all_breakpoints_safe ()) + { + if (b.inferior == inf->num && user_breakpoint_p (&b)) + { + /* Tell the user the breakpoint has been deleted. But only for + breakpoints that would not normally have been deleted at the + next stop anyway. */ + if (b.disposition != disp_del + && b.disposition != disp_del_at_next_stop) + gdb_printf (_("\ +Inferior-specific breakpoint %d deleted - inferior %d has been removed.\n"), + b.number, inf->num); + delete_breakpoint (&b); + } + } +} + /* See breakpoint.h. */ void @@ -5458,6 +5513,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread) evaluating the condition if this isn't the specified thread/task. */ if ((b->thread != -1 && b->thread != thread->global_num) + || (b->inferior != -1 && b->inferior != thread->inf->num) || (b->task != -1 && b->task != ada_get_task_number (thread))) { infrun_debug_printf ("incorrect thread or task, not stopping"); @@ -6489,6 +6545,8 @@ print_one_breakpoint_location (struct breakpoint *b, uiout->field_signed ("thread", b->thread); else if (b->task != -1) uiout->field_signed ("task", b->task); + else if (b->inferior != -1) + uiout->field_signed ("inferior", b->inferior); } uiout->text ("\n"); @@ -6551,6 +6609,13 @@ print_one_breakpoint_location (struct breakpoint *b, uiout->text ("\n"); } + if (!part_of_multiple && b->inferior != -1) + { + uiout->text ("\tstop only in inferior "); + uiout->field_signed ("inferior", b->inferior); + uiout->text ("\n"); + } + if (!part_of_multiple) { if (b->hit_count) @@ -7526,7 +7591,10 @@ delete_longjmp_breakpoint (int thread) if (b.type == bp_longjmp || b.type == bp_exception) { if (b.thread == thread) - delete_breakpoint (&b); + { + gdb_assert (b.inferior == -1); + delete_breakpoint (&b); + } } } @@ -7537,7 +7605,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread) if (b.type == bp_longjmp || b.type == bp_exception) { if (b.thread == thread) - b.disposition = disp_del_at_next_stop; + { + gdb_assert (b.inferior == -1); + b.disposition = disp_del_at_next_stop; + } } } @@ -7594,6 +7665,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp) { if (b.type == bp_longjmp_call_dummy && b.thread == tp->global_num) { + gdb_assert (b.inferior == -1); struct breakpoint *dummy_b = b.related_breakpoint; /* Find the bp_call_dummy breakpoint in the list of breakpoints @@ -8429,7 +8501,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_, gdb::unique_xmalloc_ptr cond_string_, gdb::unique_xmalloc_ptr extra_string_, enum bpdisp disposition_, - int thread_, int task_, int ignore_count_, + int thread_, int task_, int inferior_, + int ignore_count_, int from_tty, int enabled_, unsigned flags, int display_canonical_) @@ -8453,10 +8526,14 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_, gdb_assert (!sals.empty ()); - /* At most one of thread or task can be set on any breakpoint. */ - gdb_assert (thread == -1 || task == -1); + /* At most one of thread, task, or inferior can be set on any breakpoint. */ + gdb_assert (((thread == -1 ? 0 : 1) + + (task == -1 ? 0 : 1) + + (inferior == -1 ? 0 : 1)) <= 1); + thread = thread_; task = task_; + inferior = inferior_; cond_string = std::move (cond_string_); extra_string = std::move (extra_string_); @@ -8558,7 +8635,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch, gdb::unique_xmalloc_ptr cond_string, gdb::unique_xmalloc_ptr extra_string, enum bptype type, enum bpdisp disposition, - int thread, int task, int ignore_count, + int thread, int task, int inferior, int ignore_count, int from_tty, int enabled, int internal, unsigned flags, int display_canonical) @@ -8572,7 +8649,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch, std::move (cond_string), std::move (extra_string), disposition, - thread, task, ignore_count, + thread, task, inferior, ignore_count, from_tty, enabled, flags, display_canonical); @@ -8601,7 +8678,8 @@ create_breakpoints_sal (struct gdbarch *gdbarch, gdb::unique_xmalloc_ptr cond_string, gdb::unique_xmalloc_ptr extra_string, enum bptype type, enum bpdisp disposition, - int thread, int task, int ignore_count, + int thread, int task, int inferior, + int ignore_count, int from_tty, int enabled, int internal, unsigned flags) { @@ -8625,7 +8703,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch, std::move (cond_string), std::move (extra_string), type, disposition, - thread, task, ignore_count, + thread, task, inferior, ignore_count, from_tty, enabled, internal, flags, canonical->special_display); } @@ -8755,21 +8833,26 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch, } } -/* Given TOK, a string specification of condition and thread, as - accepted by the 'break' command, extract the condition - string and thread number and set *COND_STRING and *THREAD. - PC identifies the context at which the condition should be parsed. - If no condition is found, *COND_STRING is set to NULL. - If no thread is found, *THREAD is set to -1. */ +/* Given TOK, a string specification of condition and thread, as accepted + by the 'break' command, extract the condition string into *COND_STRING. + If no condition string is found then *COND_STRING is set to nullptr. + + If the breakpoint specification has an associated thread, task, or + inferior, these are extracted into *THREAD, *TASK, and *INFERIOR + respectively, otherwise these arguments are set to -1 (for THREAD and + INFERIOR) or 0 (for TASK). + + PC identifies the context at which the condition should be parsed. */ static void find_condition_and_thread (const char *tok, CORE_ADDR pc, gdb::unique_xmalloc_ptr *cond_string, - int *thread, int *task, + int *thread, int *inferior, int *task, gdb::unique_xmalloc_ptr *rest) { cond_string->reset (); *thread = -1; + *inferior = -1; *task = -1; rest->reset (); bool force = false; @@ -8786,7 +8869,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc, if ((*tok == '"' || *tok == ',') && rest) { rest->reset (savestring (tok, strlen (tok))); - return; + break; } end_tok = skip_to_space (tok); @@ -8826,6 +8909,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc, if (*task != -1) error (_("You can specify only one of thread or task.")); + if (*inferior != -1) + error (_("You can specify only one of inferior or thread.")); + tok = end_tok + 1; thr = parse_thread_id (tok, &tmptok); if (tok == tmptok) @@ -8833,6 +8919,26 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc, *thread = thr->global_num; tok = tmptok; } + else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0) + { + if (*inferior != -1) + error(_("You can specify only one inferior.")); + + if (*task != -1) + error (_("You can specify only one of inferior or task.")); + + if (*thread != -1) + error (_("You can specify only one of inferior or thread.")); + + char *tmptok; + tok = end_tok + 1; + *inferior = strtol (tok, &tmptok, 0); + if (tok == tmptok) + error (_("Junk after inferior keyword.")); + if (!valid_global_inferior_id (*inferior)) + error (_("Unknown inferior number %d."), *inferior); + tok = tmptok; + } else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0) { char *tmptok; @@ -8843,6 +8949,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc, if (*thread != -1) error (_("You can specify only one of thread or task.")); + if (*inferior != -1) + error (_("You can specify only one of inferior or task.")); + tok = end_tok + 1; *task = strtol (tok, &tmptok, 0); if (tok == tmptok) @@ -8854,7 +8963,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc, else if (rest) { rest->reset (savestring (tok, strlen (tok))); - return; + break; } else error (_("Junk at end of arguments.")); @@ -8870,7 +8979,7 @@ static void find_condition_and_thread_for_sals (const std::vector &sals, const char *input, gdb::unique_xmalloc_ptr *cond_string, - int *thread, int *task, + int *thread, int *inferior, int *task, gdb::unique_xmalloc_ptr *rest) { int num_failures = 0; @@ -8878,6 +8987,7 @@ find_condition_and_thread_for_sals (const std::vector &sals, { gdb::unique_xmalloc_ptr cond; int thread_id = -1; + int inferior_id = -1; int task_id = -1; gdb::unique_xmalloc_ptr remaining; @@ -8890,11 +9000,16 @@ find_condition_and_thread_for_sals (const std::vector &sals, try { find_condition_and_thread (input, sal.pc, &cond, &thread_id, - &task_id, &remaining); + &inferior_id, &task_id, &remaining); *cond_string = std::move (cond); - /* At most one of thread or task can be set. */ - gdb_assert (thread_id == -1 || task_id == -1); + /* A value of -1 indicates that these fields are unset. At most + one of these fields should be set (to a value other than -1) + at this point. */ + gdb_assert (((thread_id == -1 ? 1 : 0) + + (task_id == -1 ? 1 : 0) + + (inferior_id == -1 ? 1 : 0)) >= 2); *thread = thread_id; + *inferior = inferior_id; *task = task_id; *rest = std::move (remaining); break; @@ -8984,7 +9099,8 @@ int create_breakpoint (struct gdbarch *gdbarch, location_spec *locspec, const char *cond_string, - int thread, const char *extra_string, + int thread, int inferior, + const char *extra_string, bool force_condition, int parse_extra, int tempflag, enum bptype type_wanted, int ignore_count, @@ -8998,6 +9114,10 @@ create_breakpoint (struct gdbarch *gdbarch, int task = -1; int prev_bkpt_count = breakpoint_count; + gdb_assert (thread == -1 || thread > 0); + gdb_assert (inferior == -1 || inferior > 0); + gdb_assert (thread == -1 || inferior == -1); + gdb_assert (ops != NULL); /* If extra_string isn't useful, set it to NULL. */ @@ -9073,7 +9193,8 @@ create_breakpoint (struct gdbarch *gdbarch, const linespec_sals &lsal = canonical.lsals[0]; find_condition_and_thread_for_sals (lsal.sals, extra_string, - &cond, &thread, &task, &rest); + &cond, &thread, &inferior, + &task, &rest); cond_string_copy = std::move (cond); extra_string_copy = std::move (rest); } @@ -9123,7 +9244,7 @@ create_breakpoint (struct gdbarch *gdbarch, std::move (extra_string_copy), type_wanted, tempflag ? disp_del : disp_donttouch, - thread, task, ignore_count, + thread, task, inferior, ignore_count, from_tty, enabled, internal, flags); } else @@ -9192,7 +9313,9 @@ break_command_1 (const char *arg, int flag, int from_tty) create_breakpoint (get_current_arch (), locspec.get (), - NULL, 0, arg, false, 1 /* parse arg */, + NULL, + -1 /* thread */, -1 /* inferior */, + arg, false, 1 /* parse arg */, tempflag, type_wanted, 0 /* Ignore count */, pending_break_support, @@ -9304,7 +9427,8 @@ dprintf_command (const char *arg, int from_tty) create_breakpoint (get_current_arch (), locspec.get (), - NULL, 0, arg, false, 1 /* parse arg */, + NULL, -1, -1, + arg, false, 1 /* parse arg */, 0, bp_dprintf, 0 /* Ignore count */, pending_break_support, @@ -10049,6 +10173,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty, const char *cond_end = NULL; enum bptype bp_type; int thread = -1; + int inferior = -1; /* Flag to indicate whether we are going to use masks for the hardware watchpoint. */ bool use_mask = false; @@ -10103,12 +10228,13 @@ watch_command_1 (const char *arg, int accessflag, int from_tty, if (task != -1) error (_("You can specify only one of thread or task.")); + if (inferior != -1) + error (_("You can specify only one of inferior or thread.")); + /* Extract the thread ID from the next token. */ thr = parse_thread_id (value_start, &endp); - - /* Check if the user provided a valid thread ID. */ - if (*endp != ' ' && *endp != '\t' && *endp != '\0') - invalid_thread_id_error (value_start); + if (value_start == endp) + error (_("Junk after thread keyword.")); thread = thr->global_num; } @@ -10122,12 +10248,20 @@ watch_command_1 (const char *arg, int accessflag, int from_tty, if (thread != -1) error (_("You can specify only one of thread or task.")); + if (inferior != -1) + error (_("You can specify only one of inferior or task.")); + task = strtol (value_start, &tmp, 0); if (tmp == value_start) error (_("Junk after task keyword.")); if (!valid_task_id (task)) error (_("Unknown task %d."), task); } + else if (toklen == 8 && startswith (tok, "inferior")) + { + /* Support for watchpoints will be added in a later commit. */ + error (_("Cannot use 'inferior' keyword with watchpoints")); + } else if (toklen == 4 && startswith (tok, "mask")) { /* We've found a "mask" token, which means the user wants to @@ -10300,6 +10434,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty, /* At most one of thread or task can be set on a watchpoint. */ gdb_assert (thread == -1 || task == -1); w->thread = thread; + w->inferior = inferior; w->task = task; w->disposition = disp_donttouch; w->pspace = current_program_space; @@ -12234,7 +12369,8 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch, enum bptype type_wanted, enum bpdisp disposition, int thread, - int task, int ignore_count, + int task, int inferior, + int ignore_count, int from_tty, int enabled, int internal, unsigned flags) { @@ -12260,7 +12396,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch, std::move (cond_string), std::move (extra_string), disposition, - thread, task, ignore_count, + thread, task, inferior, ignore_count, from_tty, enabled, flags, canonical->special_display)); @@ -12870,10 +13006,11 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec, if (condition_not_parsed && extra_string != NULL) { gdb::unique_xmalloc_ptr local_cond, local_extra; - int local_thread, local_task; + int local_thread, local_task, local_inferior; find_condition_and_thread_for_sals (sals, extra_string.get (), &local_cond, &local_thread, + &local_inferior, &local_task, &local_extra); gdb_assert (cond_string == nullptr); if (local_cond != nullptr) @@ -13747,7 +13884,7 @@ trace_command (const char *arg, int from_tty) create_breakpoint (get_current_arch (), locspec.get (), - NULL, 0, arg, false, 1 /* parse arg */, + NULL, -1, -1, arg, false, 1 /* parse arg */, 0 /* tempflag */, bp_tracepoint /* type_wanted */, 0 /* Ignore count */, @@ -13765,7 +13902,7 @@ ftrace_command (const char *arg, int from_tty) current_language); create_breakpoint (get_current_arch (), locspec.get (), - NULL, 0, arg, false, 1 /* parse arg */, + NULL, -1, -1, arg, false, 1 /* parse arg */, 0 /* tempflag */, bp_fast_tracepoint /* type_wanted */, 0 /* Ignore count */, @@ -13803,7 +13940,7 @@ strace_command (const char *arg, int from_tty) create_breakpoint (get_current_arch (), locspec.get (), - NULL, 0, arg, false, 1 /* parse arg */, + NULL, -1, -1, arg, false, 1 /* parse arg */, 0 /* tempflag */, type /* type_wanted */, 0 /* Ignore count */, @@ -13872,7 +14009,7 @@ create_tracepoint_from_upload (struct uploaded_tp *utp) current_language); if (!create_breakpoint (get_current_arch (), locspec.get (), - utp->cond_string.get (), -1, addr_str, + utp->cond_string.get (), -1, -1, addr_str, false /* force_condition */, 0 /* parse cond/thread */, 0 /* tempflag */, @@ -14960,4 +15097,6 @@ This is useful for formatted output in user-defined commands.")); "breakpoint"); gdb::observers::thread_exit.attach (remove_threaded_breakpoints, "breakpoint"); + gdb::observers::inferior_removed.attach (remove_inferior_breakpoints, + "breakpoint"); } diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h index 709d27fa4db..3465b2b4492 100644 --- a/gdb/breakpoint.h +++ b/gdb/breakpoint.h @@ -579,7 +579,7 @@ struct breakpoint_ops struct linespec_result *, gdb::unique_xmalloc_ptr, gdb::unique_xmalloc_ptr, - enum bptype, enum bpdisp, int, int, + enum bptype, enum bpdisp, int, int, int, int, int, int, int, unsigned); }; @@ -859,6 +859,10 @@ struct breakpoint : public intrusive_list_node care. */ int thread = -1; + /* Inferior number for inferior-specific breakpoint, or -1 if this + breakpoint is for all inferiors. */ + int inferior = -1; + /* Ada task number for task-specific breakpoint, or -1 if don't care. */ int task = -1; @@ -917,7 +921,7 @@ struct code_breakpoint : public breakpoint gdb::unique_xmalloc_ptr cond_string, gdb::unique_xmalloc_ptr extra_string, enum bpdisp disposition, - int thread, int task, int ignore_count, + int thread, int task, int inferior, int ignore_count, int from_tty, int enabled, unsigned flags, int display_canonical); @@ -1597,6 +1601,7 @@ enum breakpoint_create_flags extern int create_breakpoint (struct gdbarch *gdbarch, struct location_spec *locspec, const char *cond_string, int thread, + int inferior, const char *extra_string, bool force_condition, int parse_extra, @@ -1740,6 +1745,11 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent); extern void breakpoint_set_thread (struct breakpoint *b, int thread); +/* Set the inferior for breakpoint B to INFERIOR. If INFERIOR is -1, make + the breakpoint work for any inferior. */ + +extern void breakpoint_set_inferior (struct breakpoint *b, int inferior); + /* Set the task for this breakpoint. If TASK is -1, make the breakpoint work for any task. Passing a value other than -1 for TASK should only be done if b->thread is -1; it is not valid to try and set both a thread diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index d1059e0cb7f..58d91f253b1 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -3517,6 +3517,57 @@ space as a result of inferior 1 having executed a @code{vfork} call. @end table +@menu +* Inferior-Specific Breakpoints:: Controlling breakpoints +@end menu + +@node Inferior-Specific Breakpoints +@subsection Inferior-Specific Breakpoints + +When debugging multiple inferiors, you can choose whether to set +breakpoints for all inferiors, or for a particular inferior. + +@table @code +@cindex breakpoints and inferiors +@cindex inferior-specific breakpoints +@kindex break @dots{} inferior @var{inferior-id} +@item break @var{locspec} inferior @var{inferior-id} +@itemx break @var{locspec} inferior @var{inferior-id} if @dots{} +@var{locspec} specifies a code location or locations in your program. +@xref{Location Specifications}, for details. + +Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint +command to specify that you only want @value{GDBN} to stop when a +particular inferior reaches this breakpoint. The @var{inferior-id} +specifier is one of the inferior identifiers assigned by @value{GDBN}, +shown in the first column of the @samp{info inferiors} output. + +If you do not specify @samp{inferior @var{inferior-id}} when you set a +breakpoint, the breakpoint applies to @emph{all} inferiors of your +program. + +You can use the @code{inferior} qualifier on conditional breakpoints as +well; in this case, place @samp{inferior @var{inferior-id}} before or +after the breakpoint condition, like this: + +@smallexample +(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim +@end smallexample +@end table + +Inferior-specific breakpoints are automatically deleted when the +corresponding inferior is removed from @value{GDBN}. For example: + +@smallexample +(@value{GDBP}) remove-inferiors 2 +Inferior-specific breakpoint 3 deleted - inferior 2 has been removed. +@end smallexample + +A breakpoint can't be both inferior-specific and thread-specific +(@pxref{Thread-Specific Breakpoints}), or task-specific (@pxref{Ada +Tasks}); using more than one of the @code{inferior}, @code{thread}, or +@code{task} keywords when creating a breakpoint will give an error. + @node Threads @section Debugging Programs with Multiple Threads @@ -4480,8 +4531,9 @@ situation. It is also possible to insert a breakpoint that will stop the program -only if a specific thread (@pxref{Thread-Specific Breakpoints}) -or a specific task (@pxref{Ada Tasks}) hits that breakpoint. +only if a specific thread (@pxref{Thread-Specific Breakpoints}), +specific inferior (@pxref{Inferior-Specific Breakpoints}), or a +specific task (@pxref{Ada Tasks}) hits that breakpoint. @item break When called without any arguments, @code{break} sets a breakpoint at @@ -7333,9 +7385,14 @@ Process}), or if @value{GDBN} loses the remote connection (@pxref{Remote Debugging}), etc. Note that with some targets, @value{GDBN} is only able to detect a thread has exited when the user -explictly asks for the thread list with the @code{info threads} +explicitly asks for the thread list with the @code{info threads} command. +A breakpoint can't be both thread-specific and inferior-specific +(@pxref{Inferior-Specific Breakpoints}), or task-specific (@pxref{Ada +Tasks}); using more than one of the @code{thread}, @code{inferior}, or +@code{task} keywords when creating a breakpoint will give an error. + @node Interrupted System Calls @subsection Interrupted System Calls @@ -31567,6 +31624,10 @@ If this is a thread-specific breakpoint, then this identifies the thread in which the breakpoint can trigger. +@item inferior +If this is an inferior-specific breakpoint, this this identifies the +inferior in which the breakpoint can trigger. + @item task If this breakpoint is restricted to a particular Ada task, then this field will hold the task identifier. @@ -32158,7 +32219,7 @@ @smallexample -break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ --qualified ] [ -c @var{condition} ] [ --force-condition ] [ -i @var{ignore-count} ] - [ -p @var{thread-id} ] [ @var{locspec} ] + [ -p @var{thread-id} ] [ -g @var{thread-group-id} ] [ @var{locspec} ] @end smallexample @noindent @@ -32224,6 +32285,9 @@ time the breakpoint is requested. Breakpoints created with a @var{thread-id} will automatically be deleted when the corresponding thread exits. +@item -g @var{thread-group-id} +Restrict the breakpoint to the thread group with the specified +@var{thread-group-id}. @item --qualified This option makes @value{GDBN} interpret a function name specified as a complete fully-qualified name. diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 69755e96143..be03fa4dd5b 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -3424,7 +3424,10 @@ A @code{gdb.Inferior} object has the following attributes: @defvar Inferior.num -ID of inferior, as assigned by GDB. +ID of inferior, as assigned by @value{GDBN}. You can use this to make +Python breakpoints inferior-specific, for example +(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior +attribute}). @end defvar @anchor{gdbpy_inferior_connection} @@ -6302,9 +6305,24 @@ @anchor{python_breakpoint_thread} @defvar Breakpoint.thread -If the breakpoint is thread-specific, this attribute holds the -thread's global id. If the breakpoint is not thread-specific, this -attribute is @code{None}. This attribute is writable. +If the breakpoint is thread-specific (@pxref{Thread-Specific +Breakpoints}), this attribute holds the thread's global id. If the +breakpoint is not thread-specific, this attribute is @code{None}. +This attribute is writable. + +Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can +be set to a valid id at any time, that is, a breakpoint can be thread +specific, or inferior specific, but not both. +@end defvar + +@anchor{python_breakpoint_inferior} +@defvar Breakpoint.inferior +If the breakpoint is inferior-specific (@pxref{Inferior-Specific +Breakpoints}), this attribute holds the inferior's id. If the +breakpoint is not inferior-specific, this attribute is @code{None}. + +This attribute can be written for breakpoints of type +@code{gdb.BP_BREAKPOINT} and @code{gdb.BP_HARDWARE_BREAKPOINT}. @end defvar @defvar Breakpoint.task diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c index 6c6dacb3883..59254646bcc 100644 --- a/gdb/guile/scm-breakpoint.c +++ b/gdb/guile/scm-breakpoint.c @@ -465,7 +465,7 @@ gdbscm_register_breakpoint_x (SCM self) const breakpoint_ops *ops = breakpoint_ops_for_location_spec (locspec.get (), false); create_breakpoint (get_current_arch (), - locspec.get (), NULL, -1, NULL, false, + locspec.get (), NULL, -1, -1, NULL, false, 0, temporary, bp_breakpoint, 0, @@ -784,6 +784,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue) else SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f")); + if (bp_smob->bp->inferior != -1 && id != -1) + scm_misc_error (FUNC_NAME, + _("Cannot have both 'thread' and 'inferior' " + "conditions on a breakpoint"), SCM_EOL); + breakpoint_set_thread (bp_smob->bp, id); return SCM_UNSPECIFIED; diff --git a/gdb/infcmd.c b/gdb/infcmd.c index 15702f84894..8144331bfe6 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -416,17 +416,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how) if (run_how == RUN_STOP_AT_MAIN) { /* To avoid other inferiors hitting this breakpoint, make it - inferior-specific using a condition. A better solution would be to - have proper inferior-specific breakpoint support, in the breakpoint - machinery. We could then avoid inserting a breakpoint in the program - spaces unrelated to this inferior. */ - const char *op - = ((current_language->la_language == language_ada - || current_language->la_language == language_pascal - || current_language->la_language == language_m2) ? "=" : "=="); - std::string arg = string_printf - ("-qualified %s if $_inferior %s %d", main_name (), op, - current_inferior ()->num); + inferior-specific. */ + std::string arg = string_printf ("-qualified %s inferior %d", + main_name (), + current_inferior ()->num); tbreak_command (arg.c_str (), 0); } diff --git a/gdb/inferior.h b/gdb/inferior.h index caa8e4d494a..9af0e367c6f 100644 --- a/gdb/inferior.h +++ b/gdb/inferior.h @@ -826,4 +826,15 @@ extern void print_selected_inferior (struct ui_out *uiout); extern void switch_to_inferior_and_push_target (inferior *new_inf, bool no_connection, inferior *org_inf); +/* Return true if ID is a valid global inferior number. */ + +inline bool +valid_global_inferior_id (int id) +{ + for (inferior *inf : all_inferiors ()) + if (inf->num == id) + return true; + return false; +} + #endif /* !defined (INFERIOR_H) */ diff --git a/gdb/linespec.c b/gdb/linespec.c index 7d969f37fbf..695dc6515ee 100644 --- a/gdb/linespec.c +++ b/gdb/linespec.c @@ -254,9 +254,9 @@ enum linespec_token_type /* List of keywords. This is NULL-terminated so that it can be used as enum completer. */ -const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL }; +const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL }; #define IF_KEYWORD_INDEX 0 -#define FORCE_KEYWORD_INDEX 3 +#define FORCE_KEYWORD_INDEX 4 /* A token of the linespec lexer */ diff --git a/gdb/mi/mi-cmd-break.c b/gdb/mi/mi-cmd-break.c index 48b58587488..5fabe784417 100644 --- a/gdb/mi/mi-cmd-break.c +++ b/gdb/mi/mi-cmd-break.c @@ -173,6 +173,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, int hardware = 0; int temp_p = 0; int thread = -1; + int thread_group = -1; int ignore_count = 0; const char *condition = NULL; int pending = 0; @@ -191,7 +192,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, enum opt { HARDWARE_OPT, TEMP_OPT, CONDITION_OPT, - IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT, + IGNORE_COUNT_OPT, THREAD_OPT, THREAD_GROUP_OPT, + PENDING_OPT, DISABLE_OPT, TRACEPOINT_OPT, FORCE_CONDITION_OPT, QUALIFIED_OPT, @@ -205,6 +207,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, {"c", CONDITION_OPT, 1}, {"i", IGNORE_COUNT_OPT, 1}, {"p", THREAD_OPT, 1}, + {"g", THREAD_GROUP_OPT, 1}, {"f", PENDING_OPT, 0}, {"d", DISABLE_OPT, 0}, {"a", TRACEPOINT_OPT, 0}, @@ -247,6 +250,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, if (!valid_global_thread_id (thread)) error (_("Unknown thread %d."), thread); break; + case THREAD_GROUP_OPT: + thread_group = mi_parse_thread_group_id (oarg); + break; case PENDING_OPT: pending = 1; break; @@ -360,7 +366,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, error (_("Garbage '%s' at end of location"), address); } - create_breakpoint (get_current_arch (), locspec.get (), condition, thread, + create_breakpoint (get_current_arch (), locspec.get (), condition, + thread, thread_group, extra_string.c_str (), force_condition, 0 /* condition and thread are valid. */, diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c index dc7d717ef60..809d7a0d8a0 100644 --- a/gdb/mi/mi-main.c +++ b/gdb/mi/mi-main.c @@ -1755,8 +1755,7 @@ mi_cmd_remove_inferior (const char *command, const char *const *argv, int argc) if (argc != 1) error (_("-remove-inferior should be passed a single argument")); - if (sscanf (argv[0], "i%d", &id) != 1) - error (_("the thread group id is syntactically invalid")); + id = mi_parse_thread_group_id (argv[0]); inf_to_remove = find_inferior_id (id); if (inf_to_remove == NULL) @@ -2784,6 +2783,21 @@ mi_cmd_complete (const char *command, const char *const *argv, int argc) result.number_matches == max_completions ? "1" : "0"); } +/* See mi-main.h. */ +int +mi_parse_thread_group_id (const char *id) +{ + if (*id != 'i') + error (_("thread group id should start with an 'i'")); + + char *end; + long num = strtol (id + 1, &end, 10); + + if (*end != '\0' || num > INT_MAX) + error (_("invalid thread group id '%s'"), id); + + return (int) num; +} void _initialize_mi_main (); void diff --git a/gdb/mi/mi-main.h b/gdb/mi/mi-main.h index 1741f48b3ee..668a8186cdf 100644 --- a/gdb/mi/mi-main.h +++ b/gdb/mi/mi-main.h @@ -75,4 +75,10 @@ extern void mi_cmd_fix_breakpoint_script_output (const char *command, const char *const *argv, int argc); +/* Parse a thread-group-id from ID, and return the integer part of the + ID. A valid thread-group-id is the character 'i' followed by an + integer that is greater than zero. */ + +extern int mi_parse_thread_group_id (const char *id); + #endif /* MI_MI_MAIN_H */ diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c index d11fc64df20..52fdb7c9a4e 100644 --- a/gdb/python/py-breakpoint.c +++ b/gdb/python/py-breakpoint.c @@ -287,11 +287,86 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure) return -1; } + if (self_bp->bp->inferior != -1 && id != -1) + { + PyErr_SetString (PyExc_RuntimeError, + _("Cannot have both 'thread' and 'inferior' " + "conditions on a breakpoint")); + return -1; + } + breakpoint_set_thread (self_bp->bp, id); return 0; } +/* Python function to set the inferior of a breakpoint. */ + +static int +bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure) +{ + gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self; + long id; + + BPPY_SET_REQUIRE_VALID (self_bp); + + if (newvalue == NULL) + { + PyErr_SetString (PyExc_TypeError, + _("Cannot delete 'inferior' attribute.")); + return -1; + } + else if (PyLong_Check (newvalue)) + { + if (!gdb_py_int_as_long (newvalue, &id)) + return -1; + + if (!valid_global_inferior_id (id)) + { + PyErr_SetString (PyExc_RuntimeError, + _("Invalid inferior ID.")); + return -1; + } + } + else if (newvalue == Py_None) + id = -1; + else + { + PyErr_SetString (PyExc_TypeError, + _("The value of 'inferior' must be an integer or None.")); + return -1; + } + + if (self_bp->bp->type != bp_breakpoint + && self_bp->bp->type != bp_hardware_breakpoint) + { + PyErr_SetString (PyExc_RuntimeError, + _("Cannot set 'inferior' attribute on a gdb.Breakpoint " + "of this type")); + return -1; + } + + if (self_bp->bp->thread != -1 && id != -1) + { + PyErr_SetString (PyExc_RuntimeError, + _("Cannot have both 'thread' and 'inferior' conditions " + "on a breakpoint")); + return -1; + } + + if (self_bp->bp->task != -1 && id != -1) + { + PyErr_SetString (PyExc_RuntimeError, + _("Cannot have both 'task' and 'inferior' conditions " + "on a breakpoint")); + return -1; + } + + breakpoint_set_inferior (self_bp->bp, id); + + return 0; +} + /* Python function to set the (Ada) task of a breakpoint. */ static int bppy_set_task (PyObject *self, PyObject *newvalue, void *closure) @@ -703,6 +778,20 @@ bppy_get_thread (PyObject *self, void *closure) return gdb_py_object_from_longest (self_bp->bp->thread).release (); } +/* Python function to get the breakpoint's inferior ID. */ +static PyObject * +bppy_get_inferior (PyObject *self, void *closure) +{ + gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self; + + BPPY_REQUIRE_VALID (self_bp); + + if (self_bp->bp->inferior == -1) + Py_RETURN_NONE; + + return gdb_py_object_from_longest (self_bp->bp->inferior).release (); +} + /* Python function to get the breakpoint's task ID (in Ada). */ static PyObject * bppy_get_task (PyObject *self, void *closure) @@ -941,7 +1030,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs) = breakpoint_ops_for_location_spec (locspec.get (), false); create_breakpoint (gdbpy_enter::get_gdbarch (), - locspec.get (), NULL, -1, NULL, false, + locspec.get (), NULL, -1, -1, NULL, false, 0, temporary_bp, type, 0, @@ -1350,6 +1439,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = { If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\ If the value is None, then this breakpoint is not thread-specific.\n\ No other type of value can be used.", NULL }, + { "inferior", bppy_get_inferior, bppy_set_inferior, + "Inferior ID for the breakpoint.\n\ +If the value is an inferior ID (integer), then this is an inferior-specific\n\ +breakpoint. If the value is None, then this breakpoint is not\n\ +inferior-specific. No other type of value can be used.", NULL }, { "task", bppy_get_task, bppy_set_task, "Thread ID for the breakpoint.\n\ If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\ diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c index b71e5fafc46..fa2139ba5d2 100644 --- a/gdb/python/py-finishbreakpoint.c +++ b/gdb/python/py-finishbreakpoint.c @@ -307,7 +307,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs) location_spec_up locspec = new_address_location_spec (get_frame_pc (prev_frame), NULL, 0); create_breakpoint (gdbpy_enter::get_gdbarch (), - locspec.get (), NULL, thread, NULL, false, + locspec.get (), NULL, thread, -1, NULL, false, 0, 1 /*temp_flag*/, bp_breakpoint, diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp index eb7ee5c9951..603d43f7c36 100644 --- a/gdb/testsuite/gdb.ada/tasks.exp +++ b/gdb/testsuite/gdb.ada/tasks.exp @@ -58,6 +58,8 @@ gdb_test "break break_me task 1 thread 1" \ "You can specify only one of thread or task\\." gdb_test "break break_me thread 1 task 1" \ "You can specify only one of thread or task\\." +gdb_test "break break_me inferior 1 task 1" \ + "You can specify only one of inferior or task\\." gdb_test "watch j task 1 thread 1" \ "You can specify only one of thread or task\\." gdb_test "watch j thread 1 task 1" \ diff --git a/gdb/testsuite/gdb.linespec/cpcompletion.exp b/gdb/testsuite/gdb.linespec/cpcompletion.exp index fbe19b49d43..f005707b9da 100644 --- a/gdb/testsuite/gdb.linespec/cpcompletion.exp +++ b/gdb/testsuite/gdb.linespec/cpcompletion.exp @@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} { } # Test that completion after a function name offers keyword -# (if/task/thread/-force-condition) matches in linespec mode, and also -# the explicit location options in explicit locations mode. +# (if/inferior/task/thread/-force-condition) matches in linespec mode, +# and also the explicit location options in explicit locations mode. proc_with_prefix keywords-after-function {} { set explicit_list \ diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp index b08d65953d2..668002d9038 100644 --- a/gdb/testsuite/gdb.linespec/explicit.exp +++ b/gdb/testsuite/gdb.linespec/explicit.exp @@ -412,6 +412,7 @@ namespace eval $testfile { "-qualified" "-source" "if" + "inferior" "task" "thread" } diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c new file mode 100644 index 00000000000..c171ef71b9d --- /dev/null +++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c @@ -0,0 +1,29 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2023 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +int +foo (void) +{ + return 0; +} + +int +main (void) +{ + int res = foo (); + return res; +} diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp new file mode 100644 index 00000000000..93a0eb3de49 --- /dev/null +++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp @@ -0,0 +1,108 @@ +# Copyright 2023 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Check for the delivery of '=breakpoint-deleted' notifications when +# breakpoints are deleted. Right now this test only covers +# inferior-specific breakpoints, but it could be extended to cover +# other cases too. + +# Multiple inferiors are needed, therefore only native gdb and +# extended gdbserver modes are supported. +require !use_gdb_stub + +# Separate UI doesn't work with GDB debug. +require !gdb_debug_enabled + +load_lib mi-support.exp +set MIFLAGS "-i=mi" + +standard_testfile + +if { [build_executable "failed to prepare" $testfile $srcfile] } { + return -1 +} + +# Helper proc to create a breakpoint location regexp. NUM is the +# regexp to match the number field of this location. +proc make_bp_loc { num } { + return [mi_make_breakpoint_loc \ + -number "$num" \ + -enabled "y" \ + -func "foo" \ + -inferior "2"] +} + +foreach_mi_ui_mode mode { + mi_gdb_exit + + if {$mode eq "separate"} { + set start_ops "separate-mi-tty" + } else { + set start_ops "" + } + + if [mi_gdb_start $start_ops] { + return + } + + # Load a test binary into inferior 1. + mi_gdb_load ${binfile} + + # Setup inferior 2, including loading an exec file. + mi_gdb_test "-add-inferior" \ + [multi_line "=thread-group-added,id=\"\[^\"\]+\"" \ + "~\"\\\[New inferior 2\\\]\\\\n\"" \ + "\~\"Added inferior 2\\\\n\"" \ + "\\^done,inferior=\"\[^\"\]+\"" ] \ + "mi add inferior 2" + mi_gdb_test "-file-exec-and-symbols --thread-group i2 $::binfile" \ + "\\^done" \ + "set executable of inferior 2" + + # Build regexp for the two locations. + set loc1 [make_bp_loc "$::decimal\\.1"] + set loc2 [make_bp_loc "$::decimal\\.2"] + + # Create the inferior-specific breakpoint. + mi_create_breakpoint_multi "-g i2 foo" "create breakpoint in inferior 2" \ + -inferior "2" -locations "\\\[$loc1,$loc2\\\]" + set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID"] + + if {$mode eq "separate"} { + # In 'separate' mode we delete the inferior from the CLI, and + # then look for the breakpoint-deleted notification on the MI. + with_spawn_id $gdb_main_spawn_id { + gdb_test "inferior 1" ".*" + gdb_test "remove-inferiors 2" \ + "Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\." + } + + gdb_test_multiple "" "check for b/p deleted notification on MI" { + -re "=breakpoint-deleted,id=\"$bpnum\"" { + pass $gdb_test_name + } + } + } else { + # In the non-separate mode we delete the inferior from the MI + # and expect to immediately see a breakpoint-deleted + # notification. + mi_gdb_test "-remove-inferior i2" \ + [multi_line \ + "~\"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\.\\\\n\"" \ + "=breakpoint-deleted,id=\"$bpnum\"" \ + "=thread-group-removed,id=\"i2\"" \ + "\\^done"] + } +} diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c new file mode 100644 index 00000000000..8f86d8cdcea --- /dev/null +++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c @@ -0,0 +1,52 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2022-2023 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +volatile int global_var = 0; + +static void +stop_breakpt (void) +{ + /* Nothing. */ +} + +static inline void __attribute__((__always_inline__)) +foo (void) +{ + int i; + + for (i = 0; i < 10; ++i) + global_var = 0; +} + +static void +bar (void) +{ + global_var = 0; + + foo (); +} + + +int +main (void) +{ + global_var = 0; + foo (); + bar (); + stop_breakpt (); + return 0; +} diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c new file mode 100644 index 00000000000..e5b20b6e7a8 --- /dev/null +++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c @@ -0,0 +1,52 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2022-2023 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +static int bar (void); +static int baz (void); +static int foo (void); + +static void +stop_breakpt (void) +{ + /* Nothing. */ +} + +int +main (void) +{ + int ret = baz (); + stop_breakpt (); + return ret; +} + +static int +bar (void) +{ + return baz (); +} + +static int +foo (void) +{ + return 0; +} + +static int +baz (void) +{ + return foo (); +} diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp new file mode 100644 index 00000000000..1f6573268da --- /dev/null +++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp @@ -0,0 +1,179 @@ +# Copyright 2022-2023 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Test inferior-specific breakpoints. + +standard_testfile -1.c -2.c + +if {[use_gdb_stub]} { + return +} + +set srcfile1 ${srcfile} +set binfile1 ${binfile}-1 +set binfile2 ${binfile}-2 + +if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} { + return -1 +} + +if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} { + return -1 +} + +# Start the first inferior. +clean_restart ${binfile1} +if {![runto_main]} { + return +} + +# Add a second inferior, and start this one too. +gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2" +gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2" +gdb_load $binfile2 +if {![runto_main]} { + return +} + +# Try to create a breakpoint using both the 'inferior' and 'thread' keywords, +# this should fail. Try with the keywords in both orders just in case the +# parser has a bug. +gdb_test "break foo thread 1.1 inferior 1" \ + "You can specify only one of inferior or thread\\." +gdb_test "break foo inferior 1 thread 1.1" \ + "You can specify only one of inferior or thread\\." + +# Try to create a breakpoint using the 'inferior' keyword multiple times. +gdb_test "break foo inferior 1 inferior 2" \ + "You can specify only one inferior\\." + +# Clear out any other breakpoints. +delete_breakpoints + +# Use 'info breakpoint' to check that the inferior specific breakpoint is +# present in the breakpoint list. TESTNAME is the name used for this test, +# BP_NUMBER is the number for the breakpoint, and EXPECTED_LOC_COUNT is the +# number of locations we expect for that breakpoint. +proc check_info_breakpoints { testname bp_number expected_loc_count } { + gdb_test_multiple "info breakpoints $bp_number" $testname { + -re "\r\nNum\\s+\[^\r\n\]+\r\n" { + exp_continue + } + + -re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+\\s*\r\n" { + set saw_header true + exp_continue + } + + -re "^\\s+stop only in inferior 1\r\n" { + set saw_inf_cond true + exp_continue + } + + -re "^\\s+breakpoint already hit $::decimal times\r\n" { + exp_continue + } + + -re "^$bp_number\\.\[123\]\\s+y\\s+ $::hex in foo at \[^\r\n\]+(?: inf \[12\])?\r\n" { + incr location_count + exp_continue + } + + -re "^$::gdb_prompt $" { + with_test_prefix $gdb_test_name { + gdb_assert { $saw_header \ + && $location_count == $expected_loc_count \ + && $saw_inf_cond } \ + $gdb_test_name + } + } + } +} + +# Create an inferior-specific breakpoint. Use gdb_test instead of +# gdb_breakpoint here as we want to check the breakpoint was placed in +# multiple locations. +# +# Currently GDB still places inferior specific breakpoints into every +# inferior, just like it does with thread specific breakpoints. +# Hopefully this will change in the future, at which point, this test +# will need updating. +# +# Two of these locations are in inferior 1, while the third is in +# inferior 2. +gdb_test "break foo inferior 1" \ + "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)" +set bp_number [get_integer_valueof "\$bpnum" "INVALID" \ + "get b/p number for inferior specific breakpoint"] + +set saw_header false +set location_count 0 +set saw_inf_cond false + +check_info_breakpoints "first check for inferior specific breakpoint" \ + $bp_number 3 + +# Create a multi-inferior breakpoint to stop at. +gdb_breakpoint "stop_breakpt" message +set stop_bp_num [get_integer_valueof "\$bpnum" "INVALID" \ + "get b/p number for stop_breakpt"] + +# Now resume inferior 2, this should reach 'stop_breakpt'. +gdb_test "continue" \ + "hit Breakpoint $stop_bp_num\.$decimal, stop_breakpt \\(\\) .*" \ + "continue in inferior 2" + +# Switch to inferior 1, and try there. +gdb_test "inferior 1" ".*" \ + "select inferior 1 to check the inferior-specific b/p works" +gdb_test "continue " \ + "Thread 1\\.${decimal}\[^\r\n\]* hit Breakpoint\ + $bp_number\.$decimal, foo \\(\\) .*" \ + "first continue in inferior 1" + +# Now back to inferior 2, let the inferior exit, and then remove the +# inferior, the inferior-specific breakpoint should not be deleted. +gdb_test "inferior 2" ".*" \ + "switch back to allow inferior 2 to exit" +gdb_test "continue" "\\\[Inferior 2 \[^\r\n\]+ exited normally\\\]" \ + "allow inferior 2 to exit" +gdb_test "inferior 1" ".*" \ + "back to inferior 1 so inferior 2 can be deleted" +gdb_test_no_output "remove-inferiors 2" + +gdb_test "continue " "hit Breakpoint $bp_number\.$decimal, foo \\(\\) .*" \ + "second continue in inferior 1" +gdb_test "continue " "hit Breakpoint $stop_bp_num, stop_breakpt \\(\\) .*" \ + "third continue in inferior 1" + +# Now allow inferior 1 to exit, the inferior specific breakpoint +# should not be deleted. +gdb_test "continue" \ + "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \ + "allow inferior 1 to exit" + +check_info_breakpoints "second check for inferior specific breakpoint" \ + $bp_number 2 + +# Now create another new inferior, then remove inferior 1. As a result of +# this removal, the inferior specific breakpoint should be deleted. +gdb_test "add-inferior" "Added inferior 3.*" "add empty inferior 3" +gdb_test "inferior 3" "Switching to inferior 3.*" "switch to inferior 3" +gdb_test "remove-inferiors 1" \ + "Inferior-specific breakpoint $bp_number deleted - inferior 1 has been removed\\." + +# Now check 'info breakpoints' to ensure the breakpoint is gone. +gdb_test "info breakpoints $bp_number" \ + "No breakpoint or watchpoint matching '$bp_number'\\." diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp index 76094c95d10..6278e84e4be 100644 --- a/gdb/testsuite/gdb.python/py-breakpoint.exp +++ b/gdb/testsuite/gdb.python/py-breakpoint.exp @@ -113,6 +113,8 @@ proc_with_prefix test_bkpt_basic { } { "Get Breakpoint List" 0 gdb_test "python print (blist\[1\].thread)" \ "None" "Check breakpoint thread" + gdb_test "python print (blist\[1\].inferior)" \ + "None" "Check breakpoint inferior" gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \ "True" "Check breakpoint type" gdb_test "python print (blist\[0\].number)" \ @@ -215,6 +217,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } { "check number of lines in commands" } +# Test breakpoint thread and inferior attributes. +proc_with_prefix test_bkpt_thread_and_inferior { } { + global srcfile testfile hex decimal + + # Start with a fresh gdb. + clean_restart ${testfile} + + if {![runto_main]} { + return 0 + } + + with_test_prefix "thread" { + delete_breakpoints + gdb_test "break multiply thread 1" + gdb_test "python bp = gdb.breakpoints ()\[0\]" + gdb_test "python print(bp.thread)" "1" + gdb_test "python print(bp.inferior)" "None" + gdb_test "python bp.inferior = 1" \ + "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*" + gdb_test_no_output "python bp.thread = None" + gdb_test_no_output "python bp.inferior = 1" \ + "set the inferior now the thread has been cleared" + gdb_test "info breakpoints" "stop only in inferior 1\r\n.*" + } + + with_test_prefix "inferior" { + delete_breakpoints + gdb_test "break multiply inferior 1" + gdb_test "python bp = gdb.breakpoints ()\[0\]" + gdb_test "python print(bp.thread)" "None" + gdb_test "python print(bp.inferior)" "1" + gdb_test "python bp.thread = 1" \ + "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*" + gdb_test_no_output "python bp.inferior = None" + gdb_test_no_output "python bp.thread = 1" \ + "set the thread now the inferior has been cleared" + gdb_test "info breakpoints" "stop only in thread 1\r\n.*" + } +} + proc_with_prefix test_bkpt_invisible { } { global srcfile testfile hex decimal @@ -851,6 +893,7 @@ proc_with_prefix test_bkpt_auto_disable { } { test_bkpt_basic test_bkpt_deletion test_bkpt_cond_and_cmds +test_bkpt_thread_and_inferior test_bkpt_invisible test_hardware_breakpoints test_catchpoints diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp index 275f8874f15..85768deab94 100644 --- a/gdb/testsuite/lib/completion-support.exp +++ b/gdb/testsuite/lib/completion-support.exp @@ -27,7 +27,7 @@ namespace eval completion { # List of all quote chars, including no-quote at all. variable maybe_quoted_list {"" "'" "\""} - variable keyword_list {"-force-condition" "if" "task" "thread"} + variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"} variable explicit_opts_list \ {"-function" "-label" "-line" "-qualified" "-source"} diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp index 2ff4ab93ea8..4af02655f21 100644 --- a/gdb/testsuite/lib/mi-support.exp +++ b/gdb/testsuite/lib/mi-support.exp @@ -2542,7 +2542,7 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} { # locations. # # All arguments for the breakpoint location may be specified using the -# options: number, enabled, addr, func, file, fullname, line, +# options: number, enabled, addr, func, file, fullname, line, inferior # thread-groups, and thread. # # For the option -thread the corresponding output field is only added @@ -2556,12 +2556,14 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} { proc mi_make_breakpoint_loc {args} { parse_args {{number .*} {enabled .*} {addr .*} {func .*} {file .*} {fullname .*} {line .*} - {thread-groups \\\[.*\\\]} {thread ""}} + {thread-groups \\\[.*\\\]} {thread ""} {inferior ""}} set attr_list {} foreach attr [list number enabled addr func file \ - fullname line thread-groups] { - lappend attr_list $attr [set $attr] + fullname line thread-groups inferior] { + if {$attr ne "inferior" || [set $attr] ne ""} { + lappend attr_list $attr [set $attr] + } } set result [mi_build_kv_pairs $attr_list] @@ -2635,7 +2637,7 @@ proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \ # locations. # # All arguments for the breakpoint may be specified using the options: -# number, type, disp, enabled, times, ignore, script, +# number, type, disp, enabled, times, ignore, script, inferior, # original-location, cond, evaluated-by, locations, and thread. # # Only if -script and -ignore are given will they appear in the output. @@ -2656,7 +2658,7 @@ proc mi_make_breakpoint_multi {args} { parse_args {{number .*} {type .*} {disp .*} {enabled .*} {times .*} {ignore 0} {script ""} {original-location .*} {cond ""} {evaluated-by ""} - {locations .*} {thread ""}} + {locations .*} {thread ""} {inferior ""}} set attr_list {} foreach attr [list number type disp enabled] { @@ -2665,6 +2667,12 @@ proc mi_make_breakpoint_multi {args} { lappend attr_list "addr" "" + # Only include the inferior field if it was set. This field is + # optional in the MI output. + if {$inferior ne ""} { + lappend attr_list "inferior" $inferior + } + set result [mi_make_breakpoint_1 \ $attr_list $thread $cond ${evaluated-by} $times \ $ignore $script ${original-location}]