1. Overview

There may be several symbols and debugging information in binaries. This information isn’t required for the correct execution of an executable. Stripping them from the binary leads to a smaller binary size, which may be crucial, especially in embedded systems.

In this tutorial, we’ll discuss how to strip the symbols and debugging information from binaries.

2. Using the strip Command

We’ll use the following C program, hello_world.c:

#include <stdio.h>

void main()
{ 
    printf("Hello World\n"); 
}

The program prints Hello World and then exits.

Now, we’ll generate the executable file, hello_world, using gcc:

$ gcc -o hello_world hello_world.c

Let’s check the operation of the generated executable file, hello_world:

$ ./hello_world
Hello World

The program runs as expected. Now, we’ll examine the generated executable by listing the symbols in the executable using the nm command:

$ nm hello_world
00000000004004d5 t .annobin__dl_relocate_static_pie.end
00000000004004d0 t .annobin__dl_relocate_static_pie.start
00000000004005a0 t .annobin_elf_init.c
0000000000400615 t .annobin_elf_init.c_end
00000000004004a0 t .annobin_elf_init.c_end.exit
00000000004004a0 t .annobin_elf_init.c_end.hot
00000000004004a0 t .annobin_elf_init.c_end.startup
00000000004004a0 t .annobin_elf_init.c_end.unlikely
00000000004004a0 t .annobin_elf_init.c.exit
00000000004004a0 t .annobin_elf_init.c.hot
00000000004004a0 t .annobin_elf_init.c.startup
00000000004004a0 t .annobin_elf_init.c.unlikely
00000000004004cf t .annobin_init.c
00000000004004cf t .annobin_init.c_end
00000000004004a0 t .annobin_init.c_end.exit
00000000004004a0 t .annobin_init.c_end.hot
00000000004004a0 t .annobin_init.c_end.startup
00000000004004a0 t .annobin_init.c_end.unlikely
00000000004004a0 t .annobin_init.c.exit
00000000004004a0 t .annobin_init.c.hot
00000000004004a0 t .annobin_init.c.startup
00000000004004a0 t .annobin_init.c.unlikely
0000000000400615 t .annobin___libc_csu_fini.end
0000000000400605 t .annobin___libc_csu_fini.start
0000000000400605 t .annobin___libc_csu_init.end
00000000004005a0 t .annobin___libc_csu_init.startno 
00000000004004d0 t .annobin_static_reloc.c
00000000004004d5 t .annobin_static_reloc.c_end
00000000004004a0 t .annobin_static_reloc.c_end.exit
00000000004004a0 t .annobin_static_reloc.c_end.hot
00000000004004a0 t .annobin_static_reloc.c_end.startup
00000000004004a0 t .annobin_static_reloc.c_end.unlikely
00000000004004a0 t .annobin_static_reloc.c.exit
00000000004004a0 t .annobin_static_reloc.c.hot
00000000004004a0 t .annobin_static_reloc.c.startup
00000000004004a0 t .annobin_static_reloc.c.unlikely
0000000000601024 B __bss_start
0000000000601024 b completed.7295
0000000000601020 D __data_start
0000000000601020 W data_start
00000000004004e0 t deregister_tm_clones
00000000004004d0 T _dl_relocate_static_pie
0000000000400550 t __do_global_dtors_aux
0000000000600e08 t __do_global_dtors_aux_fini_array_entry
0000000000400630 R __dso_handle
0000000000600e10 d _DYNAMIC
0000000000601024 D _edata
0000000000601028 B _end
0000000000400618 T _fini
0000000000400580 t frame_dummy
0000000000600e00 t __frame_dummy_init_array_entry
0000000000400764 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000400644 r __GNU_EH_FRAME_HDR
0000000000400460 T _init
0000000000600e08 t __init_array_end
0000000000600e00 t __init_array_start
0000000000400628 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000400610 T __libc_csu_fini
00000000004005a0 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
0000000000400586 T main
                 U puts@@GLIBC_2.2.5
0000000000400510 t register_tm_clones
00000000004004a0 T _start
0000000000601028 D __TMC_END__

As is apparent from the output of nm, there are many symbols even in an executable generated from a simple source code that just prints Hello World.

We can use the strip command to remove the symbols in an already generated binary:

$ strip hello_world –o stripped_hello_world
$ nm stripped_hello_world
nm: stripped_hello_world: no symbols 

We passed the name of the binary, hello_world, as an argument to strip. The -o option of strip specifies the name of the stripped output file, which is stripped_hello_world in our example. As the output of the nm command shows, there are no symbols in the stripped binary. Therefore, we were successful in removing all of the symbols in an executable using strip.

If we don’t use the -o option, by default, strip overwrites the existing file:

$ strip hello_world
$ nm hello_world
nm: hello_world: no symbols 

We can use the –strip-all option instead of -s. They’re the same. There are also other options of strip. For example, the –strip-debug option removes only the debugging information, whereas the –strip-symbol option removes the specified symbol. The debugging information in a binary can be produced using the -g option of gcc.

3. Using the -s Option of gcc

The strip command is useful for removing the symbols in an already generated binary. However, we can also generate a stripped binary while building it. We can use the -s option gcc for this purpose:

$ gcc –s –o hello_world hello_world.c
$ nm hello_world
nm: hello_world: no symbols 

The -s option removes all of the symbols and relocation information from the executable. As the output of running the nm hello_world command shows, the built executable has no symbols. Therefore, with this approach, we don’t need to use the strip command.

4. Conclusion

In this article, we discussed how to strip binaries. Firstly, we learned that we could remove the symbols and debugging information from an existing binary using the strip command. Then, we saw that it’s also possible to generate a stripped binary using the -s option of gcc.