Простое сопоставление памяти загрузчика процессов

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

mmap(addr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0)

который segfaults, если я не удалю флаг MAP_FIXED, потому что, кажется, адрес одного блока конфликтует с чем-то уже в памяти, возможно, даже с самим загрузчиком; адрес 0x401000 кажется проблемным.

Я даже не знаю, с чего начать. Друг предложил виртуализировать операции доступа к памяти; Я не уверен, какие удары по производительности я бы взял для этого, и я понятия не имею, как это делается, но это может быть вариантом. Что бы я действительно хотел сделать, так это создать «пустой» процесс, который, насколько это возможно, имел бы полный доступ к памяти, поэтому ничего не загружалось бы в пользовательское пространство, пока я этого не захочу. Вся концепция «пустого» процесса может быть бессмысленной, но это лучший способ описать то, что я хочу. Я очень отчаянно нуждаюсь в некоторых ссылках или примерах, которые могли бы мне помочь.


person emprice    schedule 30.07.2012    source источник
comment
создайте пустой процесс, этот процесс имеет некоторый код и данные, и они должны где-то находиться. Вам действительно нужно все адресное пространство? Возможно, есть часть адресного пространства, которое вы можете предположить, что процессы, которые вы загружаете, никогда не займут?   -  person Suma    schedule 30.07.2012
comment
Трудно сказать, на самом деле... Я хотел, чтобы он был как можно более гибким, чтобы любой адрес был действительным.   -  person emprice    schedule 30.07.2012
comment
Чем то, что вы хотите сделать, отличается от exec?   -  person Mark B    schedule 30.07.2012
comment
@MarkB Я хочу перейти к новому процессу, но я хочу загрузить инструкции и данные в память вручную. exec просто запускал бы исполняемый файл, который не был бы загрузчиком процесса.   -  person emprice    schedule 30.07.2012
comment
Зачем ты это делаешь? а ты хочешь перешить ядро?   -  person Basile Starynkevitch    schedule 30.07.2012
comment
@BasileStarynkevitch Есть несколько причин, по которым я работаю над этим. Во-первых, это интересно, и я много узнаю об управлении памятью и ядре, но это также часть более крупного проекта, целью которого является беспрепятственное выполнение EXE. Я действительно не хочу изменять ядро, хотя.   -  person emprice    schedule 30.07.2012
comment
Wine, вероятно, уже делает это, узнайте о en.wikipedia.org/wiki/Binfmt_misc.   -  person Basile Starynkevitch    schedule 30.07.2012
comment
@BasileStarynkevitch Я знаю о Wine, но не хочу использовать его для этого приложения. Я бы хотел сделать что-то вроде :DOSWin:M::MZ::/usr/bin/my_app:, если бы использовал это.   -  person emprice    schedule 30.07.2012
comment
Действительно ли mmap() вызывает ошибку сегментации или вы забыли проверить возвращаемое значение и errno?   -  person Michał Górny    schedule 31.07.2012
comment
@MichałGórny mmap() на самом деле segfaults. Когда я прошел код в gdb, он вылетел на строке с mmap(). Я никогда не пытался использовать адрес, который он возвращал.   -  person emprice    schedule 31.07.2012
comment
@nosuchthingasstars Вы загружаете статически связанные приложения? Динамически связанные библиотеки требуют много усилий и исправлений, так как вам нужно загрузить зависимые общие библиотеки и выполнить правильное перемещение.   -  person nos    schedule 31.07.2012
comment
@nos Я еще даже не дошел до этого уровня ... Я только тестирую приложения, которые сейчас ни на что не ссылаются. Код, который я сейчас тестирую, просто mov eax, 4; add eax, 5; ret скомпилирован с fasm. Я хотел бы в конечном итоге добраться до точки, где я мог бы загружать как динамически, так и статически связанные приложения, но я еще не достиг этого.   -  person emprice    schedule 31.07.2012
comment
@nosuchthingasstars: ну, я смог воспроизвести его, но, глядя на справочную страницу, я не вижу упоминания о том, что в этом случае он должен segfault. Я предлагаю вам попробовать сообщить об ошибке в glibc.   -  person Michał Górny    schedule 31.07.2012
comment
@MichałGórny Я мог бы это сделать, но меня не особо беспокоит сам segfault; Я почти уверен, основываясь на другом вопросе, который я задал здесь, что что-то уже загружено на этот адрес. Прямо сейчас я просто хочу найти способ выполнить ручную загрузку в пустом пространстве процесса, где это не будет проблемой.   -  person emprice    schedule 31.07.2012


Ответы (1)


Когда ваш процесс запущен (возможно, он дремлет в «сне (1000);»), посмотрите его /proc/pid/maps. Это скажет вам, для чего используется 0x401000.

~$ sleep 1h &
[3] 2033
~$ cat /proc/2033/maps
00110000-002af000 r-xp 00000000 08:01 1313056    /lib/i386-linux-gnu/libc-2.15.so
...

Здесь, на моем компьютере, /bin/sleep не использует этот блок, как и моя маленькая однострочная программа.

Вы, вероятно, ссылаетесь на какую-то библиотеку, которая хочет туда приземлиться?

Таким образом, одним из способов было бы выделить блок, который вам нужен, намного раньше (задолго до запуска main() - поищите эту информацию в другом месте).

Другой способ - связать свой код с каким-то адресом, который, как вы «знаете», не занят (предположительно, вы сами генерируете коды операций x86 или иным образом «связываете», так что это не должно быть натяжкой).

Другой, лучший вариант — сделать ваш код перемещаемым. Тот факт, что вы не хотите заменять адресное пространство всего процесса (именно то, что делает exec), более или менее говорит о том, что ваш код должен быть именно таким.

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

Конечно, это в значительной степени означает повторную реализацию dlopen() самостоятельно. Я предполагаю, что вы просто пытаетесь узнать, как это работает, если нет, чувак, dlopen. Нулевое правило Стефана: оно уже есть ;-)

Не забывайте поддерживать связывание других библиотек из вашего кода (без дублирования), dlclose(), инициализаторы, различные режимы RTLD_*, учитывать MYCUSTOMLD_LIBRARY_PATH, спецификатор GCC __thread и т. д. ;-)

person sgb    schedule 18.08.2012