Bug 32015 - GDB crashes while printing large D array
Summary: GDB crashes while printing large D array
Status: RESOLVED FIXED
Alias: None
Product: gdb
Classification: Unclassified
Component: exp (show other bugs)
Version: 15.1
: P2 normal
Target Milestone: 15.2
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2024-07-23 17:07 UTC by Tim Schendekehl
Modified: 2024-08-07 13:05 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed: 2024-08-02 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Tim Schendekehl 2024-07-23 17:07:06 UTC
Consider the following D program:
```
int main()
{
    ubyte[] data = new ubyte[1_000_000];
    return 0;
}
```

The program can be compiled with dmd using the following command:
```
dmd -g test.d
```
I used dmd v2.109.1 on Arch Linux x86_64. Alternatively it can be compiled with gdc or ldc with the same result.

The program should be loaded with gdb and a breakpoint should be set for the return statement. Printing the variable `data` with gdb then results in a segmentation fault in gdb. This can be done in one command:
```
gdb --batch -ex "b test.d:4" -ex "run" -ex "p data" ./test
```

Line 4 should be the return statement. For some array sizes gdb only crashes sometimes, so it may be necessary to increase the array size or run the test multiple times.

I have tested it with gdb 15.1 from the Arch Linux package and gdb 16.0.50.20240722 built from a source snapshot. Here is the full output (with some paths shortened):
```
GNU gdb (GDB) 16.0.50.20240722-git
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./test...
(gdb) b test.d:4
Breakpoint 1 at 0x251d8: file test.d, line 4.
(gdb) run
Starting program: test 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".

Breakpoint 1, D main () at test.d:4
4	    return 0;
(gdb) p data


Fatal signal: Segmentation fault
----- Backtrace -----
0x613d21952c6b gdb_internal_backtrace_1
	gdb-16.0.50.20240722/gdb/bt-utils.c:121
0x613d21952c6b _Z22gdb_internal_backtracev
	gdb-16.0.50.20240722/gdb/bt-utils.c:167
0x613d21a79584 handle_fatal_signal
	gdb-16.0.50.20240722/gdb/event-top.c:917
0x613d21a79752 handle_sigsegv
	gdb-16.0.50.20240722/gdb/event-top.c:990
0x706123a2d76f ???
0x613d21a822c0 _Z15extract_integerImSt11is_integralImEET_N3gdb10array_viewIKhEE10bfd_endian
	gdb-16.0.50.20240722/gdb/extract-store-integer.c:112
0x613d21dba1c2 extract_unsigned_integer
	gdb-16.0.50.20240722/gdb/extract-store-integer.h:45
0x613d21dba1c2 extract_unsigned_integer
	gdb-16.0.50.20240722/gdb/extract-store-integer.h:52
0x613d21dba1c2 _Z16generic_printstrP7ui_fileP4typePKhjPKciiiPK19value_print_options
	gdb-16.0.50.20240722/gdb/valprint.c:2562
0x613d2197166b _ZNK13language_defn8printstrEP7ui_fileP4typePKhjPKciPK19value_print_options
	gdb-16.0.50.20240722/gdb/c-lang.c:220
0x613d2197ab79 c_value_print_array
	gdb-16.0.50.20240722/gdb/c-valprint.c:296
0x613d2197ab79 _Z19c_value_print_innerP5valueP7ui_fileiPK19value_print_options
	gdb-16.0.50.20240722/gdb/c-valprint.c:431
0x613d21db6f14 _Z16common_val_printP5valueP7ui_fileiPK19value_print_optionsPK13language_defn
	gdb-16.0.50.20240722/gdb/valprint.c:1093
0x613d2197ad63 _Z13c_value_printP5valueP7ui_filePK19value_print_options
	gdb-16.0.50.20240722/gdb/c-valprint.c:589
0x613d21db70ec _Z11value_printP5valueP7ui_filePK19value_print_options
	gdb-16.0.50.20240722/gdb/valprint.c:1204
0x613d21bd59b8 _Z11print_valueP5valueRK19value_print_options
	gdb-16.0.50.20240722/gdb/printcmd.c:1265
0x613d21bd5c89 print_command_1
	gdb-16.0.50.20240722/gdb/printcmd.c:1381
0x613d2198ae34 _Z8cmd_funcP16cmd_list_elementPKci
	cli/cli-decode.c:2741
0x613d21d2c8d8 _Z15execute_commandPKci
	gdb-16.0.50.20240722/gdb/top.c:570
0x613d21a79fa7 _Z15command_handlerPKc
	gdb-16.0.50.20240722/gdb/event-top.c:580
0x613d21a7b98c _Z20command_line_handlerOSt10unique_ptrIcN3gdb13xfree_deleterIcEEE
	gdb-16.0.50.20240722/gdb/event-top.c:816
0x613d21a7a9fc gdb_rl_callback_handler
	gdb-16.0.50.20240722/gdb/event-top.c:272
0x613d21defda4 rl_callback_read_char
	gdb-16.0.50.20240722/readline/readline/callback.c:290
0x613d21a7ab5d gdb_rl_callback_read_char_wrapper_noexcept
	gdb-16.0.50.20240722/gdb/event-top.c:197
0x613d21a7ad0a gdb_rl_callback_read_char_wrapper
	gdb-16.0.50.20240722/gdb/event-top.c:236
0x613d21d6cbdf stdin_event_handler
	gdb-16.0.50.20240722/gdb/ui.c:154
0x613d21f041fd gdb_wait_for_event
	gdb-16.0.50.20240722/gdbsupport/event-loop.cc:694
0x613d21f04f21 _Z16gdb_do_one_eventi
	gdb-16.0.50.20240722/gdbsupport/event-loop.cc:263
0x613d21b75839 start_event_loop
	gdb-16.0.50.20240722/gdb/main.c:401
0x613d21b75839 captured_command_loop
	gdb-16.0.50.20240722/gdb/main.c:465
0x613d21b787b4 captured_main
	gdb-16.0.50.20240722/gdb/main.c:1338
0x613d21b787b4 _Z8gdb_mainP18captured_main_args
	gdb-16.0.50.20240722/gdb/main.c:1357
0x613d2189ba75 main
	gdb-16.0.50.20240722/gdb/gdb.c:38
---------------------
A fatal error internal to GDB has been detected, further
debugging is not possible.  GDB will now terminate.

This is a bug, please report it.  For instructions, see:
<https://www.gnu.org/software/gdb/bugs/>.
```
Comment 1 Tom de Vries 2024-07-24 14:31:33 UTC
I've managed to reproduce this with trunk gdb.

