When debugging a program using the GNU Project Debugger (GDB), printing the value of errno
should be easy, but sometimes it isn't. This article explains why printing errno
might not work and what to do when it doesn't.
This article will focus on systems with runtime environments which use the GNU C Library (glibc) as the C library; printing errno
on other modern multi-threaded systems which use a different C library implementation should be similar, though some of the details for accessing errno
(when it doesn't just work) might be different. This article with be most applicable to GNU/Linux systems running either the Red Hat Enterprise Linux (RHEL) or Fedora distributions. Much of the article should be applicable to other popular Linux distributions too, but be aware that package names may be different and that commands used for installing packages may also be different than those shown here for RHEL and Fedora.
About errno
The C and C++ programming languages provide access to, via the C library, a variable (or, more pedantically, a modifiable lvalue) named errno
. Historically, before multi-threaded environments became commonplace, errno
was a global variable of type int
, but for most modern systems, it's a per-thread lvalue; i.e., storage associated with errno
will need to reside in thread-local storage. The type of errno
is still the same though—it's still int
, just as it was in the past.
Code written in C or C++ can examine errno
after calling certain library functions when the return value from the call indicates that an error occurred. Values associated with errno
are positive integers which indicate the kind of error that occurred. For example, error code 2 or ENOENT
indicates "No such file or directory". On Fedora systems, if the moreutils
package is installed, a list of errno
values may be obtained by running errno -l
from the shell.
Example program
Consider this example code, named open-error.c
, written in C:
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int
main (int argc, char **argv)
{
int fd = open ("/path/to/a/file/which/does/not/exist", O_RDONLY);
if (fd < 0) /* Line 11: Set GDB breakpoint here. */
printf ("open returned error: %s (%d)\n", strerror (errno), errno);
}
This code may be compiled using the following command:
gcc -Wall -g -o open-error ./open-error.c
Running it, along with its output, looks like this:
$ ./open-error
open returned error: No such file or directory (2)
The program attempts to open a nonexistent file. Since the file does not exist, the value returned by open
is negative, causing the error message to be printed. Had the file existed, running the program would not have printed anything—it would have been opened via the call to open
and then closed during program exit.
Printing errno with GDB
Printing errno
from within GDB should be easy. To demonstrate the ideal case, where it just works, let's debug this program with GDB and set a breakpoint on line 11, which is the first executable line after the call to open
:
$ gdb -q open-error
Reading symbols from open-error...
(gdb) break 11
Breakpoint 1 at 0x40117d: file ./open-error.c, line 11.
(gdb)
Let's run the program, answering y
when asked whether to enable debuginfod
:
(gdb) run
Starting program: /tmp/examp/open-error
This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.fedoraproject.org/>
Enable debuginfod for this session? (y or [n]) y
Debuginfod has been enabled.
To make this setting permanent, add 'set debuginfod enabled on' to .gdbinit.
Downloading separate debug info for /lib64/libc.so.6
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, main (argc=1, argv=0x7fffffffdb88) at ./open-error.c:11
11 if (fd < 0) /* Line 11: Set GDB breakpoint here. */
Finally, let's print fd
and errno
:
(gdb) print fd
$1 = -1
(gdb) print errno
$2 = 2
This, ideally, is how things should work. But there are cases where printing errno
might not work. The rest of the article will examine cases where it does not work and what, if anything, can be done to still discover the value of errno
using GDB.
Missing glibc debugging information (debuginfo)
One possibility is that the system does not have the glibc-debuginfo
package installed and debuginfod
is either not used or is not available. When GDB doesn't have access to glibc debugging information, it won't have access to type information for errno
and __errno_location
, which is the function that, when called, returns the address of errno
storage for the current thread.
I'm running this example on rawhide (Fedora 41) in which the system GDB no longer contains a hack which would more frequently allow errno
to be printed. Also, on this machine, I've removed the glibc-debuginfo
package.
Below, I run the program again, but this time, I'll answer n
when asked whether to enable debuginfod
. I do this only for demonstration purposes—in practice, you should ensure that glibc debugging information is available from some source, either from installed glibc-debuginfo
or via using debuginfod
, in which debugging information is obtained on-demand.
$ gdb -q open-error
Reading symbols from open-error...
(gdb) b 11
Breakpoint 1 at 0x40117d: file ./open-error.c, line 11.
(gdb) run
Starting program: /tmp/examp/open-error
This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.fedoraproject.org/>
Enable debuginfod for this session? (y or [n]) n
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, main (argc=1, argv=0x7fffffffdb88) at ./open-error.c:11
11 if (fd < 0) /* Line 11: Set GDB breakpoint here. */
Missing debuginfo, try: dnf debuginfo-install glibc-2.39.9000-18.fc41.x86_64
Note that GDB has detected the fact that it's missing debugging information—it even provides a suggestion for installing it. But, for the time being, let's ignore that suggestion and attempt to print errno
:
(gdb) print errno
'errno' has unknown type; cast it to its declared type
Let's follow the suggestion and add a cast:
(gdb) print (int) errno
$1 = 2
So, for this case, when missing glibc debugging information, which provides the type of errno
, we can simply add a cast to print it out.
Another (better) way is to make sure that glibc-debuginfo
is installed. On Fedora, this can be done from the shell, like this:
$ sudo debuginfo-install glibc
[sudo] password for ...:
...
Installed:
glibc-debuginfo-2.39.9000-18.fc41.i686
glibc-debuginfo-2.39.9000-18.fc41.x86_64
Complete!
Once that is done, and GDB is restarted, and run using commands shown earlier, it'll be possible to print errno
without the cast:
(gdb) p errno
$1 = 2
Another way to cause GDB to load and use glibc debugging information is to enable debuginfod
. This can be done by answering y
to the question "Enable debuginfod
for this session (y or [n])". This will work even when the glibc-debuginfo
package is not installed on the system; GDB's debuginfod
support will cause any needed debugging information to be downloaded from a debuginfod
server.
Problems with finding thread-local storage
On modern systems, errno
is located in thread-local storage. This needs to be the case because if two threads both make a system call at roughly the same time, these threads need access to errno
without it being clobbered by the other thread. On GNU/Linux systems, errno
is placed in thread-local storage even in programs which don't use threads.
At the time that this article was written, on GNU/Linux, GDB finds addresses in thread-local storage by using a helper library named libthread_db.so
. If this library isn't available or if the program wasn't linked against a library containing libpthread
functionality, GDB won't be able to find thread-local storage, including errno
.
So why does GDB work for the example above? It's not multi-threaded and the compile line didn't include -lpthread
. Well, it turns out that ever since glibc-2.34, the bulk of libpthread
's functionality has been moved into libc.so
. This means that GDB is still able to use the helper library libthread_db.so
to find thread-local storage.
But, when using an older system, or even a current system using versions of glibc older than 2.34, problems with accessing errno
can arise. If the example program is built and run on Fedora 34 (which uses glibc-2.33), using a GDB built from upstream sources, attempting to print errno
will show:
(gdb) print errno
Cannot find thread-local storage for process 87818, shared library /lib64/libc.so.6:
Cannot find thread-local variables on this target
If you use the system GDB on Fedora 34 (or any other Fedora release before 41), this example will work as expected. The reason for this is that those versions of GDB were hacked to intercept errno
and rewrite it as *(*(int *(*)(void)) __errno_location) ()
. Here, __errno_location
is an internal function that returns the address of errno
for the current thread.
So, this provides us with a way to print errno
when there are problems with finding thread-local storage:
(gdb) print *(*(int *(*)(void)) __errno_location) ()
$1 = 2
That expression is cumbersome to type, but GDB's macro define
command may be used to make things easier:
(gdb) macro define errno *(*(int *(*)(void)) __errno_location) ()
(gdb) print errno
$3 = 2
Note that the macro define errno *(*(int *(*)(void)) __errno_location) ()
command can be placed in a .gdbinit
file to avoid having to enter it manually each time that GDB is used.
Statically linked programs
Even on recent Fedora releases (which all have glibc versions newer than 2.34), GDB will have trouble finding thread-local storage when the program is statically linked. For our example, this is accomplished by building the program as follows:
gcc -Wall -g -static -o open-error ./open-error.c
Then, when attempting to print out errno
, a similar message is printed as shown earlier:
(gdb) p errno
Cannot find thread-local storage for process 72156, executable file /tmp/examp/open-error:
Cannot find thread-local variables on this target
As a workaround, the macro-define trick works for this case too:
(gdb) macro define errno *(*(int *(*)(void)) __errno_location) ()
(gdb) print errno
$1 = 2
Macro debuginfo in executable, but missing glibc debuginfo
When the -g3
option is used with gcc
or clang
, the executable's debugging information will include information about preprocessor-defined macros. This can be demonstrated by building the example program as follows:
gcc -Wall -g3 -o open-error ./open-error.c
If we debug the program with GDB and run to a breakpoint placed on line 11, we will likely see the normal behavior in which errno
can be printed as shown earlier. But, if glibc's debugging info is missing and debuginfod
is disabled, we might see the following behavior instead:
(gdb) p errno
'__errno_location' has unknown return type; cast the call to its declared return type
Should this happen, it may be useful to look at how errno
is defined. This can be done using GDB's info macro
command:
(gdb) info macro errno
Defined at /usr/include/errno.h:38
included at /tmp/examp/./open-error.c:3
#define errno (*__errno_location ())
Here, errno
is defined to be a call to __errno_location
. The address obtained from that call is then dereferenced to provide the value of errno
. But, due to missing glibc debugging information, GDB doesn't know the type of __errno_location
.
This problem may be fixed by either providing glibc debugging information as shown earlier (either by installing glibc-debuginfo
package or by using debuginfod
to load it on demand), or by using the macro-define trick which was also shown earlier.
Info alert: Note
If the executable's debugging information is examined (via readelf -w
), it may be found that there should be sufficient type information provided for __errno_location
in order to make a call to __errno_location
without a cast. Unfortunately, GDB is ignoring this information. This is a bug in GDB.
Core file debugging
There may also be issues with printing errno
when debugging a core file. To help illustrate these problems, I'll make a core file from within GDB using a binary compiled with -g3
, which causes information about macros to be included in the executable's debugging information. Moreover, the machine in question does not have the glibc-debuginfo
package installed, nor have I enabled debuginfod
for this example.
As before, not shown below, I'll start GDB, and then run to line 11. After that, shown below, I'll make a core file using GDB's gcore
command:
(gdb) gcore errno.core
warning: Memory read failed for corefile section, 4096 bytes at 0xffffffffff600000.
Saved corefile errno.core
Then, starting GDB again, the core file can be debugged like this:
$ gdb -q open-error errno.core
Reading symbols from open-error...
[New LWP 2564]
This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.fedoraproject.org/>
Enable debuginfod for this session? (y or [n]) n
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `/tmp/examp/open-error'.
Program terminated with signal SIGTRAP, Trace/breakpoint trap.
#0 main (argc=1, argv=0x7fffffffdb88) at ./open-error.c:11
11 if (fd < 0) /* Line 11: Set GDB breakpoint here. */
Missing debuginfo, try: dnf debuginfo-install glibc-2.39.9000-18.fc41.x86_64
Now, let's try to print errno
:
(gdb) print errno
You can't do that without a process to debug.
GDB shows this message because it is actually trying to call __errno_location
, but this cannot be done without a running process. You could try removing the definition of errno
as shown below, but, as documented in the GDB manual, this won't work—GDB's macro undef
command only works to remove definitions of macros defined from within GDB.
(gdb) macro undef errno
(gdb) info macro errno
Defined at /usr/include/errno.h:38
included at /tmp/examp/./open-error.c:3
#define errno (*__errno_location ())
What you can do, however, is to define errno
as itself. Once that is done, print errno
will work, though a cast might still be needed:
(gdb) macro define errno errno
(gdb) print errno
'errno' has unknown type; cast it to its declared type
(gdb) print (int) errno
$1 = 2
Core files with a statically linked executable
A statically linked executable with debugging information containing macro names and their expansions may be created using this command:
gcc -Wall -g3 -static -o open-error ./open-error.c
If a core file is created as shown in the previous section, attempting to debug this core file might look like this:
$ gdb -q -iex 'set debuginfod enabled off' open-error errno.core
Reading symbols from open-error...
[New LWP 2970]
Core was generated by `/tmp/examp/open-error'.
Program terminated with signal SIGTRAP, Trace/breakpoint trap.
#0 main (argc=1, argv=0x7fffffffdb88) at ./open-error.c:11
11 if (fd < 0) /* Line 11: Set GDB breakpoint here. */
(gdb) p errno
You can't do that without a process to debug.
(gdb) macro define errno errno
(gdb) p (int) errno
Cannot find thread-local storage for LWP 2970, executable file /tmp/examp/open-error:
Cannot find thread-local variables on this target
When we had a running process, the trick that we used to get around this problem of not being able to find thread-local storage was to call __errno_location
and then dereference the result. But that won't work here due to the fact that we're debugging a core file, not a running process. To the best of my knowledge, there is no way to print errno
for this scenario.
Summary
This article discussed scenarios in which doing print errno
when debugging with GDB doesn't work.
GDB has a better chance of accessing errno
when glibc debugging information is available to GDB. It can either be installed via a suitable command, such as sudo debuginfo-install glibc
, or GDB can instead load it on demand using debuginfod
. In order to do the latter, add the following line to your .gdbinit
file:
set debuginfod enabled on
If GDB doesn't know the type of errno
, it may be possible to print it by adding a cast, i.e. print (int) errno
.
When debugging a running process, but GDB can't find thread-local storage associated with errno
, try defining errno
as a macro within GDB, like this:
macro define errno *(*(int *(*)(void)) __errno_location) ()
When debugging a core file, it may be necessary to disable a macro defining errno
like this:
macro define errno errno
Finally, there is at least one scenario in which printing errno
is simply not possible.