mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-12-05 15:15:42 +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);
|
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.s = new_name;
|
||||||
ret_comp->u.s_name.len = strlen (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;
|
struct demangle_component newobj;
|
||||||
|
|
||||||
buf.write (d_left (comp)->u.s_name.s, d_left (comp)->u.s_name.len);
|
buf.write (d_left (comp)->u.s_name.s, d_left (comp)->u.s_name.len);
|
||||||
newobj.type = DEMANGLE_COMPONENT_NAME;
|
cplus_demangle_fill_name (&newobj,
|
||||||
newobj.u.s_name.s = obstack_strdup (&info->obstack, buf.string ());
|
obstack_strdup (&info->obstack,
|
||||||
newobj.u.s_name.len = buf.size ();
|
buf.string ()),
|
||||||
|
buf.size ());
|
||||||
if (inspect_type (info, &newobj, finder, data))
|
if (inspect_type (info, &newobj, finder, data))
|
||||||
{
|
{
|
||||||
char *s;
|
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