From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id D3D553858D28 for ; Mon, 3 Jul 2023 17:38:51 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org D3D553858D28 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1688405931; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=MM54LR84M0R3MgCjzGS3gE/9utfjSDi+UtAiKU9iRiU=; b=N/muTqMP56pYelAOJWCENfggaYwAJ5Rhct+QvyZV9yL5Yp8PmYHzi58x8pjloybQqUXvja ejJgHuqOuvq979iMr0PMw5iDE9awwwC38DuLmrr2GdcBlo0LETflgteDLX5QU3dYFaT40S i7pPaX17/8FKvSuuA71xJhx/0cSM58k= Received: from mail-oi1-f198.google.com (mail-oi1-f198.google.com [209.85.167.198]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-641-6tDkcyoaOO6z2OK-jjsQvw-1; Mon, 03 Jul 2023 13:38:50 -0400 X-MC-Unique: 6tDkcyoaOO6z2OK-jjsQvw-1 Received: by mail-oi1-f198.google.com with SMTP id 5614622812f47-3a3a70425b4so808193b6e.3 for ; Mon, 03 Jul 2023 10:38:50 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1688405929; x=1690997929; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=MM54LR84M0R3MgCjzGS3gE/9utfjSDi+UtAiKU9iRiU=; b=ZBILz17dzmzmf9yecrg3LOUwoTojy6GndeqZ0+7fB0LSwdFhKoNqVbrb/yf8u83Z7G 43FOvXt0pCr3l4pHcYcuuwCPQs1yjwDL58/pSDu2XhPQd3UcJe3irJXTmhjzhTQDUGsQ p4KuvvwV5AEeBDzqGIdj9W2w+Fv2rb8Qk7hf4dFHpKChUpqxtl307vVyGv2sr3WhtCHE CUWLo0zLrg0yqXbe+0PWD3RVn77KKuQ06wXdXtnyyx9CagBfSfAy4+vkVpGt0wDa/keJ Z0gdxJqvdVknT5k3gkaej95JRG7+dhtUKN4HMfNQeLNcdTeSsY9WPam1pHpdfDD5C3IY zGAw== X-Gm-Message-State: AC+VfDz1nKv3vzhCE2Zmmli5ktAJReuoIK09v049W3L11J4EogCb5DkH zs/2kyTbSEDzBj+lBbd/ieoQLvBrXYEGeh62e8J2nUs16SZ2S0CUsVBbxG3IKmOzm+EpJicJWeT LsGa+T9fdvFOyPQxJYVBblPD31J8Yvrmi+8PJoTGRpXQf X-Received: by 2002:a05:6808:140d:b0:3a1:aef1:bbf3 with SMTP id w13-20020a056808140d00b003a1aef1bbf3mr11289864oiv.23.1688405929007; Mon, 03 Jul 2023 10:38:49 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ6sKvo4BmGp4+jNhmTbzTGmQSuph5Pz6OmrbHmP4maG7tecINjtYzoiiIeV/s+HiAKm9gl4nv4+xo/DI+QdltY= X-Received: by 2002:a05:6808:140d:b0:3a1:aef1:bbf3 with SMTP id w13-20020a056808140d00b003a1aef1bbf3mr11289836oiv.23.1688405928426; Mon, 03 Jul 2023 10:38:48 -0700 (PDT) MIME-Version: 1.0 References: <20230609000246.3070-1-amerey@redhat.com> In-Reply-To: <20230609000246.3070-1-amerey@redhat.com> From: Aaron Merey Date: Mon, 3 Jul 2023 13:38:37 -0400 Message-ID: Subject: [PING][PATCH 3/6 v4] gdb: Buffer output streams during events that might download debuginfo To: gdb-patches@sourceware.org Cc: Andrew Burgess X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Spam-Status: No, score=-11.6 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H4,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE,SPF_NONE,TXREP,T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: Ping Thanks, Aaron On Thu, Jun 8, 2023 at 8:03=E2=80=AFPM Aaron Merey wrot= e: > > v3: https://sourceware.org/pipermail/gdb-patches/2023-June/199986.html > > v4 adds output buffering for gdb_stderr. This fixes a regression > introduced by v3 where messages printed to gdb_stderr appeared out of > order with respect to buffered gdb_stdout messages. > > To ensure that writes to gdb_stderr and gdb_stdout occur in the same > order that they were written to the buffers, ID numbers are assigned > to each output_unit recorded by a buffer_file. IDs are ordered and > unique across all output_units currently in use in any buffer_file. > output_units represent a string to be written to a buffer_file's > underlying stream. These strings terminate in either a newline or a > call to wrap_here. > > When two buffer_files are simultaneously flushed, IDs for the next > output_units in each buffer_file are compared to determine which > should be written first. > > Tests for this patch include the existing gdb.base/solib-display.exp > 'after rerun' tests, in which messages are printed to both gdb_stdout > and gdb_stderr while both streams are buffered. Tests that check > the correctness of debuginfod progress messages during output stream > buffering are included in patches 5/6 and 6/6 in this series. > > Commit message: > > Introduce new ui_file buffer_file to temporarily collect output > written to gdb_stdout and gdb_stderr during print_thread, > print_frame_info and print_stop_event. > > This ensures that output from these functions is not interrupted > by debuginfod progress messages. > > With the addition of deferred debuginfo downloading it is possible > for download progress messages to print during these events. > Without any intervention we can end up with poorly formatted output: > > (gdb) backtrace > [...] > #8 0x00007fbe8af7d7cf in pygi_invoke_c_callable (Downloading separat= e debug info for /lib64/libpython3.11.so.1.0 > function_cache=3D0x561221b224d0, state=3D... > > To fix this we accumulate writes to gdb_stdout and gdb_stderr in > buffer_file objects. Debuginfod progress messages skip the buffers > and print to gdb_stdout immediately. This ensures progress messages > print first, followed by uninterrupted frame/thread/stop info: > > (gdb) backtrace > [...] > Downloading separate debug info for /lib64/libpython3.11.so.1.0 > #8 0x00007fbe8af7d7cf in pygi_invoke_c_callable (function_cache=3D0x= 561221b224d0, state=3D... > --- > gdb/cli-out.c | 34 +++++++- > gdb/cli-out.h | 3 + > gdb/debuginfod-support.c | 15 ++-- > gdb/infrun.c | 17 +++- > gdb/mi/mi-out.c | 37 +++++++++ > gdb/mi/mi-out.h | 3 + > gdb/python/py-mi.c | 3 + > gdb/stack.c | 38 ++++++--- > gdb/thread.c | 174 +++++++++++++++++++++++---------------- > gdb/ui-file.c | 136 ++++++++++++++++++++++++++++++ > gdb/ui-file.h | 92 ++++++++++++++++++++- > gdb/ui-out.c | 36 ++++++++ > gdb/ui-out.h | 79 ++++++++++++++++++ > 13 files changed, 573 insertions(+), 94 deletions(-) > > diff --git a/gdb/cli-out.c b/gdb/cli-out.c > index 20d3d93f1ad..fc9f81118c6 100644 > --- a/gdb/cli-out.c > +++ b/gdb/cli-out.c > @@ -263,6 +263,31 @@ cli_ui_out::do_redirect (ui_file *outstream) > m_streams.pop_back (); > } > > +/* If STREAM is in m_streams, then replace it with BUF_FILE. */ > + > +void > +cli_ui_out::do_redirect_to_buffer (ui_file *stream, buffer_file *buf_fil= e) > +{ > + for (auto it =3D m_streams.begin (); it !=3D m_streams.end (); ++it) > + if (*it =3D=3D stream) > + *it =3D buf_file; > +} > + > +/* Replace any buffer_files in m_streams with the buffer_file's > + underlying stream. */ > + > +void > +cli_ui_out::do_remove_buffers () > +{ > + for (auto it =3D m_streams.begin (); it !=3D m_streams.end (); ++it) > + { > + buffer_file *buf =3D dynamic_cast (*it); > + > + if (buf !=3D nullptr) > + *it =3D buf->get_stream (); > + } > +} > + > /* Initialize a progress update to be displayed with > cli_ui_out::do_progress_notify. */ > > @@ -299,7 +324,8 @@ cli_ui_out::do_progress_notify (const std::string &ms= g, > double howmuch, double total) > { > int chars_per_line =3D get_chars_per_line (); > - struct ui_file *stream =3D m_streams.back (); > + struct ui_file *stream > + =3D buffer_file::get_unbuffered_stream (m_streams.back ()); > cli_progress_info &info (m_progress_info.back ()); > > if (chars_per_line > MAX_CHARS_PER_LINE) > @@ -384,7 +410,8 @@ cli_ui_out::do_progress_notify (const std::string &ms= g, > void > cli_ui_out::clear_progress_notify () > { > - struct ui_file *stream =3D m_streams.back (); > + struct ui_file *stream > + =3D buffer_file::get_unbuffered_stream (m_streams.back ()); > int chars_per_line =3D get_chars_per_line (); > > scoped_restore save_pagination > @@ -413,10 +440,11 @@ void > cli_ui_out::do_progress_end () > { > struct ui_file *stream =3D m_streams.back (); > - m_progress_info.pop_back (); > > if (stream->isatty ()) > clear_progress_notify (); > + > + m_progress_info.pop_back (); > } > > /* local functions */ > diff --git a/gdb/cli-out.h b/gdb/cli-out.h > index 34016182269..ae4e650e7eb 100644 > --- a/gdb/cli-out.h > +++ b/gdb/cli-out.h > @@ -71,6 +71,9 @@ class cli_ui_out : public ui_out > virtual void do_wrap_hint (int indent) override; > virtual void do_flush () override; > virtual void do_redirect (struct ui_file *outstream) override; > + virtual void do_redirect_to_buffer (struct ui_file *stream, > + buffer_file *buf_file) override; > + virtual void do_remove_buffers () override; > > virtual void do_progress_start () override; > virtual void do_progress_notify (const std::string &, const char *, > diff --git a/gdb/debuginfod-support.c b/gdb/debuginfod-support.c > index 66b58e8b673..90371afe907 100644 > --- a/gdb/debuginfod-support.c > +++ b/gdb/debuginfod-support.c > @@ -168,7 +168,8 @@ progressfn (debuginfod_client *c, long cur, long tota= l) > > if (check_quit_flag ()) > { > - gdb_printf ("Cancelling download of %s %s...\n", > + ui_file *outstream =3D buffer_file::get_unbuffered_stream (gdb_std= out); > + gdb_printf (outstream, _("Cancelling download of %s %s...\n"), > data->desc, styled_fname.c_str ()); > return 1; > } > @@ -284,10 +285,14 @@ static void > print_outcome (int fd, const char *desc, const char *fname) > { > if (fd < 0 && fd !=3D -ENOENT) > - gdb_printf (_("Download failed: %s. Continuing without %s %ps.\n"), > - safe_strerror (-fd), > - desc, > - styled_string (file_name_style.style (), fname)); > + { > + ui_file *outstream =3D buffer_file::get_unbuffered_stream (gdb_std= out); > + gdb_printf (outstream, > + _("Download failed: %s. Continuing without %s %ps.\n")= , > + safe_strerror (-fd), > + desc, > + styled_string (file_name_style.style (), fname)); > + } > } > > /* See debuginfod-support.h */ > diff --git a/gdb/infrun.c b/gdb/infrun.c > index 4d6df757d23..f719a4aad49 100644 > --- a/gdb/infrun.c > +++ b/gdb/infrun.c > @@ -8706,10 +8706,10 @@ print_stop_location (const target_waitstatus &ws) > print_stack_frame (get_selected_frame (nullptr), 0, source_flag, 1); > } > > -/* See infrun.h. */ > +/* See `print_stop_event` in infrun.h. */ > > -void > -print_stop_event (struct ui_out *uiout, bool displays) > +static void > +do_print_stop_event (struct ui_out *uiout, bool displays) > { > struct target_waitstatus last; > struct thread_info *tp; > @@ -8738,6 +8738,17 @@ print_stop_event (struct ui_out *uiout, bool displ= ays) > } > } > > +/* See infrun.h. This function itself sets up buffered output for the > + duration of do_print_stop_event, which performs the actual event > + printing. */ > + > +void > +print_stop_event (struct ui_out *uiout, bool displays) > +{ > + do_with_buffered_output > + (do_print_stop_event, uiout, displays); > +} > + > /* See infrun.h. */ > > void > diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c > index 29a416a426d..91bcd1216e2 100644 > --- a/gdb/mi/mi-out.c > +++ b/gdb/mi/mi-out.c > @@ -194,6 +194,43 @@ mi_ui_out::do_redirect (ui_file *outstream) > m_streams.pop_back (); > } > > +/* If STREAM is in m_streams, then replace it with BUF_FILE. */ > + > +void > +mi_ui_out::do_redirect_to_buffer (struct ui_file *stream, buffer_file *b= uf_file) > +{ > + for (auto it =3D m_streams.begin (); it !=3D m_streams.end (); ++it) > + { > + if (*it !=3D stream) > + continue; > + > + string_file *sstream =3D dynamic_cast (*it); > + if (sstream !=3D nullptr) > + { > + /* Forward the contents of SSTREAM to BUF_FILE. */ > + buf_file->write (sstream->data (), sstream->size ()); > + sstream->clear (); > + } > + > + *it =3D buf_file; > + } > +} > + > +/* Replace any buffer_files in m_streams with the buffer_file's > + underlying stream. */ > + > +void > +mi_ui_out::do_remove_buffers () > +{ > + for (auto it =3D m_streams.begin (); it !=3D m_streams.end (); ++it) > + { > + buffer_file *buf =3D dynamic_cast (*it); > + > + if (buf !=3D nullptr) > + *it =3D buf->get_stream (); > + } > +} > + > void > mi_ui_out::field_separator () > { > diff --git a/gdb/mi/mi-out.h b/gdb/mi/mi-out.h > index 10c9f8a4585..ee089abfdef 100644 > --- a/gdb/mi/mi-out.h > +++ b/gdb/mi/mi-out.h > @@ -79,6 +79,9 @@ class mi_ui_out : public ui_out > virtual void do_wrap_hint (int indent) override; > virtual void do_flush () override; > virtual void do_redirect (struct ui_file *outstream) override; > + virtual void do_redirect_to_buffer (struct ui_file *stream, > + buffer_file *buf_file) override; > + virtual void do_remove_buffers () override; > > virtual bool do_is_mi_like_p () const override > { return true; } > diff --git a/gdb/python/py-mi.c b/gdb/python/py-mi.c > index 0fcd57844e7..0d672155265 100644 > --- a/gdb/python/py-mi.c > +++ b/gdb/python/py-mi.c > @@ -62,6 +62,9 @@ class py_ui_out : public ui_out > void do_progress_notify (const std::string &, const char *, double, do= uble) > override > { } > + void do_redirect_to_buffer (struct ui_file *, buffer_file *) override > + { } > + void do_remove_buffers () override { } > > void do_table_begin (int nbrofcols, int nr_rows, const char *tblid) ov= erride > { > diff --git a/gdb/stack.c b/gdb/stack.c > index 002bf580634..608bb5fc1f0 100644 > --- a/gdb/stack.c > +++ b/gdb/stack.c > @@ -220,7 +220,8 @@ static void print_frame_local_vars (frame_info_ptr fr= ame, > const char *regexp, const char *t_reg= exp, > int num_tabs, struct ui_file *stream)= ; > > -static void print_frame (const frame_print_options &opts, > +static void print_frame (struct ui_out *uiout, > + const frame_print_options &opts, > frame_info_ptr frame, int print_level, > enum print_what print_what, int print_args, > struct symtab_and_line sal); > @@ -1020,16 +1021,15 @@ get_user_print_what_frame_info (gdb::optional *what) > Used in "where" output, and to emit breakpoint or step > messages. */ > > -void > -print_frame_info (const frame_print_options &fp_opts, > - frame_info_ptr frame, int print_level, > - enum print_what print_what, int print_args, > - int set_current_sal) > +static void > +do_print_frame_info (struct ui_out *uiout, const frame_print_options &fp= _opts, > + frame_info_ptr frame, int print_level, > + enum print_what print_what, int print_args, > + int set_current_sal) > { > struct gdbarch *gdbarch =3D get_frame_arch (frame); > int source_print; > int location_print; > - struct ui_out *uiout =3D current_uiout; > > if (!current_uiout->is_mi_like_p () > && fp_opts.print_frame_info !=3D print_frame_info_auto) > @@ -1105,7 +1105,8 @@ print_frame_info (const frame_print_options &fp_opt= s, > || print_what =3D=3D LOC_AND_ADDRESS > || print_what =3D=3D SHORT_LOCATION); > if (location_print || !sal.symtab) > - print_frame (fp_opts, frame, print_level, print_what, print_args, sa= l); > + print_frame (uiout, fp_opts, frame, print_level, > + print_what, print_args, sal); > > source_print =3D (print_what =3D=3D SRC_LINE || print_what =3D=3D SRC_= AND_LOC); > > @@ -1185,6 +1186,23 @@ print_frame_info (const frame_print_options &fp_op= ts, > gdb_flush (gdb_stdout); > } > > +/* Redirect output to a temporary buffer for the duration > + of do_print_frame_info. */ > + > +void > +print_frame_info (const frame_print_options &fp_opts, > + frame_info_ptr frame, int print_level, > + enum print_what print_what, int print_args, > + int set_current_sal) > +{ > + using ftype =3D void (ui_out *, const frame_print_options &, frame_inf= o_ptr, > + int, enum print_what, int, int); > + > + do_with_buffered_output (do_print_frame_info, current_uiout, > + fp_opts, frame, print_level, print_what= , > + print_args, set_current_sal); > +} > + > /* See stack.h. */ > > void > @@ -1309,13 +1327,13 @@ find_frame_funname (frame_info_ptr frame, enum la= nguage *funlang, > } > > static void > -print_frame (const frame_print_options &fp_opts, > +print_frame (struct ui_out *uiout, > + const frame_print_options &fp_opts, > frame_info_ptr frame, int print_level, > enum print_what print_what, int print_args, > struct symtab_and_line sal) > { > struct gdbarch *gdbarch =3D get_frame_arch (frame); > - struct ui_out *uiout =3D current_uiout; > enum language funlang =3D language_unknown; > struct value_print_options opts; > struct symbol *func; > diff --git a/gdb/thread.c b/gdb/thread.c > index 7f7f035b5ab..03105fbd4ce 100644 > --- a/gdb/thread.c > +++ b/gdb/thread.c > @@ -1040,6 +1040,106 @@ thread_target_id_str (thread_info *tp) > return target_id; > } > > +/* Print thread TP. GLOBAL_IDS indicates whether REQUESTED_THREADS > + is a list of global or per-inferior thread ids. */ > + > +static void > +do_print_thread (ui_out *uiout, const char *requested_threads, > + int global_ids, int pid, int show_global_ids, > + int default_inf_num, thread_info *tp, > + thread_info *current_thread) > +{ > + int core; > + > + if (!should_print_thread (requested_threads, default_inf_num, > + global_ids, pid, tp)) > + return; > + > + ui_out_emit_tuple tuple_emitter (uiout, NULL); > + > + if (!uiout->is_mi_like_p ()) > + { > + if (tp =3D=3D current_thread) > + uiout->field_string ("current", "*"); > + else > + uiout->field_skip ("current"); > + > + uiout->field_string ("id-in-tg", print_thread_id (tp)); > + } > + > + if (show_global_ids || uiout->is_mi_like_p ()) > + uiout->field_signed ("id", tp->global_num); > + > + /* Switch to the thread (and inferior / target). */ > + switch_to_thread (tp); > + > + /* For the CLI, we stuff everything into the target-id field. > + This is a gross hack to make the output come out looking > + correct. The underlying problem here is that ui-out has no > + way to specify that a field's space allocation should be > + shared by several fields. For MI, we do the right thing > + instead. */ > + > + if (uiout->is_mi_like_p ()) > + { > + uiout->field_string ("target-id", target_pid_to_str (tp->ptid)); > + > + const char *extra_info =3D target_extra_thread_info (tp); > + if (extra_info !=3D nullptr) > + uiout->field_string ("details", extra_info); > + > + const char *name =3D thread_name (tp); > + if (name !=3D NULL) > + uiout->field_string ("name", name); > + } > + else > + { > + uiout->field_string ("target-id", thread_target_id_str (tp)); > + } > + > + if (tp->state =3D=3D THREAD_RUNNING) > + uiout->text ("(running)\n"); > + else > + { > + /* The switch above put us at the top of the stack (leaf > + frame). */ > + print_stack_frame (get_selected_frame (NULL), > + /* For MI output, print frame level. */ > + uiout->is_mi_like_p (), > + LOCATION, 0); > + } > + > + if (uiout->is_mi_like_p ()) > + { > + const char *state =3D "stopped"; > + > + if (tp->state =3D=3D THREAD_RUNNING) > + state =3D "running"; > + uiout->field_string ("state", state); > + } > + > + core =3D target_core_of_thread (tp->ptid); > + if (uiout->is_mi_like_p () && core !=3D -1) > + uiout->field_signed ("core", core); > +} > + > +/* Redirect output to a temporary buffer for the duration > + of do_print_thread. */ > + > +static void > +print_thread (ui_out *uiout, const char *requested_threads, > + int global_ids, int pid, int show_global_ids, > + int default_inf_num, thread_info *tp, thread_info *current_= thread) > + > +{ > + using ftype =3D void (ui_out *, const char *, int, int, int, > + int, thread_info *, thread_info *); > + > + do_with_buffered_output > + (do_print_thread, uiout, requested_threads, global_ids, pid, > + show_global_ids, default_inf_num, tp, current_thread); > +} > + > /* Like print_thread_info, but in addition, GLOBAL_IDS indicates > whether REQUESTED_THREADS is a list of global or per-inferior > thread ids. */ > @@ -1123,82 +1223,12 @@ print_thread_info_1 (struct ui_out *uiout, const = char *requested_threads, > for (inferior *inf : all_inferiors ()) > for (thread_info *tp : inf->threads ()) > { > - int core; > - > any_thread =3D true; > if (tp =3D=3D current_thread && tp->state =3D=3D THREAD_EXITED) > current_exited =3D true; > > - if (!should_print_thread (requested_threads, default_inf_num, > - global_ids, pid, tp)) > - continue; > - > - ui_out_emit_tuple tuple_emitter (uiout, NULL); > - > - if (!uiout->is_mi_like_p ()) > - { > - if (tp =3D=3D current_thread) > - uiout->field_string ("current", "*"); > - else > - uiout->field_skip ("current"); > - > - uiout->field_string ("id-in-tg", print_thread_id (tp)); > - } > - > - if (show_global_ids || uiout->is_mi_like_p ()) > - uiout->field_signed ("id", tp->global_num); > - > - /* Switch to the thread (and inferior / target). */ > - switch_to_thread (tp); > - > - /* For the CLI, we stuff everything into the target-id field. > - This is a gross hack to make the output come out looking > - correct. The underlying problem here is that ui-out has no > - way to specify that a field's space allocation should be > - shared by several fields. For MI, we do the right thing > - instead. */ > - > - if (uiout->is_mi_like_p ()) > - { > - uiout->field_string ("target-id", target_pid_to_str (tp->pt= id)); > - > - const char *extra_info =3D target_extra_thread_info (tp); > - if (extra_info !=3D nullptr) > - uiout->field_string ("details", extra_info); > - > - const char *name =3D thread_name (tp); > - if (name !=3D NULL) > - uiout->field_string ("name", name); > - } > - else > - { > - uiout->field_string ("target-id", thread_target_id_str (tp)= ); > - } > - > - if (tp->state =3D=3D THREAD_RUNNING) > - uiout->text ("(running)\n"); > - else > - { > - /* The switch above put us at the top of the stack (leaf > - frame). */ > - print_stack_frame (get_selected_frame (NULL), > - /* For MI output, print frame level. */ > - uiout->is_mi_like_p (), > - LOCATION, 0); > - } > - > - if (uiout->is_mi_like_p ()) > - { > - const char *state =3D "stopped"; > - > - if (tp->state =3D=3D THREAD_RUNNING) > - state =3D "running"; > - uiout->field_string ("state", state); > - } > - > - core =3D target_core_of_thread (tp->ptid); > - if (uiout->is_mi_like_p () && core !=3D -1) > - uiout->field_signed ("core", core); > + print_thread (uiout, requested_threads, global_ids, pid, > + show_global_ids, default_inf_num, tp, current_thr= ead); > } > > /* This end scope restores the current thread and the frame > diff --git a/gdb/ui-file.c b/gdb/ui-file.c > index e0814fe2b2d..c18e6d288c8 100644 > --- a/gdb/ui-file.c > +++ b/gdb/ui-file.c > @@ -234,6 +234,142 @@ string_file::can_emit_style_escape () > > > > +/* See ui-file.h. */ > + > +void > +buffer_file::wrap_here (int indent) > +{ > + m_output_units.emplace_back (m_string, indent); > + m_string.clear (); > +} > + > +/* See ui-file.h. */ > + > +int buffer_file::num_buffers =3D 0; > + > +/* See ui-file.h. */ > + > +unsigned long buffer_file::output_unit::id_counter =3D 0; > + > +/* See ui-file.h. */ > + > +void > +buffer_file::write (const char *buf, long length_buf) > +{ > + /* Record each line separately. */ > + for (size_t prev =3D 0, cur =3D 0; cur < length_buf; ++cur) > + if (buf[cur] =3D=3D '\n' || cur =3D=3D length_buf - 1) > + { > + std::string msg (buf + prev, cur - prev + 1); > + > + /* Prepend a partial line if one was already written to > + this buffer_file. */ > + if (!m_string.empty ()) > + { > + msg.insert (0, m_string); > + m_string.clear (); > + } > + > + m_output_units.emplace_back (msg); > + prev =3D cur + 1; > + } > +} > + > +/* See ui-file.h. */ > + > +void > +buffer_file::flush_next_unit () > +{ > + if (m_output_units.empty ()) > + return; > + > + output_unit unit =3D std::move (m_output_units.front ()); > + > + m_output_units.pop_front (); > + m_stream->puts (unit.m_msg.c_str ()); > + > + if (unit.m_wrap_hint >=3D 0) > + m_stream->wrap_here (unit.m_wrap_hint); > +} > + > +/* See ui-file.h. */ > + > +void > +buffer_file::flush_streams (buffer_file *other) > +{ > + /* Make sure all string contents of these buffer_files are > + contained in their output_units. */ > + this->wrap_here (-1); > + other->wrap_here (-1); > + > + while (!this->m_output_units.empty () > + || !other->m_output_units.empty ()) > + { > + output_unit *out1 =3D nullptr; > + output_unit *out2 =3D nullptr; > + > + if (!this->m_output_units.empty ()) > + out1 =3D &this->m_output_units.front (); > + if (!other->m_output_units.empty ()) > + out2 =3D &other->m_output_units.front (); > + > + bool flush_this_next =3D output_unit::compare (out1, out2); > + > + /* out1 or out2 will be invalidated by flush_next_unit. */ > + if (flush_this_next) > + this->flush_next_unit (); > + else > + other->flush_next_unit (); > + } > +} > + > +/* See ui-file.h. */ > + > +ui_file * > +buffer_file::get_unbuffered_stream (ui_file *stream) > +{ > + buffer_file *buf =3D dynamic_cast (stream); > + > + if (buf =3D=3D nullptr) > + return stream; > + > + return get_unbuffered_stream (buf->m_stream); > +} > + > +/* See ui-file.h. */ > + > +bool > +buffer_file::isatty () > +{ > + return m_stream->isatty (); > +} > + > +/* See ui-file.h. */ > + > +bool > +buffer_file::output_unit::compare (output_unit *out1, > + output_unit *out2) > +{ > + gdb_assert (out1 !=3D nullptr || out2 !=3D nullptr); > + > + if (out1 !=3D nullptr && out2 =3D=3D nullptr) > + return true; > + if (out1 =3D=3D nullptr && out2 !=3D nullptr) > + return false; > + > + return out1->m_id < out2->m_id; > +} > + > +buffer_file::output_unit::output_unit (std::string msg, int wrap_hint) > + : m_msg (msg), m_wrap_hint (wrap_hint), m_id (++id_counter) > +{ > + /* IDs are assigned starting at 1. 0 indicates ID_COUNTER overflow. = */ > + if (m_id =3D=3D 0) > + error (_("Message ID overflow")); > +} > + > + > + > stdio_file::stdio_file (FILE *file, bool close_p) > { > set_stream (file); > diff --git a/gdb/ui-file.h b/gdb/ui-file.h > index de24620e247..e045750c4ef 100644 > --- a/gdb/ui-file.h > +++ b/gdb/ui-file.h > @@ -19,6 +19,7 @@ > #ifndef UI_FILE_H > #define UI_FILE_H > > +#include > #include > #include "ui-style.h" > > @@ -220,13 +221,102 @@ class string_file : public ui_file > bool empty () const { return m_string.empty (); } > void clear () { return m_string.clear (); } > > -private: > +protected: > /* The internal buffer. */ > std::string m_string; > > bool m_term_out; > }; > > +/* A string_file implementation that collects output on behalf of a > + given ui_file. Provides access to the underlying ui_file so > + that buffering can be selectively bypassed. */ > + > +class buffer_file : public string_file > +{ > +public: > + explicit buffer_file (bool term_out, ui_file *stream) > + : string_file (term_out), m_stream (stream) > + { > + ++num_buffers; > + } > + > + ~buffer_file () > + { > + /* Reset ID_COUNTER if possible to help prevent overflow. */ > + if (--num_buffers =3D=3D 0) > + output_unit::id_counter =3D 0; > + } > + > + /* Return the stream associated with this buffer_file. */ > + ui_file *get_stream () > + { > + return m_stream; > + } > + > + /* Record the wrap hint. When flushing the buffer, the underlying > + ui_file's wrap_here will be called at the current place in the > + output. */ > + void wrap_here (int indent) override; > + > + /* Flush collected output from this buffer_file as well as OTHER to th= eir > + underlying ui_files. Output is written to the underlying files in = the > + same order in which it was written to this buffer_file or OTHER. *= / > + void flush_streams (buffer_file *other); > + > + /* Return true if the underlying stream is a tty. */ > + bool isatty () override; > + > + /* Record BUF and schedule it to be written to the underlying stream > + during flush_streams. */ > + void write (const char *buf, long length_buf) override; > + > + /* Return a pointer to STREAM's underlying ui_file. Recursively calle= d > + until a non-buffer_file is found. */ > + static ui_file *get_unbuffered_stream (ui_file *stream); > + > + /* Counter indicating the number of buffer_files currently in use. */ > + static int num_buffers; > + > +private: > + > + /* The underlying output stream. */ > + ui_file *m_stream; > + > + struct output_unit > + { > + output_unit (std::string msg, int wrap_hint =3D -1); > + > + /* String to be written to underlying buffer. */ > + std::string m_msg; > + > + /* Argument to wrap_here. -1 indicates no wrap. Used to call wrap_= here > + at appropriate times during buffer_file flush. */ > + int m_wrap_hint; > + > + /* ID for this output_unit. */ > + unsigned long m_id; > + > + /* Return true if OUT1 should be flushed before OUT2 and false if > + otherwise. At least one of OUT1 and OUT1 should be non-nullptr. = */ > + static bool compare (output_unit *out1, output_unit *out2); > + > + /* Counter used to generate output_unit IDs. IDs are unique across > + all current output_units. IDs are used to ensure that output_uni= ts > + across multiple buffer_files are flushed in the same order in whi= ch > + they were written. See buffer_file::flush_streams. */ > + static unsigned long id_counter; > + }; > + > + /* Write one output_unit to the underlying stream. output_unit is > + selected via FIFO and is removed from m_output_units after being > + written. */ > + void flush_next_unit (); > + > + /* output_units scheduled to be flushed to the underlying output strea= m. */ > + std::list m_output_units; > +}; > + > /* A ui_file implementation that maps directly onto 's FILE. > A stdio_file can either own its underlying file, or not. If it > owns the file, then destroying the stdio_file closes the underlying > diff --git a/gdb/ui-out.c b/gdb/ui-out.c > index 9380630c320..14bb5068b4c 100644 > --- a/gdb/ui-out.c > +++ b/gdb/ui-out.c > @@ -799,6 +799,18 @@ ui_out::redirect (ui_file *outstream) > do_redirect (outstream); > } > > +void > +ui_out::redirect_to_buffer (struct ui_file *stream, buffer_file *buf_fil= e) > +{ > + do_redirect_to_buffer (stream, buf_file); > +} > + > +void > +ui_out::remove_buffers () > +{ > + do_remove_buffers (); > +} > + > /* Test the flags against the mask given. */ > ui_out_flags > ui_out::test_flags (ui_out_flags mask) > @@ -871,3 +883,27 @@ ui_out::ui_out (ui_out_flags flags) > ui_out::~ui_out () > { > } > + > +ui_out_buffer_pop::ui_out_buffer_pop (ui_out *uiout) > +: m_uiout (uiout), > + m_stdout_buf (uiout->can_emit_style_escape (), gdb_stdout), > + m_stderr_buf (uiout->can_emit_style_escape (), gdb_stderr), > + m_prev_stdout (gdb_stdout), > + m_prev_stderr (gdb_stderr) > +{ > + m_uiout->redirect_to_buffer (gdb_stdout, &m_stdout_buf); > + gdb_stdout =3D &m_stdout_buf; > + > + if (m_prev_stdout !=3D m_prev_stderr) > + { > + m_uiout->redirect_to_buffer (gdb_stderr, &m_stderr_buf); > + gdb_stderr =3D &m_stderr_buf; > + } > +} > + > +ui_out_buffer_pop::~ui_out_buffer_pop () > +{ > + m_uiout->remove_buffers (); > + gdb_stdout =3D m_prev_stdout; > + gdb_stderr =3D m_prev_stderr; > +} > diff --git a/gdb/ui-out.h b/gdb/ui-out.h > index ba5b1de68ab..964e6e3a8a4 100644 > --- a/gdb/ui-out.h > +++ b/gdb/ui-out.h > @@ -262,6 +262,12 @@ class ui_out > /* Redirect the output of a ui_out object temporarily. */ > void redirect (ui_file *outstream); > > + /* Redirect STREAM to BUF_FILE temporarily. */ > + void redirect_to_buffer (ui_file *stream, buffer_file *buf_file); > + > + /* Remove all buffer_files from this ui_out. */ > + void remove_buffers (); > + > ui_out_flags test_flags (ui_out_flags mask); > > /* HACK: Code in GDB is currently checking to see the type of ui_out > @@ -360,6 +366,9 @@ class ui_out > virtual void do_wrap_hint (int indent) =3D 0; > virtual void do_flush () =3D 0; > virtual void do_redirect (struct ui_file *outstream) =3D 0; > + virtual void do_redirect_to_buffer (struct ui_file *stream, > + buffer_file *buf_file) =3D 0; > + virtual void do_remove_buffers () =3D 0; > > virtual void do_progress_start () =3D 0; > virtual void do_progress_notify (const std::string &, const char *, > @@ -470,4 +479,74 @@ class ui_out_redirect_pop > struct ui_out *m_uiout; > }; > > +/* On construction, redirect gdb_stdout and gdb_stderr to buffer_files. > + Replace occurances of gdb_stdout and gdb_stderr in M_UIOUT with these > + buffer_files. On destruction restore M_UIOUT, gdb_stdout and gdb_std= err. */ > + > +class ui_out_buffer_pop > +{ > +public: > + ui_out_buffer_pop (ui_out *uiout); > + > + ~ui_out_buffer_pop (); > + > + /* Flush buffered output to the underlying output streams. */ > + void flush () > + { > + m_stdout_buf.flush_streams (&m_stderr_buf); > + } > + > + ui_out_buffer_pop (const ui_out_buffer_pop &) =3D delete; > + ui_out_buffer_pop &operator=3D (const ui_out_buffer_pop &) =3D delete; > + > +private: > + /* ui_out being temporarily redirected. */ > + struct ui_out *m_uiout; > + > + /* Buffer to which stdout is temporarily redirected to for the lifetim= e > + of this object. */ > + buffer_file m_stdout_buf; > + > + /* Buffer to which stderr is temporarily redirected to for the lifetim= e > + of this object. */ > + buffer_file m_stderr_buf; > + > + /* Original gdb_stdout at the time of this object's construction. */ > + ui_file *m_prev_stdout; > + > + /* Original gdb_stdout at the time of this object's construction. */ > + ui_file *m_prev_stderr; > +}; > + > +/* Redirect output for the duration of FUNC. */ > + > +template > +void > +do_with_buffered_output (F func, ui_out *uiout, Arg... args) > +{ > + ui_out_buffer_pop buf (uiout); > + > + try > + { > + func (uiout, std::forward (args)...); > + } > + catch (gdb_exception &ex) > + { > + /* Ideally flush would be called in the destructor of buf, > + however flushing might cause an exception to be thrown. > + Catch it and ensure the first exception propagates. */ > + try > + { > + buf.flush (); > + } > + catch (const gdb_exception &ignore) > + { > + } > + > + throw_exception (std::move (ex)); > + } > + > + /* Try was successful. Let any further exceptions propagate. */ > + buf.flush (); > +} > #endif /* UI_OUT_H */ > -- > 2.40.1 >