Skip to content

Commit

Permalink
luajit stack unwinding support
Browse files Browse the repository at this point in the history
Works with openresty and luajit with caveats. lua_State is looked up via
uprobes on lua_pcall and lua_resume.  If uprobes aren't enabled an
attempt will be made to look up lua_State via nginx_lua_co_ctx object
which seems to work well for openresty but isn't well tested.

Fixes: parca-dev#1889
  • Loading branch information
gnurizen committed May 15, 2024
1 parent 369ee3a commit 4cd9cff
Show file tree
Hide file tree
Showing 24 changed files with 3,514 additions and 24 deletions.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ OUT_BPF := $(OUT_BPF_DIR)/native.bpf.o
OUT_RBPERF := $(OUT_BPF_DIR)/rbperf.bpf.o
OUT_PYPERF := $(OUT_BPF_DIR)/pyperf.bpf.o
OUT_JVM := $(OUT_BPF_DIR)/jvm.bpf.o
OUT_LUA := $(OUT_BPF_DIR)/lua.bpf.o
OUT_BPF_CONTAINED_DIR := pkg/contained/bpf/$(ARCH)
OUT_PID_NAMESPACE := $(OUT_BPF_CONTAINED_DIR)/pid_namespace.bpf.o

Expand Down Expand Up @@ -167,6 +168,7 @@ $(OUT_BPF): $(BPF_SRC) libbpf | $(OUT_DIR)
cp bpf/out/$(ARCH)/rbperf.bpf.o $(OUT_RBPERF)
cp bpf/out/$(ARCH)/pyperf.bpf.o $(OUT_PYPERF)
cp bpf/out/$(ARCH)/jvm.bpf.o $(OUT_JVM)
cp bpf/out/$(ARCH)/lua.bpf.o $(OUT_LUA)
cp bpf/out/$(ARCH)/pid_namespace.bpf.o $(OUT_PID_NAMESPACE)
else
$(OUT_BPF): $(DOCKER_BUILDER) | $(OUT_DIR)
Expand Down Expand Up @@ -240,6 +242,9 @@ test/integration/ruby: $(GO_SRC) $(LIBBPF_HEADERS) $(LIBBPF_OBJ) bpf
test/integration/java: $(GO_SRC) $(LIBBPF_HEADERS) $(LIBBPF_OBJ) bpf
sudo --preserve-env=CI,C_INCLUDE_PATH,LIBRARY_PATH,PKG_CONFIG_PATH $(GO_ENV) $(CGO_ENV) $(GO) test $(SANITIZERS) -v ./test/integration/java -count=1

test/integration/lua: $(GO_SRC) $(LIBBPF_HEADERS) $(LIBBPF_OBJ) bpf
sudo --preserve-env=CI,C_INCLUDE_PATH,LIBRARY_PATH,PKG_CONFIG_PATH $(GO_ENV) $(CGO_ENV) $(GO) test $(SANITIZERS) -v ./test/integration/lua -count=1

.PHONY: integration-stress
integration-stress:
sudo --preserve-env=CI $(GO_ENV) $(CGO_ENV) $(GO) test $(SANITIZERS) ./test/integration/... -count=5
Expand Down
22 changes: 19 additions & 3 deletions bpf/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ OUT_BPF := $(OUT_BPF_DIR)/native.bpf.o
OUT_RBPERF := $(OUT_BPF_DIR)/rbperf.bpf.o
OUT_PYPERF := $(OUT_BPF_DIR)/pyperf.bpf.o
OUT_JVM := $(OUT_BPF_DIR)/jvm.bpf.o
OUT_LUA := $(OUT_BPF_DIR)/lua.bpf.o
OUT_PID_NAMESPACE_DETECTOR := $(OUT_BPF_DIR)/pid_namespace.bpf.o
BPF_BUNDLE := $(OUT_DIR)/parca-agent.bpf.tar.gz

Expand All @@ -28,6 +29,7 @@ BPF_SRC := unwinders/native.bpf.c unwinders/go_runtime.h
RBPERF_SRC := unwinders/rbperf.bpf.c
PYPERF_SRC := unwinders/pyperf.bpf.c
JVM_SRC := unwinders/jvm.bpf.c
LUA_SRC := unwinders/lua.bpf.c
OUT_PID_NAMESPACE_DETECTOR_SRC := pid_namespace.bpf.c
BPF_INCLUDES := unwinders/

