In this section we’re going to have fun.
Update your section repository,
cd s02, and
make. This will built a number
of fun programs.
Let’s run one:
$ ./fun01 😿😿😿😿😿😿😿😿 no fun 😿😿😿😿😿😿😿😿
That wasn’t fun!
These programs are puzzles. Look at
fundriver.cc and you’ll see the ground
rules. The driver’s
main function first creates a single C string that
contains all program arguments, separated by spaces. It then calls the
function, passing in that string. The
fun function returns an integer; if
fun(str) returns 0, then the driver has fun, and if it returns anything
else, no fun is had (the function
boo() is called, which prints the
We want to have fun, how can we have fun? Well might as well look to see what
the function is doing! (Open
Looks like this
fun function will return 0 if and only if the arguments
contain an exclamation point. Let’s test that:
$ ./fun01 ! 🎉🎉🎉🎉🎊🎊🎊🎊🌽🌽🌽🎊🎊🎊🎊🎉🎉🎉🎉 FUN 🎉🎉🎉🎉🎊🎊🎊🎊🌽🌽🌽🎊🎊🎊🎊🎉🎉🎉🎉 $ ./fun01 'yay!' 🎉🎉🎉🎉🎊🎊🎊🎊🌽🌽🌽🎊🎊🎊🎊🎉🎉🎉🎉 FUN 🎉🎉🎉🎉🎊🎊🎊🎊🌽🌽🌽🎊🎊🎊🎊🎉🎉🎉🎉 $ ./fun01 'amazing!!!!!!!!!!!!!!!!!' 🎉🎉🎉🎉🎊🎊🎊🎊🌽🌽🌽🎊🎊🎊🎊🎉🎉🎉🎉 FUN 🎉🎉🎉🎉🎊🎊🎊🎊🌽🌽🌽🎊🎊🎊🎊🎉🎉🎉🎉 $ ./fun01 'amazing?' 😿😿😿😿😿😿😿😿 no fun 😿😿😿😿😿😿😿😿
Now let’s say that the idea of not having fun is so painful to you that you will do literally anything to avoid it.
Is there any way that you could, for example, prevent the
from running? That you could stop the program if it reached
This calls for a debugger breakpoint. A debugger is a program that manages the execution of another program. It lets you run a program, stop it, and examine variables, registers, and the contents of memory. Among the most powerful debugger features is the ability to stop a program if it ever reaches an instruction. This is called “setting a breakpoint”: the breakpoint marks a location that, when reached, “breaks” the program and returns control to the debugger.
How would you stop the program from executing
$ gdb fun01 (gdb) b boo
Now if we run the program with non-fun arguments
we will stop before printing “no fun”!
If you’re not careful, though, it’s possible to accidentally step through and
print the message. You can do this one step at a time (demo
r, followed by
ses); or you can do it by continuing the program by accident (demo
r followed by
What if you wanted to make this kind of accident wicked unlikely? Well, you could set more breakpoints!
(gdb) b foo (gdb) r Breakpoint 1, main (argc=1, argv=0x7fffffffdf88) at fundriver.cc:34 34 boo(); (gdb) x/20i $pc => 0x400c47 <main(int, char**)+375>: lea 0x344(%rip),%rsi # 0x400f92 0x400c4e <main(int, char**)+382>: lea 0x20156b(%rip),%rdi # 0x6021c0 <_ZSt4cerr@@GLIBCXX_3.4> 0x400c55 <main(int, char**)+389>: callq 0x400a70 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x400c5a <main(int, char**)+394>: mov $0x1,%edi 0x400c5f <main(int, char**)+399>: callq 0x400a90 <exit@plt>
We’ve stopped at the first instruction in the
boo function (which has
actually been inlined into
main, but never mind). But can also set more
breakpoints! For example, at the second instruction and the third:
(gdb) b *0x400c4e (gdb) b *0x400c55
But what if you forgot these breakpoints??? Well, that’s a good case for
xfor examining memory
x/20i $pcfor examining instructions
- autodisplay instructions:
Now let’s work through a couple more funs. We’ll try to understand the operation of the funs using GDB and assembly, though for the first 6 funs, the C++ is there if you get stuck.
ASSEMBLY IS HARD. And trying to understand assembly from first principles, without running it, is really hard! As with many aspects of systems, you will have more luck with an approach motivated by experimental science. Try and guess at an input that will work, using cues from the assembly. Develop a hypothesis and test it. For the bomb, you don’t need to fully understand the assembly, you just need to find an input that passes each phase. (That said, you will often end up understanding the assembly—but only after completing the phase with the help of experiments.)
It is also often effective to alternate between working top down, starting from the entry to a function, and bottom up, starting at the return statement. Working from the bottom up, you can eliminate error paths and trace through how the desired result is calculated. Working from the top down, you can develop hypothesis about how the input should look. As long as you have breakpoints set, you can experiment with a free and easy heart. (And if the bomb goes off, who really cares?)
strtol. Hypothesis: string should be an integer! Adds 1 to the return from
fun03: Checks first character of string, then returns second. Answer: any single-character string. Introduces control flow.
fun04: Any two-character string with both characters same. More control flow.
fun05: Any nonzero-character string with all characters same. A loop.
fun06: Any string with length a multiple of 4. For loop.
fun07: Checks for primes. Work top-down: expects an integer. Test a bunch of integers at command line. Ask students to spot the pattern.
fun08: Reads three integers, returns
a + b - c. Examples:
1 1 2,
0 0 0.
fun09: Expects a filename of an openable file. Pretty simple.
fun10: A power of 2 greater than 128. Bitwise arithmetic.
fun11: A six-or-more character string consisting of uppercase letters.
fun12: A five-or-more character palindrome.
The section cheatsheet has a bunch of material on assembly that can be used for reference or presentation if the above doesn’t work for you.