Подтвердить что ты не робот

Можно ли полностью отключить GNU ld -dynamic-linker (PT_INTERP)?

Я экспериментирую с концепцией исполняемых файлов PIE с чистой статической связью в Linux, но сталкиваюсь с проблемой, с которой линкер GNU binutils настаивает на добавлении заголовка PT_INTERP в выходной двоичный код, когда используется -pie, даже если также дано -static. Есть ли способ подавить это поведение? То есть, есть ли способ сказать GNU ld специально не записывать определенные заголовки в выходной файл? Возможно, с компоновщиком script?

(Пожалуйста, не отвечайте с утверждениями о том, что это не сработает, я хорошо знаю, что программе по-прежнему нужна передислокация - перенаправление с использованием load-address-rel только из-за моего использования -Bsymbolic - и у меня есть специальный код запуска вместо стандартного Scrt1.o, чтобы обработать это. Но я не могу заставить его вызываться без динамического компоновщика, уже выполняющего работу и выполняющего работу, если hexedit заголовок PT_INTERP из двоичного файла.)

4b9b3361

Ответ 1

Думаю, я мог бы найти решение: просто используя -shared вместо -pie, чтобы сделать pie файлы. Вам нужно несколько дополнительных компоновщиков, чтобы исправлять поведение, но, похоже, он не нуждается в пользовательском компоновщике script. Другими словами, компоновщик -shared script уже по существу является правильным для связывания статических файлов pie.

Если я его обработаю, я обновлю ответ с помощью точной командной строки, которую я использую.

Обновление: Работает! Вот командная строка:

gcc -shared -static-libgcc -Wl,-static -Wl,-Bsymbolic \
    -nostartfiles -fPIE Zcrt1.s Zcrt2.c /usr/lib/crti.o hello.c /usr/lib/crtn.o

где Zcrt1.s - это модифицированная версия Scrt1.s, которая вызывает функцию в Zcrt2.c перед выполнением нормальной работы, а код в Zcrt2.c обрабатывает вектор aux только за массивами argv и environment, чтобы найти DYNAMIC, затем перебирает таблицы перемещений и применяет все относительные перемещения (единственные, которые должны существовать).

Теперь все это может (с небольшой работой) быть завернуто в script или gcc specfile...

Ответ 2

Возможно, я наивна, но... woudn't достаточно поиска по умолчанию компоновщик script, отредактировать его и удалить строку, которая ссылается в разделе .interp?

Например, на моем компьютере сценарии находятся в /usr/lib/ldscripts, а соответствующая строка interp : { *(.interp) } в разделе SECTIONS.

Вы можете выполнить dumpp по умолчанию script, используя следующую команду:

$ ld --verbose ${YOUR_LD_FLAGS} | \
    gawk 'BEGIN { s = 0 } { if ($0 ~ /^=/) s = !s; else if (s == 1) print; }'