Expand All @@ -46,11 +48,11 @@ format: c/fmt

.PHONY: c/fmt
c/fmt:
$(CMD_CLANG_FORMAT) -i --style=file $(BPF_HEADERS) $(BPF_SRC) $(RBPERF_SRC) $(PYPERF_SRC) $(JVM_SRC) $(OUT_PID_NAMESPACE_DETECTOR_SRC)
$(CMD_CLANG_FORMAT) -i --style=file $(BPF_HEADERS) $(BPF_SRC) $(RBPERF_SRC) $(PYPERF_SRC) $(JVM_SRC) $(LUA_SRC) $(OUT_PID_NAMESPACE_DETECTOR_SRC)

.PHONY: format-check
format-check:
$(CMD_CLANG_FORMAT) --dry-run -Werror --style=file $(BPF_HEADERS) $(BPF_SRC) $(RBPERF_SRC) $(PYPERF_SRC) $(JVM_SRC) $(OUT_PID_NAMESPACE_DETECTOR_SRC)
$(CMD_CLANG_FORMAT) --dry-run -Werror --style=file $(BPF_HEADERS) $(BPF_SRC) $(RBPERF_SRC) $(PYPERF_SRC) $(JVM_SRC) $(LUA_SRC) $(OUT_PID_NAMESPACE_DETECTOR_SRC)

# compilation options:
BPF_CFLAGS = -Wno-address-of-packed-member \
Expand All @@ -76,7 +78,7 @@ BPF_CFLAGS = -Wno-address-of-packed-member \

# tasks:
.PHONY: clang
clang: $(OUT_BPF) $(OUT_RBPERF) $(OUT_PYPERF) $(OUT_JVM) $(OUT_PID_NAMESPACE_DETECTOR)
clang: $(OUT_BPF) $(OUT_RBPERF) $(OUT_PYPERF) $(OUT_JVM) $(OUT_LUA) $(OUT_PID_NAMESPACE_DETECTOR)

bpf_bundle_dir := $(OUT_DIR)/parca-agent.bpf
$(BPF_BUNDLE): $(BPF_SRC) $(LIBBPF_HEADERS)/bpf $(BPF_HEADERS)
Expand Down Expand Up @@ -152,3 +154,17 @@ $(OUT_JVM): $(JVM_SRC) $(LIBBPF_HEADERS) $(BPF_HEADERS) | $(OUT_DIR)
-O2 -emit-llvm -c -g $< -o $(@:.o=.ll)
$(CMD_LLC) -march=bpf -filetype=obj -o $@ $(@:.o=.ll)
rm $(@:.o=.ll)

$(OUT_LUA): $(LUA_SRC) $(LIBBPF_HEADERS) $(BPF_HEADERS) | $(OUT_DIR)
mkdir -p $(OUT_BPF_DIR)
$(CMD_CC) -S \
-D__BPF_TRACING__ \
-D__KERNEL__ \
-D__TARGET_ARCH_$(SHORT_ARCH) \
-I $(VMLINUX_INCLUDE_PATH) \
-I $(LIBBPF_HEADERS) \
-I $(BPF_INCLUDES) \
$(BPF_CFLAGS) \
-O2 -emit-llvm -c -g $< -o $(@:.o=.ll)
$(CMD_LLC) -march=bpf -filetype=obj -o $@ $(@:.o=.ll)
rm $(@:.o=.ll)
270 changes: 270 additions & 0 deletions bpf/unwinders/lj_bc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
/*
** Bytecode instruction format.
** Copyright (C) 2005-2023 Mike Pall. See Copyright Notice in luajit.h
*/

#ifndef _LJ_BC_H
#define _LJ_BC_H

// #include "lj_def.h"
// #include "lj_arch.h"

/* Bytecode instruction format, 32 bit wide, fields of 8 or 16 bit:
**
** +----+----+----+----+
** | B | C | A | OP | Format ABC
** +----+----+----+----+
** | D | A | OP | Format AD
** +--------------------
** MSB LSB
**
** In-memory instructions are always stored in host byte order.
*/

