0

I have a very large C++ application running on a *nix environment, which is occasionally setting a particular date attribute on a structure, way off what it should be. I know which structure and which attribute is going wrong, but what I need to do is to capture the stack trace where the value is being set incorrectly.

My initial approach would be to create an instrumented build adding debug where the value is changed. If the attribute was private in a class, I could just manipulate the mutator to log where the issue was happening. Unfortunately, it's been implemented as a public attribute in a struct and there are literally hundreds of places where I would need to refactor to replace direct use with a mutator, or other instrumented code. It would take me days to do this as I don't have access to a refactoring tool that would simplify the approach. Indeed, as the code is just a temporary instrumented version, refactoring is just a means to an end.

I'm wondering whether GDB has some means to help me with this. Ideally, I'd like to:

  • Run the code in the debugger
  • Add watchpoints, but not on a specific variable but any instance of this structure, to catch when the attribute is changed.
  • Ideally, add a criterion that means it only triggers if the value set is, say, less than a certain amount.
  • Capture the stack trace and ideally some of the variable values at this point.

Is this even possible with GDB? If not, are there other alternative approaches that might speed this up dramatically?

6
  • 1
    Is your test case deterministic enough that the dud value occurs in the same place every time? (you may need to disable ASLR and some other protections) - in which case the classic hardware watchpoint would work.
    – pjc50
    Feb 21 at 13:08
  • Yes, it is deterministic but only within the context of a very complex process, where static analysis has not proved fruitful. The problem with a classic watchpoint is that I'm observing many instances of the class in question being created, passed around, destroyed etc, so just providing a memory address isn't going to be enough. Given that gdb is really more suited to C than C++, so will presumably find detecting instantiation of instances tricky, let alone changes of member variables, I think this is going to be a big ask. Feb 21 at 15:12
  • 3
    I would not throw the idea of making the attribute private and get it behind a mutator too quickly over board. Maybe you can find a way for a global search/replace to accomplish this. maybe you can restrict the replacement of the public struct by a more encapusulated class to a smaller areas of the code.
    – Doc Brown
    Feb 21 at 15:28
  • 2
    Change the variable's type to something that blows up if assigned the "wrong" value and acts like the other type otherwise.
    – Mat
    Feb 22 at 6:23
  • "I don't have access to a refactoring tool that would simplify the approach." There are so many high quality and free tools out there, I highly doubt this constraint is fixed Feb 22 at 7:10

2 Answers 2

3

If this is on x86_64 Linux (perhaps a big assumption), then you could use Undo's UDB to debug this. As UDB records the execution of the application while it runs, you would be able to set a breakpoint where you detect the bad value, and then run until the error is detected. When stopped at that breakpoint (and you know the memory address of the bad value), you can then set a watchpoint at the address of the bad data, and run the application backwards, until it hits that breakpoint. From there you can explore more than just the call stack.

There is a recent blog on undo.io which covers how to do something similar to this on SQLite with VSCode, although it will also work from the commandline UDB (based on GDB).

Disclaimer: I do work for Undo, but I genuinely believe the free trial version would likely be sufficient to help you with this. There is a performance impact for recording, but it could save a lot of time diagnosing your issue and lead you directly to the bug.

3

My initial approach would be to create an instrumented build adding debug where the value is changed. If the attribute was private in a class, I could just manipulate the mutator to log where the issue was happening. Unfortunately, it's been implemented as a public attribute in a struct and there are literally hundreds of places where I would need to refactor to replace direct use with a mutator, or other instrumented code.

One of the key features of C++'s type system is that you can write classes which behave syntactically the same as built-in types.

So, instead of moving the field behind a mutator, just replace the field with a class instance which has the appropriate assignment operator (this is your mutator). Add whatever other operators and constructors are necessary. If you do this correctly, no calling code needs to change at all, only to be recompiled.

You can see an example of a suitable assignment operator in the BoundedValue validating type wrapper I wrote elsewhere:

template <typename T, int Tmin, int Tmax> class BoundedValue
{
    T val_;
public:

    enum { min_value=Tmin, max_value=Tmax };
    // ...

    // run-time checked assignment:
    BoundedValue& operator= (T const& val) {
        BOUNDED_VALUE_ASSERT(min_value, max_value, val); // your check here
        val_ = val;
        return *this;
    }
};
6
  • This is a really interesting approach. I am just wondering how many operator overloads one will have to implement in such a type to make it behave, for example, almost like an int. Or maybe adding implicit type conversion to "int" will be sufficient?
    – Doc Brown
    Feb 25 at 9:06
  • Depends entirely on how it's used - in the simplest case, an assignment operator and a cast operator are enough. Worst case, if the type is used explicitly in expressions (with type deduction for example), you could indeed need a load of operators.
    – Useless
    Feb 28 at 10:12
  • 1
    Again, it depends how you use it, and you haven't shown that in your question. wrapper.method() won't work because you can't overload operator. and the left-hand-side of an x.y() expression doesn't participate in any implicit conversions. Conversely x + y does participate in implicit conversion.
    – Useless
    Feb 28 at 19:07
  • 1
    So, if you're explicitly calling methods directly on the object, your only option is to manually forward those methods. But, if the "value" is an object with methods and internal state, how was it getting mutated in the first place? Were you actually talking about mutating a public value.inner_value? Because then that inner value is the one you should be wrapping ...
    – Useless
    Feb 28 at 19:09
  • 1
    tl;dr you really need to decide whether the attribute you care about is a single primitive or a user-defined type. Primitives don't have methods and all their build-in operations are overloadable. UDTs may have methods and can't be wrapped so easily/generically.
    – Useless
    Feb 28 at 19:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.