|

strace linux debugging: EADDRINUSE y permission denied

En pocas palabras: strace intercepta cada syscall de un proceso en Linux y muestra argumentos, valor de retorno y errno exacto. Así detecta EADDRINUSE en bind() (puerto 8080 ocupado) o EACCES en openat() (permisos insuficientes), sin reiniciar el servicio ni modificar el código, actuando directamente en la frontera con el kernel.

strace es una herramienta de línea de comandos para Linux que intercepta y registra cada syscall que hace un proceso, con sus argumentos y su valor de retorno. La usan developers y sysadmins para diagnosticar errores como EADDRINUSE, EACCES o EMFILE justo en el límite entre el programa y el kernel, según detalla la guía técnica de Code Beneath.

En 30 segundos

  • strace intercepta cada syscall de un proceso y muestra argumentos, valor de retorno y errno exacto, sin modificar código.
  • Se instala con un solo comando: apt-get install strace en Debian/Ubuntu, yum install strace en RHEL/CentOS.
  • strace -p PID se pega a un proceso corriendo en producción sin necesidad de reiniciarlo.
  • El flag -e trace=network,file corta el ruido y deja solo las syscalls relevantes al problema.
  • Un desbalance openat/close confirma un leak: en el caso de la fuente, 1001 openat contra 2 close antes de un EMFILE.

¿Qué es strace y para qué sirve en el strace linux debugging?

strace intercepta y loguea cada syscall que hace tu programa, junto con los argumentos exactos y el valor de retorno del kernel. Cuando bind() falla con EADDRINUSE, strace muestra el estado del socket y la dirección. Cuando open() devuelve permission denied, ves los flags y los bits de modo que se intentaron usar.

El límite del kernel es donde viven la mayoría de los fallos reales. Una app en Java puede reportar “cannot listen on port 8080”, pero eso es un wrapper sobre un bind() que falló. El net package de Go dice “address already in use”, pero el error de verdad viene del kernel devolviendo EADDRINUSE. strace sacó esa capa de abstracción del medio (que, seamos honestos, suele esconder más de lo que explica).

¿Cómo instalo strace en Ubuntu o Debian?

En Debian o Ubuntu instalás strace con apt-get install strace. En RHEL o CentOS usás yum install strace. Después corrés cualquier comando con el prefijo strace, por ejemplo: strace -o trace.log ls /tmp. El flag -o escribe la salida a un archivo en vez de mandarla a stderr.

Para un proceso que ya está corriendo, te pegás con strace -p 1234 -o trace.log. Ahí vas a ver cientos de líneas. Para no perderte, filtrás por tipo de syscall con -e: strace -e openat,open,read,write ls /tmp. Ojo con un detalle: en kernels modernos las operaciones de archivo usan openat en vez de open, así que si filtrás mal te quedás sin nada en la salida. Te puede servir nuestra cobertura de los fundamentales de Linux para DevOps.

¿Qué significa el error EADDRINUSE en Linux?

EADDRINUSE significa que bind() devolvió -1 porque otro proceso ya tiene reservado ese puerto, o porque el socket anterior quedó en estado TIME_WAIT. Lo ves clarito con strace apuntando a un servicio Go que intenta bindear el puerto 8080.

Tomá este caso real: un servicio Go levanta un listener en 127.0.0.1:8080 con net.Listen. Al reiniciarlo, revienta con “address already in use”. Corriendo strace -e socket,bind,listen sobre el proceso, la traza muestra socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3, y después bind(3, {sin_family=AF_INET, sin_port=htons(8080), sin_addr=inet_aton(“127.0.0.1”)}, 16) = -1 EADDRINUSE (Address already in use). El file descriptor del socket es 3, el puerto va codificado en network byte order gracias a htons, y el -1 con errno EADDRINUSE te dice que otro proceso tiene el puerto tomado.

Para encontrar quién lo tiene, corrés netstat -tulnp | grep 8080 o ss -tulnp | grep 8080, y también sirve sudo lsof -i :8080. Pero fijate que strace ya te dijo qué syscall falló y por qué, antes de que tuvieras que correr nada más. Si el error hubiera sido EACCES en lugar de EADDRINUSE, el dato cambia por completo: significaría que el puerto está por debajo de 1024 y necesitás privilegios de root para bindearlo. Para ver la secuencia completa de red (socket, bind, listen, accept, shutdown, close), agregás -e trace=network,desc, que además incluye dup, dup2, dup3, fcntl y close, clave para encontrar leaks de file descriptors.

Si este servicio corre en un VPS de producción, el mismo trace lo podés hacer en caliente con strace -p sin bajar el proceso.

