Как стать автором
Обновить

Комментарии 30

Слайсинг в линуксе. Любопытно. Я в свое время делал слайсинг для винды, как вижу по исходникам — различия минимальны, различаются только названия вызываемых функций.

Единственное — статью читал по диагонали, у вас именно слайсинг или все-таки замена таблицы импорта/экспорта?
Я бы точно назвал это «заменой таблицы импорта».

Термин «слайсинг» никогда не слышал. Что это?
Слайсинг — это методика перенаправления вызовов функций, основанная на замене начала кода тела функции на инструкцию безусловного перехода на собственный обработчик. При этом, для вызова исходной функции используется «трамплин» — небольшая фукнция, выполняющая сначала «замененный» код из изначальной, а потом передающая управление на первый неизменный байт исходной функции.
Спасибо, +1 к знаниям.
Если в контексте обрезки таблицы виртуальных функций в С++ во время явного приведения типов, тогда точно нет.
НЛО прилетело и опубликовало эту надпись здесь
Википедия подсказала, что таки замена таблицы импорта.
Если нужно просто перехватить вызов определённых функций для определённого исполняемого файла, то метод с LD_PRELOAD проще. А вообще да, мсье знает толк в извращениях. :)
Безусловно проще :) Просто однажды он не подошел и пришлось так изголяться…

> Описанный метод осуществляет переопределение исключительно для конкретной динамически компонуемом библиотеки, а не для всего процесса (исполняемого файла), как при использовании переменной окружения LD_PRELOAD динамического загрузчика, что позволяет остальным модулям безболезненно пользоваться оригиналом функции.
не подскажете есть ли существующие программы которые позволяют перехватывать системные вызовы? Под windows таких очень много.

Меня инетересует как узнать например где программа пытается найти ищет mp3 файл (логи в учет не берем)
filemon вам в помощь
FileMon давным-давно deprecated. Process Monitor заменил его и RegMon.
под windows я знаю несколько вариантов, меня интересует тот-же функционал но под linux
Вам, я думаю, подойдет strace
гугль подсказывает, что с такими опциями:
strace -f -e trace=open -p
strace, если я правильно понял вопрос.
ниразу не системный программист но что-то подобное слышал про утилиту strace
ой, ответили уже… Пардон
В линуксе и правда гарантируется однократная загрузка библиотеки для всех процессов? А как?
Товарищ, ты нереально молодец. Я думал, что никто кроме разработчиков ядра и системных библиотек в дакие дебри не залазит.

У меня есть вопрос. Я его задавал на Лоре, там конечно окатили сверхмощной струёй фекалий. Хотел бы услышать твое мнение о возможности создания более нового механизма загрузки библиотек…

Цитата с лора:

— — — — — 8< — — — — -

"… В линухе всю подсистему разделяемых библиотек давно пора менять. Одних напрягает то, что программа собранная статически держит свою копию кода всех библиотек в памяти. Других напрягают глюки, которые неизбежны при отличии версии библиотеки в системе на 0.001 от версии, применяемой раработчиком. Это такие две крайности, и в линухе традиционно используется второй вариант (с глюками).

При этом никто не думает о том, что возможен более другой механизм загрузки библиотечного кода.

Например, о каждой функции в памяти можно предоставлять следующую информацию:

— имя библиотеки, в состав которой она входит,
— версию билиотеки, в состав которой она входит,
— длина кода в байтах,
— и хеш кода

Запускаемая программа компилируется статически, и содержит все библиотеки, которые ей нужны. Однако, при загрузке программы, грузится в память не весь код, а только реализации функций, которых нет в памяти. Быстро сориентироваться загрузчик может по информации об имени, версии, длине, и хешу. (Может быть, даже одного хеша будет достаточно. При совпадении хешей можно вразнорядку проверять каждый 2/4/8/16/32… чтобы снизить вероятность коллизий до 0.000000000001%. А можно и все байты проверять — загрузить код и выкинуть, если каждый байт совпадает с тем что уже есть).

Таким образом, в память будет грузится только то, чего в памяти нет. 90% кода в разных подверсиях библиотек обычно остается неизменным. Значит, подгружаться будет, к примеру, только 10% от размера статического бинарника. При этом дается гарантия, что код программы байт в байт соответсвует тому, который был у разработчика. Ну разве это не прекрасно?"

— — — — — 8< — — — — -

