Skip to content

Commit 582baa5

Browse files
Unify compact unwind tables code (#1248)
Before, the code to convert from the initial DWARF unwind information rows to the compact representation we use for BPF was duplicated and not well tested. This commit: - cleans the code - adds comprehensive unittests - prepares the ground for snapshot tests in forthcoming PR Current code to convert to compact unwind tables will be cleaned up in another PR Signed-off-by: Francisco Javier Honduvilla Coto <javierhonduco@gmail.com> Signed-off-by: Francisco Javier Honduvilla Coto <javierhonduco@gmail.com>
1 parent ad2b6c6 commit 582baa5

File tree

3 files changed

+368
-6
lines changed

3 files changed

+368
-6
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright 2022 The Parca Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
//
14+
15+
package unwind
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/parca-dev/parca-agent/internal/dwarf/frame"
21+
)
22+
23+
type BpfCfaType uint16
24+
25+
const (
26+
//nolint: deadcode,varcheck
27+
cfaTypeUndefined BpfCfaType = iota
28+
cfaTypeRbp
29+
cfaTypeRsp
30+
cfaTypeExpression
31+
)
32+
33+
type BpfRbpType uint16
34+
35+
const (
36+
RbpRuleOffsetUnchanged BpfRbpType = iota
37+
RbpRuleOffset
38+
RbpRuleRegister
39+
rbpTypeExpression
40+
)
41+
42+
// CompactUnwindTableRows encodes unwind information using 2x 64 bit words.
43+
type CompactUnwindTableRow struct {
44+
pc uint64
45+
_reservedDoNotUse uint16
46+
cfaType uint8
47+
rbpType uint8
48+
cfaOffset int16
49+
rbpOffset int16
50+
}
51+
52+
func (cutr *CompactUnwindTableRow) Pc() uint64 {
53+
return cutr.pc
54+
}
55+
56+
func (cutr *CompactUnwindTableRow) ReservedDoNotUse() uint16 {
57+
return cutr._reservedDoNotUse
58+
}
59+
60+
func (cutr *CompactUnwindTableRow) CfaType() uint8 {
61+
return cutr.cfaType
62+
}
63+
64+
func (cutr *CompactUnwindTableRow) RbpType() uint8 {
65+
return cutr.rbpType
66+
}
67+
68+
func (cutr *CompactUnwindTableRow) CfaOffset() int16 {
69+
return cutr.cfaOffset
70+
}
71+
72+
func (cutr *CompactUnwindTableRow) RbpOffset() int16 {
73+
return cutr.rbpOffset
74+
}
75+
76+
type CompactUnwindTable []CompactUnwindTableRow
77+
78+
func (t CompactUnwindTable) Len() int { return len(t) }
79+
func (t CompactUnwindTable) Less(i, j int) bool { return t[i].pc < t[j].pc }
80+
func (t CompactUnwindTable) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
81+
82+
// BuildCompactUnwindTable produces a compact unwind table for the given
83+
// frame description entries.
84+
func BuildCompactUnwindTable(fdes frame.FrameDescriptionEntries) (CompactUnwindTable, error) {
85+
table := make(CompactUnwindTable, 0, 4*len(fdes)) // heuristic: we expect each function to have ~4 unwind entries.
86+
for _, fde := range fdes {
87+
frameContext := frame.ExecuteDwarfProgram(fde, nil)
88+
for insCtx := frameContext.Next(); frameContext.HasNext(); insCtx = frameContext.Next() {
89+
row := unwindTableRow(insCtx)
90+
compactRow, err := rowToCompactRow(row)
91+
if err != nil {
92+
return CompactUnwindTable{}, err
93+
}
94+
table = append(table, compactRow)
95+
}
96+
}
97+
return table, nil
98+
}
99+
100+
// rowToCompactRow converts an unwind row to a compact row.
101+
func rowToCompactRow(row *UnwindTableRow) (CompactUnwindTableRow, error) {
102+
var cfaType uint8
103+
var rbpType uint8
104+
var cfaOffset int16
105+
var rbpOffset int16
106+
107+
// CFA.
108+
//nolint:exhaustive
109+
switch row.CFA.Rule {
110+
case frame.RuleCFA:
111+
if row.CFA.Reg == frame.X86_64FramePointer {
112+
cfaType = uint8(cfaTypeRbp)
113+
} else if row.CFA.Reg == frame.X86_64StackPointer {
114+
cfaType = uint8(cfaTypeRsp)
115+
}
116+
cfaOffset = int16(row.CFA.Offset)
117+
case frame.RuleExpression:
118+
cfaType = uint8(cfaTypeExpression)
119+
cfaOffset = int16(ExpressionIdentifier(row.CFA.Expression))
120+
default:
121+
return CompactUnwindTableRow{}, fmt.Errorf("CFA rule is not valid: %d", row.CFA.Rule)
122+
}
123+
124+
// Frame pointer.
125+
switch row.RBP.Rule {
126+
case frame.RuleOffset:
127+
rbpType = uint8(RbpRuleOffset)
128+
rbpOffset = int16(row.RBP.Offset)
129+
case frame.RuleRegister:
130+
rbpType = uint8(RbpRuleRegister)
131+
case frame.RuleExpression:
132+
rbpType = uint8(rbpTypeExpression)
133+
case frame.RuleUndefined:
134+
case frame.RuleUnknown:
135+
case frame.RuleSameVal:
136+
case frame.RuleValOffset:
137+
case frame.RuleValExpression:
138+
case frame.RuleCFA:
139+
}
140+
141+
return CompactUnwindTableRow{
142+
pc: row.Loc,
143+
_reservedDoNotUse: 0,
144+
cfaType: cfaType,
145+
rbpType: rbpType,
146+
cfaOffset: cfaOffset,
147+
rbpOffset: rbpOffset,
148+
}, nil
149+
}
150+
151+
// CompactUnwindTableRepresentation converts an unwind table to its compact table
152+
// representation.
153+
func CompactUnwindTableRepresentation(unwindTable UnwindTable) (CompactUnwindTable, error) {
154+
compactTable := make(CompactUnwindTable, 0, len(unwindTable))
155+
156+
for i := range unwindTable {
157+
row := unwindTable[i]
158+
159+
compactRow, err := rowToCompactRow(&row)
160+
if err != nil {
161+
return CompactUnwindTable{}, err
162+
}
163+
164+
compactTable = append(compactTable, compactRow)
165+
}
166+
167+
return compactTable, nil
168+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Copyright 2022 The Parca Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
//
14+
15+
package unwind
16+
17+
import (
18+
"testing"
19+
20+
"github.com/parca-dev/parca-agent/internal/dwarf/frame"
21+
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func TestCompactUnwindTable(t *testing.T) {
26+
tests := []struct {
27+
name string
28+
input UnwindTableRow
29+
want CompactUnwindTableRow
30+
wantErr bool
31+
}{
32+
{
33+
name: "CFA with Offset on x86_64 stack pointer",
34+
input: UnwindTableRow{
35+
Loc: 123,
36+
CFA: frame.DWRule{Rule: frame.RuleCFA, Reg: frame.X86_64StackPointer, Offset: 8},
37+
RBP: frame.DWRule{Rule: frame.RuleUnknown},
38+
RA: frame.DWRule{Rule: frame.RuleOffset, Offset: -8},
39+
},
40+
want: CompactUnwindTableRow{
41+
pc: 123,
42+
_reservedDoNotUse: 0,
43+
cfaType: 2,
44+
rbpType: 0,
45+
cfaOffset: 8,
46+
rbpOffset: 0,
47+
},
48+
},
49+
{
50+
name: "CFA with Offset on x86_64 frame pointer",
51+
input: UnwindTableRow{
52+
Loc: 123,
53+
CFA: frame.DWRule{Rule: frame.RuleCFA, Reg: frame.X86_64FramePointer, Offset: 8},
54+
RBP: frame.DWRule{Rule: frame.RuleUnknown},
55+
RA: frame.DWRule{Rule: frame.RuleOffset, Offset: -8},
56+
},
57+
58+
want: CompactUnwindTableRow{
59+
pc: 123,
60+
_reservedDoNotUse: 0,
61+
cfaType: 1,
62+
rbpType: 0,
63+
cfaOffset: 8,
64+
rbpOffset: 0,
65+
},
66+
},
67+
{
68+
name: "CFA known expression PLT 1",
69+
input: UnwindTableRow{
70+
Loc: 123,
71+
CFA: frame.DWRule{Rule: frame.RuleExpression, Expression: Plt1[:]},
72+
RBP: frame.DWRule{Rule: frame.RuleUnknown},
73+
RA: frame.DWRule{Rule: frame.RuleOffset, Offset: -8},
74+
},
75+
76+
want: CompactUnwindTableRow{
77+
pc: 123,
78+
_reservedDoNotUse: 0,
79+
cfaType: 3,
80+
rbpType: 0,
81+
cfaOffset: 1,
82+
rbpOffset: 0,
83+
},
84+
},
85+
{
86+
name: "CFA known expression PLT 2",
87+
input: UnwindTableRow{
88+
Loc: 123,
89+
CFA: frame.DWRule{Rule: frame.RuleExpression, Expression: Plt2[:]},
90+
RBP: frame.DWRule{Rule: frame.RuleUnknown},
91+
RA: frame.DWRule{Rule: frame.RuleOffset, Offset: -8},
92+
},
93+
94+
want: CompactUnwindTableRow{
95+
pc: 123,
96+
_reservedDoNotUse: 0,
97+
cfaType: 3,
98+
rbpType: 0,
99+
cfaOffset: 2,
100+
rbpOffset: 0,
101+
},
102+
},
103+
{
104+
name: "CFA not known expression",
105+
input: UnwindTableRow{
106+
Loc: 123,
107+
CFA: frame.DWRule{Rule: frame.RuleExpression, Expression: []byte{'l', 'o', 'l'}},
108+
RBP: frame.DWRule{Rule: frame.RuleUnknown},
109+
RA: frame.DWRule{Rule: frame.RuleOffset, Offset: -8},
110+
},
111+
112+
want: CompactUnwindTableRow{
113+
pc: 123,
114+
_reservedDoNotUse: 0,
115+
cfaType: 3,
116+
rbpType: 0,
117+
cfaOffset: 0,
118+
rbpOffset: 0,
119+
},
120+
},
121+
{
122+
name: "RBP offset",
123+
input: UnwindTableRow{
124+
Loc: 123,
125+
CFA: frame.DWRule{Rule: frame.RuleCFA, Reg: frame.X86_64StackPointer, Offset: 8},
126+
RBP: frame.DWRule{Rule: frame.RuleOffset, Offset: 64},
127+
RA: frame.DWRule{Rule: frame.RuleOffset, Offset: -8},
128+
},
129+
130+
want: CompactUnwindTableRow{
131+
pc: 123,
132+
_reservedDoNotUse: 0,
133+
cfaType: 2,
134+
rbpType: 1,
135+
cfaOffset: 8,
136+
rbpOffset: 64,
137+
},
138+
},
139+
{
140+
name: "RBP register",
141+
input: UnwindTableRow{
142+
Loc: 123,
143+
CFA: frame.DWRule{Rule: frame.RuleCFA, Reg: frame.X86_64StackPointer, Offset: 8},
144+
RBP: frame.DWRule{Rule: frame.RuleRegister, Reg: 0xBAD},
145+
RA: frame.DWRule{Rule: frame.RuleOffset, Offset: -8},
146+
},
147+
148+
want: CompactUnwindTableRow{
149+
pc: 123,
150+
_reservedDoNotUse: 0,
151+
cfaType: 2,
152+
rbpType: 2,
153+
cfaOffset: 8,
154+
rbpOffset: 0,
155+
},
156+
},
157+
{
158+
name: "RBP expression",
159+
input: UnwindTableRow{
160+
Loc: 123,
161+
CFA: frame.DWRule{Rule: frame.RuleCFA, Reg: frame.X86_64StackPointer, Offset: 8},
162+
RBP: frame.DWRule{Rule: frame.RuleExpression, Expression: Plt1[:]},
163+
RA: frame.DWRule{Rule: frame.RuleOffset, Offset: -8},
164+
},
165+
166+
want: CompactUnwindTableRow{
167+
pc: 123,
168+
_reservedDoNotUse: 0,
169+
cfaType: 2,
170+
rbpType: 3,
171+
cfaOffset: 8,
172+
rbpOffset: 0,
173+
},
174+
},
175+
{
176+
name: "Invalid CFA rule returns error",
177+
input: UnwindTableRow{},
178+
want: CompactUnwindTableRow{},
179+
wantErr: true,
180+
},
181+
}
182+
183+
for _, test := range tests {
184+
t.Run(test.name, func(t *testing.T) {
185+
have, err := CompactUnwindTableRepresentation(UnwindTable{test.input})
186+
if test.wantErr {
187+
require.Error(t, err)
188+
} else {
189+
require.NoError(t, err)
190+
require.Equal(t, CompactUnwindTable{test.want}, have)
191+
}
192+
})
193+
}
194+
}

pkg/stack/unwind/dwarf_expression.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ const (
2828

2929
// DWARF expressions that we recognize.
3030

31-
// plt1 is equivalent to: sp + 8 + ((((ip & 15) >= 11)) << 3.
32-
var plt1 = [...]byte{
31+
// Plt1 is equivalent to: sp + 8 + ((((ip & 15) >= 11)) << 3.
32+
var Plt1 = [...]byte{
3333
frame.DW_OP_breg7,
3434
frame.DW_OP_const1u,
3535
frame.DW_OP_breg16,
@@ -42,8 +42,8 @@ var plt1 = [...]byte{
4242
frame.DW_OP_plus,
4343
}
4444

45-
// plt2 is quivalent to: sp + 8 + ((((ip & 15) >= 10)) << 3.
46-
var plt2 = [...]byte{
45+
// Plt2 is quivalent to: sp + 8 + ((((ip & 15) >= 10)) << 3.
46+
var Plt2 = [...]byte{
4747
frame.DW_OP_breg7,
4848
frame.DW_OP_const1u,
4949
frame.DW_OP_breg16,
@@ -80,11 +80,11 @@ func ExpressionIdentifier(expression []byte) DwarfExpressionID {
8080
cleanedExpression = append(cleanedExpression, opcode)
8181
}
8282

83-
if equalBytes(plt1[:], cleanedExpression) {
83+
if equalBytes(Plt1[:], cleanedExpression) {
8484
return ExpressionPlt1
8585
}
8686

87-
if equalBytes(plt2[:], cleanedExpression) {
87+
if equalBytes(Plt2[:], cleanedExpression) {
8888
return ExpressionPlt2
8989
}
9090

0 commit comments

Comments
 (0)