Why your errno value isn't printing in GDB—and what to do about it | Red Hat Developer (2024)

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>intmain (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-erroropen 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-errorReading symbols from open-error...(gdb) break 11Breakpoint 1 at 0x40117d: file ./open-error.c, line 11.(gdb) 

Let's run the program, answering y when asked whether to enable debuginfod:

(gdb) runStarting program: /tmp/examp/open-errorThis GDB supports auto-downloading debuginfo from the following URLs: <https://debuginfod.fedoraproject.org/>Enable debuginfod for this session? (y or [n]) yDebuginfod 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:1111 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-errorReading symbols from open-error...(gdb) b 11Breakpoint 1 at 0x40117d: file ./open-error.c, line 11.(gdb) runStarting program: /tmp/examp/open-errorThis GDB supports auto-downloading debuginfo from the following URLs: <https://debuginfod.fedoraproject.org/>Enable debuginfod for this session? (y or [n]) nDebuginfod 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:1111 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 errnoCannot 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 errnoCannot 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 errnoDefined 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.corewarning: 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.coreReading 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]) nDebuginfod 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:1111 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 errnoYou 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 errnoDefined 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.coreReading 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:1111 if (fd < 0)/* Line 11: Set GDB breakpoint here. */(gdb) p errnoYou can't do that without a process to debug.(gdb) macro define errno errno(gdb) p (int) errnoCannot 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.

Why your errno value isn't printing in GDB—and what to do about it | Red Hat Developer (2024)
Top Articles
Latest Posts
Article information

Author: Laurine Ryan

Last Updated:

Views: 5731

Rating: 4.7 / 5 (77 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Laurine Ryan

Birthday: 1994-12-23

Address: Suite 751 871 Lissette Throughway, West Kittie, NH 41603

Phone: +2366831109631

Job: Sales Producer

Hobby: Creative writing, Motor sports, Do it yourself, Skateboarding, Coffee roasting, Calligraphy, Stand-up comedy

Introduction: My name is Laurine Ryan, I am a adorable, fair, graceful, spotless, gorgeous, homely, cooperative person who loves writing and wants to share my knowledge and understanding with you.