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 в основном выполняется в исполняемом файле)?
Некоторые ресурсы:
- blog on relocations: http://eli.thegreenplace.net/2011/08/25/load-time-relocation-of-shared-libraries/
- упоминает коды операций в конце: http://wiki.netbsd.org/examples/elf_executables_for_powerpc/
- мой связанный без ответа вопрос: Рефредация ELF Relocation
Уф! Это зверь вопроса. Поздравляю, если вы сделали это так далеко.:) Спасибо заранее за любую помощь, которую вы можете мне дать.