/* Operand ranges and related constants. */
#define BCMAX_A 0xff
#define BCMAX_B 0xff
#define BCMAX_C 0xff
#define BCMAX_D 0xffff
#define BCBIAS_J 0x8000
#define NO_REG BCMAX_A
#define NO_JMP (~(BCPos)0)

/* Macros to get instruction fields. */
#define bc_op(i) ((BCOp)((i) & 0xff))
#define bc_a(i) ((BCReg)(((i) >> 8) & 0xff))
#define bc_b(i) ((BCReg)((i) >> 24))
#define bc_c(i) ((BCReg)(((i) >> 16) & 0xff))
#define bc_d(i) ((BCReg)((i) >> 16))
#define bc_j(i) ((ptrdiff_t)bc_d(i) - BCBIAS_J)

/* Macros to set instruction fields. */
#define setbc_byte(p, x, ofs) ((uint8_t *)(p))[LJ_ENDIAN_SELECT(ofs, 3 - ofs)] = (uint8_t)(x)
#define setbc_op(p, x) setbc_byte(p, (x), 0)
#define setbc_a(p, x) setbc_byte(p, (x), 1)
#define setbc_b(p, x) setbc_byte(p, (x), 3)
#define setbc_c(p, x) setbc_byte(p, (x), 2)
#define setbc_d(p, x) ((uint16_t *)(p))[LJ_ENDIAN_SELECT(1, 0)] = (uint16_t)(x)
#define setbc_j(p, x) setbc_d(p, (BCPos)((int32_t)(x) + BCBIAS_J))

/* Macros to compose instructions. */
#define BCINS_ABC(o, a, b, c) (((BCIns)(o)) | ((BCIns)(a) << 8) | ((BCIns)(b) << 24) | ((BCIns)(c) << 16))
#define BCINS_AD(o, a, d) (((BCIns)(o)) | ((BCIns)(a) << 8) | ((BCIns)(d) << 16))
#define BCINS_AJ(o, a, j) BCINS_AD(o, a, (BCPos)((int32_t)(j) + BCBIAS_J))

