[3/3] gdb/mi: include ranges in =library-unloaded event

Message ID 67839e239d2f1df7e7a493d322f600dae17f2fee.1735041587.git.aburgess@redhat.com
State New
Headers
Series Don't disable breakpoints in still loaded libraries |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed

Commit Message

Andrew Burgess Dec. 24, 2024, 12:05 p.m. UTC
  Having looked at the dlmopen support in GDB, it occurred to me that
the current MI =library-unloaded event doesn't incude enough
information to be useful.

Consider the gdb.mi/mi-dlmopen.exp test, this test loads libraries
into multiple linker namespaces, and then unloads these libraries.

We should probably figure out a way to include the linker namepsace ID
in GDB's output, e.g. in the =library-loaded and =library-unloaded MI
events, and in the output of 'info sharedlibrary'.  But this commit is
not about doing that.

This commit includes the 'ranges' information in the =library-unloaded
event output.  This is the same ranges information as is included in
the =library-loaded output.  Even without the linker namespace ID,
this should allow MI consumers to figure out which library instance is
being unloaded.

Here is the 'info sharedlibrary' output for mi-dlmopen.exp at the
point where all the shared libraries are loaded:

  info sharedlibrary
  &"info sharedlibrary\n"
  ~"From                To                  Syms Read   Shared Object Library\n"
  ~"0x00007ffff7fca000  0x00007ffff7ff03f5  Yes         /lib64/ld-linux-x86-64.so.2\n"
  ~"0x00007ffff7eda3d0  0x00007ffff7f4e898  Yes         /lib64/libm.so.6\n"
  ~"0x00007ffff7d0e800  0x00007ffff7e6dccd  Yes         /lib64/libc.so.6\n"
  ~"0x00007ffff7fbd040  0x00007ffff7fbd116  Yes         /tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib.1.so\n"
  ~"0x00007ffff7fb8040  0x00007ffff7fb80f9  Yes         /tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib-dep.so\n"
  ~"0x00007ffff7bfe3d0  0x00007ffff7c72898  Yes         /lib64/libm.so.6\n"
  ~"0x00007ffff7a32800  0x00007ffff7b91ccd  Yes         /lib64/libc.so.6\n"
  ~"0x00007ffff7fca000  0x00007ffff7ff03f5  Yes         /lib64/ld-linux-x86-64.so.2\n"
  ~"0x00007ffff7fb3040  0x00007ffff7fb3116  Yes         /tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib.1.so\n"
  ~"0x00007ffff7fae040  0x00007ffff7fae0f9  Yes         /tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib-dep.so\n"
  ~"0x00007ffff7ce1040  0x00007ffff7ce1116  Yes         /tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib.1.so\n"
  ~"0x00007ffff7cdc040  0x00007ffff7cdc0f9  Yes         /tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib-dep.so\n"
  ~"0x00007ffff79253d0  0x00007ffff7999898  Yes         /lib64/libm.so.6\n"
  ~"0x00007ffff7759800  0x00007ffff78b8ccd  Yes         /lib64/libc.so.6\n"
  ~"0x00007ffff7fca000  0x00007ffff7ff03f5  Yes         /lib64/ld-linux-x86-64.so.2\n"
  ~"0x00007ffff7cd7040  0x00007ffff7cd7116  Yes         /tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib.2.so\n"
  ^done
  (gdb)

Notice that dlmopen-lib.1.so is loaded multiple times.  Here is the
=library-unloaded event when one copy of this library is unloaded
before this patch:

  =library-unloaded,id="/tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib.1.so",
                    target-name="/tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib.1.so",
		    host-name="/tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib.1.so",
		    thread-group="i1",

It is not possible, given this information, to know which copy of
dlmopen-lib.1.so has actually been unloaded.  An MI consumer would
need to query the full shared library list and update from that
information.

After this patch the new output is:

  =library-unloaded,id="/tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib.1.so",
                    target-name="/tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib.1.so",
		    host-name="/tmp/build/gdb/testsuite/outputs/gdb.mi/mi-dlmopen/dlmopen-lib.1.so",
		    thread-group="i1",
		    ranges=[{from="0x00007ffff7fbd040",to="0x00007ffff7fbd116"}],
		    still-in-use="false"

The new 'ranges' field allows an MI consumer to uniquely identify
which library instance was just unmapped.  A frontent could,
e.g. update a library list with no need to query the full shared
library list.

