微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 libtool 实现向后兼容的 Linux 共享库版本控制

如何解决使用 libtool 实现向后兼容的 Linux 共享库版本控制

我维护一个共享库,它使用 libtool,(主要)在 Linux 上运行并输出以下文件

lrwxrwxrwx  1 root root    18 jun 10 16:12 libxxx.so -> libxxx.so.0.0.1
lrwxrwxrwx  1 root root    18 jun 10 16:12 libxxx.so.0 -> libxxx.so.0.0.1
-rwxr-xr-x  1 root root  760K jun 10 16:12 libxxx.so.0.0.1

libtool version-info 当前为 0:1:0

我想向库的 API/ABI 添加功能,而不是删除修改任何现有的 API/ABI,以便:

  • 生成一个库,该库仍然可以由针对该库的旧版本构建的二进制文件使用。因此,新库可作为替代品,无需重建旧二进制文件
  • 当未找到包含新 API 的库时,针对新库构建并使用新 API/ABI 的二进制文件将在加载阶段失败。

如何使用 libtool 实现此目的?

我尝试按照建议将版本信息设置为 1:0:1 here

使用旧版本的程序可能会使用新版本作为直接替换,但使用新版本的程序可能会使用前一个版本中不存在的 API。换句话说,如果在运行时链接到旧版本,链接到新版本的程序可能会失败并显示“未解析的符号”:将修订版设置为 0,提高当前和年龄。

这会产生以下文件

rwxrwxrwx  1 root root    18 jun 10 16:24 libxxx.so -> libxxx.so.0.1.0
lrwxrwxrwx  1 root root    18 jun 10 16:24 libxxx.so.0 -> libxxx.so.0.1.0
-rwxr-xr-x  1 root root  760K jun 10 16:24 libxxx.so.0.1.0

但是,如果在运行时输入的代码路径包含旧库中不存在的新符号之一,则针对新库构建的二进制文件将加载并在运行时失败并显示 undefined symbol 错误

>

我可以将 SONAME 增加libxxx.so.1,但随后我破坏了针对旧版本构建的二进制文件,而新版本仍然兼容。

解决方法

但是,如果在运行时输入的代码路径包含旧库中不存在的新符号之一,则针对新库构建的二进制文件将加载并在运行时失败并显示未定义符号错误。

TL;DR:我认为没有一种解决方案可以现在达到预期的结果(除非您已经在使用版本化符号),但是您现在可以让它变得更好,并且下次可以完全修复它。


这是一个由 GNU symbol version extension 解决的问题。

最好有个例子。初始设置:

// foo_v1.c
int foo() { return 42; }

// main_v1.c
int main() { return foo(); }

gcc -fPIC -shared -o foo.so foo_v1.c
gcc -w main_v1.c ./foo.so -o main_v1

./main_v1; echo $?
42

现在让我们修改 foo.c 使其成为一个替代品,但具有新功能:

mv foo.so foo.so.v1

// foo_v2.c
int foo() { return 42; }
int bar() { return 24; }

gcc -fPIC -shared -o foo.so foo_v2.c

./main_v1; echo $?
42

一切仍然有效(如预期)。现在让我们构建需要新函数的 main_v2

// main_v2.c
int main() { return foo() - bar(); }

gcc -w main_v2.c ./foo.so -o main_v2

./main_v2; echo $?
18

一切仍然有效。现在我们打破常规:

mv foo.so foo.so.v2
cp foo.so.v1 foo.so
./main_v1; echo $?
42
./main_v2
./main_v2: symbol lookup error: ./main_v2: undefined symbol: bar

瞧:我们在运行时出现故障,而不是在加载时出现(预期的)故障。 (这在上面的输出中实际上并不可见,但可以通过在 printf 中添加例如 main 来验证。)

解决方案: 让我们给 foo.so 一个版本脚本:

// foo.lds
FOO_v2 {
  global: bar;
};

gcc -fPIC -shared -o foo.so foo_v2.c -Wl,--version-script=foo.lds
gcc -w main_v2.c ./foo.so -o main_v2a

./main_v1; echo $?
42
./main_v2a; echo $?
18

正如我们所见,新版本的库仍然可以正常工作。

但是当我们对旧的 main_v2a 运行新的 foo.so 时会发生什么?

mv foo.so foo.so.v2
cp foo.so.v1 foo.so
./main_v2

./main_v2a: ./foo.so: no version information available (required by ./main_v2a)
./main_v2a: symbol lookup error: ./main_v2a: undefined symbol: bar,version FOO_v2

这稍微好一点:失败仍然在运行时发生,但加载器确实提到了FOO_v2,暗示这是由某种“版本太旧”引起的”问题。

加载器在加载时没有失败的原因是(旧的)foo.so 没有任何版本信息。

如果您现在重复此过程,使用新函数创建 FOO_v3,并尝试针对 main_v3 版本的库运行 foo.so.v2,您将在加载时失败:

// foo_v3.c
int foo() { return 42; }
int bar() { return 24; }
int baz() { return 12; }

// foo_v3.lds
FOO_v2 {
  global: bar;
};
FOO_v3 {
  global: baz;
} FOO_v2;

// main_v3.c
int main() { return foo() - bar() - baz(); }

gcc -fPIC -shared -o foo.so foo_v3.c -Wl,--version-script=foo_v3.lds
gcc -w main_v3.c ./foo.so -o main_v3

./main_v3; echo $?
6

现在让我们对 main_v3 运行 foo.so.v2

cp foo.so.v2 foo.so

./main_v3
./main_v3: ./foo.so: version `FOO_v3' not found (required by ./main_v3)

这一次,失败发生在加载时。 QED。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。