Well, what I spent a few hours working on anyway. I've not done any hobby programming in a long time and I decided I'd use some of my leisure time to get back into the swing of things. I picked up something I started fiddling with about six months ago, which is a symbolic debugger.
See, on Linux there is basically precisely one symbolic debugger (i.e. one designed for high level languages, not assembler). That's gdb, the GNU command-line debugger. All other debuggers are GUI front-ends for gdb. But gdb was never designed to be a GUI front-end - GUIs barely existed when development began on it - and it shows. Linux GUI debuggers tend to be a bit on the clunky side because they're converting user mouseclicks into text commands like a user would actually type into gdb then parsing the response out on the side. It tends to make for a more fragile, less smooth UI.
Gdb is also old, and it shows in the code and in the way it does things - for example, it's built to support one host and one target; you have a gdb for Linux x86 and you want to cross-debug to a Linux PPC development board? Too bad! Go compile a new version for just that purpose. It's also the one part of the traditional GNU toolchain that isn't getting some kind of overhaul or competition - there's a new compiler (llvm) and a new linker (gold) but no new debugger. So I thought it might be an interesting area to play around in; I don't know if anything will ever come of it, but when I messed around with compiler stuff I tended to become discouraged because llvm had already done a far more awesome job of it than I could ever hope to do.
Anyway,
here is a screenshot of what I have so far. It's attached to a 64-bit Linux/X86 process which just sits in an infinite loop printing its getpid() to stdout.
To the top is the memory editor displaying a random chunk of memory. To the left is a disassembly of main() showing the process stopped at a breakpoint. In the middle is the register display. To the right - and this is the new bit - you can tell it's in main() because it displays the ELF symbol table for the process with the addresses of all the functions. This is what begins to make it a symbolic debugger rather than an assembly-language one since it's beginning to map the assembler to high-level language constructs.
Doing this properly is going to require parsing DWARF3 (the real debugging information; the ELF symbol table is mostly to help out linkers and loaders and is pretty minimal in what it tells you). That's the next task, and is going to take some work.
However, this thing already has some nice architectural features. It's designed to be a GUI backend and is asynchronous - you send out a 'read this register' command, later you get a 'this register's value is' response, and in the meantime the GUI runs smoothly. It also is designed around the concept of a host portion and a target portion, the target portion being isolated in a separate class - this is designed to allow remote debugging - and supports reading files of arbitrary endianness at runtime. It also should support debugging 64-bit processes when compiled as a 32-bit process, which is trickier than you might think in some ways.
Currently it supports 32-bit and 64-bit x86/Linux/ELF, but the support's there for adding other platforms in a reasonably modular manner.