Go back to cdw documentation

Debugging curses or ncurses programs with gdb in emacs

This article describes setup for debugging console programs that use curses/ncurses (possibly even slang) libraries with gdb debugger running in GNU emacs 23. I experienced some problems with this setup, and I decided to describe solution that worked for me.

Author: Kamil Ignacak, acerion at wp dot pl
Version: 1
Date of last revision: 19 Feb 2010

Table of contents:



Introduction

Simplest way to debug console program is to start gdb and run the program inside of gdb. Big disadvantage of this solution is that gdb commands and output are mixed with debugged program's output. If debugged program uses curses/ncurses or slang user interface library, the problem only gets worse.

Solution to this problem, found in many materials on the internet and in books is to use two separate terminals: one terminal for interacting with gdb, second terminal for interacting with debugged program. This works fine as long as we work gdb + debugged program alone, but I discovered some problems when I tried to run gdb inside of emacs and debug a program from within emacs + gdb pair. These problems (and method of solving them) are described in long version of solution.

This article is mostly a compilation of pieces of information found in various places on the internet.

This article is about debugging with emacs + gdb pair, but instructions listed below will work both if we run gdb inside emacs and if we run "standalone" gdb.

First I provide short version of solution for impatient readers, then long version with screenshots and more explanation.

Tools used:

  • sh shell (bash 4.1.0),
  • GNU emacs 23.1.1,
  • gdb 7.0.1,
  • konsole and xterm terminal emulators,
  • demo_forms - sample program from ncurses sources.

This document uses following symbols:

  • $ - to denote shell prompt
  • (gdb) - to denote gdb prompt
Top of page


Solution - short version

  1. We start two separate terminals, let's call them gdb terminal and program terminal.
  2. In gdb terminal we run emacs, in emacs we issue command
    M-x gdb
  3. If we run emacs in directory where debugged program is located, emacs will prompt us with something like this: "gdb --annotate=3 <name of debugged program>". Otherwise we will see only "gdb --annotate=3". In second case we can specify full path to debugged program in current emacs prompt, or we can specify it later in gdb with gdb's "file" command [1]. In either case we press Enter in emacs prompt.
  4. In program terminal we issue command
    $ tty
    Result will be something like "/dev/pts/3" or "/dev/tty2" depending on what terminal we use.
  5. In gdb we issue command similar to this:
    (gdb) tty /dev/pts/3
    Instead of "/dev/pts/3" we have to use real value returned by tty command in program terminal in previous step.
  6. In program terminal we issue command
    $ echo $TERM
    Result will be something like xterm, vt100, rxvt or linux.
  7. In gdb we issue command
    (gdb) set env TERM=name
    where "name" is a result of the command used in previous step.
  8. In program terminal we issue command
    $ stty -a
    Information printed by stty will contain information about rows and columns which will look like this: "rows 41; columns 126;". These two values will be used in next step.
  9. In gdb we issue two commands:
    (gdb) set env COLUMNS=80
    (gdb) set env ROWS=25
    The two numerical values used here are just examples, but they must be no larger than respective values reported by stty in previous step. I'm not sure about this, but for some cases it may turn out that they have to be equal to values reported by stty. NOTE: if debugged program has some requirements regarding minimal number of rows and columns, we have to make sure that size of program terminal (i.e. the number of rows and columns printed by stty command) meets these requirements. Resize the program terminal if necessary.
  10. In program terminal we run command
    $ sleep 1000000
    The value's units are seconds, so the value must be large enough so that debugged program doesn't start behaving strangely during debugging session.

That's all: emacs/gdb + external terminal combo is now ready for debugging session. If we haven't done this before, we can specify full path to debugged program now with "file" command in gdb [1].

See Cleaning up section for information on what to do after ending debugging session.

Top of page


Solution - long version

Solution most often found on the internet when searching for "debugging console programs in gdb" is to redirect input and output of debugged program to a different, separate terminal. Basics of redirecting input/output are described in paragraph 4.6 of gdb manual [2].

It turns out that in some cases this is not enough, because we need to configure our debugging environment a bit further. Steps listed below present full reasoning behind short version of procedure that you've seen above. This full reasoning includes some trials and errors, and full procedure with backtracking goes like this:

  1. We start two terminals, let's call them gdb terminal and program terminal. We can arrange them on our screen so that we can see them both at the same time.
  2. In gdb terminal we run emacs, in emacs we issue command
    M-x gdb
  3. If we run emacs in directory where debugged program is located, emacs will prompt us with something like this: "gdb --annotate=3 <name of debugged program>". Otherwise we will see only "gdb --annotate=3". In second case we can specify full path to debugged program in current emacs prompt, or we can specify it later in gdb with gdb's "file" command [1]. In either case we press Enter in emacs prompt.
  4. In program terminal we issue command
    $ tty
    Result will be something like "/dev/pts/3" or "/dev/tty2" depending on what terminal we use. Value returned by tty program is a device file describing current terminal. It will be "real" terminal, like "/dev/tty2" if we don't use X Window environment, or it will be "pseudo" terminal, like "/dev/pts/5" if we use a terminal emulator provided by desktop environment, e.g. konsole, gnome-terminal, xterm etc.
  5. In gdb we issue command similar to this:
    (gdb) tty /dev/pts/3
    Instead of "/dev/pts/3" we have to use real value returned by tty command in program terminal in previous step. gdb uses inferior-tty variable to store information about debugged program's terminal (input and output device), so we can use set and show gdb commands to operate on this variable.
  6. In program terminal we run command
    $ sleep 1000000
    This is to ensure that the keys that we press when using program terminal are sent to debugged program, and not to shell in program terminal [3]. The value's units are seconds, so the value must be large enough so that debugged program doesn't start behaving strangely during debugging session.

