This is just a mini introduction to using GDB’s text user interface, or GDBTUI. I use a Linux VM on my home laptop; tunneling in to my university’s linux server is painfully slow, so I just develop locally and then scp (ssh copy) my complete ASM file to the university server to verify that it compiles/runs there. If you have a Linux box that struggles with a VM, try out an older window manager like Openbox, though you’ll lose some fancy features.
On my Linux VM, I use Codeblocks (which I’m not a fan of for assembly), you’re probably using GDB to debug. GDB can be a pain to use with input and output, usually by redirecting input/output to another terminal or files, or integrating it into vi/emacs.
First, you just open/edit the ASM file in Geany, for this example it’s “Homework2.asm”. I have three terminals:
-
Terminal 1: Use to compile/run the app with the Homework2.sh script. The app runs, allowing me to use it as normal. Simply type
./Homework2.out
. It runs the app and, in this case, just sits there waiting for you to enter the first number. -
Terminal 2: This terminal is solely for getting the process ID of my running app. While the app is sitting in Terminal 1 waiting for input, I type
ps aux | grep Homework2.out
in this terminal. This lists all the processes with “Homework2.out” in the process name. It’ll show geany, GDB if it’s already running and the app running in the first terminal, like this:
steve@debian:~$ ps aux | grep Homework2.out steve 5180 0.0 0.2 44668 11284 pts/0 S+ 08:30 0:00 /usr/bin/gdb --tui Homework2.out steve 5346 0.0 0.0 2004 268 pts/2 S+ 08:34 0:00 ./Homework2.out steve 5349 0.0 0.0 8024 912 pts/3 S+ 08:35 0:00 grep --color=auto Homework2.out
That second one is my program running in Terminal 1. Its process ID is 5346, the first four-digit number on that line.
- Terminal 3: In this terminal, run GDBTUI. Just type
./gdbtui
. An overview of the TUI features can be found here. Size this terminal properly before running GDBTUI; there’s sometimes a goofy bug, depending on your WM, that will crash it if you try to resize after firing up the terminal. I just make the terminal about twice its regular size. There are multiple layouts available for GDBTUI. The one that’s best for us has three “windows” in the terminal: “Registers”, “Assembly”, and “Command”.
Once GDBTUI is running, you’ll be at the GDB command prompt. The commands in GDBTUI are all the same as they are for GDB: si, c, r, etc. There’s some extra TUI commands. First, type layout prev
. This should just bring you to a layout that shows the three “windows” I mentioned above. If not, continue cycling with layout next
or layout prev
to see the other layouts.
Now that you’ve got the proper layout, just type attach 5346
and GDB will attach to your program in Terminal 1. Set your breakpoints as needed and you can now interact with the app in Terminal 1 while viewing the assembly and register values in the GDBTUI interface.
Some extra things that may help you:
- You can adjust the individual “windows” in GDBTUI. Type
info win
to get a list of the “windows” in the interface. For example:
(gdb) info win ASM (13 lines) REGS (12 lines) CMD (12 lines) <has focus>
To adjust the heights, described in lines of text, just type winheight ASM +5
. Substitute ASM
for whatever window and you can use +/- to adjust the height.
-
You can change the focus within GDBTUI. Notice CMD has focus for me. That means I can use Up and Down to scroll through previously commands. Change focus by typing
focus CMD
, replacingCMD
with whatever window. If you focus onASM
, you can use Up and Down to scroll through the code in the disassembly window. -
Place labels throughout your code, like after parts of code getting input so GDBTUI will pause there and you can continue stepping through with
si
line-by-line. GDBTUI will automatically step into the functionsread_int
,read_char
and whatever else, so I always place a random label after all my input/output. For me, I set a breakpoint at asm_main to start. I typec
in GDB so it just continues and waits for my input since that’s the first thing I do. After the input, it’ll just continue. I want it to break right after that, so I just have a label calledcmps
after all my input/output so I don’t have to step through all of the instructions inread_int
orread_char
(and there are a lot). I just set a new breakpoint atcmps
, do my input and GDB breaks after that. -
You can view memory locations and the stack like this:
(gdb) x/10x $sp 0xffcb1c40: 0xf7561940 0x00000400 0xf7733000 0xf763bf13 0xffcb1c50: 0xf7709000 0xf75d1ab3 0x00000000 0xf7733000 0xffcb1c60: 0x00000400 0x00000001 (gdb)
The first x
is for “examine”. The /10x
tells GDB how many words to display. You can increase or decrease “10” as needed. The $sp
is the location. $sp
tells GDB to display starting at the stack pointer. You can replace $sp
with a memory location like 0xffcb1c40
to view the contents of a variable.
-
Most systems should have
disable-randomization
set by default. If not, just typeset disable-randomization on
. This causes GDB to reuse the same memory locations over and over while you’re debugging. It makes it easier since you’ll start to become familiar with memory locations and won’t have to look those up every time you run the app. -
This is a great reference to have open while coding too: http://x86.renejeschke.de/.
Once the program crashes or closes, GDBTUI will still be open. You can simply make changes as needed in Geany/Vi/Emacs and run through the same process outlined above: run it in Terminal 1, get the process ID in Terminal 2 and attach GDBTUI in Terminal 3. Since GDBTUI continues running even after the app dies, it’ll maintain memory addresses, etc.
Everyone has their own method but this one works great for me. Hopefully it helps someone else out. Happy coding!