How to Create Executable Files and Produce Debug Information

Written by mvuksano | Published 2020/11/19
Tech Story Tags: linux | debugging | programming | software-development | binary | coding | executable-file | debug | web-monetization

TLDR A number of times when I wanted to understand what went wrong with a program but debug information was not available. Without debug information it is nearly impossible to make sense of what happened. In this short article I will show you how to create an executable file as well as produce debug information but in a completely separate file. In a future post, I'll talk more about how to do understand failures in production. To truly understand failure of a system you will need to inspect that system to understand failure.via the TL;DR App

In this short article I will show you how to create an executable file as well as produce debug information but in a completely separate file.
There has been a number of times when I wanted to understand what went wrong with a program but debug information was not available. Without debug information it is nearly impossible to make sense of what happened. This holds true especially for larger programs.
Today we will work with something super simple:
int factorial(int n) {
  if(n==1) {
    return 1;
  }
  else {
    return n * factorial(n-1);
  }
}

int main(int argc, char **argv) {
  int x = factorial(5);
  switch(x) {
    case 120:
      return 0;
    default:
      return -1;
  }

  return -1;
}
Save this into a file called
main.c
.
We can now use c compiler to produce an executable:
gcc -o fact main.c
This gives us a binary file, but unfortunatelly there's no debug information in it. If we run a program like this in production and for whatever reason need to debug this program to understand what's wrong we're out of luck. I understand that some of you may say - "hey, you should never debug programs in production" but there will be situations where that's the only way.
Ideally, programs should be so good by the time they go to production that they don't fail. In a future post, I'll talk more about how to do understand failures in production. For now, you will need to trust me that to truly understand failure of a system you will need to inspect that system.
Now let's compile this program with debug info:
gcc -g -Og -o fact-debug main
Now we can use
eu-readelf 
(or
readelf
) to look at section headers in two binaries . With a bit of
sed
and
readelf
wizardry you will notice that debug version of the binary has a few more sections:
debug_aranges
,
debug_info
,
debug_abbrev
,
debug_line
and
debug_str
.
If you try using
gdb
to step through
fact
binary (one that doesn't include debug info) you will notice that there's no source code that you can use to step through. You can step through the program and understand what's happening from disassembly but it will be hard to make sense. On the other hand, if you use gdb to run fact-debug binary (one that includes debug information) you will notice that you can see source code while stepping through.
Now let's extract debug information from fact-debug binary and put it in another file. That can be done using
obj-copy
and
eu-strip
command:
objcopy --only-keep-debug fact-debug fact-debug-info
eu-strip -g fact-debug
Now you have the best of both worlds - an executable file that does not include debug information as well as access to debug information if the need arises.
As a teaser to my next article, I encourage you to check out
build-id
associated with the executable. It can be retrieved using
eu-readelf -n fact-debug
.
I'll show you how to put it to good use using the latest
gdb
.

Written by mvuksano | PSS - Pragmatic problem solver @ Facebook
Published by HackerNoon on 2020/11/19