Page MenuHomePhabricator

[Debug-Info] strict dwarf for DW_OP_stack_value
Needs ReviewPublic

Authored by shchenz on Apr 25 2021, 12:14 AM.

Details

Reviewers
dblaikie
probinson
aprantl
jsji
Esme
Group Reviewers
Restricted Project
Summary

This is the last patch for strict dwarf we found on AIX.

This is for the location expression DW_OP_stack_value, we found this operation is emitted in DWARF 3.

I tried to check this in a low level interface, for example, if we found an attribute is DW_AT_location, then we go through all its location values to find unmatched location expressions. But seems it can not distinguish the operations with other unsigned consts like offsets for some operations.

Now DW_OP_stack_value is added to location attribute as an unsigned value. See:

addUInt(*Loc, dwarf::DW_FORM_data1, dwarf::DW_OP_stack_value);

void DwarfUnit::addUInt(DIEValueList &Block, dwarf::Form Form,
                        uint64_t Integer) {
  addUInt(Block, (dwarf::Attribute)0, Form, Integer);
}

There may be some other const unsigned value for the location attribute, like:

void DwarfExpression::addOpPiece(unsigned SizeInBits, unsigned OffsetInBits) {
    emitOp(dwarf::DW_OP_bit_piece);
    emitUnsigned(SizeInBits);
    emitUnsigned(OffsetInBits);
}

void DIEDwarfExpression::emitUnsigned(uint64_t Value) {
  CU.addUInt(getActiveDIE(), dwarf::DW_FORM_udata, Value);
}

So it is hard to tell in the low-level API if the unsigned integer is from a DW_OP_ or from an unsigned const like above offsets.

I also went through the places where we generate DW_OP_stack_value in llc, it includes 3 places:
1: in DwarfCompileUnit.cpp, it is generated for Wasm target, so I don't change it.
2: in DwarfExpression.cpp, all the places in this file are guarded by DWARF 4. I saw the same strict DWARF check in this file. See https://github.com/llvm/llvm-project/blob/c3f95e9197643b699b891ca416ce7d72cf89f5fc/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp#L314-L322
3: in DwarfUnit.cpp, this is what I fixed in this patch.

Diff Detail

Event Timeline

shchenz created this revision.Apr 25 2021, 12:14 AM
shchenz requested review of this revision.Apr 25 2021, 12:14 AM
Herald added a project: Restricted Project. · View Herald TranscriptApr 25 2021, 12:14 AM

I think this fix is too narrow/special-cased. Other places where expressions are emitted might use DW_OP_stack_value (check out all the test cases in llvm/test that mention DW_OP_stack_value - I think they're not only from global variables) - so probably the solution is to have some abstraction for emitting a DwarfExpression into a block for an attribute (if we don't have one already) and have that abstraction scan the operations to see if there's a DW_OP_stack_value, and if there is, the attribute would be skipped. Also refactoring any emission of expressions to ensure they all use such an abstraction so they go through a common codepath.

I think this fix is too narrow/special-cased. Other places where expressions are emitted might use DW_OP_stack_value (check out all the test cases in llvm/test that mention DW_OP_stack_value - I think they're not only from global variables) - so probably the solution is to have some abstraction for emitting a DwarfExpression into a block for an attribute (if we don't have one already) and have that abstraction scan the operations to see if there's a DW_OP_stack_value, and if there is, the attribute would be skipped. Also refactoring any emission of expressions to ensure they all use such an abstraction so they go through a common codepath.

Thanks, David, just back from a vacation. I will have a check if we can abstract some logic here.

I think this fix is too narrow/special-cased. Other places where expressions are emitted might use DW_OP_stack_value (check out all the test cases in llvm/test that mention DW_OP_stack_value - I think they're not only from global variables) - so probably the solution is to have some abstraction for emitting a DwarfExpression into a block for an attribute (if we don't have one already) and have that abstraction scan the operations to see if there's a DW_OP_stack_value, and if there is, the attribute would be skipped. Also refactoring any emission of expressions to ensure they all use such an abstraction so they go through a common codepath.

Hi David @dblaikie , I did more investigation and still got the same conclusion in the description: it is hard to distinguish op DW_OP_stack_value from other unsigned values in location expression like offsets in the low-level/abstract API.

and also since the users of DW_OP_stack_value are very scattered, do you think it is acceptable to just fix this special narrow case we met on AIX, current llvm code base already guards some usage of DW_OP_stack_value like in file DwarfExpression.cpp? Thanks.

I think this fix is too narrow/special-cased. Other places where expressions are emitted might use DW_OP_stack_value (check out all the test cases in llvm/test that mention DW_OP_stack_value - I think they're not only from global variables) - so probably the solution is to have some abstraction for emitting a DwarfExpression into a block for an attribute (if we don't have one already) and have that abstraction scan the operations to see if there's a DW_OP_stack_value, and if there is, the attribute would be skipped. Also refactoring any emission of expressions to ensure they all use such an abstraction so they go through a common codepath.

Hi David @dblaikie , I did more investigation and still got the same conclusion in the description: it is hard to distinguish op DW_OP_stack_value from other unsigned values in location expression like offsets in the low-level/abstract API.

