PACKAGES_gldbg = mspcore mspstrings mspio mspfs
CXXFLAGS_gldbg = $(shell pkg-config --cflags $(PACKAGES_gldbg))
-LIBS_gldbg = $(shell pkg-config --libs $(PACKAGES_gldbg))
+LIBS_gldbg = $(shell pkg-config --libs $(PACKAGES_gldbg)) -lreadline
all: glwrap.so gldump gldbg
gldump: source/gldecoder.o source/gldump.o source/glprint.o
$(CC) -o $@ $^ $(LIBS) $(LDFLAGS)
-gldbg: source/gldbg.o source/gldecoder.o source/glprint.o
+gldbg: source/gldbg.o source/gldecoder.o source/glprint.o source/commandinterpreter.o source/tracer.o source/process.o
$(CXX) -o $@ $^ $(LIBS_gldbg) $(LIBS) $(LDFLAGS)
source/glwrap.o: source/functions.h gensrc/functions.enum gensrc/glwrap.funcs
source/gldump.o: source/gldecoder.h gensrc/gldecoder.struct source/glprint.h
source/glprint.o: gensrc/glprint.funcs gensrc/gldecoder.struct
-source/gldbg.o: source/gldbg.cpp source/gldbg.h source/gldecoder.h source/glprint.h
+source/gldbg.o: source/gldbg.cpp source/gldbg.h source/gldecoder.h source/tracer.h
+ $(CXX) -c $(CXXFLAGS) $(CXXFLAGS_gldbg) -o $@ $<
+
+source/tracer.o: source/tracer.cpp source/gldecoder.h source/glprint.h
+ $(CXX) -c $(CXXFLAGS) $(CXXFLAGS_gldbg) -o $@ $<
+
+source/commandinterpreter.o: source/commandinterpreter.cpp source/gldbg.h
+ $(CXX) -c $(CXXFLAGS) $(CXXFLAGS_gldbg) -o $@ $<
+
+source/process.o: source/process.cpp source/process.h
$(CXX) -c $(CXXFLAGS) $(CXXFLAGS_gldbg) -o $@ $<
gensrc/functions.enum gensrc/gldecoder.funcs gensrc/gldecoder.struct gensrc/glwrap.funcs gensrc/glprint.funcs: gensrc/.created genwrap.py gl.spec gl.tm
"GLushort":"%u",
"GLuint":"%u",
"GLsizei":"%i",
- "GLfloat":"%f",
- "GLclampf":"%f",
- "GLdouble":"%lf",
- "GLclampd":"%lf",
+ "GLfloat":"%g",
+ "GLclampf":"%g",
+ "GLdouble":"%lg",
+ "GLclampd":"%lg",
"const GLubyte *":"%s",
"GLchar *":"%s",
"GLcharARB *":"%s",
--- /dev/null
+/* $Id$
+
+This file is part of gldbg
+Copyright © 2009 Mikko Rasa, Mikkosoft Productions
+Distributed under the GPL
+*/
+
+#include <msp/core/except.h>
+#include <msp/io/file.h>
+#include <msp/io/print.h>
+#include <msp/strings/lexicalcast.h>
+#include "commandinterpreter.h"
+#include "gldbg.h"
+#include "tracer.h"
+
+using namespace std;
+using namespace Msp;
+
+CommandInterpreter::CommandInterpreter(GlDbg &d):
+ gldbg(d)
+{
+ commands["run"] = &CommandInterpreter::cmd_run;
+ commands["continue"] = &CommandInterpreter::cmd_continue;
+ commands["c"] = &CommandInterpreter::cmd_continue;
+ commands["kill"] = &CommandInterpreter::cmd_kill;
+ commands["signal"] = &CommandInterpreter::cmd_signal;
+ commands["exit"] = &CommandInterpreter::cmd_exit;
+ commands["quit"] = &CommandInterpreter::cmd_exit;
+ commands["trace"] = &CommandInterpreter::cmd_trace;
+}
+
+void CommandInterpreter::execute(const string &cmd)
+{
+ unsigned space = cmd.find(' ');
+ string name = cmd.substr(0, space);
+ CommandMap::const_iterator i = commands.lower_bound(name);
+ if(i==commands.end() || i->first.compare(0, name.size(), name))
+ throw KeyError("Unknown command", name);
+ if(i->first!=name)
+ {
+ CommandMap::const_iterator j = i;
+ if((++j)!=commands.end() && !j->first.compare(0, name.size(), name))
+ throw KeyError("Ambiguous command", name);
+ }
+
+ string args;
+ if(space!=string::npos)
+ args = cmd.substr(space+1);
+
+ (this->*(i->second))(args);
+}
+
+
+void CommandInterpreter::cmd_run(const string &)
+{
+ gldbg.launch();
+}
+
+void CommandInterpreter::cmd_continue(const string &)
+{
+ gldbg.get_process().resume();
+}
+
+void CommandInterpreter::cmd_signal(const string &args)
+{
+ gldbg.get_process().resume(lexical_cast<unsigned>(args));
+}
+
+void CommandInterpreter::cmd_kill(const string &)
+{
+ gldbg.get_process().kill();
+}
+
+void CommandInterpreter::cmd_exit(const string &)
+{
+ gldbg.quit();
+}
+
+void CommandInterpreter::cmd_trace(const string &args)
+{
+ Tracer &tracer = gldbg.get_tracer();
+ if(args[0]=='>')
+ {
+ string fn = args.substr(1);
+ if(fn=="-")
+ {
+ tracer.set_output(IO::cout);
+ IO::print("Tracing to stdout\n");
+ }
+ else
+ {
+ tracer.set_output(new IO::File(fn, IO::M_WRITE));
+ IO::print("Tracing to %s\n", fn);
+ }
+ }
+ else
+ {
+ if(args=="on")
+ {
+ tracer.enable();
+ IO::print("Tracing enabled\n");
+ }
+ else if(args=="off")
+ {
+ tracer.disable();
+ IO::print("Tracing disabled\n");
+ }
+ else if(args=="end")
+ {
+ tracer.set_output(0);
+ IO::print("Tracing terminated\n");
+ }
+ }
+}
--- /dev/null
+/* $Id$
+
+This file is part of gldbg
+Copyright © 2009 Mikko Rasa, Mikkosoft Productions
+Distributed under the GPL
+*/
+
+#ifndef COMMANDINTERPRETER_H_
+#define COMMANDINTERPRETER_H_
+
+#include <map>
+#include <string>
+
+class GlDbg;
+
+class CommandInterpreter
+{
+private:
+ typedef void (CommandInterpreter::*CommandFunc)(const std::string &);
+ typedef std::map<std::string, CommandFunc> CommandMap;
+
+ GlDbg &gldbg;
+ CommandMap commands;
+
+public:
+ CommandInterpreter(GlDbg &);
+ void execute(const std::string &);
+
+private:
+ void cmd_run(const std::string &);
+ void cmd_continue(const std::string &);
+ void cmd_signal(const std::string &);
+ void cmd_kill(const std::string &);
+ void cmd_exit(const std::string &);
+ void cmd_trace(const std::string &);
+};
+
+#endif
*/
#include <cstdlib>
-#include <cerrno>
-#include <cstring>
+#include <fcntl.h>
+#include <signal.h>
#include <sys/poll.h>
#include <sys/socket.h>
-#include <sys/wait.h>
+#include <readline/readline.h>
#include <msp/core/except.h>
#include <msp/fs/dir.h>
#include <msp/io/print.h>
Application::RegApp<GlDbg> GlDbg::reg;
GlDbg::GlDbg(int argc, char **argv):
- pid(0),
- sock_fd(-1),
- glprint(glprint_new(0, 16384))
+ cmd_interp(*this),
+ process(vector<string>(argv+1, argv+argc)),
+ flushing(false),
+ got_sigchld(false)
{
- libdir = FS::get_sys_lib_dir(argv[0], "gldbg");
- args.assign(argv+1, argv+argc);
+ FS::Path libdir = FS::get_sys_lib_dir(argv[0], "gldbg");
+ process.setenv("LD_PRELOAD", (libdir/"glwrap.so").str().c_str());
}
int GlDbg::main()
{
catch_signal(SIGINT);
- launch();
+ catch_signal(SIGCHLD);
+ set_loop_mode(TICK_BUSY);
Application::main();
}
+void GlDbg::launch()
+{
+ if(process.get_state()!=Process::INACTIVE)
+ throw InvalidState("Program is already running");
+
+ int fds[2];
+ socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
+ sock_fd = fds[0];
+
+ int flags = fcntl(sock_fd, F_GETFD);
+ fcntl(sock_fd, F_SETFD, flags|FD_CLOEXEC);
+
+ process.setenv("GLWRAP_FD", lexical_cast(fds[1]));
+ process.launch();
+ close(fds[1]);
+}
+
+void GlDbg::quit()
+{
+ if(process.get_state()!=Process::INACTIVE)
+ throw InvalidState("Program is still running");
+ exit(0);
+}
+
void GlDbg::tick()
{
- int status;
- int ret = waitpid(pid, &status, WNOHANG);
- if(ret==pid)
+ if(got_sigchld)
{
- if(WIFEXITED(status) || WIFSIGNALED(status))
+ got_sigchld = false;
+ int ret = process.check();
+ if(ret==0x100)
+ IO::print("Target process exited normally\n");
+ else if((ret>>8)==1)
+ IO::print("Target process exited with status %d\n", ret&0xFF);
+ else if((ret>>8)==2)
+ IO::print("Target process terminated with signal %d\n", ret&0xFF);
+ else if((ret>>8)==3)
{
- IO::print("Target process exited\n");
- exit(0);
+ IO::print("Target process stopped by signal %d\n", ret&0xFF);
+ flushing = true;
+ }
+ }
+
+ Process::State pstate = process.get_state();
+ if((pstate!=Process::INACTIVE && pstate!=Process::STOPPED) || flushing)
+ read_stream();
+ else
+ {
+ char *line = readline("gldbg> ");
+ if(line)
+ {
+ try
+ {
+ cmd_interp.execute(line);
+ }
+ catch(const Exception &e)
+ {
+ IO::print("%s\n", e.what());
+ }
+ free(line);
}
+ else if(pstate==Process::INACTIVE)
+ exit(0);
}
+}
+void GlDbg::read_stream()
+{
pollfd pfd = { sock_fd, POLLIN, 0 };
- ret = poll(&pfd, 1, -1);
+ int ret = poll(&pfd, 1, (flushing ? 0 : -1));
if(ret>0)
{
char rbuf[1024];
buffer.append(rbuf, ret);
while(buffer.size()>buf_offset)
{
- int ret = gldecoder_decode(glprint, buffer.data()+buf_offset, buffer.size()-buf_offset);
- if(ret<0)
+ const char *data = buffer.data()+buf_offset;
+ unsigned len = buffer.size()-buf_offset;
+ int size = gldecoder_decode(0, data, len);
+ if(size<0)
break;
- buf_offset += ret;
- IO::print("%s\n", glprint_get_buffer(glprint));
+ tracer.decode(data, len);
+ buf_offset += size;
}
if(buf_offset>8192)
{
}
}
}
+ else if(flushing)
+ flushing = false;
}
-void GlDbg::launch()
+void GlDbg::sighandler(int sig)
{
- int fds[2];
- socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
- sock_fd = fds[0];
-
- pid = fork();
- if(pid==0)
- {
- string fd_str = lexical_cast(fds[1]);
- setenv("LD_PRELOAD", (libdir/"glwrap.so").str().c_str(), true);
- setenv("GLWRAP_FD", fd_str.c_str(), true);
- close(fds[0]);
- std::vector<char *> argv(args.size()+1);
- for(unsigned i=0; i<args.size(); ++i)
- argv[i] = strdup(args[i].c_str());
- argv[args.size()] = 0;
- execvp(argv[0], &argv[0]);
- ::exit(127);
- }
- else if(pid>0)
- {
- close(fds[1]);
- }
- else
- {
- throw SystemError("Could not launch process", errno);
- }
+ if(sig==SIGCHLD)
+ got_sigchld = true;
}
#include <vector>
#include <msp/core/application.h>
#include <msp/fs/path.h>
-#include "gldecoder.h"
+#include "commandinterpreter.h"
+#include "process.h"
+#include "tracer.h"
class GlDbg: public Msp::Application
{
private:
- Msp::FS::Path libdir;
- int pid;
- std::vector<std::string> args;
+ CommandInterpreter cmd_interp;
+ Process process;
int sock_fd;
std::string buffer;
unsigned buf_offset;
- GlDecoder *glprint;
+ bool flushing;
+ Tracer tracer;
+ bool got_sigchld;
static RegApp<GlDbg> reg;
public:
GlDbg(int, char **);
int main();
+ Tracer &get_tracer() { return tracer; }
+ Process &get_process() { return process; }
+ void launch();
+ void quit();
private:
void tick();
- void launch();
+ void check_child();
+ void read_stream();
+ long ptrace(int, void *, void *);
+ virtual void sighandler(int);
};
#endif
if(len<pktlen)
return -1;
pos += read_short(&func, data+pos);
- ret = decode_func(dec, func, data+pos);
- if(ret<0)
- return -1;
- return pos+ret;
+ if(dec)
+ {
+ ret = decode_func(dec, func, data+pos);
+ if(ret<0)
+ return -1;
+ }
+ return pktlen;
}
static unsigned read_char(char *v, const char *data)
#ifdef __cplusplus
}
+
+class GlPrint
+{
+private:
+ GlDecoder *glp;
+
+public:
+ GlPrint(char *b, unsigned l): glp(glprint_new(b, l)) { }
+ ~GlPrint() { gldecoder_delete(glp); }
+
+ int decode(const char *d, unsigned l) { return gldecoder_decode(glp, d, l); }
+ char *get_buffer() { return glprint_get_buffer(glp); }
+};
#endif
#endif
--- /dev/null
+/* $Id$
+
+This file is part of gldbg
+Copyright © 2009 Mikko Rasa, Mikkosoft Productions
+Distributed under the GPL
+*/
+
+#include <cstdlib>
+#include <cerrno>
+#include <cstring>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <msp/core/except.h>
+#include "process.h"
+
+using namespace std;
+using namespace Msp;
+
+Process::Process(const vector<string> &a):
+ args(a),
+ pid(-1),
+ state(INACTIVE)
+{ }
+
+void Process::setenv(const string &key, const string &value)
+{
+ env[key] = value;
+}
+
+void Process::launch()
+{
+ if(state!=INACTIVE)
+ throw InvalidState("Program is already running");
+
+ pid = fork();
+ if(pid==0)
+ {
+ for(map<string, string>::const_iterator i=env.begin(); i!=env.end(); ++i)
+ ::setenv(i->first.c_str(), i->second.c_str(), true);
+ std::vector<char *> argv(args.size()+1);
+ for(unsigned i=0; i<args.size(); ++i)
+ argv[i] = strdup(args[i].c_str());
+ argv[args.size()] = 0;
+ ::ptrace(PTRACE_TRACEME, 0, 0, 0);
+ execvp(argv[0], &argv[0]);
+ ::exit(255);
+ }
+ else if(pid>0)
+ state = STARTING;
+ else
+ throw SystemError("Could not launch process", errno);
+}
+
+int Process::check()
+{
+ int status;
+ int ret = waitpid(pid, &status, WNOHANG);
+ if(ret==pid)
+ {
+ if(WIFEXITED(status))
+ {
+ int code = WEXITSTATUS(status);
+ state = INACTIVE;
+ return 0x100|code;
+ }
+ else if(WIFSIGNALED(status))
+ {
+ state = INACTIVE;
+ return 0x200|WTERMSIG(status);
+ }
+ else if(WIFSTOPPED(status))
+ {
+ int sig = WSTOPSIG(status);
+ if(sig==SIGTRAP && state==STARTING)
+ {
+ ptrace(PTRACE_CONT, 0, 0);
+ state = RUNNING;
+ }
+ else
+ {
+ state = STOPPED;
+ return 0x300|sig;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void Process::resume(int sig)
+{
+ if(state!=STOPPED)
+ throw InvalidState("Program is not stopped");
+ ptrace(PTRACE_CONT, 0, (void *)sig);
+ state = RUNNING;
+}
+
+void Process::kill()
+{
+ if(state==INACTIVE)
+ throw InvalidState("Program is not running");
+ ptrace(PTRACE_KILL, 0, 0);
+}
+
+long Process::ptrace(int req, void *addr, void *data)
+{
+ int ret = ::ptrace((__ptrace_request)req, pid, addr, data);
+ if(ret==-1 && ((req!=PTRACE_PEEKTEXT && req!=PTRACE_PEEKDATA && req!=PTRACE_PEEKUSER) || errno))
+ throw SystemError("ptrace error", errno);
+ return ret;
+}
--- /dev/null
+/* $Id$
+
+This file is part of gldbg
+Copyright © 2009 Mikko Rasa, Mikkosoft Productions
+Distributed under the GPL
+*/
+
+#ifndef PROCESS_H_
+#define PROCESS_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+class Process
+{
+public:
+ enum State
+ {
+ INACTIVE,
+ STARTING,
+ RUNNING,
+ STOPPED
+ };
+
+private:
+ std::vector<std::string> args;
+ std::map<std::string, std::string> env;
+ int pid;
+ State state;
+
+public:
+ Process(const std::vector<std::string> &);
+ void setenv(const std::string &, const std::string &);
+ void launch();
+ int check();
+ void stop();
+ void resume(int = 0);
+ void kill();
+ State get_state() const { return state; }
+private:
+ long ptrace(int, void *, void *);
+};
+
+#endif
--- /dev/null
+/* $Id$
+
+This file is part of gldbg
+Copyright © 2009 Mikko Rasa, Mikkosoft Productions
+Distributed under the GPL
+*/
+
+#include <msp/io/print.h>
+#include "glprint.h"
+#include "tracer.h"
+
+using namespace Msp;
+
+Tracer::Tracer():
+ glprint(new GlPrint(0, 16384)),
+ out(0),
+ delete_out(true),
+ enabled(true)
+{ }
+
+Tracer::~Tracer()
+{
+ if(delete_out)
+ delete out;
+}
+
+void Tracer::set_output(IO::Base *o)
+{
+ if(delete_out)
+ delete out;
+ out = o;
+ delete_out = true;
+}
+
+void Tracer::set_output(IO::Base &o)
+{
+ set_output(&o);
+ delete_out = false;
+}
+
+void Tracer::enable()
+{
+ if(!out)
+ throw InvalidState("Output is not set");
+ enabled = true;
+}
+
+void Tracer::disable()
+{
+ enabled = false;
+}
+
+int Tracer::decode(const char *data, unsigned len)
+{
+ if(!enabled || !out)
+ return 0;
+ int ret = glprint->decode(data, len);
+ if(ret>=0)
+ IO::print(*out, "%s\n", glprint->get_buffer());
+ return ret;
+}
--- /dev/null
+/* $Id$
+
+This file is part of gldbg
+Copyright © 2009 Mikko Rasa, Mikkosoft Productions
+Distributed under the GPL
+*/
+
+#ifndef TRACER_H_
+#define TRACER_H_
+
+#include <msp/io/base.h>
+
+struct GlPrint;
+
+class Tracer
+{
+private:
+ GlPrint *glprint;
+ Msp::IO::Base *out;
+ bool delete_out;
+ bool enabled;
+
+public:
+ Tracer();
+ ~Tracer();
+
+ void set_output(Msp::IO::Base *);
+ void set_output(Msp::IO::Base &);
+ void enable();
+ void disable();
+ int decode(const char *, unsigned);
+};
+
+#endif