Re: -z preinitarray

From: Jonathan Adams (jwadams_at_gmail.com)
Date: 04/30/05

  • Next message: Christopher Tidy: "Re: Fun question about Sun history (curiousity)"
    Date: Sat, 30 Apr 2005 02:34:38 -0700
    
    

    In article <427338f6@news.meer.net>, "Dan Koren" <dankoren@yahoo.com>
    wrote:
    >
    > "Jonathan Adams" <jwadams@gmail.com> wrote in message
    > news:jwadams-0BD266.15453329042005@news1nwk.sfbay.sun.com...
    > >
    > > It generally can't, anyway; relying on init sections to
    > > set everything up is a bad idea. It's much better to set
    > > everything up in static data,
    >
    >
    > Which may or may not be possible depending on what the code
    > is intended/designed to do.

    It's usually possible; you set up the global data so that your fast
    paths will fail, then put the check for initialization in the slow
    path.

    > > and arrange for any additional initialization to happen
    > > at the first call to malloc/calloc/realloc/etc.
    >
    > While this would certainly work, it would also require each
    > and every routine in the library to check if the library has
    > already been initialized on every call. If I decide to follow
    > such an approach, I would rather start with a statically
    > initialized call table in which every entry points to the
    > initialization section. Maybe I should simply patch the GOT! ;-)

    No, it doesn't. It does require every initial path will be able to
    reach the initialization code, however. Patching the GOT would be
    probably be a bad idea. =]

    Better designed interfaces tend to make this a lot easier; if you have to
    get a handle to do anything, the initialization can happen when the first
    handle is created.

    > > The problem is that init section ordering is not
    > > guaranteed, especially when cyclic dependencies
    > > happen. The classic problem is LD_BIND_NOW=1
    > > plus C++; libCrun.so.1, libc.so.1, and libmymalloc.so
    > > form a cyclic dependency group, the linker calls
    > > libCrun's initializer first,
    >
    >
    > Even if I set -z initfirst when building the library?

    What if a library that uses malloc() also specifies '-z initfirst'?
    (For example, the "old" libthread on Solaris 8 and prior does exactly
    that; it even calls malloc() from its init section.)

    > While quite plausible, this example is also contrived.

    It actually happened; I had to fix this precise problem for libumem in
    Solaris:

    4924624 running C++ executables with libumem and LD_BIND_NOW causes
    SIGSEGV

    Previously, I did most of my initialization in the init section; now,
    nothing done in the init section is required for proper initialization.

    Ironically, it looks like the underlying problem no longer exists.[1]
    The point remains that init sections can bite you when you least expect
    it. If you have to have one, it should do as little as possible. You
    are probably right that it is safe to use one to initialize a memory
    allocator; I'll never do it again, having been bitten once. =]

    > A correctly designed memory allocator does not (should
    > not) depend on any other library. All it needs to do is
    > make one system call to allocate a slab to get rolling.
    > Unless of course it happens to have been written in C++,
    > in which case its author should be defrocked and sent
    > to work in support for the rest of his/her life ;-)

    It has to depend on libc, to make that system call. But libc shouldn't
    directly depend on anything (besides possibly the dynamic linker),
    either. (I agree that C++ is entirely inappropriate for an allocator)

    > > Summary: init sections are evil and unreliable; avoid
    > > them if you possibly can.
    >
    > Init sections do not have to be evil and unreliable, if
    > correctly designed and implemented. Unfortunately, most
    > systems currently available in the marketplace seem to
    > have resulted from quiltwork.

    I think the real problem is that dynamic linking has both a very high
    level of granularity, and huge amounts of legacy behavior. Small
    problems can magnify into large problems unexpectedly, and different
    features can interact destructively. On top of that, you have little
    control over how people use your library.

    > Also worth mentioning is the fact that most (if not all)
    > of this trouble could be avoided if one links the library
    > with the executable and assign its initializer to the
    > .preinit_array. Unfortunately, that would render the
    > allocator useless for existing applications.

    You could make it optional; make the init function safe to call multiple
    times. Then, any fool calling malloc() in his init sections can add it
    to preinit_array.

    I'd also note that it's typically not a problem, anyway; most dynamic
    linkers will make sure a library is initialized before going through a
    PLT to it, so as long as your init section doesn't call out to someone
    who will call back into you, you'll be fine. LD_BIND_NOW breaks that,
    since all the PLTs are resolved.

    Cheers,
    - jonathan

    [1] Earlier in Solaris 10's development, libc referenced (using a weak
    symbol) some stack unwinding goop in libC (the C++ runtime). That
    caused the dependency loop; libC depends on libumem depends on libc
    depends on libC. In Solaris 10 FCS, libc uses dlsym(3dl) to look up the
    symbol, so the dependency (and the problem) are gone.


  • Next message: Christopher Tidy: "Re: Fun question about Sun history (curiousity)"
  • Quantcast