Remote Debugger

This is a mini project for making ongoing programs debugging easier, by making some variables available for setting and getting while the process is running.

The Idea is that sometimes the developer or the user wants to see the value of some variable on-the-fly, without printing it all the time, and without using gdb, etc.

So what I did is I opened another thread that listens to some port, and when it gets a command, it reads or writes from the memory. You send those commands from another process; it can be done locally on the machine, or remotely from another computer, as long as you have a route to it. The variables you want to look at must be registered. It's quite simple.

For example:
% ./DebugCaller get someGlobalParam
% ./DebugCaller set go 0
You can download all the code here.
And you can read it below. (But I really put the code here for search engines...)
Your feedback is wellcome, at yoavmil AT gmail.com .

Makefile
all:
	g++ example.cpp DebugListener.cpp -lpthread -o example
	g++ DebugCaller.cpp -o DebugCaller


DebugListener.h
#ifndef DEBUG_LISTENER_H
#define DEBUG_LISTENER_H

#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
#include 
#include 
#include 
#include 
using namespace std;

#define DEFAULT_PORT 6789
/*
  A few notes:
  the '#' means to take 'x' and make it a string "x", for example:
  #define VAR_TO_STR(x) #x
  int aaa=3;
  cout< StrStrMap;

class DebugListener { /* static class */
public:
  static void Start(int port=DEFAULT_PORT);
  static void Add(const string& key, int* i);
  static void Add(const string& key, bool* i) { Add(key,(int*)(i)); }
  static void Add(const string& key, string* i);
  static void Remove(const string& key);
  static void Destroy();

private:
  static int m_port;
  static bool m_go;
  static StrStrMap m_strToDbgElem;

  static void error(const char *msg) {
    perror(msg);
    exit(1);
  }
 
  static bool isInData(const char* key) {
    StrStrMap::iterator it = m_strToDbgElem.find(key);
    return (it!=m_strToDbgElem.end());
  }

  static void* listen(void*);
};

#endif



DebugListener.cpp
#include "DebugListener.h"

const string DebugElemInt::get() {
  char b[15]; 
  snprintf(b,sizeof(b)-1,"%d",*p);
  return string(b);
};

void DebugElemInt::set(const char* str) {
  *p=atoi(str);
}

int       DebugListener::m_port;
bool      DebugListener::m_go;
StrStrMap DebugListener::m_strToDbgElem;

void DebugListener::Start(int port /*=DEFAULT_PORT*/ ) {
  pthread_t debugThread;
  m_port=port;
  pthread_create(&debugThread,NULL,DebugListener::listen,NULL);
}

void DebugListener::Add(const string& key, int* i) {
  Remove(key); /* to prevent mem leak in case an key is overrided */
  DebugElemInt * d = new DebugElemInt();
  d->p=i;
  DebugListener::m_strToDbgElem[key]=d;
}
void DebugListener::Add(const string& key, string* i) {
  Remove(key); /* to prevent mem leak in case an key is overrided */
  DebugElemStr * d = new DebugElemStr();
  d->p=i;
  DebugListener::m_strToDbgElem[key]=d;
}
void DebugListener::Remove(const string& key) {
  StrStrMap::iterator it=m_strToDbgElem.find(key);
  if(it!=m_strToDbgElem.end())  {
    delete it->second;
    m_strToDbgElem.erase(it);
  }
}

void DebugListener::Destroy() {
  for (StrStrMap::iterator it=m_strToDbgElem.begin();it!=m_strToDbgElem.end();it++) {
    delete it->second;
    m_strToDbgElem.erase(it);
  }
}

void* DebugListener::listen(void*) {
  int sockfd, newsockfd;
  socklen_t clilen;
  char buffer[256];
  struct sockaddr_in serv_addr, cli_addr;
  int n;
  
  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd < 0) {
    error("ERROR opening socket");
  }
  
  bzero((char *) &serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  serv_addr.sin_port = htons(m_port);
  
  if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
    error("ERROR on binding");
  }
  m_go=true;
  ADD_TO_DBG_DATA_WITH_KEY("DebugListener::m_go",m_go);

  while(m_go) {
    ::listen(sockfd,5);
    clilen = sizeof(cli_addr);
    newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
    if (newsockfd < 0) {
      error("ERROR on accept");
    }
    bzero(buffer,sizeof(buffer));
    n = read(newsockfd,buffer,255);
    if (n < 0) error("ERROR reading from socket");

    //printf("Here is the message: %s\n",buffer);
    char *c;
    char *firstWord  = buffer;
    char *secondWord = NULL;
    char *thirdWord  = NULL;

    for (c=buffer ; *c ; c++ ) { 
      //go until NULL, replace ' ' with '\0', and find the 2nd and 3rd words
      if (*c==' ') {
	*c='\0';
	if (!secondWord) {
	  secondWord  = c+1;
	} else {
	  thirdWord = c+1;
	}
      }
    }

    string s;
    StrStrMap::iterator it;
    bool set;
    
    if(secondWord==NULL) {
      goto bad_input;
    }
    set = strcmp(firstWord,"set")==0;
    if (!set && strcmp(firstWord,"get")!=0) {
      goto bad_input;
    }
    
    if (set && thirdWord==NULL) {
      goto bad_input;
    }
    
    s = string(secondWord);
    
    it = m_strToDbgElem.find(s);
    if (it==m_strToDbgElem.end()) {
      s="no such global var '"+s+"'\n";
      goto send_s;
    }
    
    if (set) {
      it->second->set(thirdWord);
    }
    
    s=it->second->get();
    
  send_s:
    write(newsockfd,s.c_str(),s.length());
    close(newsockfd);
    continue;
    
  bad_input:
    s="bad input, should be \"get \" or \"set  \"\n";
    goto send_s;
  } //while(m_go)

  close(sockfd);
  return NULL; 
}



DebugCaller.cpp
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
#include 

#define PORT 6789

void error(const char *msg)
{
    perror(msg);
    exit(0);
}

int main(int argc, char *argv[])
{
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;

    char buffer[256];

    portno = PORT;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
        error("ERROR opening socket");

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); /* local host */
    /* TODO add as param to argv "-ip " */

    serv_addr.sin_port = htons(portno);
    if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) 
        error("ERROR connecting");
    buffer[0]=0;
    for (int i=1; i

example.cpp
#include "DebugListener.h"

/*
  This is an example program that uses the DebugListener.
  This program only has a few global params, and some local params.
  The program invokes the DebugListener thread, and registers some variables in it.
  Those variables may be accessed by the DebugCaller by there name or by a special string
  that reprisents them.

  This program repeatly randomly changes the value of someGlobalParam. 
  To see the current value do:
  % ./DebugCaller get someGlobalParam

  To stop this program you can run from the DebugCaller:
  % ./DebugCaller set go 0

  To stop the DebugListener do:
  % ./DebugCaller set DebugListener::m_go 0

  Important note: if the variable that you registered to the DebugListener is a local 
  function variable (and it's on the stack), be sure to unregister it before you leave the
  function. see example with 'int local'.

  DebugListener::Destroy() frees all the memory allocated, 
  but "./DebugCaller set DebugListener::m_go 0" doesn't. 
*/

int someGlobalParam=0;
int go=1;
string str="ASDF";

int main() {

  ADD_TO_DBG_DATA(someGlobalParam);
  ADD_TO_DBG_DATA(go);
  ADD_TO_DBG_DATA(str);

  int local;
  ADD_TO_DBG_DATA(local);
  
  DebugListener::Start();

  while(go) {
    local=someGlobalParam=rand();
    usleep(120);
  }

  REMOVE_FROM_DBG_DATA(local);
  DebugListener::Destroy();
  return 0;
}