This tutorial will introduce you to the GNU tools in the Linux programming environment, which we will be using while covering machine-level programming in CSCI 2500. It introduces the C compiler, gcc, the assembler, as, and the debugger, gdb. You will learn how to compile, assemble, execute, and debug C and assembly language programs.
As we've been doing in class, connect your Raspberry Pi to your host computer. We'll then use the ssh and sftp programs to communicate with it from the host computer. Depending upon the operating system, you may be using ssh and sftp directly from a Terminal window (Linux and Mac) or using a separate program like git bash, bitvise, or putty for this capability (Windows).
The tutorial files have already been installed on your Raspberry Pi, but they're linked here as well, for viewing online, or in case you need to re-copy them onto the Pi:
Switch to the tutorial_arm/ directory using the commands: cd csci2500 and cd tutorial_arm. Then compile and assemble the programs using either the make or the make all command (make and make all amount to the same command since typing make by itself defaults to make all).
In the process of "making" the programs, you will see a listing of the commands used to compile and assemble the C and assembly programs using gcc and as. It is recommended that you try executing each one of these commands on your own and use ls -lt to check what files are generated as a result of each command. Be sure to use the make clean command to delete all the executables and .o files before executing the "make" commands on your own.
You may also want to use the less Makefile command to see how the Makefile performs the compilation/assembly process.
After "making" the programs, try running the C and assembly programs using the tut_cprog and tut_asmprog executables, respectively. Remember, to run an executable that resides in the current directory, precede the executable name by ./ For example, to run tut_cprog, type ./tut_cprog
To begin debugging the C executable using the gdb debugger, execute the command: gdb tut_cprog.
We will start off by introducing a few simple debugging commands in gdb.
To begin debugging, you must first begin executing the program within gdb. When gdb starts up, the program is merely loaded; it is not yet executing. Before running the program, use the break main command to set a break point at the first instruction in main(). Then type run (or r) to begin running the program. You will note that the program breaks immediately at the entrance to main().
The step (or s) command traces into each instruction. Effectively, this means that it executes the next single machine/assembly instruction. For procedure or interrupt calls, this means that it executes only the instruction that transfers control to the procedure, causing it to jump into that procedure/interrupt (i.e. it steps 'into' the procedure/interrupt).
Use the s command a couple times to watch the program's progress as you step through the code. When you get to the function call for get_number(), notice that you actually "step into" the procedure so that you can watch the execution of each individual statement in that procedure. If you ever want to re-start from the beginning of the program, simply use the run (r) command again, which will re-start execution of the program from the beginning, stopping immediately at the first breakpoint (at main()).
The next (or n) command traces over each instruction. Effectively, this means that it executes the next single instruction in the active procedure. For procedure or interrupt calls, this means that it executes the full procedure or interrupt (i.e. it steps 'over' the procedure/interrupt).
Try stepping through the program again, but this time use the n command instead. Notice that this time, you don't jump into the get_number() function. Stepping "over" this function simply causes it to execute "behind the scenes", so that you don't have to watch all the gory details if you don't want/need to.
Sometimes it is desirable to watch how the value of a variable changes as you trace through the program. It is possible to watch variables in this fashion using the display {variable name} command.
For example, re-start execution from the beginning of the program again, but this time use the command display n to watch the value of variable "n" as you step through the beginning of the program. To see a full listing of all the variables that are currently being watched, use the info display command. Likewise, if you want to delete a variable from being watched, you can use the command delete display {variable number}.
Note: gdb can only watch variables that haven't been optimized away by the compiler. For example, some simple local variables, such as a loop index (like i in the tut_cprog.c) are optimized by the compiler such that they're only stored in registers, and never updated in memory. Consequently, attempts to watch them will fail -- you can only view their current values by watching the register(s) in which they are stored.
For some variables, you may only be interested in seeing their value sporadically, so in that case, you can use the print {variable name} command to display those values as desired.
Additionally, you can add the '&', or address of, operator before the variable name to determine the address of a variable. For example, if you wanted to know the base address for the num_set array, you would use the command: print &num_set
Often, you will want to watch a whole block of memory as opposed to a small set of variables. There's no way you can automatically have a block of memory displayed after each step like you can with variables, but you can still easily display the contents of memory with a single command after each step. The command x/{number of elements} {start address} will display the specified number of elements starting at the specified address (e.g. x/32 0x21048 will show the first 32 words of memory starting at address 0x21048). The default element and display mode is a 4-byte element displayed in hex, but you can specify the desired size and format using appropriate formatting codes. For example, x/32d will display the elements in decimal (d). When debugging C, gdb will attempt to infer the desired size and format from the variable stored at the starting address, but again, you can change as desired -- see help x for more info.
The primary problem in displaying memory is that you must know the address(es) of the memory locations you which to view. Fortunately, when debugging in C, you can simply use the name of the variable, along with the ampersand (&) to denote the address of the variable. For example, to display the first 64 elements of an array myArray, simply use the command x/64 &myArray.
It is also often useful (moreso with assembly debugging than C debugging) to view the values in the register file. Use the info registers (or i r) command to do this.
While the s and n commands display the next instruction to execute, you may often want to see more complete sections of code. You can display the 10 lines of code around the next statement to execute in the debugger using the list (or l) command. Or if you want to display the 10 lines of code around a function declaration, you can use the list {function name} command (e.g. l main).
An alternate way to view the assembly code (or when debugging in C and needing to view the equivalent assembly), use the disassemble {label/function name} (or disas {label/function name}) command (e.g. disas main).
Exit the debugger using the quit (or q) command.
Continue executing a program using the continue or (c) command. Execution will only stop after reaching a breakpoint, the end of the program, or a similar end-of-program procedure/interrupt call. This function is particularly useful in conjunction with breakpoints.
Also feel free to experiment with other debugging functions. You can use the help (or h) command to find directions for using other debugging utilities.
Continue to trace through tut_cprog in gdb and examine what each instruction does. Be sure to use s and n as needed.
To begin debugging the assembly executable using the gdb debugger, execute the command: gdb tut_asmprog.
Proceed to trace through the tut_asmprog executable just like you did with the C program -- first start by setting a break point at the main function, run the program, and then proceed to step through following the break point.
Note 1: In assembly, you'll want to use si and ni as opposed to s and n. To see the difference, try using si and ni while debugging C... can you infer the difference?
Note 2: In assembly, the BL instruction calls the specified function (e.g. get_number or printf), and since you don't want to jump into those functions, use ni when stepping to step over those function calls.