This is an archive of the discontinued LLVM Phabricator instance.

[MC][DWARF][AsmParser] Ensure nested CFI frames are diagnosed to avoid a crash or bad codegen in Dwarf streamer later on.
ClosedPublic

Authored by kristina on Sep 5 2018, 10:42 AM.

Details

Summary

Integrated assembler currently does not check whether there is an an existing CFI frame when .cfi_startproc is used, forwarding it down to MC layer. This patch makes integrated assembler check for it so it can report it more gracefully and avoid letting this leak into the MCStreamer.

The actual error reporting code in MCStreamer seems to be missing a return which allows this to trickle down, but this revision only addresses it in integrated assembler as location information is guaranteed to be available and is the part exposed to the user as opposed to MCStreamer itself. I'm not sure if the missing bailout after reportError is intentional or not, and I think a separate diff would be better for that if it's not.

This causes the following assert to fail down the line in Dwarf streamer since it shouldn't have made it that far:

<unknown>:0: error: starting new .cfi frame before finishing the previous one
clang-8: /SourceCache/llvm-trunk-8.0.4104/lib/MC/MCExpr.cpp:176: llvm::MCSymbolRefExpr::MCSymbolRefExpr(const llvm::MCSymbol *, llvm::MCSymbolRefExpr::VariantKind, const llvm::MCAsmInfo *, llvm::SMLoc): Assertion `Symbol' failed.

This patch makes it evaluate the conditional twice in successful cases (second time in MCStreamer) but only for AsmParser for that directive so that shouldn't lead to major performance regressions. This fixes the assert and presents a diagnostic error with accurate context/line information.

However if this happens within a PP evaluation, the correct line is reported but the contextual information is not correct (hence the comment), like this:

os/os_thread_x86_64.S:269:2: error: .cfi_startproc cannot be used in an existing frame
 pushq %rbp; movq %rsp, %rbp;

This is still better than an assertion down the line, and I don't want to violate MCStreamer/AsmParser layering, though MCStreamer with optional SMLoc could be a better place for this? If anyone can suggest a way to deal with PP macros in a more friendly way I'll amend the revision.

Have tested it on ELF/Linux/x86_64, with patch, instead of a hard assertion and a crash it emits the a diagnostic.

Diff Detail

Repository
rL LLVM

Event Timeline

kristina created this revision.Sep 5 2018, 10:42 AM

I don't know anything about this code, but there should probably be a test for this change, somewhere.

kristina edited the summary of this revision. (Show Details)Sep 5 2018, 10:47 AM
kristina added a reviewer: chandlerc.
kristina added a comment.EditedSep 5 2018, 10:56 AM

Yes, though I specifically tried to avoid touching MCStreamer itself for that reason since regressions there would be much worse than in AsmParser, and I'm not entirely sure how to test this either, since it's an error right down the line in MCStreamer from which it doesn't bail out after calling reportError. Would rather wait for some input from people familiar with the MC layer, as since this mostly tries to address this bug being triggered from -cc1as and this seems like the appropriate layer.

mgrang added a subscriber: mgrang.Sep 5 2018, 11:01 AM
mgrang added inline comments.
lib/MC/MCParser/AsmParser.cpp
3891 ↗(On Diff #164063)

Please remove the extra newline.

kristina updated this revision to Diff 164085.Sep 5 2018, 11:15 AM

Removed extra newline.

kristina marked an inline comment as done.Sep 5 2018, 11:19 AM
kristina edited the summary of this revision. (Show Details)Sep 7 2018, 7:04 AM
kristina retitled this revision from [MC] [AsmParser]: Ensure a new CFI frame is not opened within an existing one when using integrated-as. to [MC] [AsmParser]: Ensure a new CFI frame is not opened within an existing one when using cc1as (causing a crash due to failed assert)..Sep 7 2018, 7:18 AM
rnk added a subscriber: rnk.Sep 7 2018, 12:52 PM

I'd suggest fixing this in MCStreamer::EmitCFIStartProc by having it recover in some way that doesn't lead to future assertions. Also, it should be possible to write a standalone .s test case for this.

kristina added a comment.EditedSep 7 2018, 1:35 PM
In D51695#1227779, @rnk wrote:

I'd suggest fixing this in MCStreamer::EmitCFIStartProc by having it recover in some way that doesn't lead to future assertions. Also, it should be possible to write a standalone .s test case for this.

Oh I can definitely do a small reproduction of the bug in an cfi_nested_crash.s type thing and use that as a test case for any future regressions if that's what people keep saying.

The fix in MCStreamer would be to return after the reporting the error and to also add optional SMLoc to the signature so it could report the error when used as part of integrated as, which is what I wasn't sure about, since it's missing a return there, I don't know if it's on purpose or not, I really want someone familiar with this part to comment, the AsmParser is was a provisional source-location-aware fix to the consumer visible part of this stuff (as -cc1as or inline directives) that avoids crashes or bad DWARF tables as a band-aid.

The actual the questionable code below is what I really want an opinion on (lib/MC/MCStreamer.cpp):

void MCStreamer::EmitCFIStartProc(bool IsSimple) {
  if (hasUnfinishedDwarfFrameInfo())
    // TODO(kristina): Is this intentionally falling through or not? If so
    // it will assert down the line if used from cc1as in DWARF streamer.
    getContext().reportError(
        SMLoc(), "starting new .cfi frame before finishing the previous one");

  MCDwarfFrameInfo Frame;
  Frame.IsSimple = IsSimple;
  EmitCFIStartProcImpl(Frame);

  const MCAsmInfo* MAI = Context.getAsmInfo();
  if (MAI) {
    for (const MCCFIInstruction& Inst : MAI->getInitialFrameState()) {
      if (Inst.getOperation() == MCCFIInstruction::OpDefCfa ||
          Inst.getOperation() == MCCFIInstruction::OpDefCfaRegister) {
        Frame.CurrentCfaRegister = Inst.getRegister();
      }
    }
  }
  
  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  // This being let through will cause the crash down the line once
  // it reaches the DWARF streamer.
  DwarfFrameInfos.push_back(Frame);
}
rnk added a comment.Sep 7 2018, 2:03 PM

Passing the source location through to MCStreamer seems like a good idea. Before rL315264, this code used to use report_fatal_error. My goal was to make it less crummy, and make sure errors get diagnosed when emitting assembly or emitting object files.

kristina added a reviewer: rnk.EditedSep 7 2018, 2:09 PM

Alright, I'll handle it in MCStreamer directly instead, if SMLoc is provided will try to print an error with more context seeing as it could be from -cc1as. Would it be worth returning and bailing out instead of just reporting an error even if SMLoc isn't provided?

This is a testcase for the crash, patch fixes it, though I'll amend it to work on MCStreamer level as per what @rnk suggested. I'm going to sound really stupid asking but where should the testcase be, is it a FileCheck based one? I just made two targets, one with pre-patch toolchain and one with current (my working tree) toolchain which handles this just fine. First one crashes, second one doesn't, not sure where tests like that go.

#if !defined(__x86_64__)
	#error Unsupported architecture
#endif   

.align 2, 0x90
.type Cfi_level_0, @function
.global Cfi_level_0
.hidden Cfi_level_0
.cfi_startproc
Cfi_level_0:
	movq $123, %rax
	ret

.align 2, 0x90
.type Cfi_level_1, @function
.global Cfi_level_1
.hidden Cfi_level_1
.cfi_startproc
Cfi_level_1:
	movq $321, %rax
	int  $3

.cfi_endproc; .size Cfi_level_0, . - Cfi_level_0
rnk added a comment.Sep 10 2018, 11:41 AM

This is a testcase for the crash, patch fixes it, though I'll amend it to work on MCStreamer level as per what @rnk suggested. I'm going to sound really stupid asking but where should the testcase be, is it a FileCheck based one? I just made two targets, one with pre-patch toolchain and one with current (my working tree) toolchain which handles this just fine. First one crashes, second one doesn't, not sure where tests like that go.

Yeah, we definitely want a lit test. I think the one we already have exercises this code path, but because it doesn't create an object file, it doesn't assert. I think if you apply this patch, the test will start failing, and your fix should make it pass again:

diff --git a/llvm/test/MC/X86/cfi-scope-errors.s b/llvm/test/MC/X86/cfi-scope-errors.s
index a61f817f741e..91d42b9662b6 100644
--- a/llvm/test/MC/X86/cfi-scope-errors.s
+++ b/llvm/test/MC/X86/cfi-scope-errors.s
@@ -1,4 +1,4 @@
-# RUN: not llvm-mc %s -triple x86_64-linux -o /dev/null 2>&1 | FileCheck %s --implicit-check-not=error:
+# RUN: not llvm-mc %s -triple x86_64-linux -filetype=obj -o /dev/null 2>&1 | FileCheck %s --implicit-check-not=error:

 # FIXME: Push source locations into diagnostics.

If you thread through the SMLoc you'll even address the FIXME.

kristina planned changes to this revision.Sep 28 2018, 8:40 PM
kristina updated this revision to Diff 169934.Oct 16 2018, 6:23 PM
kristina added 1 blocking reviewer(s): rnk.

Address comments by @rnk and redo this on MCStreamer level adding SMLoc information to the diagnostic as a bonus. Room for followup patches to propagate more source locations for other directives but for now this is out of scope of this patch as it deals with the assembler crash.

kristina updated this revision to Diff 169937.Oct 16 2018, 6:25 PM

Seems to be fine (ignore the 8 failed tests, they're IPC related and fail because of the host environment constraints).

ninja check-llvm

-snip-

********************
Testing Time: 80.11s
********************
Failing Tests (8):
    LLVM :: ExecutionEngine/MCJIT/eh-lg-pic.ll
    LLVM :: ExecutionEngine/MCJIT/eh.ll
    LLVM :: ExecutionEngine/MCJIT/multi-module-eh-a.ll
    LLVM :: ExecutionEngine/MCJIT/remote/eh.ll
    LLVM :: ExecutionEngine/OrcMCJIT/eh-lg-pic.ll
    LLVM :: ExecutionEngine/OrcMCJIT/eh.ll
    LLVM :: ExecutionEngine/OrcMCJIT/multi-module-eh-a.ll
    LLVM :: ExecutionEngine/OrcMCJIT/remote/eh.ll

  Expected Passes    : 16602
  Expected Failures  : 50
  Unsupported Tests  : 11500
  Unexpected Failures: 8
kristina retitled this revision from [MC] [AsmParser]: Ensure a new CFI frame is not opened within an existing one when using cc1as (causing a crash due to failed assert). to [MC][DWARF][AsmParser] Ensure nested CFI frames are diagnosed to avoid a crash or bad codegen in Dwarf streamer later on..Oct 17 2018, 6:11 PM
kristina removed 1 blocking reviewer(s): rnk.
rnk accepted this revision as: rnk.Oct 18 2018, 9:28 PM

lgtm Thanks!

This revision is now accepted and ready to land.Oct 18 2018, 9:28 PM
This revision was automatically updated to reflect the committed changes.