Почему GCC создает общий объект вместо исполняемого двоичного файла в соответствии с файлом?

У меня есть библиотека, которую я создаю. Все мои объекты компилируются и связаны последовательно, когда я запускаю любой из следующих: ar rcs lib/libryftts.a $^

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

в моем 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 он говорит:

test: ELF 64-bit LSB shared object вместо test: ELF 64-bit LSB executable, как я ожидал. Что я делаю неправильно?

Ответ 1

Что я делаю неправильно?

Ничего.

Похоже, ваш GCC настроен на создание двоичных файлов -pie по умолчанию. Эти двоичные файлы действительно являются общими библиотеками (типа ET_DYN), за исключением того, что они выполняются так же, как обычный исполняемый файл.

Итак, вы должны просто запустить свой двоичный файл и (если он работает) не беспокоиться об этом.

Или вы могли бы связать свой двоичный файл с gcc -no-pie ..., и он должен создать исполняемый файл не PIE типа ET_EXEC, для которого file будет указывать ELF 64-bit LSB executable.

Ответ 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
    • распечатать executable
  • иначе если Elf32_Ehdr.e_type == ET_DYN
    • если присутствует DT_FLAGS_1 запись динамического раздела
      • если DF_1_PIE установлен в DT_FLAGS_1:
        • pie executable
      • еще
        • распечатать shared object
    • еще
      • если файл исполняемый пользователем, группой или другими
        • pie executable
      • еще
        • распечатать 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 или нет.