У меня есть библиотека, которую я создаю. Все мои объекты компилируются и связаны последовательно, когда я запускаю любой из следующих:
ar rcs lib/libryftts.a $^
в моем Makefile. Я также могу успешно установить их в /usr/local/lib
Когда я тестирую файл с nm, все функции есть.
Моя проблема в том, что когда я запускаю gcc testing/test.c -lryftts -o test && file ./test
или gcc testing/test.c lib/libryftts.a -o test && file ./test
он говорит:
Ответ 2
file
5.36 говорит это ясно
file
5.36 фактически печатает его четко, если исполняемый файл является PIE или нет, как показано на: https://unix.stackexchange.com/info/89211/how-to-test-whether-a-linux-binary-was-compiled -по позиционно-независимый код /435038 # 435038
Например, исполняемый файл PIE показывает как:
main.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, not stripped
и не -pie как:
main.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
Эта функция была представлена в 5.33, но она выполнила простую проверку chmod +x
. До этого он просто печатал shared object
для PIE.
В 5.34 предполагалось начать проверку более специализированных метаданных ELF DF_1_PIE
, но из-за ошибки в реализации на коммите 9109a696f3289ba00eaa222fd432755ec4287e28 он фактически сломал вещи и показал исполняемые файлы GCC PIE в качестве shared objects
.
Ошибка была исправлена в 5.36 при коммите 03084b161cf888b5286dbbcd964c31ccad4f64d9.
Эта ошибка присутствует, в частности, в Ubuntu 18.10, в которой есть file
5.34.
Он не проявляется при связывании ассемблерного кода с ld -pie
из-за совпадения.
Разбивка исходного кода приведена в разделе "Анализ исходного кода file
5.36" этого ответа.
Ядро Linux 5.0 определяет, можно ли использовать ASLR на основе ET_DYN
Основная причина file
"путаница" заключается в том, что как исполняемые файлы PIE, так и разделяемые библиотеки не зависят от позиции и могут быть размещены в рандомизированных ячейках памяти.
В fs/binfmt_elf.c ядро принимает только эти два типа файлов 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;
Тогда только для ET_DYN
он устанавливает load_bias
в значение, load_bias
от нуля. Тогда load_bias
определяет смещение ELF: Как определяется адрес текстового раздела исполняемого файла PIE в 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;
Я подтверждаю это экспериментально по адресу: Какая опция -fPIE для позиционно-независимых исполняемых файлов в gcc и ld?
разбивка поведения file
5.36
Изучив, как работает file
из его источника. Мы сделаем вывод, что:
- if
Elf32_Ehdr.e_type == ET_EXEC
- иначе если
Elf32_Ehdr.e_type == ET_DYN
- если присутствует
DT_FLAGS_1
запись динамического раздела - если
DF_1_PIE
установлен в DT_FLAGS_1
: - еще
- распечатать
shared object
- еще
- если файл исполняемый пользователем, группой или другими
- еще
- распечатать
shared object
И вот несколько экспериментов, которые подтверждают, что:
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
Протестировано в Ubuntu 18.10, GCC 8.2.0, Binutils 2.31.1.
Полный пример теста для каждого типа эксперимента описан по адресу:
ELF type
и DF_1_PIE
определяются соответственно с помощью:
readelf --file-header main.out | grep Type
readelf --dynamic main.out | grep FLAGS_1
file
5.36 анализ исходного кода
Ключевым файлом для анализа является magic/Magdir/elf.
Этот магический формат определяет типы файлов в зависимости только от значений байтов в фиксированных позициях.
Сам формат задокументирован по адресу:
man 5 magic
Поэтому на данный момент вам понадобятся следующие документы:
Под конец файла мы видим:
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
- это 4 магических байта в начале каждого файла ELF. \177
является восьмеричным для 0x7F
.
Затем, сравнивая со структурой Elf32_Ehdr
из стандарта, мы видим, что байт 4 (5-й байт, первый после магического идентификатора) определяет класс ELF:
e_ident[EI_CLASSELFCLASS]
и некоторые из его возможных значений:
ELFCLASS32 1
ELFCLASS64 2
В исходном file
мы имеем:
1 32-bit
2 64-bit
и 32-bit
и 64-bit
строки - это file
которые выводятся на стандартный вывод!
Итак, теперь мы ищем shared object
в этом файле, и нас ведут к:
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},
Так что этот elf-le
- это своего рода идентификатор, который включается в предыдущую часть кода.
Байт 16 в точности соответствует типу ELF:
Elf32_Ehdr.e_type
и некоторые из его значений:
ET_EXEC 2
ET_DYN 3
Поэтому ET_EXEC
всегда печатается как executable
.
Однако ET_DYN
имеет две возможности в зависимости от ${x
:
-
pie executable
-
shared object
${x
спрашивает: исполняемый файл или нет пользователем, группой или другим? Если да, покажите pie executable
, иначе shared object
.
Это расширение выполняется в функции varexpand
в 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;
Есть, однако, еще один взлом! В dodynamic
функции src/readelf.c
dodynamic
, если DT_FLAGS_1
запись DT_FLAGS_1
динамического раздела (PT_DYNAMIC
), то разрешения в режиме st->mode
отменяются наличием или отсутствием флага DF_1_PIE
:
case DT_FLAGS_1:
if (xdh_val & DF_1_PIE)
ms->mode |= 0111;
else
ms->mode &= ~0111;
break;
Ошибка в 5.34 заключается в том, что исходный код был написан как:
if (xdh_val == DF_1_PIE)
это означает, что если был установлен другой флаг, который GCC делает по умолчанию из-за DF_1_NOW
, исполняемый файл DF_1_NOW
как shared object
.
Эта запись флагов не описана в стандарте ELF, поэтому она должна быть расширением Binutils.
Этот флаг не имеет смысла в ядре Linux 5.0 или glibc 2.27, поэтому мне кажется, что он достаточно информативен, чтобы указать, является ли файл PIE или нет.