Cuando se compila un fuente con gcc, éste genera muchos símbolos de debug. Se trata de información acerca de qué porción de código en lenguaje de alto nivel (en este caso C) implementa qué sección de código máquina en el binario ELF. Los símbolos además identifican dónde (en qué dirección) se ubican las diferentes funciones y variables del programa, a través de una tabla, la cual es utilizada por el linker para relocar el código cuando se enlaza junto con otros archivos objeto.

Los símbolos aumentan considerablemente el tamaño de los binarios, pues agregan gran cantidad de información que no es código ejecutable, sino que es meta-información acerca del código binario, a ser interpretada eventualmente por el debugger y/o linker. Sin embargo, cuando compilamos un ejecutable para correr en un sistema en producción, difícilmente necesitemos recurrir al debugger (tarea más común en un desarrollador, mantenedor, auditor, investigador o experto en seguridad informática) o al linker (para enlazado del ejecutable). Por lo cual, eliminando los símbolos reduciremos el tamaño final del archivo ejecutable. Sin embargo, los símbolos no aumentan el consumo de memoria, pues no son cargados por el sistema operativo al lanzar un programa, ya que se encuentran en secciones de memoria non-allocable que no son necesarias para la ejecución del programa (sólo se cargan en memoria las secciones allocable).



A modo de ejemplo, voy a utilizar el ejecutable del servidor Web Nginx, compilado desde los fuentes en un sistema Debian.

El programa nginx se encuentra en la ruta /sbin/nginx:

root@linuxito:~# which nginx
/sbin/nginx

Que a su vez es un enlace simbólico al archivo /usr/local/nginx/sbin/nginx:

root@linuxito:~# ll /sbin/nginx
lrwxrwxrwx 1 root root 27 Aug 22  2015 /sbin/nginx -> /usr/local/nginx/sbin/nginx

file, ldd

Como se trata de un servidor Linux, este archivo binario tiene formato ELF:

root@linuxito:~# file /usr/local/nginx/sbin/nginx
/usr/local/nginx/sbin/nginx: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0xb0857251b516f4b30094bc529ff33215ae959512, not stripped

Y está enlazado dinámicamente con las siguientes librerías:

root@linuxito:~# ldd /usr/local/nginx/sbin/nginx
        linux-vdso.so.1 =>  (0x00007ffecf3ee000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fcc3ff21000)
        libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007fcc3fcea000)
        libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007fcc3faac000)
        libssl.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007fcc3f84d000)
        libcrypto.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (0x00007fcc3f454000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fcc3f24f000)
        libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fcc3f038000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcc3ecab000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fcc40144000)

Sin embargo, lo más interesante en la salida de file es el último mensaje:

not stripped

La frase "not stripped" indica que el binario posee símbolos.

readelf

En Linux, es posible obtener información acerca de los archivos ELF utilizando la herramienta readelf. Con la opción -s se vuelcan las tablas de símbolos:

root@linuxito:~# readelf -s /usr/local/nginx/sbin/nginx | grep "Symbol table"
Symbol table '.dynsym' contains 322 entries:
Symbol table '.symtab' contains 2195 entries:

Notar que el binario posee las tablas de símbolos .dynsym y .symtab, con más de 2500 entradas entre ambas. La tabla .dynsym es la que utiliza el linker dinámico para el enlazado dinámico con librearías compartidas, y sólo contiene símbolos globales (es una versión mucho más reducida de la tabla .symtab).

Toda esta información agrega un tamaño importante al binario final:

root@linuxito:~# ls -l /usr/local/nginx/sbin/nginx
-rwxr-xr-x 1 root staff 5544047 Oct  1  2015 /usr/local/nginx/sbin/nginx

Se observa que el binario ocupa unos 5,3 MB.

strip

Si no deseamos enlazar el binario nginx desde otro programa, y tampoco deseamos utilizar el debugger para buscar o detectar errores en el código, es probable que nos interese eliminar los símbolos del binario para reducir su tamaño.