To include the 'ranges' field I updated mi_interp::on_solib_unloaded
to call a new helper function.  The new helper function is split out
from the existing mi_output_solib_attribs.  I was tempted to just call
mi_output_solib_attribs, but doing so would mean that the
'symbols-loaded' field was also added to the =library-unloaded event,
however, the docs for 'symbols-unloaded' on =library-loaded says:

  The @var{symbols-loaded} field is emitted only for backward
  compatibility and should not be relied on to convey any useful
  information.

And it seemed silly to add a fields to =library-unloaded, which I
would then document as something that should be ignored.  The new
helper function means I can avoid emitting the 'symbols-loaded'
field.

I have also added a 'still-in-use' field.  When true this indicates
that the library was removed from the inferior's library list, but the
mapping was not removed from the inferior's address space as there is
another copy of the library that is still using the library.  In the
above list, notice that ld-linux-x86-64.so.2 appears 3 times, but each
instance is mapped as 0x00007ffff7fca000.  When one copy of
ld-linux-x86-64.so.2 is unloaded, here's the event:

  =library-unloaded,id="/lib64/ld-linux-x86-64.so.2",
                    target-name="/lib64/ld-linux-x86-64.so.2",
		    host-name="/lib64/ld-linux-x86-64.so.2",
		    thread-group="i1",
		    ranges=[{from="0x00007ffff7fca000",to="0x00007ffff7ff03f5"}],
		    still-in-use="true"

The 'still-in-use' field is 'true', this indicates there are at least
one instance of this library remaining mapped at 0x00007ffff7fca000.
---
 gdb/NEWS                            | 12 ++++++++++++
 gdb/doc/gdb.texinfo                 | 20 +++++++++++++++-----
 gdb/mi/mi-interp.c                  | 24 ++++++++++++++++--------
 gdb/testsuite/gdb.mi/mi-dlmopen.exp | 15 ++++++++++++++-
 4 files changed, 57 insertions(+), 14 deletions(-)
  

Comments

Eli Zaretskii Dec. 24, 2024, 1:30 p.m. UTC | #1
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Tue, 24 Dec 2024 12:05:37 +0000
> 
>  gdb/NEWS                            | 12 ++++++++++++
>  gdb/doc/gdb.texinfo                 | 20 +++++++++++++++-----
>  gdb/mi/mi-interp.c                  | 24 ++++++++++++++++--------
>  gdb/testsuite/gdb.mi/mi-dlmopen.exp | 15 ++++++++++++++-
>  4 files changed, 57 insertions(+), 14 deletions(-)

Thanks.

> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -110,6 +110,18 @@
>  
>    ** The "variables" request will not return artificial symbols.
>  
> +* MI changes
> +
> +** The =library-unloaded event now includes the 'ranges' field, which
> +   has the same meaning as for the =library-loaded event.
> +
> +** The =library-unloaded event now includes the 'still-in-use' field.
> +   This field is 'true' when a library is unloaded (removed from the
> +   inferior's list of loaded libraries), but the mapping within the
> +   inferior's address space is retained, as the library was mapped
> +   multiple times, and the same mapping was being reused.  In all
> +   other cases, this field will have the value 'false'.
> +
>  * New commands

This part is okay.

The documentation parts are approved with these nits fixed.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  
Andrew Burgess Jan. 13, 2025, 4:54 p.m. UTC | #2
Eli Zaretskii <eliz@gnu.org> writes:

>> From: Andrew Burgess <aburgess@redhat.com>
>> Cc: Andrew Burgess <aburgess@redhat.com>
>> Date: Tue, 24 Dec 2024 12:05:37 +0000
>> 
>>  gdb/NEWS                            | 12 ++++++++++++
>>  gdb/doc/gdb.texinfo                 | 20 +++++++++++++++-----
>>  gdb/mi/mi-interp.c                  | 24 ++++++++++++++++--------
>>  gdb/testsuite/gdb.mi/mi-dlmopen.exp | 15 ++++++++++++++-
>>  4 files changed, 57 insertions(+), 14 deletions(-)
>
> Thanks.
>
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -110,6 +110,18 @@
>>  
>>    ** The "variables" request will not return artificial symbols.
>>  
>> +* MI changes
>> +
>> +** The =library-unloaded event now includes the 'ranges' field, which
>> +   has the same meaning as for the =library-loaded event.
>> +
>> +** The =library-unloaded event now includes the 'still-in-use' field.
>> +   This field is 'true' when a library is unloaded (removed from the
>> +   inferior's list of loaded libraries), but the mapping within the
>> +   inferior's address space is retained, as the library was mapped
>> +   multiple times, and the same mapping was being reused.  In all
>> +   other cases, this field will have the value 'false'.
>> +
>>  * New commands
>
> This part is okay.
>
> The documentation parts are approved with these nits fixed.
>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>

Hi Eli!

Just got back around to this patch after the Christmas / New Year
break.  I notice you mention fixing some nits ... but don't mention any
nits :)

Is there something you'd like changed?

Thanks,
Andrew
  
Eli Zaretskii Jan. 13, 2025, 5:20 p.m. UTC | #3
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: gdb-patches@sourceware.org
> Date: Mon, 13 Jan 2025 16:54:21 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> From: Andrew Burgess <aburgess@redhat.com>
> >> Cc: Andrew Burgess <aburgess@redhat.com>
> >> Date: Tue, 24 Dec 2024 12:05:37 +0000
> >> 
> >>  gdb/NEWS                            | 12 ++++++++++++
> >>  gdb/doc/gdb.texinfo                 | 20 +++++++++++++++-----
> >>  gdb/mi/mi-interp.c                  | 24 ++++++++++++++++--------
> >>  gdb/testsuite/gdb.mi/mi-dlmopen.exp | 15 ++++++++++++++-
> >>  4 files changed, 57 insertions(+), 14 deletions(-)
> >
> > Thanks.
> >
> >> --- a/gdb/NEWS
> >> +++ b/gdb/NEWS
> >> @@ -110,6 +110,18 @@
> >>  
> >>    ** The "variables" request will not return artificial symbols.
> >>  
> >> +* MI changes
> >> +
> >> +** The =library-unloaded event now includes the 'ranges' field, which
> >> +   has the same meaning as for the =library-loaded event.
> >> +
> >> +** The =library-unloaded event now includes the 'still-in-use' field.
> >> +   This field is 'true' when a library is unloaded (removed from the
> >> +   inferior's list of loaded libraries), but the mapping within the
> >> +   inferior's address space is retained, as the library was mapped
> >> +   multiple times, and the same mapping was being reused.  In all
> >> +   other cases, this field will have the value 'false'.
> >> +
> >>  * New commands
> >
> > This part is okay.
> >
> > The documentation parts are approved with these nits fixed.
> >
> > Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> 
> Hi Eli!
> 
> Just got back around to this patch after the Christmas / New Year
> break.  I notice you mention fixing some nits ... but don't mention any
> nits :)
> 
> Is there something you'd like changed?

Sorry, I guess I pressed Send by mistake.

Yes, there are a couple of typos:

> +It is possible that a library can be appear multiple times in an
                                     ^^
That "be" should not be there.

> +inferior's address space.  When this happen, and one copy of the
                                        ^^^^^^
"happens"

Thanks.
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 186f91e1377..3b2295a3a60 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -110,6 +110,18 @@ 
 
   ** The "variables" request will not return artificial symbols.
 
+* MI changes
+
+** The =library-unloaded event now includes the 'ranges' field, which
+   has the same meaning as for the =library-loaded event.
+
+** The =library-unloaded event now includes the 'still-in-use' field.
+   This field is 'true' when a library is unloaded (removed from the
+   inferior's list of loaded libraries), but the mapping within the
+   inferior's address space is retained, as the library was mapped
+   multiple times, and the same mapping was being reused.  In all
+   other cases, this field will have the value 'false'.
+
 * New commands
 
 show jit-reader-directory
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index b985399cf34..b445bd8fa66 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -32159,12 +32159,22 @@ 
 
 @item =library-unloaded,...
 Reports that a library was unloaded by the program.  This notification
-has 3 fields---@var{id}, @var{target-name} and @var{host-name} with
-the same meaning as for the @code{=library-loaded} notification.
+has the following fields---@var{id}, @var{target-name},
+@var{host-name} and @var{ranges} with the same meaning as for the
+@code{=library-loaded} notification.
+
+It is possible that a library can be appear multiple times in an
+inferior's library list, but the library is only mapped once into the
+inferior's address space.  When this happen, and one copy of the
+library is unloaded, but there are remaining copies, the
+@var{still-in-use} field will be @samp{true}.  In all other
+situations, the @var{still-in-use} field will have the value
+@samp{false}.
+
 The @var{thread-group} field, if present, specifies the id of the
-thread group in whose context the library was unloaded.  If the field is
-absent, it means the library was unloaded in the context of all present
-thread groups.
+thread group in whose context the library was unloaded.  If the field
+is absent, it means the library was unloaded in the context of all
+present thread groups.
 
 @item =traceframe-changed,num=@var{tfnum},tracepoint=@var{tpnum}
 @itemx =traceframe-changed,end
diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
index 9512706d02f..ef0404719e7 100644
--- a/gdb/mi/mi-interp.c
+++ b/gdb/mi/mi-interp.c
@@ -721,15 +721,17 @@  mi_interp::on_target_resumed (ptid_t ptid)
 
 /* See mi-interp.h.  */
 
-void
-mi_output_solib_attribs (ui_out *uiout, const solib &solib)
+static void
+mi_output_solib_attribs_1 (ui_out *uiout, const solib &solib,
+			   bool include_symbols_loaded_p)
 {
   gdbarch *gdbarch = current_inferior ()->arch ();
 
   uiout->field_string ("id", solib.so_original_name);
   uiout->field_string ("target-name", solib.so_original_name);
   uiout->field_string ("host-name", solib.so_name);
-  uiout->field_signed ("symbols-loaded", solib.symbols_loaded);
+  if (include_symbols_loaded_p)
+    uiout->field_signed ("symbols-loaded", solib.symbols_loaded);
   if (!gdbarch_has_global_solist (current_inferior ()->arch ()))
       uiout->field_fmt ("thread-group", "i%d", current_inferior ()->num);
 
@@ -742,6 +744,14 @@  mi_output_solib_attribs (ui_out *uiout, const solib &solib)
     }
 }
 
+/* See mi-interp.h.  */
+
+void
+mi_output_solib_attribs (ui_out *uiout, const solib &solib)
+{
+  mi_output_solib_attribs_1 (uiout, solib, true);
+}
+
 void
 mi_interp::on_solib_loaded (const solib &solib)
 {
@@ -771,11 +781,9 @@  mi_interp::on_solib_unloaded (const solib &solib, bool still_in_use)
 
   ui_out_redirect_pop redir (uiout, this->event_channel);
 
-  uiout->field_string ("id", solib.so_original_name);
-  uiout->field_string ("target-name", solib.so_original_name);
-  uiout->field_string ("host-name", solib.so_name);
-  if (!gdbarch_has_global_solist (current_inferior ()->arch ()))
-    uiout->field_fmt ("thread-group", "i%d", current_inferior ()->num);
+  /* Pass false here so that 'symbols-loaded' is not included.  */
+  mi_output_solib_attribs_1 (uiout, solib, false);
+  uiout->field_string ("still-in-use", still_in_use ? "true" : "false");
 
   gdb_flush (this->event_channel);
 }
diff --git a/gdb/testsuite/gdb.mi/mi-dlmopen.exp b/gdb/testsuite/gdb.mi/mi-dlmopen.exp
index 77d166273e3..2edcd7b5c2c 100644
--- a/gdb/testsuite/gdb.mi/mi-dlmopen.exp
+++ b/gdb/testsuite/gdb.mi/mi-dlmopen.exp
@@ -167,13 +167,23 @@  proc check_solib_unload_events {} {
     # unload events.  Process them now.
     set dyld_unload_count 0
     array set unload_counts {}
+    set still_in_use_fields_correct true
     gdb_test_multiple "" "" -prompt $::mi_gdb_prompt {
-	-re "=library-unloaded,id=\"(\[^\"\]+)\",\[^\r\n\]+\r\n" {
+	-re "=library-unloaded,id=\"(\[^\"\]+)\",\[^\r\n\]+,ranges=\\\[\\{from=\"$::hex\",to=\"$::hex\"\\}\\\],still-in-use=\"(true|false)\"\r\n" {
 	    set lib $expect_out(1,string)
+	    set in_use $expect_out(2,string)
 	    if {[is_dyln $lib]} {
 		# This is the dynamic linker being unloaded.
 		incr dyld_unload_count
+		set expected_in_use "true"
+	    } else {
+		set expected_in_use "false"
 	    }
+
+	    if { $in_use ne $expected_in_use } {
+		set still_in_use_fields_correct false
+	    }
+
 	    set filename [file tail $lib]
 	    incr unload_counts($filename)
 	    exp_continue
@@ -187,6 +197,9 @@  proc check_solib_unload_events {} {
     gdb_assert { $dyld_unload_count == $dyld_count - 1 } \
 	"expected number of dynamic linker unloads"
 
+    gdb_assert { $still_in_use_fields_correct } \
+	"still-in-use fields were all correct"
+
     # Check that we saw the expected number of library-unloaded events for
     # each library.  Each DESC is a list of two elements, a filename for a
     # library, and the number of times it should have been unloaded.