Skip to content

Commit 28c11c1

Browse files
committed
Add LuaJIT interpreter
This PR contains an interpreter for LuaJIT code, which is primarily useful in practice due to OpenResty. It has been used in Parca/Polar Signals since Nov. 2024. A thorough explanation of the approach used as well as an introduction to Lua internals can be found at the accompanying blog post: https://www.polarsignals.com/blog/posts/2024/11/13/lua-unwinding .
1 parent 90d0e37 commit 28c11c1

52 files changed

Lines changed: 4794 additions & 21 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ endif
3030
export TARGET_ARCH
3131
export CGO_ENABLED = 0
3232
export GOARCH = $(TARGET_ARCH)
33-
export CC = $(ARCH_PREFIX)-linux-gnu-gcc
34-
export OBJCOPY = $(ARCH_PREFIX)-linux-gnu-objcopy
33+
# export CC = $(ARCH_PREFIX)-linux-gnu-gcc
34+
# export OBJCOPY = $(ARCH_PREFIX)-linux-gnu-objcopy
3535

3636
BRANCH = $(shell git rev-parse --abbrev-ref HEAD | tr -d '-' | tr '[:upper:]' '[:lower:]')
3737
COMMIT_SHORT_SHA = $(shell git rev-parse --short=8 HEAD)

