How to step into the virtual function of derived class using GDB?

Viewed 75

Supposed that there is a base class and two derived classes, the base class has a pure virtual function named greeting(), both two derived classes inherit greeting() from the base class and implement their own greeting() respectively.

Then there is a std::map<std::string, Base*> obj_map which stores the object names and the corresponding address.

Now I am getting the object pointer through obj_map by given object name, then use -> to call greeting().

The problem is that, if I step into the line using gdb when obj_map["..."]->greeting() is called, gdb cannot stop at the greeting() of derived class, but at operator[](key_type&& __k) of std::map.

    /*
     * @Author: dylan
     * @Date: 2022-09-08 01:09:20
     * @LastEditTime: 2022-09-08 01:18:34
     * @LastEditors: dylan
     * @Description: 
     * @FilePath: /cpptest/debug-derived-virtual-function/main.cc
     */
    #include <iostream>
    #include <string>
    #include <map>
    
    class Base {
    public:
        Base(const std::string &obj_name) : name(obj_name) { }
        virtual void greeting() = 0;
    protected:
        std::string name;
    };
    
    class Derived_1 : public Base {
    public:
        Derived_1(const std::string &obj_name) : Base(obj_name) { }
        void greeting() override {
            std::cout << "Hello, I am " << name << "!" << std::endl;
        }
    };
    
    class Derived_2 : public Base {
    public:
        Derived_2(const std::string &obj_name) : Base(obj_name) { }
        void greeting() override {
            std::cout << "Bonjour, c'est " << name << "!" << std::endl;
        }
    };
    
    int main()
    {
        Derived_1 derived_1("Ben");
        Derived_2 derived_2("Anaise");
    
        std::map<std::string, Base*> obj_map;
    
        obj_map["Ben"] = &derived_1;
        obj_map["Anaise"] = &derived_2;
    
        obj_map["Ben"]->greeting();
        obj_map["Anaise"]->greeting();
    
        return 0;
    }

Execute g++ -g3 -Wall -O0 main.cc -o main to compile.

The debug information is below:

    (gdb) 
    41
    42          std::map<std::string, Base*> obj_map;
    43
    44          obj_map["Ben"] = &derived_1;
    45          obj_map["Anaise"] = &derived_2;
    46
    47          obj_map["Ben"]->greeting();
    48          obj_map["Anaise"]->greeting();
    49
    50          return 0;
    (gdb) break 47
    Breakpoint 1 at 0x25c2: file main.cc, line 47.
    (gdb) run
    Starting program: /home/ubuntu/workspace/practice/cpptest/debug-derived-virtual-function/main 
    
    Breakpoint 1, main () at main.cc:47
    47          obj_map["Ben"]->greeting();
    (gdb) step
    std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Base*, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, Base*> > >::operator[] (
        this=0x7fffffffdda0, __k=...) at /usr/include/c++/9/bits/stl_map.h:510
    510           operator[](key_type&& __k)
    (gdb) n
    515             iterator __i = lower_bound(__k);
    (gdb) 
    517             if (__i == end() || key_comp()(__k, (*__i).first))
    (gdb) 
    521             return (*__i).second;
    (gdb) 
    522           }
    (gdb) 
    Hello, I am Ben!
    main () at main.cc:48
    48          obj_map["Anaise"]->greeting();
    (gdb) step
    std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Base*, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, Base*> > >::operator[] (
        this=0x7ffff7c393c6 <__GI__IO_fflush+134>, __k=...) at /usr/include/c++/9/bits/stl_map.h:510
    510           operator[](key_type&& __k)
    (gdb) n
    515             iterator __i = lower_bound(__k);
    (gdb) 
    517             if (__i == end() || key_comp()(__k, (*__i).first))
    (gdb) 
    521             return (*__i).second;
    (gdb) 
    522           }
    (gdb) 
    Bonjour, c'est Anaise!
    main () at main.cc:50
    50          return 0;
    (gdb) 

As you can see, the gdb did not step into the corresponding greeting() of each derived class.
So is it possible to let gdb step into the corresponding greeting() correctly?

APPEND: I am working on non-small project, every time I compile the project will take a lot of time. Is there any way can work without modifying the code? Maybe there are over 200 derived classes like this, so it's quite a hassle to figure out which function was actually called each time.

1 Answers

There are two tricks I use for situations like this. First is making use of the finish command, here's an example of it in use:

$ gdb -q test
Reading symbols from test...
(gdb) break 47
Breakpoint 1 at 0x401460: file test.cc, line 47.
(gdb) run
Starting program: /tmp/so/test 

Breakpoint 1, main () at test.cc:47
47    obj_map["Ben"]->greeting();
(gdb) step
std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Base*, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, Base*> > >::operator[] (this=0x7fffffffaba0, __k=...) at /usr/include/c++/9/bits/stl_map.h:515
515     iterator __i = lower_bound(__k);
(gdb) finish
Run till exit from #0  std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Base*, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, Base*> > >::operator[] (
    this=0x7fffffffaba0, __k=...) at /usr/include/c++/9/bits/stl_map.h:515
0x0000000000401497 in main () at test.cc:47
47    obj_map["Ben"]->greeting();
Value returned is $1 = (std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Base*, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, Base*> > >::mapped_type &) @0x419ef0: 0x7fffffffac00
(gdb) step
Derived_1::greeting (this=0x7fffffffac00) at test.cc:25
25      std::cout << "Hello, I am " << name << "!" << std::endl;
(gdb) 

So once you step into the function that you're not interested in, just use finish to return to the caller, then step again to get where you want to go.

But, if that gets annoying, then its possible to automate the process using skip function, like this (continuing from the above session):

(gdb) skip function std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Base*, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, Base*> > >::operator[]
Function std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, Base*, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, Base*> > >::operator[] will be skipped when stepping.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /tmp/so/test 

Breakpoint 1, main () at test.cc:47
47    obj_map["Ben"]->greeting();
(gdb) step
Derived_1::greeting (this=0x7fffffffac00) at test.cc:25
25      std::cout << "Hello, I am " << name << "!" << std::endl;
(gdb) 

The text I pass to skip function is just copied directly from when I first stepped into the std::map::operator[] function, I didn't have to figure it out myself! Now, when I step at line 47, GDB silently steps into, and then finishes, the operator[] call, and then does one more step, which moves us into greeting.

You can track which skips you have in place with info skip, and delete or disable them with skip delete and skip disable.

Related