如何解决有没有办法改变小提琴的工作目录?
我正在尝试使用 Fiddle 在 Ruby 中加载 C 共享库。
这是一个最小的例子:
require 'fiddle'
require 'fiddle/import'
module Era
extend Fiddle::Importer
dlload './ServerApi.so'
extern 'int era_init_lib()'
extern 'void era_deinit_lib()'
extern 'int era_process_request(const char* request,char** response)'
extern 'void era_free(char* response)'
end
Era.era_init_lib
begin
# ...
ensure
Era.era_deinit_lib
end
共享库加载没有问题。但是,当我调用 Era.era_init_lib
时,它会尝试加载其他库(Network.so
和 Protobuf.so
)。这些文件位于当前工作目录中(与 ServerApi.so
位于同一目录中)。
但是,当我尝试执行上面的代码时,我收到以下错误:
! Failed to load library: /home/username/.rvm/rubies/ruby-2.6.5/bin/Network.so,error: /home/username/.rvm/rubies/ruby-2.6.5/bin/Network.so: cannot open shared object file: No such file or directory
如果我将文件放在错误位置,说明一切正常。
我的猜测是fiddle的C工作目录与Ruby工作目录不同。我想将项目文件保留在项目中,而不是在Ruby安装目录中。
如何使用项目文件夹中的 Network.so
?
所有 *.so
文件均由第三方提供。我没有源代码,因此无法更改这些文件。函数签名由 documentation 提供。
在 Network.so
中搜索 strace
会得到以下结果:
readlink("/proc/self/exe","/home/username/.rvm/rubies/ruby-2."...,4096) = 44
openat(AT_FDCWD,"/home/username/.rvm/rubies/ruby-2.6.5/bin/Network.so",O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
futex(0x7fcc16666d90,FUTEX_WAKE_PRIVATE,2147483647) = 0
futex(0x7fcc16b44520,2147483647) = 0
write(2,"! Failed to load library: ",26! Failed to load library: ) = 26
write(2,50/home/username/.rvm/rubies/ruby-2.6.5/bin/Network.so) = 50
write(2,",error: ",9,error: ) = 9
write(2,109/home/username/.rvm/rubies/ruby-2.6.5/bin/Network.so: cannot open shared object file: No such file or directory) = 109
write(2,"\n",1) = 1
我还编写了一个 C 脚本,它执行相同的操作,当将文件放入同一目录时,该脚本运行良好。所以这可能是库的错误,我假设它检查了当前正在运行的程序的位置,然后尝试从该文件夹加载库。这可以解释作为 Ruby 脚本运行时的行为(因为它作为 Ruby 程序的一部分运行),而 C 二进制文件独立运行。
对于那些想要重新创建 (Linux) 问题的人。您可以从 here 下载必要的文件。这为您提供了 server-linux-x86_64.sh
文件。
支持的发行版有:Suse、Ubuntu、Debian、Red Hat 和 CentOS,但其他发行版也可能正常运行。
您可以运行安装程序,它会将文件放在 /opt/eset/RemoteAdministrator/Server
中。或者,假设大多数人不想安装完整的应用程序,您可以运行以下命令:
sed '1,/^# Start of TAR\.GZ file #$/d' server-linux-x86_64.sh | sed '1d' > server-linux-x86_64.tar.gz
从 .sh 文件中删除所有安装程序指令,只留下二进制 .tar.gz 数据,将其写入 server-linux-x86_64.tar.gz
。
将文件 ServerApi.so
、Protobuf.so
和 Network.so
复制到您喜欢的目录中。在同一目录中创建一个 Ruby 脚本(带有问题代码)并运行该脚本。
解决方法
因为ServerApi.so
会检查/proc/self/exe
是否为后续所有要加载的文件的位置,而通过正常方式修改这个目标是非常困难的,所以只修改ServerApi.so
本身更容易以便它使用除 proc
之外的其他东西作为源。
如果我们运行 strings ServerApi.so
,我们可以验证要检查的位置是否存储在 ServerApi.so
中的字符串中:
strings ServerApi.so | grep 'proc/self/exe'
B/proc/self/exe
所以现在我们需要做的就是将此字符串修改为其他适合我们的内容。
修改字符串的最简单方法是将其替换为与原始长度完全相同的内容。这样我们就不必担心更改字符串末尾的零填充或意外更改 ServerApi.so
的总大小。
在这里我们可以看到一个合适的候选人可能是/tmp/scriptexe
:
/proc/self/exe
/tmp/scriptexe <- same length
所以让我们这样做:
sed -e 's/proc\/self\/exe/tmp\/scriptexe/' ServerApi.so > ServerApi_Mod.so
现在我们可以验证更改:
strings ServerApi_Mod.so | grep scriptexe
B/tmp/scriptexe
接下来我们需要创建 /tmp/scriptexe
来实际指向我们的 Ruby 脚本:
ln -s /the/full/path/to/our/ruby/script.rb /tmp/scriptexe
然后我们修改我们的脚本:
dlload './ServerApi_Mod.so
现在我们可以正常运行了:
ruby script.rb
一切正常。
,如果我们读取 strace
输出,我们会看到库从 /proc/self/exe
获取当前可执行位置,然后从那里搜索后续库。
/proc/self/exe
不容易修改,但通过使用指向当前目录中 Ruby 可执行文件的硬链接,我们可以诱使它指向一个新文件夹。
问题是制作硬链接需要 root。
无论如何,这里有一个独立的解决方案(请注意,它会在您第一次运行时要求输入 root 密码,以创建硬链接)。
将其放在脚本的顶部:
# Obtain path to current executable
exe = File.readlink("/proc/self/exe")
# Check if we are running the hard-liked version
if !exe.match /localruby/
if !File.exist?('localruby')
# Create a hard link to the current Ruby exe using sudo
system("sudo ln #{exe} localruby")
end
puts "Restarting..."
# In order to prevent infinite busy loop in case of some mishap
sleep 1
# Rerun self using the hard-linked Ruby executable.
# This will make /proc/self/exe point to the hard-link,which then
# allows the ESET library to search for .so files in current folder.
exec('./localruby',File.expand_path(__FILE__))
end
require 'fiddle'
require 'fiddle/import'
# ...rest of your script goes here...
无需任何额外 Ruby 代码的简单解决方案是手动创建硬链接,然后始终使用 ./localruby myscript.rb
运行脚本,而不是使用普通的 ruby myscript.rb
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。