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

Обработка перемещений ELF - понимание релоков, символов, данных разделов и их совместная работа

TL; DR

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

Вот конкретные проблемы, которые у меня возникают. Подробнее см. Ниже.

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

Мой подход до сих пор

Я пытаюсь создать файл перемещения в определенном [недокументированном] проприетарном формате, который в большой степени основан на ELF. Я написал инструмент, который принимает файл ELF и частично связанный файл (PLF) и обрабатывает их для вывода полностью разрешенного файла rel. Этот файл rel используется для загрузки/выгрузки данных по мере необходимости для сохранения памяти. Платформа - 32-битный КПП. Одна морщина заключается в том, что инструмент написан для Windows в С#, но данные предназначены для PPC, поэтому есть интересные проблемы с endian и т.д., Чтобы следить за.

Я пытался понять, как обрабатываются перемещения, когда используется для разрешения неразрешенных символов и т.д. То, что я сделал до сих пор, - это скопировать соответствующие разделы из PLF, а затем для каждого соответствующего раздела .rela, я анализирую записи и пытаюсь исправить данные раздела и генерировать новые записи перемещений по мере необходимости. Но это моя трудность. Я вышел из своей стихии, и подобные вещи, как правило, выполняются линкерами и загрузчиками, поэтому на них не так много хороших примеров. Но я нашел несколько из них, которые помогли, в том числе ЭТО ОДИН.

Итак, что происходит:

  • Скопировать данные раздела из PLF, которые будут использоваться для файла rel. Меня интересуют только .init(нет данных),.text,.ctors,.dtors,.rodata,.data,.bss(без данных) и другой пользовательский раздел, который мы используем.
  • Итерации по разделам .rela в PLF и чтение в записях Elf32_Rela.
  • Для каждой записи я вытаскиваю поля r_offset, r_info и r_addend и извлекаю соответствующую информацию из r_info (символ и тип reloc).
  • Из таблицы символов PLF я могу получить символOffset, symbolSection и symbolValue.
  • Из ELF я получаю адрес загрузки symbolSection.
  • Я вычисляю int localAddress = (.relaSection.Offset + r_offset).
  • Я получаю uint relocValue из содержимого symbolSection в r_offset.
  • Теперь у меня есть вся информация, в которой я нуждаюсь, поэтому я использую переключатель типа reloc и обрабатываю данные. Это те типы, которые я поддерживаю:
      R_PPC_NONE
      R_PPC_ADDR32
      R_PPC_ADDR24
      R_PPC_ADDR16
      R_PPC_ADDR16_LO
      R_PPC_ADDR16_HI
      R_PPC_ADDR16_HA
      R_PPC_ADDR14
      R_PPC_ADDR14_BRTAKEN
      R_PPC_ADDR14_BRNTAKEN
      R_PPC_REL24
      R_PPC_REL14
      R_PPC_REL14_BRTAKEN
      R_PPC_REL14_BRNTAKEN
  • Теперь что??? Мне нужно обновить данные раздела и собрать записи перемещения компаньона. Но я не понимаю, что нужно делать и как это сделать.

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

Итак, при создании нашего собственного инструмента, который поддерживает специальные разделы, идеально, если есть какие-то яркие идеи для другого способа достижения этой цели, я все уши! Мы использовали идею использования раздела .dtor для наших данных, так как в любом случае она почти пуста. Но это беспорядочно и, возможно, не работает, если оно предотвращает чистое завершение работы.


Перемещение плюс примерный код

Когда я обрабатываю перестановки, я отрабатываю уравнения и информацию, найденные в документах ABI ЗДЕСЬ (около раздела 4.13, страница 80ish), а также ряд других примеров кода и сообщений в блоге, которые я откопал. Но все это так запутанно и не совсем прописано, и весь код, который я нашел, делает вещи немного по-другому.

Например,

  • R_PPC_ADDR16_LO → half16: #lo (S + A)
  • R_PPC_ADDR14_BRTAKEN → low14 *: (S + A) → 2
  • и т.д.

Итак, когда я вижу такой код, как его расшифровать?

Здесь один пример (из этот источник)

