This is an archive of the discontinued LLVM Phabricator instance.

Correctly update isSignalFrame when unwinding the stack via dwarf.
ClosedPublic

Authored by saugustine on Oct 31 2019, 12:46 PM.

Details

Summary

A "signal frame" is a function or block of code where execution arrives via a signal or interrupt, rather than via a normal call instruction. In fact, a particular instruction is interrupted by the signal and needs to be restarted. Therefore, when the signal handler is complete, execution needs to return to the interrupted instruction, rather than the instruction immediately following the call instruction, as in a normal call.

Stack unwinders need to know this to correctly unwind signal frames. Dwarf handily provides an "S" in the CIE augmentation string to describe this case, and the libunwind API provides various functions to for unwinders to determine it,.

The llvm libunwind implementation correctly sets it's internal variable "isSignalFrame" when initializing an unwind context. However, upon stepping up the stack, the current implementation correctly reads the augmentation string and sets it in the CIE info (which it then discards), libunwind doesn't update it's internal unwind context data structure.

This change fixes that, and provides compatibility with both the canonical libunwind and the libgcc implementation.

Event Timeline

saugustine created this revision.Oct 31 2019, 12:46 PM
Herald added a project: Restricted Project. · View Herald Transcript
jgorbe added a subscriber: jgorbe.Oct 31 2019, 2:29 PM
lhames added a subscriber: lhames.Nov 6 2019, 3:21 PM

Looks sane to me, but I'm not a cfi_signal_frame expert.

Do you know the ipBefore == isSignalFrame relationship is true on all platforms?

Looks sane to me, but I'm not a cfi_signal_frame expert.

Do you know the ipBefore == isSignalFrame relationship is true on all platforms?

I think so. The default inside the canonical libunwind on github has it that way, as does the default inside libgcc (unwind-dw2.c). Strangely, inside libgcc several targets override the default implementation of Unwind_GetIPInfo, but all set ipBefore equivalent to isSignalFrame.

So if there is an exception. I don't know what it would be.

Outside of the code factoring which I haven't looked at I thought I'd link this:

https://www.airs.com/blog/archives/460

as the best description I've got for what cfi_signal_frame is supposed to be doing here - look for the bit with the augmentation value of 'S' in there for the description.

Sterling: Probably wouldn't hurt to describe this much more in the commit/patch message for future archaeologists.

saugustine edited the summary of this revision. (Show Details)Nov 7 2019, 10:24 AM
echristo accepted this revision.Nov 7 2019, 10:39 AM

LGTM. I know that lhames was in the process of reviewing so wait for an OK there please.

This revision is now accepted and ready to land.Nov 7 2019, 10:39 AM
lhames accepted this revision.Nov 7 2019, 1:08 PM

Ok. So the documentation (sparse as it is) syncs up with what you're seeing in existing implementations. Looks good to me. :)

MaskRay added inline comments.Nov 7 2019, 1:16 PM
libunwind/src/UnwindLevel1-gcc-ext.c
226

"Negative" in the comment but <= 0 in the code?

MaskRay added inline comments.Nov 7 2019, 1:22 PM
libunwind/src/UnwindLevel1-gcc-ext.c
226
_LIBUNWIND_HIDDEN int __unw_is_signal_frame(unw_cursor_t *cursor) {
  _LIBUNWIND_TRACE_API("__unw_is_signal_frame(cursor=%p)",
                       static_cast<void *>(cursor));
  AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor;
  return co->isSignalFrame(); /////// this returns bool
}

Does it fail if we simply do *ipBefore = __unw_is_signal_frame((unw_cursor_t *)context);, like libgcc/unwind-dw2.c?

MaskRay added inline comments.Nov 7 2019, 1:24 PM
libunwind/test/signal_frame.pass.cpp
2

It'd be nice to add a file-level comment explaining the purpose of the test. The filename signal_frame.pass encodes some information but it is probably not very obvious.

saugustine updated this revision to Diff 228314.Nov 7 2019, 2:22 PM
saugustine marked 3 inline comments as done.
  • Add file header and explanatory comment.
saugustine marked an inline comment as done.Nov 7 2019, 2:23 PM

Updated for all comments, and committing shortly.

libunwind/src/UnwindLevel1-gcc-ext.c
226

The documented interface for __unw_is_signal_frame is an integer, with negative meaning any of several possible errors. I could split this conditional into two:

if (isSignalFrame < 0 || isSignalFrame == 0)...

but that seems a little absurd.

This implementation of __unw_is_signal_frame doesn't actually return negative values: True, but relying on that would be ignoring the documented interface.

Libgcc is subtly different than the code you actually proposed. It uses _Unwind_isSignalFrame (not __unw_is_signal_frame) which has a documented interface of zero or one, and there have been various cleanups in gcc land when various bits of code treat the two the same.

I could switch to _Unwind_isSignalFrame, of course, but most of the code in this file uses the __unw_ interface and I chose to be consistent with the surrounding code.

MaskRay accepted this revision.Nov 7 2019, 2:43 PM
MaskRay added inline comments.
libunwind/test/signal_frame.pass.cpp
1

I don't know why we need the header. Most clang/lld/... tests don't have a license notice.

16

Delete void (this is C++)/

lhames added inline comments.Nov 7 2019, 2:47 PM
libunwind/src/UnwindLevel1-gcc-ext.c
226

FWIW

if (!isSignalFrame || isSignalFrame < 0) ...

seems totally reasonable to me. I actually did a double-take on the <= 0 test the first time I saw it too.

This revision was automatically updated to reflect the committed changes.