¿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
-
imprimir
-
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 enDT_FLAGS_1
:-
imprimir
pie executable
-
imprimir
-
más
-
imprimir
shared object
-
imprimir
-
si
-
más
-
si el archivo es ejecutable por usuario, grupo u otros
-
imprimir
pie executable
-
imprimir
-
más
-
imprimir
shared object
-
imprimir
-
si el archivo es ejecutable por usuario, grupo u otros
-
si la entrada de sección dinámica
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:
-
gcc -pie
ygcc -no-pie
: ¿Cuál es la opción -fPIE para ejecutables independientes de la posición en gcc y ld?Tenga en cuenta que
-pie
está activado de forma predeterminada desde Ubuntu 17.10, relacionado: ¿ direcciones absolutas de 32 bits ya no se permiten en Linux x86-64? -
gcc -shared
(biblioteca compartidagcc -shared
): https://github.com/cirosantilli/cpp-cheat/tree/b80ccb4a842db52d719a16d3716b02b684ebbf11/shared_library/basic -
Experimentos con
ld
: ¿Cómo crear un ELF ejecutable independiente de posición estáticamente vinculado en Linux?
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:
- http://www.sco.com/developers/devspecs/gabi41.pdf Estándar ELF en la sección de encabezado ELF
- http://www.cirosantilli.com/elf-hello-world/#elf-header Introducción y desglose de mi formato de archivo ELF
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.