interpreter/go/go_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func BenchmarkGolang(b *testing.B) {
3333
if err != nil {
3434
b.Fatalf("Failed to create hostID: %v", err)
3535
}
36-
loaderInfo := interpreter.NewLoaderInfo(hostFileID, elfRef)
36+
loaderInfo := interpreter.NewLoaderInfo(hostFileID, elfRef, nil)
3737
rm := remotememory.NewProcessVirtualMemory(libpfPID)
3838

3939
b.ReportAllocs()

interpreter/instancestubs.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@
44
package interpreter // import "go.opentelemetry.io/ebpf-profiler/interpreter"
55

66
import (
7+
"unsafe"
8+
9+
"go.opentelemetry.io/ebpf-profiler/host"
710
"go.opentelemetry.io/ebpf-profiler/libc"
811
"go.opentelemetry.io/ebpf-profiler/libpf"
12+
"go.opentelemetry.io/ebpf-profiler/lpm"
913
"go.opentelemetry.io/ebpf-profiler/metrics"
1014
"go.opentelemetry.io/ebpf-profiler/process"
1115
"go.opentelemetry.io/ebpf-profiler/reporter"
16+
"go.opentelemetry.io/ebpf-profiler/util"
1217
)
1318

1419
// InstanceStubs provides empty implementations of Instance hooks that are
@@ -36,3 +41,32 @@ func (is *InstanceStubs) GetAndResetMetrics() ([]metrics.Metric, error) {
3641
func (is *InstanceStubs) ReleaseResources() error {
3742
return nil
3843
}
44+
45+
type EbpfHandlerStubs struct{}
46+
47+
func (m *EbpfHandlerStubs) UpdatePidInterpreterMapping(_ libpf.PID,
48+
pfx lpm.Prefix, _ uint8, _ host.FileID, _ uint64) error {
49+
return nil
50+
}
51+
52+
func (m *EbpfHandlerStubs) DeletePidInterpreterMapping(_ libpf.PID, _ lpm.Prefix) error {
53+
return nil
54+
}
55+
56+
func (m *EbpfHandlerStubs) CoredumpTest() bool {
57+
return false
58+
}
59+
60+
func (m *EbpfHandlerStubs) UpdateProcData(libpf.InterpreterType, libpf.PID,
61+
unsafe.Pointer) error {
62+
return nil
63+
}
64+
65+
func (m *EbpfHandlerStubs) DeleteProcData(libpf.InterpreterType, libpf.PID) error {
66+
return nil
67+
}
68+
69+
func (m *EbpfHandlerStubs) UpdateInterpreterOffsets(ebpfProgIndex uint16, fileID host.FileID,
70+
offsetRanges []util.Range) error {
71+
return nil
72+
}

interpreter/loaderinfo.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"go.opentelemetry.io/ebpf-profiler/host"
1010
"go.opentelemetry.io/ebpf-profiler/libpf"
1111
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
12+
sdtypes "go.opentelemetry.io/ebpf-profiler/nativeunwind/stackdeltatypes"
1213
"go.opentelemetry.io/ebpf-profiler/util"
1314
)
1415

@@ -19,13 +20,17 @@ type LoaderInfo struct {
1920
fileID host.FileID
2021
// elfRef provides a cached access to the ELF file.
2122
elfRef *pfelf.Reference
23+
// deltas contains the stack deltas for the executable.
24+
deltas sdtypes.StackDeltaArray
2225
}
2326

2427
// NewLoaderInfo returns a populated LoaderInfo struct.
25-
func NewLoaderInfo(fileID host.FileID, elfRef *pfelf.Reference) *LoaderInfo {
28+
func NewLoaderInfo(fileID host.FileID, elfRef *pfelf.Reference,
29+
deltas sdtypes.StackDeltaArray) *LoaderInfo {
2630
return &LoaderInfo{
2731
fileID: fileID,
2832
elfRef: elfRef,
33+
deltas: deltas,
2934
}
3035
}
3136

@@ -60,3 +65,8 @@ func (i *LoaderInfo) FileID() host.FileID {
6065
func (i *LoaderInfo) FileName() string {
6166
return i.elfRef.FileName()
6267
}
68+
69+
// Deltas returns the stack deltas for the executable of this LoaderInfo.
70+
func (i *LoaderInfo) Deltas() sdtypes.StackDeltaArray {
71+
return i.deltas
72+
}

interpreter/luajit/bc.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Copyright 2024 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+
12+
package luajit // import "go.opentelemetry.io/ebpf-profiler/interpreter/luajit"
13+
14+
// See https://github.com/openresty/luajit2/blob/7952882d/src/lj_bc.h#L34
15+
16+
var bcMode = []uint16{
17+
0x3183, 0x3183, 0x3983, 0x3983, 0x2183, 0x2183, 0x2503, 0x2503,
18+
0x2483, 0x2483, 0x2403, 0x2403, 0xb181, 0xb181, 0xb180, 0xb180,
19+
0xb303, 0xb303, 0xb181, 0xb181, 0x8181, 0x2981, 0x5499, 0x5c99,
20+
0x6499, 0x6c99, 0x7499, 0x5499, 0x5c99, 0x6499, 0x6c99, 0x7499,
21+
0x5199, 0x5999, 0x6199, 0x6999, 0x7199, 0x7999, 0x4221, 0xb501,
22+
0xb701, 0xb381, 0xb481, 0xb401, 0xb102, 0xb281, 0xb185, 0xb505,
23+
0xb485, 0xb405, 0xb684, 0x1601, 0x1301, 0x1581, 0x0501, 0x0d03,
24+
0x0199, 0x0519, 0x0319, 0x0199, 0x099b, 0x0d1b, 0x0b1b, 0x0c82,
25+
0x099b, 0x4b32, 0x4b32, 0x4b02, 0x4b02, 0x4b32, 0x4b32, 0xb332,
26+
0xb682, 0xb302, 0xb304, 0xb304, 0xb304, 0xb682, 0xb682, 0xb682,
27+
0xb682, 0xb302, 0xb682, 0xb682, 0xb302, 0xb684, 0xb684, 0xb304,
28+
0xb684, 0xb004, 0xb004, 0xb304, 0xb004, 0xb004, 0xb304, 0xb004,
29+
0xb004}
30+
31+
func bcOp(ins uint32) uint32 {
32+
return ins & 0xff
33+
}
34+
35+
func bcModeMM(op uint32) uint32 {
36+
return uint32(bcMode[op] >> 11)
37+
}
38+
39+
func bcModeA(op uint32) uint32 {
40+
return uint32(bcMode[op] & 7)
41+
}
42+
43+
func bcA(ins uint32) uint32 {
44+
return (ins >> 8) & 0xff
45+
}
46+
47+
func bcB(ins uint32) uint32 {
48+
return ins >> 24
49+
}
50+
51+
func bcC(ins uint32) uint32 {
52+
return (ins >> 16) & 0xff
53+
}
54+
55+
func bcD(ins uint32) uint32 {
56+
return ins >> 16
57+
}
58+
59+
// Return the register used to store the function called at pc or metaname if it's a metamethod
60+
func getSlotOrMetaname(ins uint32) (slot uint32, metaname string) {
61+
op := bcOp(ins)
62+
mm := bcModeMM(op)
63+
if mm == MMcall {
64+
slot := bcA(ins)
65+
if bcOp(ins) == BC_ITERC {
66+
slot -= 3
67+
}
68+
return slot, ""
69+
} else if mm != MMMax {
70+
return 0, ljMetaNames[mm]
71+
}
72+
return 0, ""
73+
}
74+
func bcModeAIsBase(op uint32) bool {
75+
return bcModeA(op) == BCMbase
76+
}
77+
func bcModeAIsDst(op uint32) bool {
78+
return bcModeA(op) == BCMdst
79+
}
80+
81+
var ljMetaNames = []string{
82+
"index", "newindex", "gc",
83+
"mode", "eq", "len", "lt", "le", "concat",
84+
"call", "add", "sub", "mul", "div", "mod", "pow", "unm",
85+
"metatable", "tostring", "new", "pairs",
86+
"ipairs",
87+
}
88+
89+
const (
90+
BCMdst = 1
91+
BCMbase = 2
92+
MMcall = 9
93+
MMMax = 0x16
94+
)
95+
96+
//nolint:stylecheck
97+
const (
98+
BC_ISLT = iota
99+
BC_ISGE
100+
BC_ISLE
101+
BC_ISGT
102+
BC_ISEQV
103+
BC_ISNEV
104+
BC_ISEQS
105+
BC_ISNES
106+
BC_ISEQN
107+
BC_ISNEN
108+
BC_ISEQP
109+
BC_ISNEP
110+
BC_ISTC
111+
BC_ISFC
112+
BC_IST
113+
BC_ISF
114+
BC_ISTYPE
115+
BC_ISNUM
116+
BC_MOV
117+
BC_NOT
118+
BC_UNM
119+
BC_LEN
120+
BC_ADDVN
121+
BC_SUBVN
122+
BC_MULVN
123+
BC_DIVVN
124+
BC_MODVN
125+
BC_ADDNV
126+
BC_SUBNV
127+
BC_MULNV
128+
BC_DIVNV
129+
BC_MODNV
130+
BC_ADDVV
131+
BC_SUBVV
132+
BC_MULVV
133+
BC_DIVVV
134+
BC_MODVV
135+
BC_POW
136+
BC_CAT
137+
BC_KSTR
138+
BC_KCDATA
139+
BC_KSHORT
140+
BC_KNUM
141+
BC_KPRI
142+
BC_KNIL
143+
BC_UGET
144+
BC_USETV
145+
BC_USETS
146+
BC_USETN
147+
BC_USETP
148+
BC_UCLO
149+
BC_FNEW
150+
BC_TNEW
151+
BC_TDUP
152+
BC_GGET
153+
BC_GSET
154+
BC_TGETV
155+
BC_TGETS
156+
BC_TGETB
157+
BC_TGETR
158+
BC_TSETV
159+
BC_TSETS
160+
BC_TSETB
161+
BC_TSETM
162+
BC_TSETR
163+
BC_CALLM
164+
BC_CALL
165+
BC_CALLMT
166+
BC_CALLT
167+
BC_ITERC
168+
BC_ITERN
169+
BC_VARG
170+
BC_ISNEXT
171+
BC_RETM
172+
BC_RET
173+
BC_RET0
174+
BC_RET1
175+
BC_FORI
176+
BC_JFORI
177+
BC_FORL
178+
BC_IFORL
179+
BC_JFORL
180+
BC_ITERL
181+
BC_IITERL
182+
BC_JITERL
183+
BC_LOOP
184+
BC_ILOOP
185+
BC_JLOOP
186+
BC_JMP
187+
BC_FUNCF
188+
BC_IFUNCF
189+
BC_JFUNCF
190+
BC_FUNCV
191+
BC_IFUNCV
192+
BC_JFUNCV
193+
BC_FUNCC
194+
BC_FUNCCW
195+
BC__MAX
196+
)

0 commit comments

Comments
 (0)