Ejemplo hipotético: imaginá un API en Node.js detrás de un load balancer que, después de un deploy, no termina de levantar en el nodo nuevo. Un primer strace -e trace=network sobre el proceso que falla mostraría la secuencia socket() seguida de un bind(). Si el errno devuelto fuera EADDRINUSE, la lectura razonable es que el proceso anterior no liberó el puerto a tiempo, algo típico cuando el script de deploy no espera un graceful shutdown real antes de lanzar la nueva instancia. Si en cambio el mismo bind() devolviera EACCES, la causa ya no sería “el otro proceso”, sino que el contenedor nuevo corre con un usuario sin capability para bindear puertos bajo 1024, un problema frecuente al migrar de bare metal a Docker sin ajustar esas capabilities. Mismo síntoma hacia afuera (“el deploy no levanta”), dos errno distintos y dos soluciones completamente distintas: ahí es donde strace deja de ser un comando de diagnóstico y pasa a ser el criterio para no arreglar lo que no está roto.

¿Cómo uso strace para ver por qué un archivo da permission denied?

Filtrás openat con strace -e openat y el programa que falla, y la línea de salida muestra el path exacto, los flags usados y el errno EACCES devuelto por el kernel. Esto te confirma que el archivo existe, pero que no tenés permiso de lectura. Lo explicamos a fondo en entender cómo funciona el kernel de Linux.

Ponele que tenés un programa en C que intenta leer /etc/shadow con fopen. Lo compilás, lo corrés con strace -e openat ./shadow, y la salida dice: openat(AT_FDCWD, “/etc/shadow”, O_RDONLY) = -1 EACCES (Permission denied). AT_FDCWD significa “usá el directorio actual como base”, después va el path, y los flags indican que se pidió solo lectura. El retorno -1 con EACCES te dice que el archivo existe pero no tenés permiso.

Chequeás los permisos con ls -la /etc/shadow. Típicamente aparece como -rw-r—– (modo 640), con dueño root:shadow. Si corrés como usuario normal, te comés el EACCES. Si corrés con sudo strace -e openat ./shadow, la cosa cambia: openat devuelve un file descriptor válido (casi siempre 3 o 4), el syscall tuvo éxito, y después vienen los read o mmap para traer los datos a memoria. ¿Necesitabas revisar permisos antes con ls? No, strace ya te lo mostró con el flag, el path y el errno exactos, sin que tengas que adivinar nada.

¿Cómo detecto con strace que un proceso no cierra file descriptors?

Filtrás strace por openat y close, y contás las ocurrencias de cada una con un pipe a awk. Si el número de openat supera por mucho al de close, tenés un leak confirmado a nivel de syscall, antes de que explote con “too many open files”.

El caso de la fuente es un script en Python que abre /etc/hostname mil veces en un loop sin cerrar el file handle. Corriendo strace -e openat,close python3 leak.py 2>&1 | tail -50, se ve cómo los file descriptors suben de a uno (3, 4, 5…) sin que aparezca ningún close() en el medio. Cuando se llega al límite (1024 por default, ajustable con ulimit -n), el siguiente openat falla con EMFILE.

Para confirmarlo sin tener que leer línea por línea, corrés: strace -e openat,close python3 leak.py 2>&1 | grep -E ‘^(openat|close)’ | awk ‘{print $1}’ | sort | uniq -c. El resultado en este caso fue 2 close contra 1001 openat. Un desbalance así de brutal no deja lugar a dudas (spoiler: el fix fue agregar f.close() después de leer el archivo, y listo). En revisar qué puertos están abiertos en tu sistema profundizamos sobre esto.

¿Qué criterio práctico ayuda a elegir el filtro correcto en strace?

Antes de tirar strace a ciegas sobre todo el proceso, conviene arrancar desde el síntoma hacia el filtro, no al revés. Estos son los cruces más comunes entre lo que ves afuera y por dónde empezar a mirar adentro:

  • El proceso no arranca o tira “address already in use”: empezá con -e trace=network y mirá directamente la línea de bind().
  • Un archivo o un socket da “permission denied”: empezá con -e trace=file o directamente -e openat, y fijate en el errno sobre esa línea puntual (EACCES no es lo mismo que ENOENT, aunque el mensaje de la app a veces los mezcle).
  • Hay degradación lenta o “too many open files” después de horas corriendo: empezá con -e openat,close y contá con sort | uniq -c antes de asumir nada.
  • No tenés ni idea de por dónde arrancar: corré -c primero. El resumen de syscalls por cantidad y tiempo te dice qué grupo (red, archivo, proceso) domina el trace, y recién ahí decidís el -e.

La lógica de fondo es simple: el síntoma que reporta la aplicación (un mensaje de error en el log, un timeout, un crash) casi nunca coincide uno a uno con la causa real. strace no reemplaza ese razonamiento, pero achica el espacio de hipótesis a lo que el kernel efectivamente devolvió.

¿Cómo filtro la salida de strace para no ver tanto ruido?

Filtrás con el flag -e seguido de trace= y el grupo de syscalls que te interesa: file, network, process o signal. Esto corta de raíz el ruido que genera la inicialización de libc, que por sí sola ya tira decenas de llamadas a mmap, mprotect y brk.

  • -e trace=file: muestra solo operaciones de archivo como openat, readv, write, stat y unlink.
  • -e trace=network: deja ver únicamente socket, bind, listen, accept, send y recv.
  • -e trace=process: filtra control de procesos: fork, exec, exit, wait.
  • -s 200: muestra hasta 200 bytes del buffer real de read o write, útil para ver requests HTTP completos.
  • -f: sigue procesos hijos creados con fork o clone, algo que sin este flag se pierde por completo.
  • -c: tira un resumen de cuántas veces se llamó cada syscall y cuánto tiempo consumió, ideal como primer paso antes de filtrar en detalle.