/* Bytecode instruction definition. Order matters, see below.
**
** (name, filler, Amode, Bmode, Cmode or Dmode, metamethod)
**
** The opcode name suffixes specify the type for RB/RC or RD:
** V = variable slot
** S = string const
** N = number const
** P = primitive type (~itype)
** B = unsigned byte literal
** M = multiple args/results
*/
#define BCDEF(_) \
/* Comparison ops. ORDER OPR. */ \
_(ISLT, var, ___, var, lt) \
_(ISGE, var, ___, var, lt) \
_(ISLE, var, ___, var, le) \
_(ISGT, var, ___, var, le) \
\
_(ISEQV, var, ___, var, eq) \
_(ISNEV, var, ___, var, eq) \
_(ISEQS, var, ___, str, eq) \
_(ISNES, var, ___, str, eq) \
_(ISEQN, var, ___, num, eq) \
_(ISNEN, var, ___, num, eq) \
_(ISEQP, var, ___, pri, eq) \
_(ISNEP, var, ___, pri, eq) \
\
/* Unary test and copy ops. */ \
_(ISTC, dst, ___, var, ___) \
_(ISFC, dst, ___, var, ___) \
_(IST, ___, ___, var, ___) \
_(ISF, ___, ___, var, ___) \
_(ISTYPE, var, ___, lit, ___) \
_(ISNUM, var, ___, lit, ___) \
\
/* Unary ops. */ \
_(MOV, dst, ___, var, ___) \
_(NOT, dst, ___, var, ___) \
_(UNM, dst, ___, var, unm) \
_(LEN, dst, ___, var, len) \
\
/* Binary ops. ORDER OPR. VV last, POW must be next. */ \
_(ADDVN, dst, var, num, add) \
_(SUBVN, dst, var, num, sub) \
_(MULVN, dst, var, num, mul) \
_(DIVVN, dst, var, num, div) \
_(MODVN, dst, var, num, mod) \
\
_(ADDNV, dst, var, num, add) \
_(SUBNV, dst, var, num, sub) \
_(MULNV, dst, var, num, mul) \
_(DIVNV, dst, var, num, div) \
_(MODNV, dst, var, num, mod) \
\
_(ADDVV, dst, var, var, add) \
_(SUBVV, dst, var, var, sub) \
_(MULVV, dst, var, var, mul) \
_(DIVVV, dst, var, var, div) \
_(MODVV, dst, var, var, mod) \
\
_(POW, dst, var, var, pow) \
_(CAT, dst, rbase, rbase, concat) \
\
/* Constant ops. */ \
_(KSTR, dst, ___, str, ___) \
_(KCDATA, dst, ___, cdata, ___) \
_(KSHORT, dst, ___, lits, ___) \
_(KNUM, dst, ___, num, ___) \
_(KPRI, dst, ___, pri, ___) \
_(KNIL, base, ___, base, ___) \
\
/* Upvalue and function ops. */ \
_(UGET, dst, ___, uv, ___) \
_(USETV, uv, ___, var, ___) \
_(USETS, uv, ___, str, ___) \
_(USETN, uv, ___, num, ___) \
_(USETP, uv, ___, pri, ___) \
_(UCLO, rbase, ___, jump, ___) \
_(FNEW, dst, ___, func, gc) \
\
/* Table ops. */ \
_(TNEW, dst, ___, lit, gc) \
_(TDUP, dst, ___, tab, gc) \
_(GGET, dst, ___, str, index) \
_(GSET, var, ___, str, newindex) \
_(TGETV, dst, var, var, index) \
_(TGETS, dst, var, str, index) \
_(TGETB, dst, var, lit, index) \
_(TGETR, dst, var, var, index) \
_(TSETV, var, var, var, newindex) \
_(TSETS, var, var, str, newindex) \
_(TSETB, var, var, lit, newindex) \
_(TSETM, base, ___, num, newindex) \
_(TSETR, var, var, var, newindex) \
\
/* Calls and vararg handling. T = tail call. */ \
_(CALLM, base, lit, lit, call) \
_(CALL, base, lit, lit, call) \
_(CALLMT, base, ___, lit, call) \
_(CALLT, base, ___, lit, call) \
_(ITERC, base, lit, lit, call) \
_(ITERN, base, lit, lit, call) \
_(VARG, base, lit, lit, ___) \
_(ISNEXT, base, ___, jump, ___) \
\
/* Returns. */ \
_(RETM, base, ___, lit, ___) \
_(RET, rbase, ___, lit, ___) \
_(RET0, rbase, ___, lit, ___) \
_(RET1, rbase, ___, lit, ___) \
\
/* Loops and branches. I/J = interp/JIT, I/C/L = init/call/loop. */ \
_(FORI, base, ___, jump, ___) \
_(JFORI, base, ___, jump, ___) \
\
_(FORL, base, ___, jump, ___) \
_(IFORL, base, ___, jump, ___) \
_(JFORL, base, ___, lit, ___) \
\
_(ITERL, base, ___, jump, ___) \
_(IITERL, base, ___, jump, ___) \
_(JITERL, base, ___, lit, ___) \
\
_(LOOP, rbase, ___, jump, ___) \
_(ILOOP, rbase, ___, jump, ___) \
_(JLOOP, rbase, ___, lit, ___) \
\
_(JMP, rbase, ___, jump, ___) \
\
/* Function headers. I/J = interp/JIT, F/V/C = fixarg/vararg/C func. */ \
_(FUNCF, rbase, ___, ___, ___) \
_(IFUNCF, rbase, ___, ___, ___) \
_(JFUNCF, rbase, ___, lit, ___) \
_(FUNCV, rbase, ___, ___, ___) \
_(IFUNCV, rbase, ___, ___, ___) \
_(JFUNCV, rbase, ___, lit, ___) \
_(FUNCC, rbase, ___, ___, ___) \
_(FUNCCW, rbase, ___, ___, ___)

/* Bytecode opcode numbers. */
typedef enum {
#define BCENUM(name, ma, mb, mc, mt) BC_##name,
BCDEF(BCENUM)
#undef BCENUM
BC__MAX
} BCOp;