and also since the users of DW_OP_stack_value are very scattered, do you think it is acceptable to just fix this special narrow case we met on AIX, current llvm code base already guards some usage of DW_OP_stack_value like in file DwarfExpression.cpp? Thanks.

Have you tried walking through an expression with the DIExpressionCursor? Like the code in DwarfExpression::addExpression which iterates a DIExpressionCursor and is able to visit each operation without muddling up the operations from their operands. Maybe that or maybe some nearby/related APIs might be useful for this task.

shchenz added a comment.EditedMay 11 2021, 3:30 AM

I think this fix is too narrow/special-cased. Other places where expressions are emitted might use DW_OP_stack_value (check out all the test cases in llvm/test that mention DW_OP_stack_value - I think they're not only from global variables) - so probably the solution is to have some abstraction for emitting a DwarfExpression into a block for an attribute (if we don't have one already) and have that abstraction scan the operations to see if there's a DW_OP_stack_value, and if there is, the attribute would be skipped. Also refactoring any emission of expressions to ensure they all use such an abstraction so they go through a common codepath.

Hi David @dblaikie , I did more investigation and still got the same conclusion in the description: it is hard to distinguish op DW_OP_stack_value from other unsigned values in location expression like offsets in the low-level/abstract API.

and also since the users of DW_OP_stack_value are very scattered, do you think it is acceptable to just fix this special narrow case we met on AIX, current llvm code base already guards some usage of DW_OP_stack_value like in file DwarfExpression.cpp? Thanks.

Have you tried walking through an expression with the DIExpressionCursor? Like the code in DwarfExpression::addExpression which iterates a DIExpressionCursor and is able to visit each operation without muddling up the operations from their operands. Maybe that or maybe some nearby/related APIs might be useful for this task.

DIExpressionCursorseems be able to iterate the expressions defined in IR Metadata/Debug instructions. We always initialize the DIExpressionCursor with a return value of function getXXXExpr.

For now, there are two kinds of location info:
1: from DIELoc class directly

DIELoc *VBaseLocationDie = new (DIEValueAllocator) DIELoc;
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_dup);
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_deref);
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_constu);
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_udata, DT->getOffsetInBits());
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_minus);
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_deref);
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_plus);

  addBlock(MemberDie, dwarf::DW_AT_data_member_location, VBaseLocationDie);

2: from DIEDwarfExpression DwarfExpr. This kind can interact with DIExpressionCursor.

DIELoc *Loc = new (DIEValueAllocator) DIELoc;
DIEDwarfExpression DwarfExpr(*Asm, *this, *Loc);
// If there is an expression, emit raw unsigned bytes.
DwarfExpr.addFragmentOffset(Expr);
DwarfExpr.addUnsignedConstant(Entry->getInt());
DwarfExpr.addExpression(Expr);
addBlock(*VariableDie, dwarf::DW_AT_location, DwarfExpr.finalize());

refactor 1: The DIELoc kind initialization lost the DW_OP_ info as all operations are just unsigned integers, so we need to refactor all the places which use kind 1 to kind 2, which means always passing DwarfExpr.finalize() to addBlock().

refactor 2: After using DwarfExpr.finalize(), we also need to use DwarfExpr.addExpression(DIExpression) to add a single operation, which means the only way to add an operations is to call DwarfExpr.addExpression(). And then maybe we can check strict DWARF in DwarfExpr.addExpression()

refactor 3: and at last, we need to use addBlock() to add location-related attributes for all places.

Two remaining issues for the above proposal:
1: Is it possible to convert addUInt(LocDIE, form, DW_OP); to DwarfExpr.addExpression(Expr);?
2: How to make sure location-related attributes can only be added by calling addBlock(DwarfExpr.finalize())?

This seems like really a big effort if the above solution makes sense.

I think this fix is too narrow/special-cased. Other places where expressions are emitted might use DW_OP_stack_value (check out all the test cases in llvm/test that mention DW_OP_stack_value - I think they're not only from global variables) - so probably the solution is to have some abstraction for emitting a DwarfExpression into a block for an attribute (if we don't have one already) and have that abstraction scan the operations to see if there's a DW_OP_stack_value, and if there is, the attribute would be skipped. Also refactoring any emission of expressions to ensure they all use such an abstraction so they go through a common codepath.

Hi David @dblaikie , I did more investigation and still got the same conclusion in the description: it is hard to distinguish op DW_OP_stack_value from other unsigned values in location expression like offsets in the low-level/abstract API.

and also since the users of DW_OP_stack_value are very scattered, do you think it is acceptable to just fix this special narrow case we met on AIX, current llvm code base already guards some usage of DW_OP_stack_value like in file DwarfExpression.cpp? Thanks.

Have you tried walking through an expression with the DIExpressionCursor? Like the code in DwarfExpression::addExpression which iterates a DIExpressionCursor and is able to visit each operation without muddling up the operations from their operands. Maybe that or maybe some nearby/related APIs might be useful for this task.

DIExpressionCursorseems be able to iterate the expressions defined in IR Metadata/Debug instructions. We always initialize the DIExpressionCursor with a return value of function getXXXExpr.

