mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-11-16 12:34:43 +00:00
[gdb/c++] Fix hang on whatis std::string::npos
Consider the following scenario, exercising "whatis std::string::npos":
...
$ cat test.cc
int main (void) {
std::string foo = "bar";
return foo.size ();
}
$ g++ test.cc -g
$ gdb -q -batch -iex "set trace-commands on" a.out -x gdb.in
+start
Temporary breakpoint 1 at 0x4021c7: file test.cc, line 3.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Temporary breakpoint 1, main () at test.cc:3
3 std::string foo = "bar";
+info auto-load python-scripts
No auto-load scripts.
+whatis std::string
type = std::__cxx11::basic_string<char, std::char_traits<char>, \
std::allocator<char> >
+whatis std::string::npos
type = const std::__cxx11::basic_string<char, std::char_traits<char>, \
std::allocator<char> >::size_type
...
After installing the package containing the pretty-printers:
...
$ zypper install libstdc++6-pp
...
and adding some commands to use them, we get instead:
...
$ gdb -q -batch -iex "set trace-commands on" a.out -x gdb.in
+add-auto-load-safe-path /usr/share/gdb/auto-load
+add-auto-load-scripts-directory /usr/share/gdb/auto-load
+start
...
+info auto-load python-scripts
Loaded Script
Yes /usr/share/gdb/auto-load/usr/lib64/libstdc++.so.6.0.34-gdb.py
+whatis std::string
type = std::string
+whatis std::string::npos
type = const std::__cxx11::basic_string<char, std::char_traits<char>, \
std::allocator<char> >::size_type
...
Note that "whatis std::string" now prints "std::string", but that
"whatis std::string::npos" still uses the longer name for std::string.
This is when compiling gdb with -O0. With -O2 -fstack-protector-strong, we
have a hang instead:
...
+whatis std::string
type = std::string
+whatis std::string::npos
<HANG>
...
Valgrind complains about an uninitialized field
demangle_component::d_counting, which is fixed by using
cplus_demangle_fill_name in replace_typedefs_qualified_name.
After fixing that, the hang is also reproducible at -O0.
The hang happens because we're stuck in the while loop in
replace_typedefs_qualified_name, replacing "std::string::size_type" with
"std::string::size_type".
Fix this in inspect_type by checking for this situation, getting us instead:
...
+whatis std::string
type = std::string
+whatis std::string::npos
type = const std::string::size_type
$
...
The test-case is a bit unusual:
- pretty-print.cc is a preprocessed c++ source, reduced using cvise [1], then
hand-edited to fix warnings with gcc and clang.
- the pretty-printer .py file is a reduced version of
/usr/share/gcc-15/python/libstdcxx/v6/printers.py.
Using the test-case (and the cplus_demangle_fill_name fix), I managed to
reproduce the hang on both:
- openSUSE Leap 15.6 with gcc 7, and
- openSUSE Tumbleweed with gcc 15.
The test-case compiles with clang, but the hang didn't reproduce.
Tested on x86_64-linux.
PR testsuite/33480
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33480
[1] https://github.com/marxin/cvise
This commit is contained in:
@@ -165,7 +165,7 @@ inspect_type (struct demangle_parse_info *info,
|
||||
{
|
||||
const char *new_name = (*finder) (otype, data);
|
||||
|
||||
if (new_name != NULL)
|
||||
if (new_name != nullptr && strcmp (new_name, name) != 0)
|
||||
{
|
||||
ret_comp->u.s_name.s = new_name;
|
||||
ret_comp->u.s_name.len = strlen (new_name);
|
||||
@@ -378,9 +378,10 @@ replace_typedefs_qualified_name (struct demangle_parse_info *info,
|
||||
struct demangle_component newobj;
|
||||
|
||||
buf.write (d_left (comp)->u.s_name.s, d_left (comp)->u.s_name.len);
|
||||
newobj.type = DEMANGLE_COMPONENT_NAME;
|
||||
newobj.u.s_name.s = obstack_strdup (&info->obstack, buf.string ());
|
||||
newobj.u.s_name.len = buf.size ();
|
||||
cplus_demangle_fill_name (&newobj,
|
||||
obstack_strdup (&info->obstack,
|
||||
buf.string ()),
|
||||
buf.size ());
|
||||
if (inspect_type (info, &newobj, finder, data))
|
||||
{
|
||||
char *s;
|
||||
|
||||
80
gdb/testsuite/gdb.cp/pretty-print.cc
Normal file
80
gdb/testsuite/gdb.cp/pretty-print.cc
Normal file
@@ -0,0 +1,80 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2025 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 <http://www.gnu.org/licenses/>. */
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
inline namespace __cxx11 {}
|
||||
|
||||
template <typename> struct allocator {};
|
||||
|
||||
template <class> struct char_traits;
|
||||
|
||||
inline namespace __cxx11 {
|
||||
|
||||
template <typename _CharT, typename = char_traits<_CharT>,
|
||||
typename = allocator<_CharT>>
|
||||
|
||||
class basic_string;
|
||||
|
||||
} // namespace __cx11
|
||||
|
||||
typedef basic_string<char> string;
|
||||
|
||||
template <typename> struct allocator_traits;
|
||||
|
||||
template <typename _Tp> struct allocator_traits<allocator<_Tp>> {
|
||||
using pointer = _Tp *;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
struct __alloc_traits : std::allocator_traits<std::allocator<char>> {};
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
inline namespace __cxx11
|
||||
{
|
||||
|
||||
template <typename, typename, typename> struct basic_string
|
||||
{
|
||||
typedef long size_type;
|
||||
|
||||
size_type npos;
|
||||
|
||||
struct _Alloc_hider
|
||||
{
|
||||
_Alloc_hider(__alloc_traits::pointer, const allocator<char> &);
|
||||
} _M_dataplus;
|
||||
|
||||
basic_string(char *, allocator<char> __a = allocator<char>())
|
||||
: _M_dataplus(0, __a) {}
|
||||
};
|
||||
|
||||
} // namespace __cxx11
|
||||
|
||||
} // namespace std
|
||||
|
||||
static char bar[] = "bar";
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
std::string foo = bar;
|
||||
return 0;
|
||||
}
|
||||
53
gdb/testsuite/gdb.cp/pretty-print.exp
Normal file
53
gdb/testsuite/gdb.cp/pretty-print.exp
Normal file
@@ -0,0 +1,53 @@
|
||||
# Copyright 2025 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Print std::string::npos using c++ pretty-printer. Todo: convert this to a
|
||||
# DWARF assembly test-case, such that the regression test also works for clang.
|
||||
|
||||
standard_testfile .cc
|
||||
|
||||
set opts {}
|
||||
lappend opts debug
|
||||
lappend opts c++
|
||||
if {[have_compile_flag -fno-eliminate-unused-debug-symbols]} {
|
||||
lappend opts additional_flags=-fno-eliminate-unused-debug-symbols
|
||||
# Work around clang warning -Wunused-command-line-argument.
|
||||
lappend opts nowarnings
|
||||
}
|
||||
|
||||
if {[prepare_for_testing "failed to prepare" $testfile $srcfile $opts]} {
|
||||
return
|
||||
}
|
||||
|
||||
gdb_test_no_output "source $srcdir/$subdir/pretty-print.py" \
|
||||
"load libstdc++ pretty printers"
|
||||
|
||||
gdb_test_no_output \
|
||||
"python register_type_printers(gdb.current_objfile())" \
|
||||
"register libstdc++ pretty printers"
|
||||
|
||||
gdb_test "whatis std::string" \
|
||||
"std::string"
|
||||
|
||||
# Regression test for PR c++/33480. GDB used to hang, at least if the GDB
|
||||
# build optimization flags triggered some uninitialized variables in the right
|
||||
# way. I was not able to reproduce the hang with clang, due to different
|
||||
# debug info.
|
||||
#
|
||||
# Prints different strings due to different debug info:
|
||||
# - std::string::size_type with gcc, and
|
||||
# - size_type with clang
|
||||
gdb_test "whatis std::string::npos" \
|
||||
"(std::string::)?size_type"
|
||||
82
gdb/testsuite/gdb.cp/pretty-print.py
Normal file
82
gdb/testsuite/gdb.cp/pretty-print.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# Copyright (C) 2008-2025 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Reduced copy of /usr/share/gcc-15/python/libstdcxx/v6/printers.py.
|
||||
|
||||
import gdb
|
||||
import gdb.printing
|
||||
import gdb.types
|
||||
|
||||
|
||||
class FilteringTypePrinter(object):
|
||||
|
||||
def __init__(self, template, name, targ1=None):
|
||||
self._template = template
|
||||
self.name = name
|
||||
self._targ1 = targ1
|
||||
self.enabled = True
|
||||
|
||||
class _recognizer(object):
|
||||
def __init__(self, template, name, targ1):
|
||||
self._template = template
|
||||
self.name = name
|
||||
self._targ1 = targ1
|
||||
self._type_obj = None
|
||||
|
||||
def recognize(self, type_obj):
|
||||
if type_obj.tag is None:
|
||||
return None
|
||||
|
||||
if self._type_obj is None:
|
||||
if self._targ1 is not None:
|
||||
s = "{}<{}".format(self._template, self._targ1)
|
||||
if not type_obj.tag.startswith(s):
|
||||
return None
|
||||
elif not type_obj.tag.startswith(self._template):
|
||||
return None
|
||||
|
||||
try:
|
||||
self._type_obj = gdb.lookup_type(self.name).strip_typedefs()
|
||||
except:
|
||||
pass
|
||||
|
||||
if self._type_obj is None:
|
||||
return None
|
||||
|
||||
t1 = gdb.types.get_basic_type(self._type_obj)
|
||||
t2 = gdb.types.get_basic_type(type_obj)
|
||||
if t1 == t2:
|
||||
return self.name
|
||||
|
||||
if self._template.split("::")[-1] == "basic_string":
|
||||
s1 = self._type_obj.tag.replace("__cxx11::", "")
|
||||
s2 = type_obj.tag.replace("__cxx11::", "")
|
||||
if s1 == s2:
|
||||
return self.name
|
||||
|
||||
return None
|
||||
|
||||
def instantiate(self):
|
||||
return self._recognizer(self._template, self.name, self._targ1)
|
||||
|
||||
|
||||
def add_one_type_printer(obj, template, name, targ1=None):
|
||||
printer = FilteringTypePrinter("std::" + template, "std::" + name, targ1)
|
||||
gdb.types.register_type_printer(obj, printer)
|
||||
|
||||
|
||||
def register_type_printers(obj):
|
||||
for ch in (("", "char"),):
|
||||
add_one_type_printer(obj, "__cxx11::basic_string", ch[0] + "string", ch[1])
|
||||
Reference in New Issue
Block a user