有一个广泛使用的Ruby gem(ruby-oci8),它使用C扩展来调用Oracle C库(Oracle Instant Client)。它创建了一个bundle(oci8lib_191.bundle),它调用Oracle库中的例程(libclntsh.dylib.11.1)。
但是,如果使用LDAP来解析其数据库名称,则会出现问题。客户端崩溃:
Assertion failed: (LDAP_VALID( ld )), function ldap_first_entry, file getentry.c, line 35.
Oracle库包含自己的LDAP例程。
nm /Applications/OracleInstantClient/libclntsh.dylib.11.1 | grep ldap_first_entry
0000000000f0fc50 T _ldap_first_entry
0000000000f15620 T _ora_ldap_first_entry
但是,我已经使用gdb验证了当客户端崩溃时,它会在OS X LDAP库的代码内崩溃。
(gdb) bt
#0 0x00007fff8403d212 in __pthread_kill ()
#1 0x00007fff8da78af4 in pthread_kill ()
#2 0x00007fff8dabcdce in abort ()
#3 0x00007fff8dabde2a in __assert_rtn ()
#4 0x00007fff86e233e2 in ldap_first_entry ()
(gdb) info symbol 0x00007fff86e233e2
ldap_first_entry + 98 in section LC_SEGMENT.__TEXT.__text of /System/Library/Frameworks/LDAP.framework/Versions/A/LDAP
所以,显然正在发生的事情是当bundle试图调用ldap_first_entry()时,它与OS X版本而不是Oracle的自定义版本(libclntsh.dylib.11.1)相关联。
我的第一个想法是在动态库存在时使用用于链接静态库的相同技巧。也就是说,将绝对路径传递给库。但是,如您所见,这会导致错误:
gcc -dynamic -bundle -o oci8lib_191.bundle oci8lib.o env.o error.o oci8.o ocihandle.o connection_pool.o stmt.o bind.o metadata.o attr.o lob.o oradate.o ocinumber.o ocidatetime.o object.o apiwrap.o encoding.o oranumber_util.o thread_util.o -L. -L/usr/local/lib -L. -L/usr/local/lib -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress -Wl,-flat_namespace -L/Applications/OracleInstantClient -l/Applications/OracleInstantClient/libclntsh.dylib.11.1 -lpthread -ldl -lobjc
ld: library not found for -l/Applications/OracleInstantClient/libclntsh.dylib.11.1
该库确实存在于列出的路径中:
xanadu:~ wwilliam$ file /Applications/OracleInstantClient/libclntsh.dylib.11.1
/Applications/OracleInstantClient/libclntsh.dylib.11.1: Mach-O 64-bit dynamically linked shared library x86_64
我也试过-rpath:
gcc -dynamic -bundle -o oci8lib_191.bundle oci8lib.o env.o error.o oci8.o ocihandle.o connection_pool.o stmt.o bind.o metadata.o attr.o lob.o oradate.o ocinumber.o ocidatetime.o object.o apiwrap.o encoding.o oranumber_util.o thread_util.o -L. -L/usr/local/lib -L. -L/usr/local/lib -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress -Wl,-flat_namespace -L/Applications/OracleInstantClient -Wl,-rpath,/Applications/OracleInstantClient -lclntsh -lpthread -ldl -lobjc
仅供参考,DYLD_LIBRARY_PATH已设置:
DYLD_LIBRARY_PATH=:/Applications/OracleInstantClient
那么,如何确保bundle链接到Oracle版本的ldap_first_entry()?
我正在使用OS X 10.8.2和Xcode版本4.6(4H127)。
答案 0 :(得分:0)
更新:
我与提问者邮寄了,并且在他的应用程序顶部添加了“require'oci8'”,崩溃消失了。
根据DYLD_PRINT_LIBRARIES = 1和DYLD_PRINT_BINDINGS = 1输出的日志,此问题是由函数插入引起的。 libclntsh.dylib和OS X LDAP库导出_ldap_first_entry,其实现方式不同。当在libclntsh.dylib之前将OS X LDAP库加载到进程内存中并且libclntsh.dylib中的函数尝试在libclntsh.dylib中使用_ldap_first_entry时,它会在OS X LDAP库中使用_ldap_first_entry。 一般情况下,它可能发生在Unix(OS X除外)上,只有当一个库与flat_namespace链接时才会发生在OS X上。
IMO,他没有明确使用OS X LDAP库。他使用了一种授权模块,它在内部依赖于库。
其余的没有改变。
我有个主意。但我还没有测试过它。
IMO,ruby不直接使用OS X版本库。像ruby-ldap这样的扩展库使用它。如果是这样,在扩展库可以解决问题之前“require'oci8'”。
require 'oci8' # This must be before any other extension libraries using LDAP.
require 'ldap' # for example
但它引起了另一个问题。当ruby-ldap库尝试使用OS X版本_ldap_first_entry时,将调用Oracle版本_ldap_first_entry并且进程崩溃。您需要自定义oci8以避免它。
在ext / oci8 / oci8lib.c中更改DLOPEN_FLAG
,如下所示,
#define DLOPEN_FLAG (RTLD_NOW|RTLD_LOCAL)
然后安装如下
gem build ruby-oci8.gemspec
gem install ruby-oci8-2.1.5.gem -- --with-runtime-check
并使用它。
require 'oci8' # This must be before any other extension libraries using LDAP.
require 'ldap'
RTLD_LOCAL
在其他扩展库中隐藏libclntsh.dylib中的符号。
RTLD_NOW
立即解析符号并阻止libclntsh.dylib在后续加载的库中使用符号。
--with-runtime-check
迫使ruby-oci8使用dlopen()。
我希望OS X dlopen()规范与Linux相同并且有效。
答案 1 :(得分:0)
你需要使用'otool -L< dylib的路径>'转储您的可执行文件或dylib所依赖的dylib。它们应该构建为通过相对路径('@executable_path /../ Frameworks / *'(或@loader_path或@rpath))而不是系统路径来访问bundle框架。
请参阅dyld手册页的“DYNAMIC LIBRARY LOADING”部分:https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man1/dyld.1.html
dylib的install_path用于告诉exec& dylibs链接到它应该期望在运行时找到它的地方。您可以(尝试)通过install_name_tool命令行工具更改dylib的install_path。 (我说'尝试',因为它可能会无声地失败,因此请务必检查后续文字是否确实有效。)