Вот. Интересует твое мнение по вопросам:

— Необходима ли вообще такая подсистема загрузки кода?

— Возможна ли реализация такой подсистемы, например путем замены или доработки библиотеки ld-linux.so?

— Что ты вообще думаешь о текущем dll и so хелле, нужно ли его искоренять?

— Должен ли пользователь иметь возможность устанавливать именно ту программу, которая ему нужна, именно той версии, которая ему нужна? Или пользователю обязательно надо пользоваться только той версией, которая есть в репозитарии?
НЛО прилетело и опубликовало эту надпись здесь
> Однако, при загрузке программы, грузится в память не весь код, а только реализации функций, которых нет в памяти.

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

Таким образом, все равно будет получаться, что загружаться будут «только реализации функций, которых нет в памяти».

>> Быстро сориентироваться загрузчик может по информации об имени, версии, длине, и хешу.

Не забывайте, что загрузчик еще должен знать, где нужно искать требуемую функцию.
Если я вас правильно понимаю, вы предлагаете маппировать в виртуальную память не образы библиотек, а только функции. При таком раскладе, загрузчику придется выполнить огромную работу, чтобы хотя бы найти место, откуда стоит начать поиски.

Если же маппировать в виртуальную память все же нужно, то тогда получится, что каждое приложение будет оперировать со своим набором библиотек, которые отличаются только версиями, что наоборот будет overhead'ом для памяти.

Отсюда плавно вытекает мое ИМХО на ваши вопросы:
описанная подсистема не учитывает множества нюансов и вряд ли имеет смысл, хотя реализовать ее может и можно (но промолчу, какими усилиями это возможно).
DLL-hell — это плохо, а все плохое конечно надо искоренять. Но как — не знаю :)
А пользователь должен иметь возможность дело все, что душе пожелает, но разработчик должен стараться, чтобы ему было достаточно версий из репозитариев.
Добавлю ещё, что засчёт использования PIC-кода, код одной и той же библиотеки будет загружен в память только в одном экземпляре для всех её пользователей (название «shared library» как раз отсюда). Так как PIC-код не требует релокаций, для процесса не будет создаваться индивидуальных экземпляров страниц, они будут пользоваться одними и теми же. Свои копии будут только для относительно небольших секций GOT и PLT. Ради этого, собственно, все эти пляски с -fPIC и затеяны.

И поэтому же, кстати, адреса загрузки секций, как и их физические адреса в файле всегда выровнены на границу страницы.

В винде, кажется, пошли другим путем — там одна библиотека будет всем потребителям загружена на одни и те же виртуальные адреса.
Спасибо, да будет так :)
очень любопытная статья. Всегда было интересно почитать про библиотеки и линковку программ, но всегда наталкивался на то, что не хватает базы, чтобы начать читать. После этой статьи, думаю, многие тексты станут мне намного понятнее.
А возможно ли скомпоновать единый elf-бинарник, который будет работать и как shared object и в то же время мог бы запускаться сам по себе (иметь точку входа)?
Да. Далеко за примером ходить не надо, достаточно запустить в линуксе /lib/ld-linux.so.2 из командной строки.
Ну и ещё /lib/libc.so.6, и тд.
Как уже метко подметил hmage, это возможно. Только при компиляции нужно вручную указать имя новой точки входа, а в модуле — путь к загрузчику.

Набросал пример:

libtest.c — библиотека, что будет запускаться
#include <stdio.h>
#include <stdlib.h>

char const interp[] __attribute__((section(".interp"))) = "/lib/ld-linux.so.2";
//path to your dynamic linker (make sure that symbolic link refers to the real linker for your system)

void func(int i)
{
    printf("func got %i\n", i);
}

int main()
{
    func(0);

    exit(0);  //do not return, only exit!
}


test.c — некий импортер этой библиотеки
#include <dlfcn.h>

void func(int);

int main()
{
    void *handle = dlopen("libtest.so", RTLD_LAZY);

    func(1);

    dlclose(handle);

    return 0;
}


Компилируется так:
bash$ gcc -fPIC -shared -Wl,-e,main -o libtest.so libtest.c  #"-Wl,-e,main" to make a special named entry point 
bash$ gcc -L$PWD -ltest -ldl -o test test.c

А запускается так:
bash$ export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
bash$ ./test
func got 1
bash$ ./libtest.so
func got 0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории