发展周期(五):使用glibc动态链接和加载

原文链接:https://developer.chrome.com/native-client/devguide/devcycle/dynamic-loading

注意:已针对ChromeOS以外的平台公布了此处所述技术的弃用。
请访问我们的 迁移指南 了解详情。


使用glibc动态链接和加载

Portable Native Client目前仅支持静态链接,唯一可用的C库是newlib。此页面仅对Native Client有效,但PNaCl最终将支持某种形式的动态链接。

本文档介绍如何使用Native Client SDK中的glibc库创建和部署动态链接和加载的应用程序。在阅读本文档之前,我们建议您阅读构建本机客户端模块

C标准库:glibc和newlib

Native Client SDK附带两个C标准库 - glibc和newlib。这些库在下表中描述。

图书馆 链接 执照

glibc的

用于C编程语言的POSIX标准运行时库的GNU实现。glibc专为便携性和性能而设计,是C库中最受欢迎的实现之一。它由一组相互依赖的库组成,包括libc,libpthreads,libdl等。有关glibc的文档,常见问题解答和其他信息,请参阅GLIBC

动态或静态 GNU较宽松通用公共许可证(LGPL)

newlib

newlib是一个旨在用于嵌入式系统的C库。像glibc一样,newlib是几个图书馆的集合体。它可以在BSD类型的免费软件许可证下使用,这通常使其更适合在商业闭源应用程序中静态链接。有关文档,常见问题解答以及有关newlib的其他信息,请参阅newlib

静态的 Berkeley Software Distribution(BSD)类型的免费软件许可证

对于专有(闭源)应用程序,您的选项是静态链接到newlib,或动态链接到glibc。我们建议动态链接到glibc,原因如下:

  • glibc库分布广泛(它包含在Linux发行版中),因此它成熟,强化,功能丰富。您的代码更有可能与glibc一起开箱即用。
  • 如果您可以构建应用程序以推迟加载与用户初始交互不需要的代码,则动态加载可以为您的应用程序提供巨大的性能优势。将这些代码放在共享库中并在运行时加载库需要一些工作,但是回报通常是值得的。在将来的版本中,Chrome还可能支持在应用程序之间缓存常见的动态链接库,例如libc.so。这可以显着减少下载大小并提供进一步的潜在性能优势(例如,hello_world示例只需要下载大约30KB的.nexe文件,而不是下载的.nexe文件和几个库。 1.5MB)。

Native Client对动态链接和加载的支持基于glibc。因此, 如果您的Native Client应用程序必须动态链接和加载代码(例如,由于许可考虑因素),我们建议您使用glibc库。

免责声明:

  • 上述任何内容均不构成法律建议,或描述您为了符合LGPL或newlib许可而需要履行的法律义务。以上描述仅是对newlib和glibc之间差异的技术解释,以及您必须在两个库之间做出的选择。

笔记:

  • 很少使用与glibc的静态链接。请谨慎使用此功能。
  • Native Client中的标准C ++运行时由libstdc ++提供; 这个库独立于glibc并在其上分层。由于许可限制,libstdc ++必须静态链接以用于商业用途,即使应用程序的其余部分是动态链接的。

SDK工具链

Native Client SDK包含多个工具链,这些工具链由目标体系结构和C库区分 :

Target architecture C library Toolchain directory
x86 glibc toolchain/<platform>_x86_glibc
ARM glibc toolchain/<platform>_arm_glibc
x86 newlib toolchain/<platform>_pnacl
ARM newlib toolchain/<platform>_pnacl
PNaCl newlib toolchain/<platform>_pnacl

在上面列出的目录中,<platform>是您的开发机器的平台(即win,mac或linux)。例如,在Windows SDK中,使用glibc的x86工具链就在toolchain/win_x86_glibc

注意: PNaCl工具链目前仅限于newlib。

要在应用程序中使用glibc库和动态链接,必须 使用glibc工具链。请注意,您必须使用一个工具链在应用程序中构建所有代码。来自多个工具链的代码不能混用。

指定和提供共享库

newlib和glibc应用程序之间的一个重要区别是glibc应用程序必须明确列出和部署它们使用的共享库。

在桌面环境中,当用户启动动态链接的应用程序时,操作系统的程序加载器通过从可执行文件头读取显式的模块间依赖关系来确定应用程序所需的库集,并将所需的库加载到该地址空间中。申请流程。通常,所需的库将作为应用程序安装过程的一部分安装在系统上。桌面应用程序开发人员通常不了解或考虑应用程序所需的库,因为这些详细信息由用户的操作系统处理。

在Native Client沙箱中,动态链接不能以相同的方式依赖于操作系统或本地文件系统。相反,应用程序开发人员必须识别应用程序所需的库集,在Native Client 清单文件中列出这些库,并将库与应用程序一起部署。下面提供了有关如何构建动态链接的Native Client应用程序,生成Native Client清单(.nmf)文件以及部署应用程序的说明。

构建动态链接的应用程序

使用glibc工具链构建的应用程序将默认动态链接。在运行时使用加载共享库的应用程序dlopen() 必须与libdl库(-ldl)链接。

与其他基于gcc的工具链一样,构建NaCl的动态库通常通过链接-shared标志并使用标志进行编译来完成-fPIC。当使用SO_RULEMakefile规则时,SDK构建系统将自动执行此操作。

Native Client SDK包含一个演示如何构建共享库的示例,以及如何使用该dlopen()接口在运行时加载该库(在应用程序运行之后)。许多应用程序在启动时而不是在运行时加载和链接共享库,因此不使用该dlopen()接口。SDK示例仍然具有指导性,因为它演示了如何使用x86 glibc工具链构建Native Client模块(.nexe文件)和共享库(.so文件),以及如何为glibc应用程序生成Native Client清单文件。

SDK示例位于其中examples/tutorial/dlopen,包含三个C ++文件:

eightball.cc

此文件实现了该函数Magic8Ball(),该函数用于为用户问题提供异想天开的答案。此文件被编译为一个名为的共享库libeightball.so。该库包含在.nmf文件中,因此可以直接加载dlopen()

reverse.cc

此文件实现该函数Reverse(),该函数返回传递给它的字符串的反转副本。此文件被编译为一个名为的共享库libreverse.so。该库包含在.nmf文件中,并使用nacl_io库通过http mount加载。

dlopen.cc

此文件实现Native Client模块,该模块加载两个共享库并处理与JavaScript的通信。该文件被编译为Native Client可执行文件(.nexe)。

make在dlopen目录中运行以查看Makefile执行的命令,以构建x86 32位和64位.nexe和.so文件,并生成.nmf文件。这些命令如下所述。

注意: SDK中大多数示例的Makefile使用多个工具链(x86 newlib,x86 glibc,ARM newlib,ARM glibc和PNaCl)构建示例。除了一些例外(在发行说明中列出),在每个示例的目录中运行“make”使用SDK工具链构建示例的多个版本。dlopen示例是其中一个例外 - 它仅使用x86 glibc工具链构建,因为它是目前唯一支持glibc并因此支持动态链接和加载的工具链。查看Makefiles示例和生成的.nmf文件,了解有关如何构建动态链接应用程序的详细信息。

为动态链接的应用程序生成Native Client清单文件

Native Client清单文件指定要运行的可执行文件的名称,还必须指定应用程序直接依赖的任何共享库。对于间接依赖关系(例如通过dlopen()其打开的 库),在清单文件中列出库也很方便。但是,可以在运行时加载清单中未提及的任意共享库,方法是使用nacl_io库 挂载包含共享库的文件系统,然后允许dlopen()访问它们。

在这个例子中,我们演示了直接从manifest文件(libeightball.so)加载和通过http mount(libreverse.so)间接加载。