With system gdb 13.2, I get this:
...
$ gdb --batch -ex "b test.d:4" -ex "run" -ex "p data" ./test
Breakpoint 1 at 0x438839: file ./test.d, line 4.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, _Dmain () at ./test.d:4
4	    return 0;
$1 = <error reading variable: value requires 1000000 bytes, which is more than max-value-size>
...
Comment 2 Tom de Vries 2024-07-24 15:06:03 UTC
Reproduced with gdb-15-branch and gdb-14-branch, doesn't reproduce with gdb-13-branch.
Comment 3 Tom de Vries 2024-07-24 16:45:15 UTC
Bisects to:
...
commit a0c07915778486a950952139d27c01d4285b02b4
Author: Andrew Burgess <andrew.burgess@embecosm.com>
Date:   Fri Feb 10 23:49:19 2023 +0000

    GDB: Introduce limited array lengths while printing values
...
Comment 4 Hannes Domani 2024-07-25 14:18:49 UTC
This can also be reproduce with C:
```
char a[1000000];

int main()
{
  return a[0];
}
```

No problem with 'print', but 'output' crashes:
```
(gdb) print a
$1 = {0 '\000' <repeats 65536 times>, <unavailable> <repeats 934464 times>}
(gdb) output a

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
```
Comment 5 Tom de Vries 2024-07-26 15:36:45 UTC
(In reply to Hannes Domani from comment #4)
> This can also be reproduce with C:

Using this patch:
...
diff --git a/gdb/printcmd.c b/gdb/printcmd.c
index e37e30edb87..7e6ca7dcf15 100644
--- a/gdb/printcmd.c
+++ b/gdb/printcmd.c
@@ -1449,6 +1449,8 @@ output_command (const char *exp, int from_tty)
      need to load as many array elements as we plan to print.  */
   scoped_array_length_limiting limit_large_arrays (opts.print_max);
 
+  val->fetch_limited ();
+
   print_formatted (val, fmt.size, &opts, gdb_stdout);
 
   annotate_value_end ();
diff --git a/gdb/value.c b/gdb/value.c
index 09fb19b9bf8..2f600794148 100644
--- a/gdb/value.c
+++ b/gdb/value.c
@@ -1687,13 +1687,8 @@ value::set_component_location (const struct value *whole)
     }
 }
 