This is where most of widely available instructions end. They work pretty well for non-(n)curses program executed under gdb + emacs pair, or for ncurses program executed under gdb alone. However it is not so easy for mix of emacs+gdb+ncurses program. This is what we can see in program terminal when we follow steps listed above, running in gdb demo_forms, a sample ncurses program:

only few chars visible at the bottom of screen

This is clearly not what we expected. Other, more advanced programs using ncurses library give a hint at a source of this problem. Midnight Commander, when invoked in gdb, prints this message before exiting: "Your terminal lacks the ability to clear the screen or position the cursor.". This means that terminal mode used by gdb may be incorrect. This leads to next steps in our procedure, where we first check what is the terminal mode of program terminal, and then we check what gdb thinks about its current terminal mode:

  1. In program terminal we exit demo_forms program, press Ctrl+C to exit sleep command and we issue command
    $ echo $TERM
    Result will be something like xterm, vt100, rxvt or linux. This tells us what mode of terminal is used by program terminal. The same mode must be used by gdb. We can first check what is the current mode of terminal in gdb (what gdb thinks that the current terminal mode is):
  2. In gdb we use "show env name" command to check value of gdb's environment variable: [4]
    (gdb) show env TERM
    Result will be most probably "TERM = dumb" or maybe "TERM = emacs". There is clearly a mismatch between what gdb thinks about mode of terminal to which we want to redirect input and output of debugged program, and what the real mode of program terminal is. This probably can be fixed permanently with an entry in gdb configuration file, but immediate fix is in next step:
  3. In gdb we issue command
    (gdb) set env TERM=name
    where "name" is a result of the command "echo $TERM" issued in program terminal.
  4. In program terminal we again issue command
    $ sleep 1000000

After completing these steps and running the demo_forms sample program in gdb we can see that results are a lot better, but there are some problems with frames displayed in terminal window:

still some distortions

Program's output looks like it doesn't quite fit into terminal, as if the debugged program thinks that terminal's size is different than it really is. Let's investigate further:

  1. In program terminal we exit demo_forms program and press Ctrl+C to exit sleep command. Then we issue command
    $ stty -a
    Output of stty command contains information about number of rows and columns. The information will look like this: "rows 41; columns 126;". These values are number of rows and columns that program terminal can display given its current X/Y size and size of font used by terminal. The values will be useful later.
  2. At this point we can again use "show env name" command in gdb to check current settings in gdb:
    (gdb) show env COLUMNS
    (gdb) show env ROWS
    It will probably turn out that these values are larger than rows/columns size in program terminal. It may even turn out that ROWS or COLUMNS is not defined at all. In either case we fix this as shown in next step:
  3. In gdb we issue two commands:
    (gdb) set env COLUMNS=80
    (gdb) set env ROWS=25
    The two numerical values used here are just examples, but they must be no larger than respective values reported by stty in program terminal. I'm not sure about this, but for some cases it may turn out that they have to be equal to values reported by stty. NOTE: if debugged program has some requirements regarding minimal number of rows and columns, we have to make sure that size of program terminal (i.e. the number of rows and columns printed by stty command) meets these requirements. Resize the program terminal if necessary.
  4. In program terminal we issue again sleep command:
    $ sleep 1000000
  5. In gdb we run demo_forms program again (the "r" command in gdb), and this time output of the program in program terminal looks correctly:
  6. fully correct output

And that is all, this is how emacs, gdb and additional terminal are configured for debugging session. When you finish your debugging session you may need to clean up in your program terminal.

Top of page


Cleaning up

When we finish debugging session we can restore control in program terminal by first closing debugged program (if we haven't done that already), and then pressing Ctrl+C to exit that "sleep 1000000" command. If debugged program didn't exit correctly it may happen that program terminal doesn't react correctly to keyboard input. We have to press Ctrl+j to restore it's state. Alternatively we can try issuing "reset" command (we may not see entered characters).

Top of page


References

[1] Debugging with GDB - 18.1 Commands to Specify Files

[2] Debugging with GDB - 4.6 Your Program's Input and Output

[3] Using GNU's GDB Debugger / Debugging Ncurses Programs, a tutorial written by Peter Jay Salzman. Most comprehensive source on the topic described here that I found on the internet. Currently (17 Feb 2010) the site is offline, so try to find cached article.

Update, 28 Dec 2010: you can find the document here: http://www3.sympatico.ca/rsquared/gdb/dnp.html, as republished by Rob Somers (thanks for the link Rob!). You may want to check whole tutorial on gdb.

[4] Debugging with GDB - 4.4 Your Program's Environment

A small survey about debugging techniques

A discussion about emacs and gdb

A discussion about gdb and ncurses

libchewing-0.3.2/test/README - a small readme file from chewing library package


Top of page


Change Log

  • 19 Feb 2010: initial version of the document: version 1

Copyright

Public Domain

Top of page
Go back to cdw documentation