链接到OS X上的特定Oracle即时客户端动态库

时间:2013-03-04 16:03:08

标签: ruby xcode macos oracle oci8

有一个广泛使用的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)。

2 个答案:

答案 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。 (我说'尝试',因为它可能会无声地失败,因此请务必检查后续文字是否确实有效。)