c gcc shared-libraries static-libraries

¿Por qué GCC crea un objeto compartido en lugar de un binario ejecutable de acuerdo con el archivo?



shared-libraries static-libraries (2)

Tengo una biblioteca que estoy construyendo. Todos mis objetos se compilan y enlazan sucesivamente cuando ejecuto cualquiera de los siguientes: ar rcs lib/libryftts.a $^

gcc -shared $^ -o lib/libryftts.so

en mi Makefile También puedo instalarlos con éxito en /usr/local/lib Cuando /usr/local/lib el archivo con nm, todas las funciones están ahí. Mi problema es que cuando ejecuto gcc testing/test.c -lryftts -o test && file ./test o gcc testing/test.c lib/libryftts.a -o test && file ./test dice:

test: ELF 64-bit LSB shared object lugar de test: ELF 64-bit LSB executable como era de esperar. ¿Qué estoy haciendo mal?


¿Qué estoy haciendo mal?

Nada.

Parece que su GCC está configurado para construir binarios -pie de forma predeterminada. Estos archivos binarios realmente son bibliotecas compartidas (del tipo ET_DYN ), excepto que se ejecutan como lo haría un ejecutable normal.

Por lo tanto, solo debe ejecutar su binario y (si funciona) no preocuparse por eso.

O podría vincular su binario con gcc -no-pie ... y eso debería producir un ejecutable que no sea PIE del tipo ET_EXEC , para cuyo file dirá ELF 64-bit LSB executable .


file 5.36 lo dice claramente

file 5.36 realmente lo imprime claramente si el ejecutable es PIE o no, como se muestra en: https://unix.stackexchange.com/questions/89211/how-to-test-whether-a-linux-binary-was-compiled-as-position-independent-code/435038#435038

Por ejemplo, un ejecutable PIE se muestra como:

main.out: ELF 64-bit LSB pie ejecutable, x86-64, versión 1 (SYSV), vinculado dinámicamente, no eliminado

y no PIE como:

main.out: ELF ejecutable LSB de 64 bits, x86-64, versión 1 (SYSV), enlazado estáticamente, no eliminado

La función se introdujo en 5.33, pero solo realizó una simple verificación chmod +x . Antes de eso, acaba de imprimir shared object para PIE.

En 5.34, estaba destinado a comenzar a verificar los metadatos más especializados DF_1_PIE ELF, pero debido a un error en la implementación en commit 9109a696f3289ba00eaa222fd432755ec4287e28 en realidad rompió cosas y mostró los ejecutables de GCC PIE como shared objects .

El error se corrigió en 5.36 en commit 03084b161cf888b5286dbbcd964c31ccad4f64d9 .

El error está presente en particular en Ubuntu 18.10 que tiene el file 5.34.

No se manifiesta al vincular el código de ensamblaje con ld -pie debido a una coincidencia.

El desglose del código fuente se muestra en la sección "análisis del código fuente del file 5.36" de esta respuesta.

El kernel 5.0 de Linux determina si ASLR se puede usar en base a ET_DYN

La causa raíz de la "confusión" del file es que tanto los archivos ejecutables PIE como las bibliotecas compartidas son independientes de la posición y pueden ubicarse en ubicaciones de memoria aleatorias.

En fs/binfmt_elf.c el núcleo solo acepta esos dos tipos de archivos ELF:

/* First of all, some simple consistency checks */ if (interp_elf_ex->e_type != ET_EXEC && interp_elf_ex->e_type != ET_DYN) goto out;

Entonces, solo para ET_DYN establece el load_bias en algo que no es cero. load_bias es lo que determina el desplazamiento de ELF: ¿cómo se determina la dirección de la sección de texto de un ejecutable PIE en Linux?