查看dlopen示例中的清单文件,了解glibc样式清单文件的结构。(make如果尚未执行此操作,请在dlopen目录中运行以生成清单文件。)以下摘录自dlopen.nmf

{
  "files": {
    "libeightball.so": {
      "x86-64": {
        "url": "lib64/libeightball.so"
      },
      "x86-32": {
        "url": "lib32/libeightball.so"
      }
    },
    "libstdc++.so.6": {
      "x86-64": {
        "url": "lib64/libstdc++.so.6"
      },
      "x86-32": {
        "url": "lib32/libstdc++.so.6"
      }
    },
    "libppapi_cpp.so": {
      "x86-64": {
        "url": "lib64/libppapi_cpp.so"
      },
      "x86-32": {
        "url": "lib32/libppapi_cpp.so"
      }
    },
... etc.

在大多数情况下,您可以使用create_nmf.pySDK中的脚本为应用程序生成清单文件。该脚本位于tools目录(例如pepper_28/tools)中。

dlopen示例中的Makefile使用NMF_RULESDK构建系统提供的内容自动生成清单。Running make V=1将显示用于生成nmf的完整命令行:

create_nmf.py -o dlopen.nmf glibc/Release/dlopen_x86_32.nexe \
   glibc/Release/dlopen_x86_64.nexe glibc/Release/libeightball_x86_32.so \
   glibc/Release/libeightball_x86_64.so  -s ./glibc/Release \
   -n libeightball_x86_32.so,libeightball.so \
   -n libeightball_x86_64.so,libeightball.so

运行python create_nmf.py --help以查看命令行标志的完整描述。下面描述了一些重要的标志。

-s 目录

使用目录来暂存库(库被添加到lib32和 lib64子文件夹)

-L 目录

目录添加到库搜索路径。默认搜索路径已包含工具链和SDK库目录。

注意:create_nmf脚本只能自动检测显式共享库依赖项(例如,使用编译器/链接器的-l标志指定的依赖项)。如果你想在运行时包含你想要dlopen()的库,你必须在你的调用中明确地列出它们 create_nmf

作为使用的替代方法create_nmf,可以使用诸如以下工具手动计算共享库依赖项列表objdump_

部署动态链接的应用程序

如上所述,应用程序的清单文件必须明确列出应用程序直接依赖的所有可执行代码模块,包括来自应用程序本身(.nexe.so文件)的模块,来自Native Client SDK的模块(例如libppapi_cpp.so),以及来自 Webports或应用程序使用的中间件系统。您必须提供所有这些模块作为应用程序部署过程的一部分。

分发您的应用程序中所述,部署Chrome应用程序有两种基本方法:

  • 托管应用程序:所有模块一起托管在您选择的Web服务器上
  • 打包应用程序:所有模块都打包到一个文件中,托管在Chrome Web Store中,然后下载到用户的计算机上

网上商店文档包含一个方便的指南,可帮助您选择使用哪个

您必须部署应用程序清单文件中列出的所有模块,以用于托管应用程序或打包的应用程序案例。对于托管应用程序,您必须将模块上载到Web服务器。对于打包的应用程序,您必须将这些模块包含在应用程序的Chrome Web Store .crx文件中。模块应使用与Native Client清单文件中的URL /名称一致的URL /名称,并相对于清单文件的位置进行命名。请记住,清单文件中指定的某些库可能位于您使用-L选项指定的目录中 create_nmf.py。您可以自由重命名/重新排列Native Client清单文件引用的文件和目录,只要模块在清单文件指示的位置可用即可。如果移动或重命名模块,则可能更容易重新运行create_nmf.py以生成新的清单文件,而不是编辑原始清单文件。对于托管应用程序,您可以通过查看托管测试部署的Web服务器的请求日志来检查测试期间的名称不匹配。

在运行时打开共享库

Native Client支持一个版本的POSIX标准dlopen()接口,用于在应用程序运行后显式打开库。调用dlopen()可能导致库下载,并自动加载指定库所需的所有库。

警告:由于dlopen()可能会阻塞,因此必须先dlopen()关闭应用程序的主线程。dlopen()来自主线程的初始调用 将始终在Native Client的当前实现中失败。

打开库的最佳实践dlopen()是在应用程序初始化期间使用工作线程异步预加载库,以便在需要时可以使用库。您可以致电dlopen()第二时候,你需要使用库-根据规范要求,后续调用dlopen()返回的句柄以前加载库。请注意,只有dlclose()在不再需要库时才应该调用以关闭库; 否则,后续调用dlopen()可能导致再次获取库。

SDK中的dlopen示例演示了如何在运行时打开共享库。重申一下,该示例包含三个C ++文件:

  • eightball.cc:这是实现该功能的共享库 Magic8Ball()(此文件被编译为libeightball.so)
  • reverse.cc:这是实现该功能的共享库 Reverse()(该文件被编译为libreverse.so)
  • dlopen.cc:这是Native Client模块,它加载共享库并调用Magic8Ball()Reverse()响应来自JavaScript的请求。

当Native Client模块启动时,它会启动一个调用dlopen()加载两个共享库的工作线程 。一旦模块具有库的句柄,它就会使用获取Magic8Ball()Reverse() 函数的地址dlsym()。当用户键入查询并单击“询问!”时 按钮,模块调用Magic8Ball()以生成答案,并将结果返回给用户。同样,当用户单击“反向”按钮时,它会调用该Reverse()函数来反转字符串。

故障排除

如果您的.nexe未加载,则查找可帮助您解决Chrome控制台和Chrome标准输出的信息的最佳位置。有关更多信息,请参阅调试

以下是一些常见错误消息及其含义的解释:

/main.nexe:加载共享库时出错:/main.nexe:无法为可执行文件分配代码和数据空间

.nexe可能未正确编译(例如,.nexe可能是静态链接的)。尝试使用glibc工具链进行清理和重新编译。

/main.nexe:加载共享库时出错:libpthread.so.xxxx:无法打开共享对象文件:权限被拒绝

(xxxx是版本号,例如,5055067a。).nmf文件中的路径错误可能导致此错误。仔细检查.nmf文件中的路径是否正确。

/main.nexe:加载共享库时出错:/main.nexe:无法打开共享对象文件:没有这样的文件或目录

如果.nmf文件中的main.nexe条目没有明显问题,请检查main.nexe的请求位置。使用Chrome的开发人员工具:单击菜单图标菜单图标,选择工具>开发人员工具,单击网络选项卡,然后查看名称列中的路径。

NaCl模块加载失败:ELF可执行文本/ rodata段的起始地址错误

使用newlib样式的.nmf文件而不是glibc样式的.nmf文件时会发生此错误。确保使用glic工具链构建应用程序,并使用create_nmf.py脚本生成.nmf文件。

NativeClient:NaCl模块加载失败:Nexe在启动期间崩溃

此错误消息表示模块在加载时崩溃。您可以通过查看Chrome开发者工具中的“网络”标签来确定崩溃的模块(参见上文)。崩溃的模块将是最后一个被加载的模块。

/lib/main.nexe:加载共享库时出错:/lib/main.nexe:只能加载ET_DYN和ET_EXEC

此错误消息表示.nmf文件中列出的.so文件出错 - 文件类型或类型错误,或者缺少预期的库。

未定义引用'dlopen'colle2:ld返回1退出状态

这是一个链接器排序问题,通常是由链接时命令行标志的不正确排序引起的。在-o标志之后将命令行字符串重新配置为列出库。

CC-By 3.0许可下提供的内容

posted @ 2018-07-24 09:26  SunkingYang  阅读(741)  评论(0编辑  收藏  举报