case ELF::R_PPC64_ADDR14 : {
    assert(((Value + Addend) & 3) == 0);
    // Preserve the AA/LK bits in the branch instruction
    uint8_t aalk = *(LocalAddress+3);
    writeInt16BE(LocalAddress + 2, (aalk & 3) | ((Value + Addend) & 0xfffc));
} break;

case ELF::R_PPC64_REL24 : {
    uint64_t FinalAddress = (Section.LoadAddress + Offset);
    int32_t delta = static_cast<int32_t>(Value - FinalAddress + Addend);
    if (SignExtend32<24>(delta) != delta)
        llvm_unreachable("Relocation R_PPC64_REL24 overflow");
    // Generates a 'bl <address>' instruction
    writeInt32BE(LocalAddress, 0x48000001 | (delta & 0x03FFFFFC));
} break;

Вот некоторые из другого примера (здесь)

case R_PPC_ADDR32: /* word32 S + A */
    addr = elf_lookup(lf, symidx, 1);
    if (addr == 0)
        return -1;
    addr += addend;
    *where = addr;
    break;

case R_PPC_ADDR16_LO: /* #lo(S) */
    if (addend != 0) {
        addr = relocbase + addend;
    } else {
        addr = elf_lookup(lf, symidx, 1);
        if (addr == 0)
            return -1;
    }
    *hwhere = addr & 0xffff;
    break;

case R_PPC_ADDR16_HA: /* #ha(S) */
    if (addend != 0) {
        addr = relocbase + addend;
    } else {
        addr = elf_lookup(lf, symidx, 1);
        if (addr == 0)
            return -1;
    }
    *hwhere = ((addr >> 16) + ((addr & 0x8000) ? 1 : 0)) & 0xffff;
    break;

И еще один пример (отсюда)

case R_PPC_ADDR16_HA:
    write_be16 (dso, rela->r_offset, (value + 0x8000) >> 16);
    break;
case R_PPC_ADDR24:
    write_be32 (dso, rela->r_offset, (value & 0x03fffffc) | (read_ube32 (dso, rela->r_offset) & 0xfc000003));
    break;
case R_PPC_ADDR14:
    write_be32 (dso, rela->r_offset, (value & 0xfffc) | (read_ube32 (dso, rela->r_offset) & 0xffff0003));
    break;
case R_PPC_ADDR14_BRTAKEN:
case R_PPC_ADDR14_BRNTAKEN:
    write_be32 (dso, rela->r_offset, (value & 0xfffc)
                                    | (read_ube32 (dso, rela->r_offset) & 0xffdf0003)
                                    | ((((GELF_R_TYPE (rela->r_info) == R_PPC_ADDR14_BRTAKEN) << 21)
                                    ^ (value >> 10)) & 0x00200000));
    break;
case R_PPC_REL24:
    write_be32 (dso, rela->r_offset, ((value - rela->r_offset) & 0x03fffffc) | (read_ube32 (dso, rela->r_offset) & 0xfc000003));
    break;
case R_PPC_REL32:
    write_be32 (dso, rela->r_offset, value - rela->r_offset);
    break;

Я действительно хочу понять магию, которую эти парни делают здесь, и почему их код не всегда выглядит одинаково. Я думаю, что некоторые из кода делают предположения, что данные уже были правильно замаскированы (для веток и т.д.), А некоторые из них - нет. Но я вообще этого не понимаю.


Следуя символам/данным/переездам и т.д.

Когда я просматриваю данные в шестнадцатеричном коде, я вижу кучу "48 00 00 01". Я понял, что это код операции и нуждается в обновлении с информацией о перемещении (это специально для ветки "bl" и ссылки), но мой инструмент не работает на подавляющем большинстве из них и тех, которые я делаю обновление имеют в них неправильные значения (по сравнению с примером, сделанным устаревшим инструментом). Очевидно, что я пропускаю часть процесса.

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

Если вы посмотрите ближе к концу этот файл в функции RuntimeDyldELF::processRelocationRef, вы увидите некоторые созданные записи переадресации. Они также выполняют функции заглушки. Я подозреваю, что это недостающее звено для меня, но это так же ясно, как и грязь, и я не буду следовать ему даже немного.

