1

我正在尝试使用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.soProtobuf.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文件均由第三方提供。我没有源代码,因此无法更改这些文件。函数签名由文档提供。


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, FUTEX_WAKE_PRIVATE, 2147483647) = 0
write(2, "! Failed to load library: ", 26! Failed to load library: ) = 26
write(2, "/home/username/.rvm/rubies/ruby-2."..., 50/home/username/.rvm/rubies/ruby-2.6.5/bin/Network.so) = 50
write(2, ", error: ", 9, error: )                = 9
write(2, "/home/username/.rvm/rubies/ruby-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)问题的人。您可以从这里下载必要的文件。这给了你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。在同一目录中创建一个 Ruby 脚本(带有问题代码)并运行该脚本。Protobuf.soNetwork.so

4

2 回答 2

1

因为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

一切都应该工作。

于 2021-01-26T19:03:08.160 回答
0

如果我们阅读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.

于 2021-01-22T22:45:31.720 回答