Thursday, March 11, 2004

How to Walk an STL Map in GDB

Sometimes, you need to walk a map in GDB. Without a process running, you aren''t allowed to call functions on the structure, such as "find" or "[]". However, the data is still available, and can be retrieved if you know how to walk the tree structure.

This assumes that you are using GCC (may be version specific, I used 3.2.3).

The following code is used as an example:

#include <map>
#include <vector>
#include <iostream>

using namespace std;
map<int, int> map_to_walk;
vector<int> vector_to_walk;


int main() {

    cout << "Hello" << endl;

    map_to_walk[5] = 6;

    vector_to_walk.push_back(5);

   cout << "There" << endl;
}

Pretty basic stuff.

At the first cout, the map has been constructed. At that point, it looks like this:

(gdb) p map_to_walk 
$1 = {
    _M_t = {<_Rb_tree_base<std::pair<const int, int>,std::allocator<std::pair<const int, int> > >> = 
            {<_Rb_tree_alloc_base<std::pair<const int, int>,std::allocator<std::pair<const int, int> >,true>> = 
             {_M_header = 0x22e78}, 
             <No data fields>}, 
            _M_node_count = 0, 
            _M_key_compare = {<binary_function<int,int,bool>> = {<No data fields>}, <No data fields>}
    }
}

I've changed the formatting a little to make it easier to see some things.

I'm guessing, but I think the fields are:

  • _M_t - the implementation of the tree
  • ._M_node_count - the number of nodes in the map.
  • ._M_header - the root node in the tree.
(gdb) p map_to_walk._M_t._M_header    
$3 = (_Rb_tree_node<std::pair<const int, int> > *) 0x22e78

The root node concept is borne out by the fact that it is a tree node, and it does exist. Looking at the node we see:

(gdb) p *map_to_walk._M_t._M_header
$4 = {<_Rb_tree_node_base> = {_M_color = _M_red, _M_parent = 0x0, 
    _M_left = 0x22e78, _M_right = 0x22e78}, _M_value_field = {first = 0, 
    second = 0}}

With both the left and right children pointing back to us.

Now, after inserting an element, we see the following:

(gdb) p *map_to_walk._M_t._M_header
$3 = {<_Rb_tree_node_base> = {_M_color = _M_red, _M_parent = 0x22e90, 
    _M_left = 0x22e90, _M_right = 0x22e90}, _M_value_field = {first = 0, 
    second = 0}}

The root node is still empty. The node count is now 1. The only thing that has changed is that the _M_left and _M_right pointers have moved to point to something else.

If we look at left/right, we see that they are _Rb_tree_node_base pointers. GDB doesn't do downcasts, so we don't get anything else. However, if we force the cast, we get the following:

(gdb) p *(_Rb_tree_node<std::pair<const int, int> > *)map_to_walk._M_t._M_header._M_left
$14 = {<_Rb_tree_node_base> = {_M_color = _M_black, _M_parent = 0x22e78, 
    _M_left = 0x0, _M_right = 0x0}, _M_value_field = {first = 5, second = 6}}

Woohoo, we see our value sitting right there! From there, I'm hoping that it is easy to track down.

STL vectors in GDB

STL vectors are even easier. The vector contains a single buffer of the elements. It contains a pointer to the start and the end of the elements, as well as a pointer to the end of the allocated space.

  • _M_start : The 0th element
  • _M_finish : The last+1 element (equals end())
  • _M_end_of_storage: The end of the currently allocated buffer.

So, to get at a specific element in the vector, you start with the _M_start pointer (0th) and then add the index of the value you are looking for:

(gdb) p vector_to_walk
$1 = {<_Vector_base<int,std::allocator<int> >> = {<_Vector_alloc_base<int,std::allocator<int>,true>> = {_M_start = 0x24c18, _M_finish = 0x24c1c, 
      _M_end_of_storage = 0x24c1c}, <No data fields>}, <No data fields>}
(gdb) p vector_to_walk._M_start 
$2 = (int *) 0x24c18
(gdb) p *vector_to_walk._M_start
$3 = 5
(gdb) p vector_to_walk._M_finish - vector_to_walk._M_start
$4 = 1

Saturday, March 06, 2004

OO Design for Unit Tests

Hmmm. I'm working a contract right now, so there hasn't been much progress on the requirements management system.

