сборка динамических библиотек из Ocaml

Landstreicher

Нужно собрать динамическую библиотеку (.so-файл) из исходников на языке OCaml. Требуется обеспечить работоспособность на x86 и x86-64.
Все делается в соответствии с руководством http://caml.inria.fr/pub/docs/manual-ocaml/manual032.html
Однако, при сборке выдается ошибка:
ld: caml_lib.o: relocation R_X86_64_32S against `caml_curry2_1' can not be used when making a shared object; recompile with -fPIC
Объектный файл caml_lib.o создается с помощью ocamlopt, который компилирует программу в код на ассемблере, а затем вызывает ассемблер as. Если с помощью опции -S попросить его оставить временный .S-файл, то видно, что в нем попадаются команды типа call <имя_функции>, которые приводят к relocation R_X86_64_32S, которые не могут быть в .so-файлах.
Компилятор C при генерации ассемблерных файлов вставляет специальные указания, например, call <имя_функции>@PLT, которые приводят к другому типу relocation: R_X86_64_PLT32 (для кода) и R_X86_64_GOTPCREL (для данных).
Кто-нибудь сталкивался с этой проблемой? Или, может быть, у кого-то есть идеи, как это решить?
Хотелось бы понять, в чем разница между разными типами relocation.

Landstreicher

С Haskell еще лучше:

$ cat adder.hs
module Adder where

f :: IO
f = putStrLn "hello"

$ ghc -c -fPIC adder.hs
ghc-6.6.1: panic! (the 'impossible' happened)
(GHC version 6.6.1 for x86_64-unknown-linux):
initializePicBase

Please report this as a GHC bug: http://www.haskell.org/ghc/reportabug

Читаем сырцы:

initializePicBase :: Reg -> [NatCmmTop] -> NatM [NatCmmTop]

#if darwin_TARGET_OS
....
#elif powerpc_TARGET_ARCH && linux_TARGET_OS
....
#elif i386_TARGET_ARCH && linux_TARGET_OS
....
#else
initializePicBase picReg proc = panic "initializePicBase"
#endif

x86_64_TARGET_ARCH отсутствует напрочь (вне зависимости от _TARGET_OS)

Landstreicher

up!
чего все молчат-то?

pilot

Кто-нибудь сталкивался с этой проблемой?
Ага:
http://caml.inria.fr/pub/ml-archives/caml-list/2005/08/9fbbc...
http://caml.inria.fr/pub/ml-archives/caml-list/2005/08/0904e...
Только их что-то никто не спас

Marinavo_0507

напиши скрипт, который call будет заменять на правильные команды

Landstreicher

> напиши скрипт, который call будет заменять на правильные команды
Не совсем, понимаю как правильно заменять.
Попробовал в паре мест заменить func на PLT. Собралось. Но при запуске падает в segfault. По всей видимости, такой простой замены не хватает. Может кто-то объяснит, как работают relocation-ы, и что делает ассемблер, когда обрабатывает команду 'call PLT' ?

Papazyan

а -fPIC впехнуть нельзя?

Landstreicher

Update.
Почитал документацию про x86-64 ABI. Понял, что обращение к переменным в PIC требует дополнительного уровня косвенности. То есть, код
movq %rbx, varname(%rip)
меняется не на
movq %rbx, GOTPCREL(%rip)
а на следующее
movq GOTPCREL(%rip %rax
movq %rbx, (%rax)
Теперь понятно, почему мои предыдущие попытки приводят к segfault.
Я попробовал позаменять так часть вызовов — программа продолжила работать правильно. Можно дозаменять остальные и попробовать собрать shared библиотеку.
НО:
Каждое такое обращение к переменной требует кучу дополнительных действий.
Например, выделение блока памяти небольшого размера устроено так:
FUNCTION(caml_alloc1)
subq $16, %r15
cmpq caml_young_limit(%rip %r15
jb .L100
ret
.L100:
// вызвать GC
Текущий указатель на кучу лежит в регистре %r15. Если начать вставлять операции по типу описанных выше, то получится что-то типа:
push %r12 // %r12 - какой-нибудь временный регистр
subq $16, %r15
movq caml_yGOTPCREL(%rip %r12
cmpq %(r12 %r15
jb .L100
pop %r12
ret
Такое изменение замедлит функцию caml_alloc1 в несколько раз, чего сильно не хотелось бы.
Как можно бороться с такой проблемой?
Я слышал, что в некоторых языках/компиляторах выделяют отдельный регистр, в который при инициализации записывают адрес GOT, а обычный код его не трогает. При этом все ссылки на переменные записываются относительно него, например в виде GOTOFF(%r11).
Можно попробовать применить этот способ и в Ocaml. Фишка в том, что у него собственный кодогенератор, который сам распределяет переменные по регистрам. Причем по умолчанию он юзает все доступные регистры (что весьма логично). Соответственно, надо хачить этот кодогенератор, чтобы оставлял один регистр свободным.
С учетом этого, что лучше сделать: вставить вызовы push/pop/GOTPCREL везде или выделить отдельный регистр для GOT?
Как делают другие языки, например, C, C++, Java, C#, Lisp, Scheme ?

Landstreicher

По поводу Haskell: вопрос ответ еще сцылка.
Будем надеяться, что товарищей все получится. Конец лета — не за горами.

Landstreicher

Скачал текущую версию GHC из darcs-репозитория (GHC 6.7.20070609).
Стало значительно лучше. На приведенном выше примере генерит код следующего вида:

movq stg_CAF_BLACgotpcrel(%rip%rax
movq %rax,-8(%r12)
movq %rbx,%rdi
movl $0,%eax
call plt
leaq -8(%r12%rax
movq %rax,8(%rbx)
movq stg_IND_Sgotpcrel(%rip%rax
movq %rax%rbx)
movq stg_upd_gotpcrel(%rip%rax
movq %rax,-16(%rbp)

В большинстве мест @gotpcrel и @plt расставлены, нереализованные ранее функции содержат теперь осмысленный код. Правда, остались еще неправильные relacation в данных, но в коде вроде бы все ок. Думаю, скоро сделают.
Оставить комментарий
Имя или ник:
Комментарий: