Module libloading::changelog::r0_7_0

source ·
Expand description

Release 0.7.0 (2021-02-06)

§Breaking changes

§Loading functions are now unsafe

A number of associated methods involved in loading a library were changed to be unsafe. The affected functions are: Library::new, os::unix::Library::new, os::unix::Library::open, os::windows::Library::new, os::windows::Library::load_with_flags. This is the most prominent breaking change in this release and affects majority of the users of libloading.

In order to see why it was necessary, consider the following snippet of C++ code:

#include <vector>
#include <iostream>

static std::vector<unsigned int> UNSHUU = { 1, 2, 3 };

int main() {
    std::cout << UNSHUU[0] << UNSHUU[1] << UNSHUU[2] << std::endl; // Prints 123
    return 0;
}

The std::vector type, much like in Rust’s Vec, stores its contents in a buffer allocated on the heap. In this example the vector object itself is stored and initialized as a static variable – a compile time construct. The heap, on the other hand, is a runtime construct. And yet the code works exactly as you’d expect – the vector contains numbers 1, 2 and 3 stored in a buffer on heap. So, what makes it work out, exactly?

Various executable and shared library formats define conventions and machinery to execute arbitrary code when a program or a shared library is loaded. On systems using the PE format (e.g. Windows) this is available via the optional DllMain initializer. Various systems utilizing the ELF format take a slightly different approach of maintaining an array of function pointers in the .init_array section. A very similar mechanism exists on systems that utilize the Mach-O format.

For the C++ program above, the object stored in the UNSHUU global variable is constructed by code run as part of such an initializer routine. This initializer is run before the entry point (the main function) is executed, allowing for this magical behaviour to be possible. Were the C++ code built as a shared library instead, the initialization routines would run as the resulting shared library is loaded. In case of libloading – during the call to Library::new and other methods affected by this change.

These initialization (and very closely related termination) routines can be utilized outside of C++ too. Anybody can build a shared library in variety of different programming languages and set up the initializers to execute arbitrary code. Potentially code that does all sorts of wildly unsound stuff.

The routines are executed by components that are an integral part of the operating system. Changing or controlling the operation of these components is infeasible. With that in mind, the initializer and termination routines are something anybody loading a library must carefully evaluate the libraries loaded for soundness.

In practice, a vast majority of the libraries can be considered a good citizen and their initialization and termination routines, if they have any at all, can be trusted to be sound.

Also see: issue #86.

§Better & more consistent default behaviour on UNIX systems

On UNIX systems the Library::new, os::unix::Library::new and os::unix::Library::this methods have been changed to use RTLD_LAZY | RTLD_LOCAL as the default set of loader options (previously: RTLD_NOW). This has a couple benefits. Namely:

  • Lazy binding is generally quicker to execute when only a subset of symbols from a library are used and is typically the default when neither RTLD_LAZY nor RTLD_NOW are specified when calling the underlying dlopen API;
  • On most UNIX systems (macOS being a notable exception) RTLD_LOCAL is the default when neither RTLD_LOCAL nor RTLD_GLOBAL are specified. The explicit setting of the RTLD_LOCAL flag makes this behaviour consistent across platforms.

§Dropped support for Windows XP/Vista

The (broken) support for Windows XP and Windows Vista environments was removed. This was prompted primarily by a similar policy change in the Rust project but also as an acknowledgement to the fact that libloading never worked in these environments anyway.

§More accurate error variant names

Finally, the Error::LoadLibraryW renamed to Error::LoadLibraryExW to more accurately represent the underlying API that’s failing. No functional changes as part of this rename intended.