Вы можете слегка изменить gawk script, чтобы удалить строку interp (или просто использовать grep -v и использовать script для связи вашей программы.

Ответ 3

Развернувшись на моей предыдущей заметке, так как это не помещается в эту пустую коробку (и это просто идея или дискуссия, пожалуйста, не чувствуйте себя обязанным принимать или вознаграждать награду), возможно, самый простой и чистый способ сделать это в juts добавить шаг после сборки, чтобы удалить заголовок PT_INTERP из полученного двоичного файла?

Еще проще, чем вручную редактировать заголовки и, возможно, переключать все вокруг, это просто заменить PT_INTERP на PT_NULL. Я не знаю, можете ли вы найти способ простого исправления файла с помощью существующих инструментов (какой-то скриптовый поиск и замена), или если вам нужно написать небольшую программу для этого. Я знаю, что libbfd (библиотека дескрипторов файлов GNU) может быть вашим другом в последнем случае, так как это облегчит этот бизнес.

Я думаю, я просто не понимаю, почему важно, чтобы это выполнялось с помощью опции ld. Если доступно, я могу понять, почему это было бы предпочтительнее; но, как некоторые (по общему признанию, светлые) Googling указывает, что такой функции нет, может быть, не так сложно просто сделать это отдельно и после этого. (Возможно, добавление флага в ld проще, чем сценарий замены PT_INTERP на PT_NULL, но убедить разработчиков подключить его вверх по течению - это другое дело.)


По-видимому (и, пожалуйста, поправьте меня, если вы уже это видели) вы можете переопределить поведение ld в отношении любого из заголовков ELF в вашем компоновщике script с помощью команды PHDRS и с помощью :none указать, что конкретный тип заголовка не должен включаться в какой-либо сегмент. Я не уверен в синтаксисе, но я предполагаю, что он будет выглядеть примерно так:

PHDRS
{
  headers PT_PHDR PHDRS ;
  interp PT_INTERP ;
  text PT_LOAD FILEHDR PHDRS ;
  data PT_LOAD ;
  dynamic PT_DYNAMIC ;
}

SECTIONS
{
  . = SIZEOF_HEADERS;
  .interp : { } :none
  ...
}

Из ld docs вы можете переопределить компоновщик script с помощью --library-path:

--library-path=searchdir

Добавить путь searchdir в список путей, которые ld будет искать архивные библиотеки и ld-скрипты управления. Вы можете использовать эту опцию любым количество раз. Поиск каталогов осуществляется в том порядке, в котором они указаны в командной строке. Каталоги, указанные на поиск в командной строке перед каталогами по умолчанию. Все -L параметры применяются ко всем параметрам -l, независимо от порядка, в котором отображаются параметры. Набор найденных путей по умолчанию (без заданный с помощью `-L '), зависит от режима эмуляции ld и в некоторых случаях также о том, как он был настроен. См. Раздел Среда Переменные. Пути также можно указать в ссылке script с SEARCH_DIR. Каталоги, указанные таким образом, точка, в которой в командной строке появляется компоновщик script.

Кроме того, из раздел "Неявные скрипты компоновщика" :

Если вы укажете входной файл компоновщика, который не может распознать компоновщик как объектный файл или файл архива, он попытается прочитать файл как компоновщик script. Если файл не может быть проанализирован как компоновщик script, компоновщик сообщит об ошибке.

Которое, казалось бы, подразумевает значения в пользовательских скриптах компоновщика, в отличие от неявно определенных скриптов компоновщика, заменит значения в сценариях по умолчанию.

Ответ 4

Я не эксперт в GNU ld, но я нашел следующую информацию в документации:

Для сброса входных секций может использоваться специальная функция `/DISCARD/'. Любые разделы, назначенные разделу вывода с именем `/DISCARD/' не включаются в окончательный вывод линии.

Надеюсь, это поможет вам.

UPDATE:

(Это первая версия решения, которая не работает, потому что раздел INTERP удаляется вместе с заголовком PT_INTERP.)

main.c:

int main(int argc, char **argv)                                                                                                                               
{                                                                                                                                                             
    return 0;                                                                                                                                                 
}

main.x:

SECTIONS {                                                                                                                                                    
    /DISCARD/ : { *(.interp) }                                                                                                                                
}

команда сборки:

$ gcc -nostdlib -pie -static -Wl,-T,main.x main.c
$ readelf -S a.out | grep .interp

команда сборки без опции -Wl, -T, main.x:

$ gcc -nostdlib -pie -static main.c 
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000000218
$ readelf -S a.out | grep .interp
  [ 1] .interp           PROGBITS        00000134 000134 000013 00   A  0   0  1

ОБНОВЛЕНИЕ 2:

Идея этого решения заключается в том, что исходный раздел 'INTERP' (. interp в файле компоновщика script) переименовывается в .interp1. Другими словами, все содержимое раздела помещается в секцию .interp1. Поэтому мы можем безопасно удалить раздел INTERP (теперь пустой), не опасаясь потерять настройки компоновщика по умолчанию script, и поэтому заголовок INTERP_PT также будет удален.

SECTIONS {
    .interp1 : { *(.interp); } : NONE
    /DISCARD/ : { *(.interp) }
}

Чтобы показать, что содержимое раздела INTERP присутствует в файле (как .interp1), но заголовок INTERP_PT удален, я использую комбинацию readelf + grep.

$ gcc -nostdlib -pie -Wl,-T,main.x main.c
$ readelf -l a.out | grep interp
   00     .note.gnu.build-id .text .interp1 .dynstr .hash .gnu.hash .dynamic .got.plt 
$ readelf -S a.out | grep interp
  [ 3] .interp1          PROGBITS        0000002e 00102e 000013 00   A  0   0  1