This History of Debugging

This History of Debugging

Knowing the history of software development is liberating. It sheds light on what we are doing and why it. I’ve found it especially useful for understanding debugging.

When Maurice Wilkes, the creator of the first operational computer, first started running down the stairs from his office to the EDSAC machine room with a newly modified strip of paper tape to load into the machine, he realized that he’d have to do it for the rest of his life. He was debugging, and up until that point it had never occurred to us that we wouldn’t get it right the first time.

Over the next couple of decades, three types of debugging emerged. 

The Birth of Debuggers

The first debugger was designed for the big single user machines used in universities, such as the TXO at MIT. It worked like this: you signed up for a time slot to sit in front of the machine and for that period of time you were the sole ruler of all the blinking lights. You wanted to make the most of your time, so you arrived with your source code pre-punched on paper tape. After loading the assembler binary into the machine, the assembler read your program, and punched yet another paper tape with your program’s binary output. You then read your binary into the machine and ran it.

At this point your program would, of course, not work right. Time to debug.

In order to make a change you figured out your issue on paper, punched a new paper tape with the modified source, and start the whole assembly process all over again. This was far too time consuming, so programmers decided to write a tiny programs called debuggers, and put them at the top of memory, safely out of the way, and jump to them after they loaded their program binary. With these tiny programs they could examine, modify, and step their programs. This way, they avoided the task of retyping the whole program to make simple changes.

These first debuggers were the ancestors of gdb and lldb and had names like DDT and ODT.

Card Decks and Listings

The second approach (which was actually used a bit earlier than DDT) was used in batch operating systems. The programmer often didn’t even see the computer. They punched a deck of cards, and after binding them with high-quality rubber bands, the deck would be handed over to an operator who would read them into a batch of programs to be run all at once. When I started as a student, there was only one run a day (and the quality of the rubber bands was very important—if you dropped your punch deck and the band broke, good luck getting them back in the right order!).

Two techniques were used in developing batch programs: snap-shots and core dumps. The programmer would put calls to macros that printed the current register values at various points in the program and arrange them so that if there was an abnormal termination of the program, the printout of the run would include an octal or hex dump of all of memory. The programmer would then walk through the code in their head and see what clues were still in memory when the program bought the farm.

Truly Interactive

This worked well, but with the advent of CTSS in the early 1960s, there was a new way to debug.

CTSS was the first timesharing system. Through the use of processes, users had the illusion of a stand-alone computer but also had mass storage handy. Just by typing a line, a user could run the assembler.

This meant you could also interactively add output to your program and use the language itself to debug it. Unix, built by folks who had used CTSS and its large offspring Multics, took all this to new heights with the invention of pipes. The software tool concept was born.

The loop caused by this new timesharing way of debugging made it incredibly effective: think, edit, compile, test. This loop was so fast that programmers didn’t have to resort to the in-core debuggers such as DDT anymore. Just add prints to the program and run it again, dozens of times an hour. It’s the same feeling that scripting languages give their users today.

But there was still the DDT like debuggers if you preferred them. The first version of the Unix manual includes a page on the debugger DB. Later, Steve Bourne wrote the famous debugger, adb. For GNU there’s gdb and lldb, of course. For Plan 9 we have a descendent of adb named db and a debugger based on a scripting language called acid.

Early in my career I asked Dennis Ritchie what he used to debug his programs. He replied, “my brain.” He wasn’t trying to be a smart aleck, and instead was pointing out that we have to think about the program to debug it. He also added that for the most part he just used prints in the code to debug. Ken Thompson does the same.

Unix would create a file named “core” that was inspired by the batch core dumps. Adb would then be used to get a back trace of where the program wandered out into the weeds. Even though adb could debug a program in the DDT fashion, we mostly just used it to get a backtrace. Niklaus Wirth’s Oberon system gives a back trace automatically, and that’s the extent of his debugger. On modern system the program image is too large to dump. On Plan 9 the process goes into a broken state without saving an image to the disk. We can then get a backtrace from it where it stands.

I’ve seen pressure in the industry to use programs like gdb and lldb. Like with programming languages, use the debugging method you’re most familiar with. If you’re more comfortable using prints, I hope learningthe history of debugging frees you from any worry.

I think the 'broken' state from Plan 9 was yet another neat ignored idea from that system. It's brilliant. And, you might want to mention that it is part of the ability to tar up /proc, copy it somewhere else, mount it, and continue debug from there (well, not really, yet; just post mortem. But still ...). There's still nothing like it anywhere else. Nice article.

Thanks for the comment, Tim. I don't know if Dennis every did such, but I do know many who have developed lots of embedded systems with lots of global register with nothing but a print() statement and a serial port. Many of the peripherals we have written drivers for, like the Intel 10G Ethernet NICs, have had hundreds of register and all the code was developed in such a fashion. Of course, the point is that you can use whatever you want, fancy IDE or simple print() statements. They are both just as productive.

Like
Reply

Good history Brantley. I bet Ritchie never wrote code for a microcontroller peripheral that required a few hundred registers to keep up with.

To view or add a comment, sign in

Others also viewed

Explore content categories