Абсолютные адреса в позиционно-независимом коде (PIC)

Я пытаюсь создать и связать один образ для загрузки в качестве ядра ОС (т.е. в QEMU), ориентируясь на aarch64-unknown-none-softfloat. Я использую собственный файл linker.ld, который устанавливает точку входа для ядра ENTRY(_reset) и позиционирует изображение

. = 0x40080000

где программный счетчик (ПК) находится в состоянии сброса.

Это работает нормально, пока я не сопоставлю страницы по адресу 0x40080000 с верхней памятью, где будет находиться ядро, и не включу преобразование виртуальной памяти. Чтобы отладочная информация совпадала после переключения, я изменяю номинальное положение изображения на

. = 0xffffff8200000000

и восстановить.

Я обнаружил, что доступ:

  • к некоторой (внешней пабе) статике, и
  • некоторыми функциями основной библиотеки

заключается в чтении абсолютного адреса откуда-то из .rodata. Это ломает код, когда он запускается до сопоставления. И если я верну его обратно, это сломает код, когда я запущу его после сопоставления.

Код, который он генерирует, выглядит примерно так на O1 (косвенно через страницу, относящуюся к ПК):

adrp  x0, 0x10000 // page offset from PC up to rodata
add   x0, 0x120   // byte offset from page in rodata
ldr   x0, [x0]    // use as address

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

Я пробовал другие модели перемещения, включая Pic и RopiRwpi, но не вижу, чтобы они генерировали другой код.

Спасибо!

РЕДАКТИРОВАТЬ: Большое спасибо за предложения по временной карте. Я видел, что используется. Меня больше интересуют параметры компилятора, которые позволят -no-dynamic-linker работать, избегая генерации кода, требующего перемещения R_AARCH64_ABS64, в силу гарантии того, что код и данные будут находиться на заданном расстоянии друг от друга.


person Alister Lee    schedule 08.06.2020    source источник


Ответы (2)


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

См. пример точка входа и скрипт компоновщика (не Rust ядро/aarch64).

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

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

person Keeley Hoek    schedule 08.06.2020
comment
Большое спасибо за ссылки и предложение временно сопоставить. Я видел, что используется. Меня больше интересуют параметры компилятора, которые позволят -no-dynamic-linker работать, избегая генерации кода, требующего перемещения R_AARCH64_ABS64, в силу гарантии того, что код и данные будут находиться на заданном расстоянии друг от друга. - person Alister Lee; 09.06.2020

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

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

Если вы чувствуете, что ваша логика сопоставления слишком длинная/сложная для выполнения на таком этапе, вы можете обойтись тривиальными таблицами страниц, которые сначала отображают все как RWX, затем вы можете переключиться, вызвать свои библиотеки и другой код, а затем приступайте к построению финальных таблиц страниц. Такие «тривиальные» таблицы страниц могут быть построены во время компиляции, должны занимать не более 4 страниц памяти (по две на каждый TTBR), а их использование для переключения на старшие адреса должно занимать не более 20 инструкций.

person Siguza    schedule 08.06.2020
comment
Большое спасибо за предложения по временной карте. Я видел, что используется. Меня больше интересуют параметры компилятора, которые позволят -no-dynamic-linker работать, избегая генерации кода, требующего перемещения R_AARCH64_ABS64, в силу гарантии того, что код и данные будут находиться на заданном расстоянии друг от друга. - person Alister Lee; 09.06.2020