why use lazy binding for Position-Independent Code Function Calls

Viewed 921

I'm reading an textbook which says:

The motivation for lazy binding is that a typical application program will call only a handful of the hundreds or thousands of functions exported by a shared library such as libc.so. By deferring the resolution of a function’s address until it is actually called, the dynamic linker can avoid hundreds or thousands of unnecessary relocations at load time.

I kind of get the idea, but still confused. Let's say a program only call 10 functions of a shared library which has 100 fucntions inside. without lazy binding, the linker will only need to do 10 relocations for the functions the program use, so with lazy binding, how does dynamic linker can avoid hundreds or thousands(100 functions in this scenario) of unnecessary relocations? it is like you try to solve a problem which doesn't actually exist?

From what I can see about the lazy binding, there is no relocation needed at the load time, so you do save the time of doing 10 relocation,that's the only benefit I can see, is my understanding correct? but the author seems to indicate without lazy binding, the linker needs to do 100 relocations?

2 Answers

The text is confusing as written, but what it's talking about are functions that are referenced but never called, not ones that are never referenced. Regardless of whether lazy binding is in use or not, no lookups are needed for functions which are not referenced.

If your program references 10 functions in the library but calls 0 of them, the difference in lazy binding vs immediate binding is 0 vs 10 lookups. If your program references 100 functions in the library but only calls 10 of them, the difference is 10 vs 100 lookups. The intended case where lazy binding is supposed to save you time is when very large numbers of functions are referenced because of one shared library depending on another. For example, say your program uses library A, which depends on library B, and library A references all 10000 functions in library B, but your program only uses a small portion of the functionality in library A that only calls one function in library B. Now, instead of 10000 lookups, only one gets done. This sounds like a major benefit.

On the flip side, however, lazy bindings are seriously error-prone (due to the mechanism of how the lazy resolver is called, how it interacts with call ABI contracts about register usage, and the possibility that the first call happens in a really awkward context like from a signal handler) and doing lookups one at a time as the functions are called, rather than all at once, is a lot more disruptive of program flow, cache utilization, etc. and will take significantly more total time if all the functions in the library are actually called. Of course this is a one-time (per process instance) cost.

The textbook is a bit misleading. Symbol binding used to be quite slow, and the point of lazy binding is that with it, programs could reach a point more quickly which they do something. Basically, startup performance is reduced. It can be noticeable even today if a small program depends on a large library which depends on another large library. Without lazy binding, the first library needs to be fully relocated right at process start, and with lazy binding, only the actually used parts are relocated. But as you point out, in the situation without these two libraries, lazy binding does not make much of a difference.

There is also a semantic difference with lazy binding, and that's probably the more important aspect today. With it, you can dlopen another shared object that provides symbol dependencies of the current shared object. With non-lazy binding, the symbols are not present during initial relocation, leading to a load failure. Such an approach can make it really difficult to migrate away from lazy binding, say for increased security hardening.

Related