| Program Library HOWTOProgram Library HOWTO 程序库指南 | ||
|---|---|---|
| PrevPrev 上一页 | NextNext 下一页 | |
Shared libraries are libraries that are loaded by programs when they start. When a shared library is installed properly, all programs that start afterwards automatically use the new shared library. It's actually much more flexible and sophisticated than this, because the approach used by Linux permits you to: Shared libraries are libraries that are loaded by programs when they start. When a shared library is installed properly, all programs that start afterwards automatically use the new shared library. It's actually much more flexible and sophisticated than this, because the approach used by Linux permits you to: 共享库是程序启动时加载的库。当共享库正确安装后,所有随后启动的程序都会自动使用这个新的共享库。实际上,它比这要灵活和复杂得多,因为Linux所采用的方法允许你:
update libraries and still support programs that want to use older, non-backward-compatible versions of those libraries;update libraries and still support programs that want to use older, non-backward-compatible versions of those libraries;更新库,同时仍支持那些想要使用这些库的较旧、不向后兼容版本的程序;
override specific libraries or even specific functions in a library when executing a particular program.override specific libraries or even specific functions in a library when executing a particular program.在执行特定程序时,覆盖特定的库,甚至覆盖库中的特定函数。
do all this while programs are running using existing libraries.do all this while programs are running using existing libraries.在程序运行时,使用现有库来完成所有这些操作。
For shared libraries to support all of these desired properties, a number of conventions and guidelines must be followed. You need to understand the difference between a library's names, in particular its ``soname'' and ``real name'' (and how they interact). You also need to understand where they should be placed in the filesystem.For shared libraries to support all of these desired properties, a number of conventions and guidelines must be followed. You need to understand the difference between a library's names, in particular its ``soname'' and ``real name'' (and how they interact). You also need to understand where they should be placed in the filesystem.为了让共享库支持所有这些期望的特性,必须遵循一些约定和准则。你需要理解库的名称之间的区别,特别是它的“soname”(共享目标文件名)和“real name”(真实名称)(以及它们如何相互作用)。你还需要了解它们应该放在文件系统的哪个位置。
Every shared library has a special name called the ``soname''. The soname has the prefix ``lib'', the name of the library, the phrase ``.so'', followed by a period and a version number that is incremented whenever the interface changes (as a special exception, the lowest-level C libraries don't start with ``lib''). A fully-qualified soname includes as a prefix the directory it's in; on a working system a fully-qualified soname is simply a symbolic link to the shared library's ``real name''.Every shared library has a special name called the ``soname''. The soname has the prefix ``lib'', the name of the library, the phrase ``.so'', followed by a period and a version number that is incremented whenever the interface changes (as a special exception, the lowest-level C libraries don't start with ``lib''). A fully-qualified soname includes as a prefix the directory it's in; on a working system a fully-qualified soname is simply a symbolic link to the shared library's ``real name''.每个共享库都有一个特殊的名称,称为“soname”。soname 具有前缀“lib”、库的名称、短语“.so”,后跟一个句点和一个版本号,每当接口发生变化时,该版本号就会递增(作为一个特殊例外,最低级别的 C 库不以“lib”开头)。完全限定的 soname 包含其所在目录作为前缀;在正常运行的系统上,完全限定的 soname 只是指向共享库“真实名称”的符号链接。
Every shared library also has a ``real name'', which is the filename containing the actual library code. The real name adds to the soname a period, a minor number, another period, and the release number. The last period and release number are optional. The minor number and release number support configuration control by letting you know exactly what version(s) of the library are installed. Note that these numbers might not be the same as the numbers used to describe the library in documentation, although that does make things easier.Every shared library also has a ``real name'', which is the filename containing the actual library code. The real name adds to the soname a period, a minor number, another period, and the release number. The last period and release number are optional. The minor number and release number support configuration control by letting you know exactly what version(s) of the library are installed. Note that these numbers might not be the same as the numbers used to describe the library in documentation, although that does make things easier.每个共享库还有一个“真实名称”,即包含实际库代码的文件名。真实名称在soname后添加一个句点、一个次要版本号、另一个句点和发布号。最后一个句点和发布号是可选的。次要版本号和发布号通过让你确切了解已安装库的具体版本,来支持配置控制。请注意,这些编号可能与文档中用于描述该库的编号不同,尽管相同会让事情更简单。
In addition, there's the name that the compiler uses when requesting a library, (I'll call it the ``linker name''), which is simply the soname without any version number.In addition, there's the name that the compiler uses when requesting a library, (I'll call it the ``linker name''), which is simply the soname without any version number.此外,还有编译器在请求库时使用的名称(我称之为“链接器名称”),它就是没有任何版本号的 SONAME。
The key to managing shared libraries is the separation of these names. Programs, when they internally list the shared libraries they need, should only list the soname they need. Conversely, when you create a shared library, you only create the library with a specific filename (with more detailed version information). When you install a new version of a library, you install it in one of a few special directories and then run the program ldconfig(8). ldconfig examines the existing files and creates the sonames as symbolic links to the real names, as well as setting up the cache file /etc/ld.so.cache (described in a moment).The key to managing shared libraries is the separation of these names. Programs, when they internally list the shared libraries they need, should only list the soname they need. Conversely, when you create a shared library, you only create the library with a specific filename (with more detailed version information). When you install a new version of a library, you install it in one of a few special directories and then run the program ldconfig(8). ldconfig examines the existing files and creates the sonames as symbolic links to the real names, as well as setting up the cache file /etc/ld.so.cache (described in a moment).管理共享库的关键在于这些名称的分离。程序在内部列出所需的共享库时,只需列出它们需要的soname。相反,当你创建共享库时,你只需用特定的文件名(包含更详细的版本信息)来创建该库。当你安装一个新版本的库时,将其安装在几个特殊目录中的一个,然后运行ldconfig(8)程序。ldconfig会检查现有文件,并将soname创建为指向真实名称的符号链接,同时设置缓存文件/etc/ld.so.cache(稍后会介绍)。
ldconfig doesn't set up the linker names; typically this is done during library installation, and the linker name is simply created as a symbolic link to the ``latest'' soname or the latest real name. I would recommend having the linker name be a symbolic link to the soname, since in most cases if you update the library you'd like to automatically use it when linking. I asked H. J. Lu why ldconfig doesn't automatically set up the linker names. His explanation was basically that you might want to run code using the latest version of a library, but might instead want development to link against an old (possibly incompatible) library. Therefore, ldconfig makes no assumptions about what you want programs to link to, so installers must specifically modify symbolic links to update what the linker will use for a library.ldconfig doesn't set up the linker names; typically this is done during library installation, and the linker name is simply created as a symbolic link to the ``latest'' soname or the latest real name. I would recommend having the linker name be a symbolic link to the soname, since in most cases if you update the library you'd like to automatically use it when linking. I asked H. J. Lu why ldconfig doesn't automatically set up the linker names. His explanation was basically that you might want to run code using the latest version of a library, but might instead want development to link against an old (possibly incompatible) library. Therefore, ldconfig makes no assumptions about what you want programs to link to, so installers must specifically modify symbolic links to update what the linker will use for a library.ldconfig不会设置链接器名称;通常这是在库安装期间完成的,链接器名称只是作为指向“最新”soname或最新真实名称的符号链接而创建。我建议将链接器名称作为指向soname的符号链接,因为在大多数情况下,如果你更新了库,你会希望在链接时自动使用它。我问过H. J. Lu为什么ldconfig不自动设置链接器名称。他的解释大致是,你可能希望运行使用库的最新版本的代码,但可能希望开发链接到旧的(可能不兼容的)库。因此,ldconfig不会假设你希望程序链接到什么,所以安装程序必须专门修改符号链接,以更新链接器将用于某个库的内容。
Thus, /usr/lib/libreadline.so.3 is a fully-qualified soname, which ldconfig would set to be a symbolic link to some realname like /usr/lib/libreadline.so.3.0. There should also be a linker name, /usr/lib/libreadline.so which could be a symbolic link referring to /usr/lib/libreadline.so.3.Thus, /usr/lib/libreadline.so.3 is a fully-qualified soname, which ldconfig would set to be a symbolic link to some realname like /usr/lib/libreadline.so.3.0. There should also be a linker name, /usr/lib/libreadline.so which could be a symbolic link referring to /usr/lib/libreadline.so.3.因此,/usr/lib/libreadline.so.3 是一个完全限定的 soname,ldconfig 会将其设置为指向某个真实名称(如 /usr/lib/libreadline.so.3.0)的符号链接。还应该有一个链接器名称 /usr/lib/libreadline.so,它可以是指向 /usr/lib/libreadline.so.3 的符号链接。
Shared libraries must be placed somewhere in the filesystem. Most open source software tends to follow the GNU standards; for more information see the info file documentation at info:standards#Directory_Variables. The GNU standards recommend installing by default all libraries in /usr/local/lib when distributing source code (and all commands should go into /usr/local/bin). They also define the convention for overriding these defaults and for invoking the installation routines.Shared libraries must be placed somewhere in the filesystem. Most open source software tends to follow the GNU standards; for more information see the info file documentation at info:standards#Directory_Variables. The GNU standards recommend installing by default all libraries in /usr/local/lib when distributing source code (and all commands should go into /usr/local/bin). They also define the convention for overriding these defaults and for invoking the installation routines.共享库必须放在文件系统的某个位置。大多数开源软件倾向于遵循GNU标准;欲了解更多信息,请参阅info:standards#Directory_Variables处的信息文件文档。GNU标准建议在分发源代码时,默认将所有库安装到/usr/local/lib中(所有命令应放入/usr/local/bin)。它们还定义了覆盖这些默认设置以及调用安装程序的约定。
The Filesystem Hierarchy Standard (FHS) discusses what should go where in a distribution (see http://www.pathname.com/fhs). According to the FHS, most libraries should be installed in /usr/lib, but libraries required for startup should be in /lib and libraries that are not part of the system should be in /usr/local/lib.The Filesystem Hierarchy Standard (FHS) discusses what should go where in a distribution (see http://www.pathname.com/fhs). According to the FHS, most libraries should be installed in /usr/lib, but libraries required for startup should be in /lib and libraries that are not part of the system should be in /usr/local/lib.文件系统层次结构标准(FHS)讨论了在发行版中各个内容应该放在哪里(参见http://www.pathname.com/fhs)。根据FHS,大多数库应该安装在/usr/lib中,但启动所需的库应该放在/lib中,而非系统一部分的库则应该放在/usr/local/lib中。
There isn't really a conflict between these two documents; the GNU standards recommend the default for developers of source code, while the FHS recommends the default for distributors (who selectively override the source code defaults, usually via the system's package management system). In practice this works nicely: the ``latest'' (possibly buggy!) source code that you download automatically installs itself in the ``local'' directory (/usr/local), and once that code has matured the package managers can trivially override the default to place the code in the standard place for distributions. Note that if your library calls programs that can only be called via libraries, you should place those programs in /usr/local/libexec (which becomes /usr/libexec in a distribution). One complication is that Red Hat-derived systems don't include /usr/local/lib by default in their search for libraries; see the discussion below about /etc/ld.so.conf. Other standard library locations include /usr/X11R6/lib for X-windows. Note that /lib/security is used for PAM modules, but those are usually loaded as DL libraries (also discussed below).There isn't really a conflict between these two documents; the GNU standards recommend the default for developers of source code, while the FHS recommends the default for distributors (who selectively override the source code defaults, usually via the system's package management system). In practice this works nicely: the ``latest'' (possibly buggy!) source code that you download automatically installs itself in the ``local'' directory (/usr/local), and once that code has matured the package managers can trivially override the default to place the code in the standard place for distributions. Note that if your library calls programs that can only be called via libraries, you should place those programs in /usr/local/libexec (which becomes /usr/libexec in a distribution). One complication is that Red Hat-derived systems don't include /usr/local/lib by default in their search for libraries; see the discussion below about /etc/ld.so.conf. Other standard library locations include /usr/X11R6/lib for X-windows. Note that /lib/security is used for PAM modules, but those are usually loaded as DL libraries (also discussed below).这两份文档之间其实并没有真正的冲突:GNU 标准为源代码开发者推荐了默认设置,而 FHS 则为发行商推荐了默认设置(发行商通常会通过系统的包管理系统有选择地覆盖源代码的默认设置)。在实际操作中,这种方式效果很好:你下载的“最新”(可能有漏洞!)源代码会自动安装到“本地”目录(/usr/local),而一旦该代码成熟,包管理器就能轻松地覆盖默认设置,将代码放置在发行版的标准位置。 请注意,如果你的库调用了只能通过库来调用的程序,你应该将这些程序放在 /usr/local/libexec 中(在发行版中,该目录会变为 /usr/libexec)。 一个复杂情况是,基于红帽的系统在默认的库搜索路径中不包含 /usr/local/lib,有关这一点,请参阅下面关于 /etc/ld.so.conf 的讨论。 其他标准库位置包括用于 X 窗口系统的 /usr/X11R6/lib。需要注意的是,/lib/security 用于存放 PAM 模块,但这些模块通常作为动态链接库加载(下面也会讨论这一点)。
On GNU glibc-based systems, including all Linux systems, starting up an ELF binary executable automatically causes the program loader to be loaded and run. On Linux systems, this loader is named /lib/ld-linux.so.X (where X is a version number). This loader, in turn, finds and loads all other shared libraries used by the program.On GNU glibc-based systems, including all Linux systems, starting up an ELF binary executable automatically causes the program loader to be loaded and run. On Linux systems, this loader is named /lib/ld-linux.so.X (where X is a version number). This loader, in turn, finds and loads all other shared libraries used by the program.在基于GNU glibc的系统(包括所有Linux系统)上,启动ELF二进制可执行文件会自动导致程序加载器被加载并运行。在Linux系统上,这个加载器名为/lib/ld-linux.so.X(其中X是版本号)。而这个加载器会进而查找并加载该程序所使用的所有其他共享库。
The list of directories to be searched is stored in the file /etc/ld.so.conf. Many Red Hat-derived distributions don't normally include /usr/local/lib in the file /etc/ld.so.conf. I consider this a bug, and adding /usr/local/lib to /etc/ld.so.conf is a common ``fix'' required to run many programs on Red Hat-derived systems.The list of directories to be searched is stored in the file /etc/ld.so.conf. Many Red Hat-derived distributions don't normally include /usr/local/lib in the file /etc/ld.so.conf. I consider this a bug, and adding /usr/local/lib to /etc/ld.so.conf is a common ``fix'' required to run many programs on Red Hat-derived systems.要搜索的目录列表存储在文件/etc/ld.so.conf中。许多源自Red Hat的发行版通常不会在文件/etc/ld.so.conf中包含/usr/local/lib。我认为这是一个漏洞,而将/usr/local/lib添加到/etc/ld.so.conf是在源自Red Hat的系统上运行许多程序时所需的一种常见“修复”方法。
If you want to just override a few functions in a library, but keep the rest of the library, you can enter the names of overriding libraries (.o files) in /etc/ld.so.preload; these ``preloading'' libraries will take precedence over the standard set. This preloading file is typically used for emergency patches; a distribution usually won't include such a file when delivered.If you want to just override a few functions in a library, but keep the rest of the library, you can enter the names of overriding libraries (.o files) in /etc/ld.so.preload; these ``preloading'' libraries will take precedence over the standard set. This preloading file is typically used for emergency patches; a distribution usually won't include such a file when delivered.如果你只想覆盖库中的几个函数,而保留库的其余部分,可以在/etc/ld.so.preload中输入覆盖库(.o文件)的名称;这些“预加载”库将优先于标准库集。这个预加载文件通常用于紧急补丁;发行版在交付时通常不会包含这样的文件。
Searching all of these directories at program start-up would be grossly inefficient, so a caching arrangement is actually used. The program ldconfig(8) by default reads in the file /etc/ld.so.conf, sets up the appropriate symbolic links in the dynamic link directories (so they'll follow the standard conventions), and then writes a cache to /etc/ld.so.cache that's then used by other programs. This greatly speeds up access to libraries. The implication is that ldconfig must be run whenever a DLL is added, when a DLL is removed, or when the set of DLL directories changes; running ldconfig is often one of the steps performed by package managers when installing a library. On start-up, then, the dynamic loader actually uses the file /etc/ld.so.cache and then loads the libraries it needs. Searching all of these directories at program start-up would be grossly inefficient, so a caching arrangement is actually used. The program ldconfig(8) by default reads in the file /etc/ld.so.conf, sets up the appropriate symbolic links in the dynamic link directories (so they'll follow the standard conventions), and then writes a cache to /etc/ld.so.cache that's then used by other programs. This greatly speeds up access to libraries. The implication is that ldconfig must be run whenever a DLL is added, when a DLL is removed, or when the set of DLL directories changes; running ldconfig is often one of the steps performed by package managers when installing a library. On start-up, then, the dynamic loader actually uses the file /etc/ld.so.cache and then loads the libraries it needs. 在程序启动时搜索所有这些目录会非常低效,因此实际使用了缓存机制。程序ldconfig(8)默认会读取文件/etc/ld.so.conf,在动态链接目录中设置适当的符号链接(使其遵循标准约定),然后将缓存写入/etc/ld.so.cache,供其他程序使用。这大大加快了对库的访问速度。这意味着,每当添加、删除动态链接库(DLL),或者动态链接库目录集发生变化时,都必须运行ldconfig;包管理器在安装库时,通常会执行运行ldconfig这一步骤。因此,动态加载器在启动时实际上会使用/etc/ld.so.cache文件,然后加载所需的库。
By the way, FreeBSD uses slightly different filenames for this cache. In FreeBSD, the ELF cache is /var/run/ld-elf.so.hints and the a.out cache is /var/run/ld.so.hints. These are still updated by ldconfig(8), so this difference in location should only matter in a few exotic situations.By the way, FreeBSD uses slightly different filenames for this cache. In FreeBSD, the ELF cache is /var/run/ld-elf.so.hints and the a.out cache is /var/run/ld.so.hints. These are still updated by ldconfig(8), so this difference in location should only matter in a few exotic situations.顺便说一下,FreeBSD对这个缓存使用的文件名略有不同。在FreeBSD中,ELF缓存是/var/run/ld-elf.so.hints,a.out缓存是/var/run/ld.so.hints。这些仍然由ldconfig(8)更新,所以位置上的这种差异只在少数特殊情况下有影响。
Various environment variables can control this process, and there are environment variables that permit you to override this process.Various environment variables can control this process, and there are environment variables that permit you to override this process.多种环境变量可以控制这一过程,也有一些环境变量允许你覆盖此过程。
You can temporarily substitute a different library for this particular execution. In Linux, the environment variable LD_LIBRARY_PATH is a colon-separated set of directories where libraries should be searched for first, before the standard set of directories; this is useful when debugging a new library or using a nonstandard library for special purposes. The environment variable LD_PRELOAD lists shared libraries with functions that override the standard set, just as /etc/ld.so.preload does. These are implemented by the loader /lib/ld-linux.so. I should note that, while LD_LIBRARY_PATH works on many Unix-like systems, it doesn't work on all; for example, this functionality is available on HP-UX but as the environment variable SHLIB_PATH, and on AIX this functionality is through the variable LIBPATH (with the same syntax, a colon-separated list).You can temporarily substitute a different library for this particular execution. In Linux, the environment variable LD_LIBRARY_PATH is a colon-separated set of directories where libraries should be searched for first, before the standard set of directories; this is useful when debugging a new library or using a nonstandard library for special purposes. The environment variable LD_PRELOAD lists shared libraries with functions that override the standard set, just as /etc/ld.so.preload does. These are implemented by the loader /lib/ld-linux.so. I should note that, while LD_LIBRARY_PATH works on many Unix-like systems, it doesn't work on all; for example, this functionality is available on HP-UX but as the environment variable SHLIB_PATH, and on AIX this functionality is through the variable LIBPATH (with the same syntax, a colon-separated list).你可以为这个特定的执行临时替换一个不同的库。在Linux中,环境变量LD_LIBRARY_PATH是一组用冒号分隔的目录,在搜索标准目录集之前,会先在这些目录中搜索库;这在调试新库或为特殊目的使用非标准库时很有用。环境变量LD_PRELOAD列出了带有覆盖标准集函数的共享库,就像/etc/ld.so.preload所做的那样。这些是由加载器/lib/ld-linux.so实现的。需要注意的是,虽然LD_LIBRARY_PATH在许多类Unix系统上有效,但并非在所有系统上都有效;例如,这种功能在HP-UX上可用,但对应的环境变量是SHLIB_PATH,而在AIX上,这种功能通过变量LIBPATH实现(语法相同,也是用冒号分隔的列表)。
LD_LIBRARY_PATH is handy for development and testing, but shouldn't be modified by an installation process for normal use by normal users; see ``Why LD_LIBRARY_PATH is Bad'' at http://www.visi.com/~barr/ldpath.html for an explanation of why. But it's still useful for development or testing, and for working around problems that can't be worked around otherwise. If you don't want to set the LD_LIBRARY_PATH environment variable, on Linux you can even invoke the program loader directly and pass it arguments. For example, the following will use the given PATH instead of the content of the environment variable LD_LIBRARY_PATH, and run the given executable: LD_LIBRARY_PATH is handy for development and testing, but shouldn't be modified by an installation process for normal use by normal users; see ``Why LD_LIBRARY_PATH is Bad'' at http://www.visi.com/~barr/ldpath.html for an explanation of why. But it's still useful for development or testing, and for working around problems that can't be worked around otherwise. If you don't want to set the LD_LIBRARY_PATH environment variable, on Linux you can even invoke the program loader directly and pass it arguments. For example, the following will use the given PATH instead of the content of the environment variable LD_LIBRARY_PATH, and run the given executable: LD_LIBRARY_PATH在开发和测试中很方便,但不应在安装过程中为普通用户的正常使用而修改;有关原因的解释,请参见http://www.visi.com/~barr/ldpath.html上的《为什么LD_LIBRARY_PATH不好》。但它在开发或测试中仍然有用,也可用于解决其他方法无法解决的问题。如果不想设置LD_LIBRARY_PATH环境变量,在Linux上甚至可以直接调用程序加载器并向其传递参数。例如,以下命令将使用给定的PATH而不是环境变量LD_LIBRARY_PATH的内容,并运行给定的可执行文件:
/lib/ld-linux.so.2 --library-path PATH EXECUTABLE /lib/ld-linux.so.2 --library-path PATH EXECUTABLE/lib/ld-linux.so.2 --library-path 路径 可执行文件 |
Another useful environment variable in the GNU C loader is LD_DEBUG. This triggers the dl* functions so that they give quite verbose information on what they are doing. For example: Another useful environment variable in the GNU C loader is LD_DEBUG. This triggers the dl* functions so that they give quite verbose information on what they are doing. For example: GNU C 加载器中另一个有用的环境变量是 LD_DEBUG。它会触发 dl* 函数,使这些函数输出关于其正在执行的操作的详细信息。例如:
export LD_DEBUG=files
command_to_run export LD_DEBUG=files
command_to_run export LD_DEBUG=filescommand_to_run |
Setting LD_DEBUG to ``help'' and then trying to run a program will list the possible options. Again, LD_DEBUG isn't intended for normal use, but it can be handy when debugging and testing.Setting LD_DEBUG to ``help'' and then trying to run a program will list the possible options. Again, LD_DEBUG isn't intended for normal use, but it can be handy when debugging and testing.将LD_DEBUG设置为“help”,然后尝试运行程序,会列出可能的选项。同样,LD_DEBUG并非用于正常使用,但在调试和测试时可能会很有用。
There are actually a number of other environment variables that control the loading process; their names begin with LD_ or RTLD_. Most of the others are for low-level debugging of the loader process or for implementing specialized capabilities. Most of them aren't well-documented; if you need to know about them, the best way to learn about them is to read the source code of the loader (part of gcc).There are actually a number of other environment variables that control the loading process; their names begin with LD_ or RTLD_. Most of the others are for low-level debugging of the loader process or for implementing specialized capabilities. Most of them aren't well-documented; if you need to know about them, the best way to learn about them is to read the source code of the loader (part of gcc).实际上,还有许多其他环境变量可以控制加载过程;它们的名称以LD_或RTLD_开头。其他大多数变量用于加载器进程的低级调试或实现特定功能。其中大多数没有详细的文档说明;如果你需要了解它们,最好的方法是阅读加载器(gcc的一部分)的源代码。
Permitting user control over dynamically linked libraries would be disastrous for setuid/setgid programs if special measures weren't taken. Therefore, in the GNU loader (which loads the rest of the program on program start-up), if the program is setuid or setgid these variables (and other similar variables) are ignored or greatly limited in what they can do. The loader determines if a program is setuid or setgid by checking the program's credentials; if the uid and euid differ, or the gid and the egid differ, the loader presumes the program is setuid/setgid (or descended from one) and therefore greatly limits its abilities to control linking. If you read the GNU glibc library source code, you can see this; see especially the files elf/rtld.c and sysdeps/generic/dl-sysdep.c. This means that if you cause the uid and gid to equal the euid and egid, and then call a program, these variables will have full effect. Other Unix-like systems handle the situation differently but for the same reason: a setuid/setgid program should not be unduly affected by the environment variables set. Permitting user control over dynamically linked libraries would be disastrous for setuid/setgid programs if special measures weren't taken. Therefore, in the GNU loader (which loads the rest of the program on program start-up), if the program is setuid or setgid these variables (and other similar variables) are ignored or greatly limited in what they can do. The loader determines if a program is setuid or setgid by checking the program's credentials; if the uid and euid differ, or the gid and the egid differ, the loader presumes the program is setuid/setgid (or descended from one) and therefore greatly limits its abilities to control linking. If you read the GNU glibc library source code, you can see this; see especially the files elf/rtld.c and sysdeps/generic/dl-sysdep.c. This means that if you cause the uid and gid to equal the euid and egid, and then call a program, these variables will have full effect. Other Unix-like systems handle the situation differently but for the same reason: a setuid/setgid program should not be unduly affected by the environment variables set. 如果不采取特殊措施,允许用户控制动态链接库对于setuid/setgid程序来说将是灾难性的。因此,在GNU加载器(它会在程序启动时加载程序的其余部分)中,如果程序是setuid或setgid的,这些变量(以及其他类似变量)会被忽略,或者其可执行的操作会受到极大限制。加载器通过检查程序的凭证来确定程序是否为setuid或setgid;如果用户ID(uid)与有效用户ID(euid)不同,或者组ID(gid)与有效组ID(egid)不同,加载器就会假定该程序是setuid/setgid程序(或由其衍生而来),从而极大地限制其控制链接的能力。阅读GNU glibc库的源代码,尤其是elf/rtld.c和sysdeps/generic/dl-sysdep.c文件,就可以看到这一点。这意味着,如果让uid和gid分别与euid和egid相等,然后再调用程序,这些变量将能充分发挥作用。其他类Unix系统处理这种情况的方式不同,但原因相同:setuid/setgid程序不应过度受到所设置的环境变量的影响。
Creating a shared library is easy. First, create the object files that will go into the shared library using the gcc -fPIC or -fpic flag. The -fPIC and -fpic options enable ``position independent code'' generation, a requirement for shared libraries; see below for the differences. You pass the soname using the -Wl gcc option. The -Wl option passes options along to the linker (in this case the -soname linker option) - the commas after -Wl are not a typo, and you must not include unescaped whitespace in the option. Then create the shared library using this format:Creating a shared library is easy. First, create the object files that will go into the shared library using the gcc -fPIC or -fpic flag. The -fPIC and -fpic options enable ``position independent code'' generation, a requirement for shared libraries; see below for the differences. You pass the soname using the -Wl gcc option. The -Wl option passes options along to the linker (in this case the -soname linker option) - the commas after -Wl are not a typo, and you must not include unescaped whitespace in the option. Then create the shared library using this format:创建共享库很简单。首先,使用gcc的-fPIC或-fpic标志创建将放入共享库的目标文件。-fPIC和-fpic选项启用“位置无关代码”生成,这是共享库的要求;下面会介绍两者的区别。使用-Wl gcc选项传递soname。-Wl选项将选项传递给链接器(在这种情况下是-soname链接器选项)——-Wl后面的逗号不是拼写错误,并且在该选项中不能包含未转义的空格。然后使用以下格式创建共享库:
gcc -shared -Wl,-soname,your_soname \
-o library_name file_list library_listgcc -shared -Wl,-soname,your_soname \
-o library_name file_list library_listgcc -shared -Wl,-soname,your_soname \-o library_name file_list library_list |
Here's an example, which creates two object files (a.o and b.o) and then creates a shared library that contains both of them. Note that this compilation includes debugging information (-g) and will generate warnings (-Wall), which aren't required for shared libraries but are recommended. The compilation generates object files (using -c), and includes the required -fPIC option:Here's an example, which creates two object files (a.o and b.o) and then creates a shared library that contains both of them. Note that this compilation includes debugging information (-g) and will generate warnings (-Wall), which aren't required for shared libraries but are recommended. The compilation generates object files (using -c), and includes the required -fPIC option:以下是一个示例,它会创建两个目标文件(a.o 和 b.o),然后创建一个包含这两个文件的共享库。请注意,此次编译包含调试信息(-g)并会生成警告(-Wall),这些对于共享库来说并非必需,但建议添加。编译会生成目标文件(使用 -c),并包含必需的 -fPIC 选项:
gcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c
gcc -shared -Wl,-soname,libmystuff.so.1 \
-o libmystuff.so.1.0.1 a.o b.o -lcgcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c
gcc -shared -Wl,-soname,libmystuff.so.1 \
-o libmystuff.so.1.0.1 a.o b.o -lcgcc -fPIC -g -c -Wall a.cgcc -fPIC -g -c -Wall b.cgcc -shared -Wl,-soname,libmystuff.so.1 \-o libmystuff.so.1.0.1 a.o b.o -lc |
Here are a few points worth noting: Here are a few points worth noting: 以下是几点值得注意的内容:
Don't strip the resulting library, and don't use the compiler option -fomit-frame-pointer unless you really have to. The resulting library will work, but these actions make debuggers mostly useless.Don't strip the resulting library, and don't use the compiler option -fomit-frame-pointer unless you really have to. The resulting library will work, but these actions make debuggers mostly useless.不要对生成的库进行剥离操作,除非确实必要,否则不要使用编译器选项-fomit-frame-pointer。生成的库仍然可以运行,但这些操作会使调试器基本失效。
Use -fPIC or -fpic to generate code. Whether to use -fPIC or -fpic to generate code is target-dependent. The -fPIC choice always works, but may produce larger code than -fpic (mnenomic to remember this is that PIC is in a larger case, so it may produce larger amounts of code). Using -fpic option usually generates smaller and faster code, but will have platform-dependent limitations, such as the number of globally visible symbols or the size of the code. The linker will tell you whether it fits when you create the shared library. When in doubt, I choose -fPIC, because it always works.Use -fPIC or -fpic to generate code. Whether to use -fPIC or -fpic to generate code is target-dependent. The -fPIC choice always works, but may produce larger code than -fpic (mnenomic to remember this is that PIC is in a larger case, so it may produce larger amounts of code). Using -fpic option usually generates smaller and faster code, but will have platform-dependent limitations, such as the number of globally visible symbols or the size of the code. The linker will tell you whether it fits when you create the shared library. When in doubt, I choose -fPIC, because it always works.使用-fPIC或-fpic来生成代码。是否使用-fPIC或-fpic生成代码取决于目标平台。选择-fPIC总是可行的,但生成的代码可能比使用-fpic时更大(记住这一点的助记法是,PIC是大写的,所以它可能生成更多的代码)。使用-fpic选项通常会生成更小、更快的代码,但会有平台相关的限制,例如全局可见符号的数量或代码的大小。在创建共享库时,链接器会告诉你它是否适用。如果有疑问,我会选择-fPIC,因为它总是有效的。
In some cases, the call to gcc to create the object file will also need to include the option ``-Wl,-export-dynamic''. Normally, the dynamic symbol table contains only symbols which are used by a dynamic object. This option (when creating an ELF file) adds all symbols to the dynamic symbol table (see ld(1) for more information). You need to use this option when there are 'reverse dependencies', i.e., a DL library has unresolved symbols that by convention must be defined in the programs that intend to load these libraries. For ``reverse dependencies'' to work, the master program must make its symbols dynamically available. Note that you could say ``-rdynamic'' instead of ``-Wl,export-dynamic'' if you only work with Linux systems, but according to the ELF documentation the ``-rdynamic'' flag doesn't always work for gcc on non-Linux systems.In some cases, the call to gcc to create the object file will also need to include the option ``-Wl,-export-dynamic''. Normally, the dynamic symbol table contains only symbols which are used by a dynamic object. This option (when creating an ELF file) adds all symbols to the dynamic symbol table (see ld(1) for more information). You need to use this option when there are 'reverse dependencies', i.e., a DL library has unresolved symbols that by convention must be defined in the programs that intend to load these libraries. For ``reverse dependencies'' to work, the master program must make its symbols dynamically available. Note that you could say ``-rdynamic'' instead of ``-Wl,export-dynamic'' if you only work with Linux systems, but according to the ELF documentation the ``-rdynamic'' flag doesn't always work for gcc on non-Linux systems.在某些情况下,调用gcc创建目标文件时还需要包含选项``-Wl,-export-dynamic''。通常,动态符号表只包含动态对象所使用的符号。此选项(在创建ELF文件时)会将所有符号添加到动态符号表中(更多信息请参见ld(1))。当存在“反向依赖”时,即DL库存在未解析的符号,按照惯例,这些符号必须在打算加载这些库的程序中定义,这时就需要使用该选项。为了使“反向依赖”生效,主程序必须使其符号可动态获取。请注意,如果仅在Linux系统上运行,可以使用``-rdynamic''代替``-Wl,export-dynamic'',但根据ELF文档,``-rdynamic''标志在非Linux系统上并不总是能在gcc中正常工作。
During development, there's the potential problem of modifying a library that's also used by many other programs -- and you don't want the other programs to use the ``developmental''library, only a particular application that you're testing against it. One link option you might use is ld's ``rpath'' option, which specifies the runtime library search path of that particular program being compiled. From gcc, you can invoke the rpath option by specifying it this way: During development, there's the potential problem of modifying a library that's also used by many other programs -- and you don't want the other programs to use the ``developmental''library, only a particular application that you're testing against it. One link option you might use is ld's ``rpath'' option, which specifies the runtime library search path of that particular program being compiled. From gcc, you can invoke the rpath option by specifying it this way: 在开发过程中,可能会出现这样一个问题:修改一个同时被许多其他程序使用的库——而你不希望其他程序使用这个“开发版”库,只希望你正在针对它进行测试的某个特定应用程序使用该库。你可以使用的一个链接选项是ld的“rpath”选项,它指定了正在编译的那个特定程序的运行时库搜索路径。在gcc中,你可以通过以下方式指定来调用rpath选项:
-Wl,-rpath,$(DEFAULT_LIB_INSTALL_PATH) -Wl,-rpath,$(DEFAULT_LIB_INSTALL_PATH)-Wl,-rpath,$(DEFAULT_LIB_INSTALL_PATH) |
Once you've created a shared library, you'll want to install it. The simple approach is simply to copy the library into one of the standard directories (e.g., /usr/lib) and run ldconfig(8).Once you've created a shared library, you'll want to install it. The simple approach is simply to copy the library into one of the standard directories (e.g., /usr/lib) and run ldconfig(8).创建共享库后,你会想要安装它。简单的方法是直接将库复制到某个标准目录(例如 /usr/lib)并运行 ldconfig(8)。
First, you'll need to create the shared libraries somewhere. Then, you'll need to set up the necessary symbolic links, in particular a link from a soname to the real name (as well as from a versionless soname, that is, a soname that ends in ``.so'' for users who don't specify a version at all). The simplest approach is to run: First, you'll need to create the shared libraries somewhere. Then, you'll need to set up the necessary symbolic links, in particular a link from a soname to the real name (as well as from a versionless soname, that is, a soname that ends in ``.so'' for users who don't specify a version at all). The simplest approach is to run: 首先,你需要在某个位置创建共享库。然后,你需要设置必要的符号链接,特别是从共享库版本名(soname)到实际名称的链接(以及从无版本共享库版本名的链接,即对于完全不指定版本的用户来说,是一个以“.so”结尾的共享库版本名)。最简单的方法是运行:
ldconfig -n directory_with_shared_libraries ldconfig -n directory_with_shared_librariesldconfig -n 包含共享库的目录 |
Finally, when you compile your programs, you'll need to tell the linker about any static and shared libraries that you're using. Use the -l and -L options for this.Finally, when you compile your programs, you'll need to tell the linker about any static and shared libraries that you're using. Use the -l and -L options for this.最后,当你编译程序时,需要告知链接器你正在使用的所有静态库和共享库。为此,请使用-l和-L选项。
If you can't or don't want to install a library in a standard place (e.g., you don't have the right to modify /usr/lib), then you'll need to change your approach. In that case, you'll need to install it somewhere, and then give your program enough information so the program can find the library... and there are several ways to do that. You can use gcc's -L flag in simple cases. You can use the ``rpath'' approach (described above), particularly if you only have a specific program to use the library being placed in a ``non-standard'' place. You can also use environment variables to control things. In particular, you can set LD_LIBRARY_PATH, which is a colon-separated list of directories in which to search for shared libraries before the usual places. If you're using bash, you could invoke my_program this way using:If you can't or don't want to install a library in a standard place (e.g., you don't have the right to modify /usr/lib), then you'll need to change your approach. In that case, you'll need to install it somewhere, and then give your program enough information so the program can find the library... and there are several ways to do that. You can use gcc's -L flag in simple cases. You can use the ``rpath'' approach (described above), particularly if you only have a specific program to use the library being placed in a ``non-standard'' place. You can also use environment variables to control things. In particular, you can set LD_LIBRARY_PATH, which is a colon-separated list of directories in which to search for shared libraries before the usual places. If you're using bash, you could invoke my_program this way using:如果你无法或不想将库安装在标准位置(例如,你没有权限修改/usr/lib),那么你需要改变方法。在这种情况下,你需要将其安装到某个地方,然后向你的程序提供足够的信息,以便程序能够找到该库……有几种方法可以做到这一点。在简单情况下,你可以使用gcc的-L标志。你可以使用“rpath”方法(如上所述),特别是当你只有一个特定程序需要使用放在“非标准”位置的库时。你也可以使用环境变量来控制相关设置。特别是,你可以设置LD_LIBRARY_PATH,它是一个以冒号分隔的目录列表,程序会在常规位置之前先在这些目录中搜索共享库。如果你使用bash,你可以通过以下方式调用my_program:
LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_programLD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_programLD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_program |
If you want to override just a few selected functions, you can do this by creating an overriding object file and setting LD_PRELOAD; the functions in this object file will override just those functions (leaving others as they were).If you want to override just a few selected functions, you can do this by creating an overriding object file and setting LD_PRELOAD; the functions in this object file will override just those functions (leaving others as they were).如果你只想覆盖几个选定的函数,可以通过创建一个覆盖对象文件并设置LD_PRELOAD来实现;该对象文件中的函数将仅覆盖那些函数(其他函数保持原样)。
Usually you can update libraries without concern; if there was an API change, the library creator is supposed to change the soname. That way, multiple libraries can be on a single system, and the right one is selected for each program. However, if a program breaks on an update to a library that kept the same soname, you can force it to use the older library version by copying the old library back somewhere, renaming the program (say to the old name plus ``.orig''), and then create a small ``wrapper'' script that resets the library to use and calls the real (renamed) program. You could place the old library in its own special area, if you like, though the numbering conventions do permit multiple versions to live in the same directory. The wrapper script could look something like this: Usually you can update libraries without concern; if there was an API change, the library creator is supposed to change the soname. That way, multiple libraries can be on a single system, and the right one is selected for each program. However, if a program breaks on an update to a library that kept the same soname, you can force it to use the older library version by copying the old library back somewhere, renaming the program (say to the old name plus ``.orig''), and then create a small ``wrapper'' script that resets the library to use and calls the real (renamed) program. You could place the old library in its own special area, if you like, though the numbering conventions do permit multiple versions to live in the same directory. The wrapper script could look something like this: 通常情况下,你可以放心更新库;如果存在API变更,库的创建者应该会更改版本名。这样一来,单个系统上就可以存在多个库,并且每个程序都会选择到正确的库。不过,如果某个程序在更新保持相同版本名的库后出现问题,你可以通过以下方式强制它使用旧版本的库:将旧库复制回某个位置,重命名该程序(比如在旧名称后加上``.orig''),然后创建一个小型的“包装器”脚本,该脚本会重置要使用的库并调用真正(已重命名)的程序。你可以根据喜好将旧库放在专门的位置,尽管编号约定允许多个版本共存于同一目录中。包装器脚本可能如下所示:
#!/bin/sh
export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH
exec /usr/bin/my_program.orig $* #!/bin/sh
export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH
exec /usr/bin/my_program.orig $*#!/bin/sh
export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH
exec /usr/bin/my_program.orig $* |
You can see the list of the shared libraries used by a program using ldd(1). So, for example, you can see the shared libraries used by ls by typing: You can see the list of the shared libraries used by a program using ldd(1). So, for example, you can see the shared libraries used by ls by typing: 你可以使用ldd(1)查看程序所使用的共享库列表。例如,你可以通过输入以下命令查看ls所使用的共享库:
ldd /bin/ls ldd /bin/ls LDD /bin/ls |
/lib/ld-linux.so.N (where N is 1 or more, usually at least 2). This is the library that loads all other libraries./lib/ld-linux.so.N (where N is 1 or more, usually at least 2). This is the library that loads all other libraries./lib/ld-linux.so.N(其中N为1或更大,通常至少为2)。这是加载所有其他库的库。
libc.so.N (where N is 6 or more). This is the C library. Even other languages tend to use the C library (at least to implement their own libraries), so most programs at least include this one.libc.so.N (where N is 6 or more). This is the C library. Even other languages tend to use the C library (at least to implement their own libraries), so most programs at least include this one.libc.so.N(其中N为6或更大的数字)。这是C库。即使是其他语言也倾向于使用C库(至少会用它来实现自己的库),因此大多数程序至少会包含这个库。
When a new version of a library is binary-incompatible with the old one the soname needs to change. In C, there are four basic reasons that a library would cease to be binary compatible: When a new version of a library is binary-incompatible with the old one the soname needs to change. In C, there are four basic reasons that a library would cease to be binary compatible: 当一个库的新版本与旧版本存在二进制不兼容时,其版本标识(soname)需要更改。在C语言中,库失去二进制兼容性有四个基本原因:
The behavior of a function changes so that it no longer meets its original specification,The behavior of a function changes so that it no longer meets its original specification,函数的行为发生了变化,不再符合其原始规范,
Exported data items change (exception: adding optional items to the ends of structures is okay, as long as those structures are only allocated within the library).Exported data items change (exception: adding optional items to the ends of structures is okay, as long as those structures are only allocated within the library).导出的数据项会发生变化(例外情况:只要结构仅在库内分配,向结构末尾添加可选项是允许的)。
An exported function is removed.An exported function is removed. 一个导出函数被移除。
The interface of an exported function changes.The interface of an exported function changes.导出函数的接口发生了变化。
If you can avoid these reasons, you can keep your libraries binary-compatible. Said another way, you can keep your Application Binary Interface (ABI) compatible if you avoid such changes. For example, you might want to add new functions but not delete the old ones. You can add items to structures but only if you can make sure that old programs won't be sensitive to such changes by adding items only to the end of the structure, only allowing the library (and not the application) to allocate the structure, making the extra items optional (or having the library fill them in), and so on. Watch out - you probably can't expand structures if users are using them in arrays.If you can avoid these reasons, you can keep your libraries binary-compatible. Said another way, you can keep your Application Binary Interface (ABI) compatible if you avoid such changes. For example, you might want to add new functions but not delete the old ones. You can add items to structures but only if you can make sure that old programs won't be sensitive to such changes by adding items only to the end of the structure, only allowing the library (and not the application) to allocate the structure, making the extra items optional (or having the library fill them in), and so on. Watch out - you probably can't expand structures if users are using them in arrays.如果能避免这些原因,你就能保持库的二进制兼容性。换句话说,如果你避免此类更改,就能保持应用程序二进制接口(ABI)的兼容性。例如,你可能想要添加新函数,但不删除旧函数。你可以向结构体中添加项目,但前提是你能确保旧程序不会对这类更改敏感,方法包括仅在结构体末尾添加项目、只允许库(而非应用程序)分配结构体、让额外项目成为可选项(或让库来填充它们)等等。要注意——如果用户在数组中使用结构体,你很可能无法扩展结构体。
For C++ (and other languages supporting compiled-in templates and/or compiled dispatched methods), the situation is trickier. All of the above issues apply, plus many more issues. The reason is that some information is implemented ``under the covers'' in the compiled code, resulting in dependencies that may not be obvious if you don't know how C++ is typically implemented. Strictly speaking, they aren't ``new'' issues, it's just that compiled C++ code invokes them in ways that may be surprising to you. The following is a (probably incomplete) list of things that you cannot do in C++ and retain binary compatibility, as reported by Troll Tech's Technical FAQ: For C++ (and other languages supporting compiled-in templates and/or compiled dispatched methods), the situation is trickier. All of the above issues apply, plus many more issues. The reason is that some information is implemented ``under the covers'' in the compiled code, resulting in dependencies that may not be obvious if you don't know how C++ is typically implemented. Strictly speaking, they aren't ``new'' issues, it's just that compiled C++ code invokes them in ways that may be surprising to you. The following is a (probably incomplete) list of things that you cannot do in C++ and retain binary compatibility, as reported by Troll Tech's Technical FAQ: 对于C++(以及其他支持编译时模板和/或编译时分派方法的语言),情况更为复杂。上述所有问题都存在,而且还有更多问题。原因是有些信息是在编译后的代码中“秘密地”实现的,这导致如果你不了解C++的典型实现方式,可能会看不出其中的依赖关系。严格来说,这些并不是“新”问题,只是编译后的C++代码以可能会让你感到惊讶的方式引发了这些问题。以下是(可能并不完整的)在C++中无法做到却仍能保持二进制兼容性的事项列表,来源于Troll Tech的技术常见问题解答:
add reimplementations of virtual functions (unless it it safe for older binaries to call the original implementation), because the compiler evaluates SuperClass::virtualFunction() calls at compile-time (not link-time).add reimplementations of virtual functions (unless it it safe for older binaries to call the original implementation), because the compiler evaluates SuperClass::virtualFunction() calls at compile-time (not link-time).添加虚函数的重新实现(除非旧二进制文件调用原始实现是安全的),因为编译器在编译时(而非链接时)对SuperClass::virtualFunction()调用进行求值。
add or remove virtual member functions, because this would change the size and layout of the vtbl of every subclass. add or remove virtual member functions, because this would change the size and layout of the vtbl of every subclass. 添加或删除虚成员函数,因为这会改变每个子类的虚函数表的大小和布局。
change the type of any data members or move any data members that can be accessed via inline member functions.change the type of any data members or move any data members that can be accessed via inline member functions.更改任何数据成员的类型,或者移动任何可通过内联成员函数访问的数据成员。
change the class hierarchy, except to add new leaves. change the class hierarchy, except to add new leaves. 更改类层次结构,但添加新的叶节点除外。
add or remove private data members, because this would change the size and layout of every subclass. add or remove private data members, because this would change the size and layout of every subclass. 添加或删除私有数据成员,因为这会改变每个子类的大小和布局。
remove public or protected member functions unless they are inline.remove public or protected member functions unless they are inline.除非成员函数是内联的,否则移除公共或受保护的成员函数。
make a public or protected member function inline.make a public or protected member function inline.将公共或受保护的成员函数设为内联函数。
change what an inline function does, unless the old version continues working.change what an inline function does, unless the old version continues working.改变内联函数的功能,除非旧版本仍能正常工作。
change the access rights (i.e. public, protected or private) of a member function in a portable program, because some compilers mangle the access rights into the function name.change the access rights (i.e. public, protected or private) of a member function in a portable program, because some compilers mangle the access rights into the function name.在可移植程序中更改成员函数的访问权限(即公共、受保护或私有),因为有些编译器会将访问权限混入函数名中。
Given this lengthy list, developers of C++ libraries in particular must plan for more than occasional updates that break binary compatibility. Fortunately, on Unix-like systems (including Linux) you can have multiple versions of a library loaded at the same time, so while there is some disk space loss, users can still run ``old'' programs needing old libraries.Given this lengthy list, developers of C++ libraries in particular must plan for more than occasional updates that break binary compatibility. Fortunately, on Unix-like systems (including Linux) you can have multiple versions of a library loaded at the same time, so while there is some disk space loss, users can still run ``old'' programs needing old libraries.鉴于这份冗长的清单,C++库的开发者尤其必须做好准备,应对那些打破二进制兼容性的频繁更新。幸运的是,在类Unix系统(包括Linux)上,你可以同时加载一个库的多个版本,因此,尽管会占用一些磁盘空间,用户仍然可以运行需要旧库的“旧”程序。
| PrevPrev 上一页 | HomeHome 首页 | NextNext 下一页 |
| Static LibrariesStatic Libraries 静态库 | Dynamically Loaded (DL) LibrariesDynamically Loaded (DL) Libraries 动态加载(DL)库 |