3

有一个广泛使用的 Ruby gem (ruby-oci8),它使用 C 扩展来调用 Oracle C 库 (Oracle Instant Client)。它创建一个调用 Oracle 库 (libclntsh.dylib.11.1) 中的例程的包 (oci8lib_191.bundle)。

但是,如果使用 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

因此,显然发生的事情是当捆绑包尝试调用 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()链接呢?

我正在使用带有 Xcode 版本 4.6 (4H127) 的 OS X 10.8.2。

4

2 回答 2

1

更新:

我与提问者一起邮寄,通过在他的应用程序顶部添加“需要'oci8'”,崩溃消失了。

根据 DYLD_PRINT_LIBRARIES=1 和 DYLD_PRINT_BINDINGS=1 输出的日志,这个问题是函数插入造成的。libclntsh.dylib 和 OS X LDAP 库导出实现不同的 _ldap_first_entry。当 OS X LDAP 库在 libclntsh.dylib 之前加载到进程内存中并且 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 之类的扩展库使用它。如果是这样,在扩展库之前“需要'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 相同并且可以正常工作。

于 2013-03-10T13:08:00.590 回答
0

您需要使用 'otool -L < path to dylib >' 转储可执行文件或 dylib 所依赖的 dylib。它们应该被构建为通过相对路径('@executable_path/../Frameworks/*'(或@loader_path 或@rpath))而不是系统路径来访问捆绑框架。

请参阅 dyld 手册页的“动态库加载”部分:https ://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man1/dyld.1.html

dylib 的 install_path 用于告诉与它链接的 exec 和 dylib 在运行时应该在哪里找到它。您可以(尝试)通过 install_name_tool 命令行工具更改 dylib 的 install_path。(我说“尝试”是因为它可能会默默地失败,所以总是检查后记,看看它是否真的有效。)

于 2013-03-12T21:00:10.867 回答