Incluso esta configuración es adoptada por defecto por muchas aplicaciones, por ejemplo MySQL:

root@linuxito:~# which mysqld
/usr/sbin/mysqld
root@linuxito:~# file /usr/sbin/mysqld
/usr/sbin/mysqld: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0xdf1206c84556942cfde0df876895d48f7ba2febd, stripped

Notar la descripción "stripped" al final de la salida de file que indica que el binario del servidor MySQL no posee símbolos (no puede ser enlazado dinámicamente ni debuggeado).

En sistemas Linux, la herramienta strip permite descartar todos los símbolos de un binario.

Al ejecutarla indicando un archivo como parámetro, strip elimina todos los símbolos, sobrescribiendo el archivo:

root@linuxito:~# strip /usr/local/nginx/sbin/nginx

Luego de ejecutar strip, el tamaño del ejecutable del servidor Web Nginx se reduce a poco más de 600 KB. Se ha reducido el tamaño del binario más de un 87%:

root@linuxito:~# ls -l /usr/local/nginx/sbin/nginx
-rwxr-xr-x 1 root staff 684320 Oct 20 09:57 /usr/local/nginx/sbin/nginx

Ahora file indica que se trata de un binario ELF "stripped", sin símbolos:

root@linuxito:~# file /usr/local/nginx/sbin/nginx
/usr/local/nginx/sbin/nginx: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0xb0857251b516f4b30094bc529ff33215ae959512, stripped

En realidad se trata de un binario ELF sin tabla .symtab, pues la tabla .dynsym (lógicamente) no puede ser eliminada del ejecutable (salvo que se trate de un binario enlazado estáticamente, el cual directamente no tendrá una tabla .dynsym), de lo contrario no funcionaría:

root@linuxito:~# readelf -s /usr/local/nginx/sbin/nginx | grep Symbol
Symbol table '.dynsym' contains 322 entries:

Notar que el binario continúa funcionando:

root@linuxito:~# nginx -V
nginx version: nginx/1.8.0
built by gcc 4.7.2 (Debian 4.7.2-5) 
built with OpenSSL 1.0.1e 11 Feb 2013
TLS SNI support enabled
configure arguments: --with-http_ssl_module

gcc

Para finalizar, si se desea compilar con gcc un binario enlazado estáticamente, se debe utilizar la opción -static. Esto crea un ejecutable sin tabla .dynsym, ya que no se requiere enlazar librerías compartidas, aunque con tabla .symtab (que puede luego ser "destripada").

Claro está que un binario enlazado estáticamente ve su tamaño incrementado pues incluye en un único ejecutable todas las librerías de las cuales depende.

Enlazado dinámicamente:

root@linuxito:~# gcc -o cuadrado cuadrado.c -lm
root@linuxito:~# file cuadrado
cuadrado: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0x7e22622016904fa71dfb3e6fdd1218f5a5acda12, not stripped
root@linuxito:~# readelf -s cuadrado | grep Symbol
Symbol table '.dynsym' contains 8 entries:
Symbol table '.symtab' contains 69 entries:
root@linuxito:~# ls -lh cuadrado
-rwxr-xr-x 1 root root 7.3K Oct 20 12:06 cuadrado

Enlazado estáticamente:

root@linuxito:~# gcc -static -o cuadrado cuadrado.c -lm
root@linuxito:~# file cuadrado
cuadrado: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.26, BuildID[sha1]=0x372e4dc0b26d8c69e2d9b1ab61beadf82cfccad9, not stripped
root@linuxito:~# readelf -s cuadrado | grep Symbol
Symbol table '.symtab' contains 2103 entries:
root@linuxito:~# ls -lh cuadrado
-rwxr-xr-x 1 root root 858K Oct 20 12:07 cuadrado

Referencias

Para entender más en profundidad el funcionamiento y significado de las tablas de símbolos de un binario ELF, recomiendo la siguiente lectura: Inside ELF Symbol Tables

man which
man file
man ldd
man readelf
man strip


Tal vez pueda interesarte


Compartí este artículo