For now, there are two kinds of location info:
1: from DIELoc class directly

DIELoc *VBaseLocationDie = new (DIEValueAllocator) DIELoc;
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_dup);
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_deref);
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_constu);
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_udata, DT->getOffsetInBits());
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_minus);
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_deref);
  addUInt(*VBaseLocationDie, dwarf::DW_FORM_data1, dwarf::DW_OP_plus);

  addBlock(MemberDie, dwarf::DW_AT_data_member_location, VBaseLocationDie);

2: from DIEDwarfExpression DwarfExpr. This kind can interact with DIExpressionCursor.

DIELoc *Loc = new (DIEValueAllocator) DIELoc;
DIEDwarfExpression DwarfExpr(*Asm, *this, *Loc);
// If there is an expression, emit raw unsigned bytes.
DwarfExpr.addFragmentOffset(Expr);
DwarfExpr.addUnsignedConstant(Entry->getInt());
DwarfExpr.addExpression(Expr);
addBlock(*VariableDie, dwarf::DW_AT_location, DwarfExpr.finalize());

refactor 1: The DIELoc kind initialization lost the DW_OP_ info as all operations are just unsigned integers, so we need to refactor all the places which use kind 1 to kind 2, which means always passing DwarfExpr.finalize() to addBlock().

refactor 2: After using DwarfExpr.finalize(), we also need to use DwarfExpr.addExpression(DIExpression) to add a single operation, which means the only way to add an operations is to call DwarfExpr.addExpression(). And then maybe we can check strict DWARF in DwarfExpr.addExpression()

refactor 3: and at last, we need to use addBlock() to add location-related attributes for all places.

Two remaining issues for the above proposal:
1: Is it possible to convert addUInt(LocDIE, form, DW_OP); to DwarfExpr.addExpression(Expr);?
2: How to make sure location-related attributes can only be added by calling addBlock(DwarfExpr.finalize())?

This seems like really a big effort if the above solution makes sense.

I'm not fully understanding the specifics of the refactoring you're suggesting - but, broadly speaking, yes, that's one direction we could go.

@aprantl @probinson @JDevlieghere @jhenderson - you folks have any opinions on this? I think it might be worth pushing expression emission through some common interface - the hand-crafted expressions we emit in a few places could probably be refactored to some common structure (either a DwarfExpression, or some other thing that could adapt from both DwarfExpression and these hand crafted expressions)

@aprantl @probinson @JDevlieghere @jhenderson - you folks have any opinions on this? I think it might be worth pushing expression emission through some common interface - the hand-crafted expressions we emit in a few places could probably be refactored to some common structure (either a DwarfExpression, or some other thing that could adapt from both DwarfExpression and these hand crafted expressions)

I'm afraid the DwarfExpression and related code is not something I'm really familiar with, so can't provide a useful opinion.

Hi @shchenz, is it feasible to use DIExpression::isImplicit at a higher level to filter out these kinds of expressions? I believe it's possible to parse a DIExpression before it's lowered to a DWARF expression, and there are places where we rely on the isImplicit method for correctness already. Also paging @StephenTozer who's touched this area recently.

I believe this would work for DW_AT_location attributes of DW_TAG_variable and DW_TAG_formal_parameter's, I'm less certain about other parts of DWARF that use expressions.

Hi @shchenz, is it feasible to use DIExpression::isImplicit at a higher level to filter out these kinds of expressions? I believe it's possible to parse a DIExpression before it's lowered to a DWARF expression, and there are places where we rely on the isImplicit method for correctness already. Also paging @StephenTozer who's touched this area recently.

I believe this would work for DW_AT_location attributes of DW_TAG_variable and DW_TAG_formal_parameter's, I'm less certain about other parts of DWARF that use expressions.

Hi @jmorse , first of all, thanks very much for your comment. Yes, DIExpression::isImplicit() should work to check if a DW_AT_location attribute has a DW_OP_stack_value operation. But this will only work if the location-related attributes are added by DIEDwarfExpression.addExpression(), right? There are also some other places that add location-related attributes not involving DIEDwarfExpression. See the below example:

DIELoc *Loc = new (DIEValueAllocator) DIELoc;
addOpAddress(*Loc, Asm->getSymbol(GV));
// Emit DW_OP_stack_value to use the address as the immediate value of
// the parameter, rather than a pointer to it.
addUInt(*Loc, dwarf::DW_FORM_data1, dwarf::DW_OP_stack_value);
addBlock(ParamDIE, dwarf::DW_AT_location, Loc);

We need a refactor here to change all places that call addBlock(, location_attr, ) to use DIEDwarfExpression, so that we have a common interface to add location-related attributes.
To achieve this goal, we also need some other refactors, see comment https://reviews.llvm.org/D101247#2750145

We need a refactor here to change all places that call addBlock(, location_attr, ) to use DIEDwarfExpression

Ah, I get it, it's the irregular parts of the DWARF emitting code that require maintenance. Putting everything through a common interface makes sense to me; although I'm no expert on DwarfDebug.cpp and nearby regions of code.