El tema es que, en un trace de 10.000 líneas, buscar a mano no sirve de nada. Ahí conviene grepear directo el archivo de salida con algo como grep -E ‘(bind|EADD|EACCES|EMFILE)’ server.log, y encontrás la línea exacta del fallo en segundos.

¿Qué significan los errno más comunes en strace?

Cada errno le dice algo distinto al kernel sobre por qué falló la syscall. strace traduce el número a su nombre automáticamente, pero conviene saber qué dispara cada uno.

ErrnoSyscall típicaCausa común
EADDRINUSEbindPuerto ya ocupado o en estado TIME_WAIT
EACCESopenat, bindPermisos insuficientes (modo de archivo, puerto menor a 1024)
ENOENTopenat, unlink, statEl archivo o directorio no existe
EMFILEopenat, socket, acceptSe llegó al límite de file descriptors del proceso
ECONNREFUSEDconnectNo hay nadie escuchando en esa dirección y puerto
EAGAINread, write, acceptRecurso no disponible por el momento (modo no bloqueante)
strace linux debugging diagrama explicativo

Errores comunes al usar strace

  • Olvidarse de redirigir stderr: strace escribe a stderr por default. Si redirigís stdout con >, la salida de strace se mezcla con la del programa. Usá 2> archivo o directamente -o archivo.
  • No filtrar syscalls en traces grandes: correr strace sin -e sobre un servidor web genera gigabytes de log en segundos. Arrancá amplio (trace=network,file) y después achicá a syscalls puntuales (socket, bind, accept).
  • Confundir valores de retorno: 0 es éxito, -1 es error con errno en la misma línea, y un número positivo en socket u openat es un file descriptor válido. No es tan raro leer mal esto a las tres de la mañana.
  • Correr sin root contra daemons del sistema: algunas syscalls necesitan privilegio elevado para observarse. Si te tira “Operation not permitted”, la solución es sudo strace, no insistir sin permisos.
  • Asumir que el orden de las syscalls es siempre literal: operaciones async, threads y timers pueden reordenar la salida. Mirá siempre el contexto completo: qué file descriptor, qué dirección, qué flags.

Preguntas Frecuentes

¿Necesito instalarlo distinto en RHEL que en Ubuntu?

Solo cambia el gestor de paquetes: apt-get install strace en Debian/Ubuntu, yum install strace en RHEL/CentOS. El binario y los flags son idénticos en ambas familias, así que una vez instalado no hay diferencia de uso.

¿Puedo usar strace sin reiniciar el servicio que falla?

Sí, para eso existe -p PID: te pegás a un proceso que ya está corriendo en producción, mirás el trace en vivo y te desconectás con Ctrl+C sin matarlo. Complementá con configurar tu entorno de trabajo en la terminal.

¿Qué diferencia hay entre EADDRINUSE y EACCES al bindear un puerto?

EADDRINUSE indica que otro proceso ya tiene el puerto, o quedó en TIME_WAIT. EACCES en bind, en cambio, suele significar que el puerto está por debajo de 1024 y el proceso no tiene privilegios suficientes para abrirlo. Son dos problemas distintos con soluciones distintas, aunque desde afuera el mensaje de la app a veces se parezca.

¿Alcanza con contar openat y close para confirmar un leak de file descriptors?

Es un buen primer indicio, no una prueba absoluta: un desbalance grande entre openat y close es una señal fuerte, pero conviene revisar también si algunos de esos openat corresponden a sockets o pipes que se cierran con close() desde otra ruta de código antes de concluir dónde está el leak exacto.

¿strace sirve para diagnosticar un segfault?

Muestra las syscalls hasta el momento del crash, lo que ayuda a ver qué estaba haciendo el proceso justo antes. Pero no reemplaza a un debugger como gdb para inspeccionar el estado de memoria en el punto exacto de la falla.

Conclusión

strace sigue siendo la herramienta más directa para resolver fallos de syscalls en Linux sin tocar código ni instalar nada extra en el binario. Los tres casos de la fuente (EADDRINUSE en un bind, EACCES en un openat, y un leak de file descriptors en Python) muestran lo mismo: el error que ve tu aplicación es casi siempre un wrapper de lo que el kernel ya te dijo con todas las letras.

Lo que no te dice strace es la causa de negocio detrás del fallo: por qué ese puerto quedó ocupado, o por qué nadie revisó el code review que dejó el file handle abierto. Para eso hace falta criterio y, muchas veces, revisar el deploy anterior. Un criterio práctico para empezar cualquier diagnóstico: corré primero strace -c para ver qué syscalls dominan el trace, después filtrá con -e trace= al grupo sospechoso, y recién ahí bajá a syscalls puntuales con grep sobre el log. Esto no reemplaza el debugger, pero te ahorra las primeras horas de adivinar a ciegas.

Fuentes

Te puede interesar...