-/* Access to the value history.  */
-
-/* Record a new value in the value history.
-   Returns the absolute history index of the entry.  */
-
-int
-value::record_latest ()
+void
+value::fetch_limited ()
 {
   /* We don't want this value to have anything to do with the inferior anymore.
      In particular, "set $1 = 50" should not affect the variable from which
@@ -1719,6 +1714,17 @@ value::record_latest ()
   ULONGEST limit = m_limited_length;
   if (limit != 0)
     mark_bytes_unavailable (limit, m_enclosing_type->length () - limit);
+}
+
+/* Access to the value history.  */
+
+/* Record a new value in the value history.
+   Returns the absolute history index of the entry.  */
+
+int
+value::record_latest ()
+{
+  fetch_limited ();
 
   /* Mark the value as recorded in the history for the availability check.  */
   m_in_history = true;
diff --git a/gdb/value.h b/gdb/value.h
index 13cfb007aa2..e697a7a9301 100644
--- a/gdb/value.h
+++ b/gdb/value.h
@@ -638,6 +638,8 @@ struct value
 					LONGEST bit_offset,
 					LONGEST bit_length);
 
+  void fetch_limited ();
+
   /* Record this value on the value history, and return its location
      in the history.  The value is removed from the value chain.  */
   int record_latest ();
...
I get:
...
$ gdb -q -batch a.out -ex "print a" -ex "output a"
$1 = {0 '\000' <repeats 65536 times>, <unavailable> <repeats 934464 times>}
{0 '\000' <repeats 65536 times>, <unavailable> <repeats 934464 times>}$ 
...
Comment 6 Tom de Vries 2024-07-27 07:53:47 UTC
Aha, and this bit:
...
      /* We know that this is a _huge_ array, any attempt to fetch this                                               
         is going to cause GDB to throw an error.  However, to allow                                                  
         the array to still be displayed we fetch its contents up to                                                  
         `max_value_size' and mark anything beyond "unavailable" in                                                   
         the history.  */
      if (m_type->code () == TYPE_CODE_ARRAY
          && m_type->length () > max_value_size
          && array_length_limiting_element_count.has_value ()
          && m_enclosing_type == m_type
          && calculate_limited_array_length (m_type) <= max_value_size)
        m_limited_length = max_value_size;
...
doesn't trigger for the d example because m_type->code () is not TYPE_CODE_ARRAY but TYPE_CODE_STRUCT, more specifically:
...
(gdb) ptype data
type = struct _Array_unsigned char {
    ulong length;
    ubyte *ptr;
}
...
Comment 7 Hannes Domani 2024-07-28 13:13:04 UTC
How about something like this:
```
diff --git a/gdb/value.c b/gdb/value.c
index 424a1048dd2..bca733b95de 100644
--- a/gdb/value.c
+++ b/gdb/value.c
@@ -1717,10 +1717,6 @@ value::record_latest ()
       fetch_lazy ();
     }
 
-  ULONGEST limit = m_limited_length;
-  if (limit != 0)
-    mark_bytes_unavailable (limit, m_enclosing_type->length () - limit);
-
   /* Mark the value as recorded in the history for the availability check.  */
   m_in_history = true;
 
@@ -3932,6 +3928,11 @@ value::fetch_lazy_memory ()
   if (len > 0)
     read_value_memory (this, 0, stack (), addr,
 		       contents_all_raw ().data (), len);
+
+  /* If only part of an array was loaded, mark the rest as unavailable.  */
+  if (m_limited_length > 0)
+    mark_bytes_unavailable (m_limited_length,
+			    m_enclosing_type->length () - m_limited_length);
 }
 
 /* See value.h.  */
```

This would make sure that the contents always match the unavailable bytes, and not only when being 'output' or added to the value history.
Comment 8 Tom de Vries 2024-07-30 10:32:36 UTC
(In reply to Hannes Domani from comment #7)
> How about something like this:

It works for both the c and d example, and doesn't cause regressions on x86_64-linux, so LGTM.  Thanks for working on this, and please submit this fix.
Comment 9 Hannes Domani 2024-08-02 15:23:03 UTC
(In reply to Tom de Vries from comment #8)
> It works for both the c and d example, and doesn't cause regressions on
> x86_64-linux, so LGTM.  Thanks for working on this, and please submit this
> fix.

Done:
https://sourceware.org/pipermail/gdb-patches/2024-August/210857.html
Comment 10 Sourceware Commits 2024-08-06 17:36:47 UTC
The master branch has been updated by Hannes Domani <ssbssa@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=8fdd2b2bcd8117cafcc6ef976e45f0d9f95fb528

commit 8fdd2b2bcd8117cafcc6ef976e45f0d9f95fb528
Author: Hannes Domani <ssbssa@yahoo.de>
Date:   Tue Aug 6 19:34:18 2024 +0200

    Mark unavailable bytes of limited-length arrays when allocating contents
    
    Using 'output' to print arrays larger than max-value-size, with only
    repeating elements, can cause gdb to crash:
    ```
    $ cat a.c:
    char a[1000000];
    
    int main()
    {
      return a[0];
    }
    $ gdb -q a
    (gdb) print a
    $1 = {0 '\000' <repeats 65536 times>, <unavailable> <repeats 934464 times>}
    (gdb) output a
    
    This application has requested the Runtime to terminate it in an unusual way.
    Please contact the application's support team for more information.
    ```
    
    Using 'print' works, because value::record_latest sets the unavailable
    bytes of the value when it's added to the value history.
    But 'outout' doesn't do that, so the printing tries to access more bytes
    than are available.
    
    The original problem in PR32015 was about using 'print' of a dynamic
    array in a D program.
    Here the crash happens because for 'print' the value was a struct with
    length/ptr fields, which is converted in d-valprint.c into an array.
    So value::record_latest didn't have a chance to mark the unavailable
    bytes in this case.
    
    To make sure the unavailable bytes always match the contents, this fixes
    it by marking the unavailable bytes immediately after the contents are
    allocated.
    
    Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32015
    Reviewed-By: Alexandra Petlanova Hajkova <ahajkova@redhat.com>
    Approved-By: Andrew Burgess <aburgess@redhat.com>
Comment 11 Hannes Domani 2024-08-06 17:38:00 UTC
Fixed.
Comment 12 Sourceware Commits 2024-08-07 13:04:56 UTC
The gdb-15-branch branch has been updated by Tom de Vries <vries@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=b82263060348ed854c03806675de34f70562044b

commit b82263060348ed854c03806675de34f70562044b
Author: Hannes Domani <ssbssa@yahoo.de>
Date:   Wed Aug 7 15:05:06 2024 +0200

    Mark unavailable bytes of limited-length arrays when allocating contents
    
    Using 'output' to print arrays larger than max-value-size, with only
    repeating elements, can cause gdb to crash:
    ```
    $ cat a.c:
    char a[1000000];
    
    int main()
    {
      return a[0];
    }
    $ gdb -q a
    (gdb) print a
    $1 = {0 '\000' <repeats 65536 times>, <unavailable> <repeats 934464 times>}
    (gdb) output a
    
    This application has requested the Runtime to terminate it in an unusual way.
    Please contact the application's support team for more information.
    ```
    
    Using 'print' works, because value::record_latest sets the unavailable
    bytes of the value when it's added to the value history.
    But 'outout' doesn't do that, so the printing tries to access more bytes
    than are available.
    
    The original problem in PR32015 was about using 'print' of a dynamic
    array in a D program.
    Here the crash happens because for 'print' the value was a struct with
    length/ptr fields, which is converted in d-valprint.c into an array.
    So value::record_latest didn't have a chance to mark the unavailable
    bytes in this case.
    
    To make sure the unavailable bytes always match the contents, this fixes
    it by marking the unavailable bytes immediately after the contents are
    allocated.
    
    Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32015
    Reviewed-By: Alexandra Petlanova Hajkova <ahajkova@redhat.com>
    Approved-By: Andrew Burgess <aburgess@redhat.com>
    (cherry picked from commit 8fdd2b2bcd8117cafcc6ef976e45f0d9f95fb528)