LJ_STATIC_ASSERT((int)BC_ISEQV + 1 == (int)BC_ISNEV);
LJ_STATIC_ASSERT(((int)BC_ISEQV ^ 1) == (int)BC_ISNEV);
LJ_STATIC_ASSERT(((int)BC_ISEQS ^ 1) == (int)BC_ISNES);
LJ_STATIC_ASSERT(((int)BC_ISEQN ^ 1) == (int)BC_ISNEN);
LJ_STATIC_ASSERT(((int)BC_ISEQP ^ 1) == (int)BC_ISNEP);
LJ_STATIC_ASSERT(((int)BC_ISLT ^ 1) == (int)BC_ISGE);
LJ_STATIC_ASSERT(((int)BC_ISLE ^ 1) == (int)BC_ISGT);
LJ_STATIC_ASSERT(((int)BC_ISLT ^ 3) == (int)BC_ISGT);
LJ_STATIC_ASSERT((int)BC_IST - (int)BC_ISTC == (int)BC_ISF - (int)BC_ISFC);
LJ_STATIC_ASSERT((int)BC_CALLT - (int)BC_CALL == (int)BC_CALLMT - (int)BC_CALLM);
LJ_STATIC_ASSERT((int)BC_CALLMT + 1 == (int)BC_CALLT);
LJ_STATIC_ASSERT((int)BC_RETM + 1 == (int)BC_RET);
LJ_STATIC_ASSERT((int)BC_FORL + 1 == (int)BC_IFORL);
LJ_STATIC_ASSERT((int)BC_FORL + 2 == (int)BC_JFORL);
LJ_STATIC_ASSERT((int)BC_ITERL + 1 == (int)BC_IITERL);
LJ_STATIC_ASSERT((int)BC_ITERL + 2 == (int)BC_JITERL);
LJ_STATIC_ASSERT((int)BC_LOOP + 1 == (int)BC_ILOOP);
LJ_STATIC_ASSERT((int)BC_LOOP + 2 == (int)BC_JLOOP);
LJ_STATIC_ASSERT((int)BC_FUNCF + 1 == (int)BC_IFUNCF);
LJ_STATIC_ASSERT((int)BC_FUNCF + 2 == (int)BC_JFUNCF);
LJ_STATIC_ASSERT((int)BC_FUNCV + 1 == (int)BC_IFUNCV);
LJ_STATIC_ASSERT((int)BC_FUNCV + 2 == (int)BC_JFUNCV);

/* This solves a circular dependency problem, change as needed. */
#define FF_next_N 4

/* Stack slots used by FORI/FORL, relative to operand A. */
enum { FORL_IDX, FORL_STOP, FORL_STEP, FORL_EXT };

/* Bytecode operand modes. ORDER BCMode */
typedef enum {
BCMnone,
BCMdst,
BCMbase,
BCMvar,
BCMrbase,
BCMuv, /* Mode A must be <= 7 */
BCMlit,
BCMlits,
BCMpri,
BCMnum,
BCMstr,
BCMtab,
BCMfunc,
BCMjump,
BCMcdata,
BCM_max
} BCMode;
#define BCM___ BCMnone

#define bcmode_a(op) ((BCMode)(lj_bc_mode[op] & 7))
#define bcmode_b(op) ((BCMode)((lj_bc_mode[op] >> 3) & 15))
#define bcmode_c(op) ((BCMode)((lj_bc_mode[op] >> 7) & 15))
#define bcmode_d(op) bcmode_c(op)
#define bcmode_hasd(op) ((lj_bc_mode[op] & (15 << 3)) == (BCMnone << 3))
#define bcmode_mm(op) ((MMS)(lj_bc_mode[op] >> 11))

#define BCMODE(name, ma, mb, mc, mm) (BCM##ma | (BCM##mb << 3) | (BCM##mc << 7) | (MM_##mm << 11)),
#define BCMODE_FF 0

static LJ_AINLINE int bc_isret(BCOp op) {
return (op == BC_RETM || op == BC_RET || op == BC_RET0 || op == BC_RET1);
}

LJ_DATA const uint16_t lj_bc_mode[];
LJ_DATA const uint16_t lj_bc_ofs[];

#endif
Loading

0 comments on commit 4cd9cff

Please sign in to comment.