[PATCH] D111863: [libunwind] Add an interface for dynamic .eh_frame registration

Lang Hames via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Sun Nov 7 10:37:37 PST 2021


lhames added a comment.

I've chatted about this with Joerg on a side thread and wanted to summarize my understanding of the situation.

The problem is that we have incompatible behaviors for `__register_frame` and `__deregister_frame` between unwinders:

- libgcc_s and the FreeBSD port of libunwind accept either null-terminated whole frames or single FDEs. If called with single FDEs, FreeBSD's port of libunwind will have quadratic cost for registration, as it always walks the remainder of the section. I suspect libgcc_s's behavior is similar..
- llvm-project's libunwind accepts only single FDEs.

Attempts to reliably detect the unwinder being used (so that we can call it the "best" way) have been flaky, leading to hard errors for clients. We're looking for a consistent behavior that we could rely on (at least when we can detect that it is available). Ideally, the proposed scheme should allow for efficient implementations.

The options that I have considered and discussed with others are:

1. Always registering single frames.

The problem with this is that it forces up front costs on the caller: all FDE starts must be identified, and we have one libcall per FDE to register (and probably a heap allocation embedded in that call). On top of this, two major unwinders (libgcc_s and the FreeBSD port of libunwind) seem like they have poor implementations of single-FDE processing that may have quadratic overhead in the worst case.

2. Teach libunwind's `_register_frame`  to also accept whole frames.

We could update libunwind's `__register_frame` implementation to also be able to take a whole section. For this to be useful though, we would still need some reliable way to detect whether this behavior is available on the libunwind version being used. A new special symbol would do (__unw_register_frame_accepts_whole_sections?), but this feels awkward compared to @housel's proposal for a new function with clearly defined behavior.

3. Register .eh_frame_hdr sections.

The .eh_frame_hdr section is a pre-built index of the .eh_frame section. Its format is documented in https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/ehframehdr.html. This section is not present in relocatable objects (which LLVM's JIT APIs use as input), but is synthesized by the static linker.

For ahead of time compilation this link-time synthesis provides a lot of value -- indexing the FDEs once at link times means that you never have to do it at launch time.

For a JIT I don't think this provides any value: Indexing of the .eh_frame will necessarily be happening at "launch" time. At the same time, forcing synthesis of an .eh_frame_hdr section for registration constrains unwinder implementations: they definitely can't index lazily (the caller already eagerly indexed for them), and if they don't want to use .eh_frame_hdr as their internal index data structure then the scheme has wasted two encoded pointers per FDE registered, plus 4 bytes and two more encoded values for the .eh_frame_hdr header.

4. @housel's solution -- Add a new registration function that is defined to take an __eh_frame start address.

This requires no up-front computation, we just pass in the start of the .eh_frame section. It places no constraints on the unwinder: Implementations are free to index eagerly or lazily as they like, and to choose whatever internal representation best suits them.

I think that @housel's solution is the best option, and that we should proceed with it.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D111863/new/

https://reviews.llvm.org/D111863



More information about the cfe-commits mailing list