Когда я выводю символы в каждой записи перемещения, у каждого из них есть привязка/видимость [Глобальная/Слабая/Локальная] [Функция/Объект] и значение, размер и раздел. Я знаю, где находится раздел, где находится символ, а значение является смещением к символу в этом разделе (или это виртуальный адрес?). Размер - это размер символа, но это важно? Может быть, глобальный/слабый/локальный полезен для определения, является ли это внутренним или внешним перемещением?

Может быть, эта таблица перемещений, о которой я говорю, - это фактически таблица символов для моего файла rel? Возможно, эта таблица обновляет значение символа от виртуального адреса до смещения раздела (так как это значение находится в перемещаемых файлах, а таблица символов в PLF в основном выполняется в исполняемом файле)?


Некоторые ресурсы:


Уф! Это зверь вопроса. Поздравляю, если вы сделали это так далеко.:) Спасибо заранее за любую помощь, которую вы можете мне дать.

4b9b3361

Ответ 1

Я наткнулся на этот вопрос и подумал, что он заслуживает ответа.

Имейте elf.h удобно. Вы можете найти его в Интернете.

Каждый раздел RELA содержит массив записей Elf32_Rela, как вы знаете, но также привязан к определенному другому разделу. r_offset является смещением в этом другом разделе (в данном случае - он работает по-разному для разделяемых библиотек). Вы увидите, что в заголовках разделов есть член sh_info. Это говорит вам, какой раздел. (Это индекс в таблицу заголовка раздела, как и следовало ожидать.)

"Символ", который вы получили от r_info, на самом деле является индексом в таблице символов, находящейся в другом разделе. Найдите член sh_link в заголовке раздела RELA.

В таблице символов указывается имя символа, который вы ищете, в виде элемента st_name из Elf32_Sym. st_name - это смещение в строку. Какой раздел, который есть, вы получаете от члена sh_link в заголовке раздела таблицы символов. Извините, если это запутывает.

Elf32_Shdr *sh_table = elf_image + ((Elf32_Ehdr *)elf_image)->e_shoff;
Elf32_Rela *relocs = elf_image + sh_table[relocation_section_index]->sh_offset;

unsigned section_to_modify_index = sh_table[relocation_section_index].sh_info;
char *to_modify = elf_image + sh_table[section_to_modify_index].sh_offset;

unsigned symbol_table_index = sh_table[relocation_section_index].sh_link;
Elf32_Sym *symbol_table = elf_image + sh_table[symbol_table_index].sh_offset;

unsigned string_table_index = sh_table[symbol_table].sh_link;
char *string_table = elf_image + sh_table[string_table_index].sh_offset;

Скажем, мы работаем с номером переезда i.

Elf32_Rela *rel = &relocs[i];
Elf32_Sym *sym = &symbol_table[ELF32_R_SYM(rel->r_info)];
char *symbol_name = string_table + sym->st_name;

Найдите адрес этого символа (скажем, что symbol_name == "printf" ). Конечное значение будет включено (to_modify + rel- > r_offset).

Что касается таблицы на стр. 79-83 в pdf-формате, которую вы связали, она сообщает нам, что поставить на этот адрес и сколько байтов писать. Очевидно, что адрес, который мы только что получили (printf в этом случае), является частью большинства из них. Это соответствует S в выражениях.

r_addend - это просто A. Иногда компилятору нужно добавить статическую константу в reloc, я думаю.

B - это базовый адрес общего объекта или 0 для исполняемых программ, поскольку они не перемещаются.

Итак, если ELF32_R_TYPE (rel- > r_info) == R_PPC_ADDR32, у нас есть S + A, а размер слова - слово32, поэтому мы получим:

*(uint32_t *)(to_modify + rel->r_offset) = address_of_printf + rel->r_addend;

... и мы успешно выполнили перемещение.

Я не могу помочь вам, когда дело доходит до #lo, #hi и т.д., а размеры слов - low14. Я ничего не знаю о КПП, но связанный pdf кажется достаточно разумным.

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

Я не уверен, ответил ли я на все ваши вопросы, но вы должны быть в состоянии увидеть, что ваш пример кода делает по крайней мере.

Ответ 2

Попробуйте ввести значение ELF. Он занимает около 60 страниц и значительно разъясняет вещи. Особенно это касается второй части, касающейся ссылки.