/* * If we are loading ET_EXEC or we have already performed * the ET_DYN load_addr calculations, proceed normally. */ if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) { elf_flags |= elf_fixed; } else if (loc->elf_ex.e_type == ET_DYN) { /* * This logic is run once for the first LOAD Program * Header for ET_DYN binaries to calculate the * randomization (load_bias) for all the LOAD * Program Headers, and to calculate the entire * size of the ELF mapping (total_size). (Note that * load_addr_set is set to true later once the * initial mapping is performed.) * * There are effectively two types of ET_DYN * binaries: programs (i.e. PIE: ET_DYN with INTERP) * and loaders (ET_DYN without INTERP, since they * _are_ the ELF interpreter). The loaders must * be loaded away from programs since the program * may otherwise collide with the loader (especially * for ET_EXEC which does not have a randomized * position). For example to handle invocations of * "./ld.so someprog" to test out a new version of * the loader, the subsequent program that the * loader loads must avoid the loader itself, so * they cannot share the same load range. Sufficient * room for the brk must be allocated with the * loader as well, since brk must be available with * the loader. * * Therefore, programs are loaded offset from * ELF_ET_DYN_BASE and loaders are loaded into the * independently randomized mmap region (0 load_bias * without MAP_FIXED). */ if (elf_interpreter) { load_bias = ELF_ET_DYN_BASE; if (current->flags & PF_RANDOMIZE) load_bias += arch_mmap_rnd(); elf_flags |= elf_fixed; } else load_bias = 0;

Confirmo esto experimentalmente en: ¿Cuál es la opción -fPIE para ejecutables independientes de la posición en gcc y ld?

file 5.36 desglose de comportamiento

Después de estudiar cómo funciona el file desde su origen. Concluiremos que:

  • si Elf32_Ehdr.e_type == ET_EXEC
    • imprimir executable
  • si no Elf32_Ehdr.e_type == ET_DYN
    • si la entrada de sección dinámica DT_FLAGS_1 está presente
      • si DF_1_PIE está configurado en DT_FLAGS_1 :
        • imprimir pie executable
      • más
        • imprimir shared object
    • más
      • si el archivo es ejecutable por usuario, grupo u otros
        • imprimir pie executable
      • más
        • imprimir shared object

Y aquí hay algunos experimentos que confirman que:

Executable generation ELF type DT_FLAGS_1 DF_1_PIE chdmod +x file 5.36 --------------------------- -------- ---------- -------- -------------- -------------- gcc -fpie -pie ET_DYN y y y pie executable gcc -fno-pie -no-pie ET_EXEC n n y executable gcc -shared ET_DYN n n y pie executable gcc -shared ET_DYN n n n shared object ld ET_EXEC n n y executable ld -pie --dynamic-linker ET_DYN y y y pie executable ld -pie --no-dynamic-linker ET_DYN y y y pie executable

Probado en Ubuntu 18.10, GCC 8.2.0, Binutils 2.31.1.

El ejemplo de prueba completo para cada tipo de experimento se describe en:

ELF type y DF_1_PIE se determinan respectivamente con:

readelf --file-header main.out | grep Type readelf --dynamic main.out | grep FLAGS_1

file 5.36 análisis de código fuente

El archivo clave para analizar es magic/Magdir/elf .

Este formato mágico determina los tipos de archivos dependiendo solo de los valores de bytes en posiciones fijas.

El formato en sí está documentado en:

man 5 magic

Entonces, en este punto, querrá tener a mano los siguientes documentos:

Hacia el final del archivo, vemos:

0 string /177ELF ELF !:strength *2 >4 byte 0 invalid class >4 byte 1 32-bit >4 byte 2 64-bit >5 byte 0 invalid byte order >5 byte 1 LSB >>0 use elf-le >5 byte 2 MSB >>0 use /^elf-le

/177ELF son los 4 bytes mágicos al comienzo de cada archivo ELF. /177 es el octal para 0x7F .

Luego, al comparar con la estructura Elf32_Ehdr del estándar, vemos que el byte 4 (el 5to byte, el primero después del identificador mágico) determina la clase ELF:

e_ident[EI_CLASSELFCLASS]

y algunos de sus posibles valores son:

ELFCLASS32 1 ELFCLASS64 2

En file fuente del file , entonces, tenemos:

1 32-bit 2 64-bit

y 32-bit y 64-bit son las cadenas que el file genera en stdout.

Así que ahora buscamos shared object en ese archivo, y nos llevan a:

0 name elf-le >16 leshort 0 no file type, !:mime application/octet-stream >16 leshort 1 relocatable, !:mime application/x-object >16 leshort 2 executable, !:mime application/x-executable >16 leshort 3 ${x?pie executable:shared object},

Por lo tanto, este elf-le es algún tipo de identificador que se incluye en la parte anterior del código.

El byte 16 es exactamente el tipo ELF:

Elf32_Ehdr.e_type

y algunos de sus valores son:

ET_EXEC 2 ET_DYN 3

Por lo tanto, ET_EXEC siempre se imprime como executable .

ET_DYN embargo, ET_DYN tiene dos posibilidades dependiendo de ${x :

  • pie executable
  • shared object

${x pregunta: ¿el archivo es ejecutable o no por usuario, grupo u otro? En caso afirmativo, muestre el pie executable , de lo contrario, shared object

Esta expansión se realiza en la función varexpand en src/softmagic.c :

static int varexpand(struct magic_set *ms, char *buf, size_t len, const char *str) { [...] case ''x'': if (ms->mode & 0111) { ptr = t; l = et - t; } else { ptr = e; l = ee - e; } break;

¡Sin embargo, hay un truco más! En la función src/readelf.c dodynamic , si la entrada de banderas DT_FLAGS_1 de la sección dinámica ( PT_DYNAMIC ) está presente, los permisos en st->mode se anulan por la presencia o ausencia de la bandera DF_1_PIE :

case DT_FLAGS_1: if (xdh_val & DF_1_PIE) ms->mode |= 0111; else ms->mode &= ~0111; break;

El error en 5.34 es que el código inicial se escribió como:

if (xdh_val == DF_1_PIE)

lo que significa que si se estableció otro indicador, que GCC hace de manera predeterminada debido a DF_1_NOW , el ejecutable se muestra como shared object .

La entrada de banderas DT_FLAGS_1 no se describe en el estándar ELF, por lo que debe ser una extensión de Binutils.

Ese indicador no tiene usos en el kernel de Linux 5.0 o glibc 2.27, por lo que parece ser puramente informativo para indicar que un archivo es PIE o no.