Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I define a generic wrapper class to detect writes to and reads from a specific variable in my code?

Tags:

c++

I want something similar to a data breakpoint but in a programmatic way to break on reads and writes to a specific field or variable defined via a wrapper type

class SomeSystem {
public:
   int a;
   int b;
   int c;
};

Let's say I want to detect writes to c in runtime and break on them

class SomeSystem {
public:
   int a;
   int b;
   MyWatch<int, 0xDEADBEEF> c;
};

And MyWatch something like

template<typename T, uint64_t Disambiguation>
class MyWatch {
   // ...
   void OnRead();
   void OnWrite();
};

// ...

void MyWatch<int, 0xDEADBEEF>::OnRead() {
   __debugbreak();
}

void MyWatch<int, 0xDEADBEEF>::OnWrite() {
   __debugbreak();
}

The problem is that I don't know how to make compiler treat MyWatch<int> as int implicitly in all places throughout the codebase. I plan to use it as my debug facility to debug any types, including non-trivial ones. Some of the types are using implicit bool conversions, operator->, or plain member access operator . if the type is a value-type. Is there a way to forward all mentions of MyWatch value instance to its underlying T value all at once? This of course doesn't help me to find memset-like writes to the memory but still can be useful for me. Data breakpoints sometimes not trigger and I have to care to set them after creation, so that I have to find where instance creation happens in the first place

like image 370
IC_ Avatar asked Sep 02 '25 14:09

IC_


1 Answers

You can use the cast operator to automatically convert your type to the contained type, e.g.:

operator T()
{
    OnRead();
    return value;
}

You can use the assignment operator to detect updates. Your code could look something like this:

#include <cstdint>
#include <iostream>

template<typename T, uint64_t Disambiguation>
class MyWatch {
public:
    MyWatch()
    :value{}
    {
    }

    MyWatch(T _value)
    :value(_value)
    {
    }

    MyWatch(const MyWatch& other)
    :value(other.value)
    {
        other.OnRead();
    }

    operator T()
    {
        OnRead();
        return value;
    }

    MyWatch& operator=(const MyWatch& other)
    {
        value = other.value;
        other.OnRead();
        OnWrite();
        return *this;
    }

    MyWatch& operator=(const T& other)
    {
        value = other;
        OnWrite();
        return *this;
    }

private:
    void OnRead() const;
    void OnWrite();

    T value;
};

template<>
void MyWatch<int, 0xDEADBEEF>::OnRead() const {
    std::cout << "OnRead " << this << ", " << value << "\n";
}

template<>
void MyWatch<int, 0xDEADBEEF>::OnWrite() {
    std::cout << "OnWrite " << this << ", " << value << "\n";
}

void bar(int value)
{
    std::cout << "bar " << value << "\n";
}

int main()
{
    std::cout << "construct foo\n";
    MyWatch<int, 0xDEADBEEF> foo = 1;
    std::cout << "assign foo\n";
    foo = 10;
    std::cout << "call bar\n";
    bar(foo);
    std::cout << "copy foo\n";
    MyWatch<int, 0xDEADBEEF> foo2 = foo;
    std::cout << "assign foo\n";
    foo = 20;
    std::cout << "read foo\n";
    int i = foo;
    std::cout << "copy foo 2\n";
    foo = foo2;
}

https://godbolt.org/z/G1T1rnvad

If you want to use larger types then you might want to implement move constructors and assignment operators too.

like image 196
Alan Birtles Avatar answered Sep 05 '25 04:09

Alan Birtles