I'm currently fixing problems with some existing code. At the same time, I was looking at writing some automated C-unit style unit tests for my fixes. The only problem is that the scaffolding that I would have to erect around the code to even test a single function.

It brought a lesson home to me. In a lot of object oriented code, you see people with long chains of getters until they obtain what they are looking for and call a final function on that. eg:

  transaction->getConnection()->getStatus()

The only problem is that in order to test this code in isolation, you not only have to create a stub transaction, but you have to create a stub connection too! If it looked like this instead:

  transaction->getConnectionStatus()

We would only have to stub out transaction!

I have always read in articles and books that if you "have a" member that is itself a class, instead of simply exposing it in a getter, you are better to expose the individual member functions of the enclosed member object. I guess I now understand why.

The only question is how do you maintain the isolation of interfaces, and avoid writing a lot of code?

Wednesday, February 18, 2004

RPMS

The first thing I found was how annoyed I was getting when during the debug/test cycle I would have to hand copy files around. That forced me to write a script to do the copying for me. From there, it was a quick jump to having a makefile and then RPMs.

As a packaging system, RPMs are pretty nifty. They are lightweight, and the creation process handles a lot of the gruntwork for you. Compared to HP/UX and Solaris, they are amazing! The only problem? The documentation. The documentation that is provided at rpm.org is incredibly out of date. So much so that the examples don't work anymore.

Once you get around that, it is very handy. The automatic dependency generation even found some bugs in my code!

I do wish it played better with CPAN. That it goes and finds all sorts of Perl dependencies is nice, that it isn't able to look at the already installed Perl modules (through CPAN) is a bit of a pain. I guess I'll have to figure out how to use cpan2rpm. :)

Tricks? Try to avoid having hardcoded versions in the spec file. For example, if you have:

Version: 0.4 Release: 1

in your spec file, you will have to edit it for each Version/Release that you do. Not a great way to spend your day. To get around it, you can specify things on the command line. First, you will need to add the following to the top of your specfile:

%if %{?rel:0}%{!?rel:1} %define rel 1 %endif %if %{?ver:0}%{!?ver:1} %define ver 1 %endif

How it really works, I'm not sure. Once I hit the problem with the code examples in the RPM docs not working, I stopped reading. :) However, it seems to define a variable if it isn't already defined. Once you have that, you can change your Version/Release entries to:

Version: %{ver} Release: %{rel}

Then, you invoke rpmbuild like this:

% rpmbuild --define "rel 2" --define "ver 1.0"

That sets those variables from the command line, a great time saver.

Thursday, February 12, 2004

Using XML for export/import of DB install values In many systems, you find a bunch of sql code that gets executed at install time. This code is usually brittle, hard to read, and generally nasty. My last employer had a lot of code like this, and it was just that, VERY nasty. I've just come up with, I hope, a better way. I'm using XML to store the data, and then reading it and using my existing database (see: Class::DBI) classes to handle the insertions/updates. This gives me the following benefits:

  1. I'm using the same code to do the initial insertion that I use in the regular running of the system, so I only have to fix a bug once
  2. When I migrate to different new database, I don't have to port the installation code
  3. My code handles changes, as well as additions. So, if you change the value of a column, the loader will handle that properly, instead of doing simple "inserts" - see find_or_create() on Class::DBI
  4. The XML file is generated from my test database (see: DBI::Generator::XML) so I don't have to edit it by hand
  5. I can generate test data programatically and load that at any time
  6. I can dump test data from the system without hardcoded primary key id values!
  7. If I have to edit the file by hand, I can
All sorts of benefits. :) The ability to keep on executing the script without it deleting and re-inserting all of the rows is of special benefit. It lets me progressively add more information to the rows (such as comments) which aren't needed right away. Now, without the export, there is no real benefit. Hand editing XML isn't really pleasant. Hand modifying the XML is handy, you don't have to do too much typing. :)

First Post! Wow, my first post. Never having used a blog before, this will be interesting. Essentially, I'm doing this because I find that I'm writing my friends a lot of emails on the subjects, and I don't really want them to be lost. :) As an introduction and scope, I'm writing a Project Information System. If you're interested, you can find out more information at Pollock.ca I've got an early release of the Requirements Management System up there. I will be talking about Perl, XML and software development.