Repository navigation
Conversation
TDwarfExpressionPattern::Matches() kept comparing pattern operations after the expression ran out. A DWARFExpression end iterator still holds the last decoded operation, so a pattern that repeats the last operation matched a shorter expression: [breg7 8, deref, deref] matched [breg7 8, deref]. None of the current CFA patterns repeat their last operation, so this does not change the unwind tables built today, but it makes adding patterns that share a prefix safe. Add unit tests for the matcher.
Two kinds of frames keep the caller's stack pointer in memory and could
not be unwound: the stack stopped at them.
- GCC dynamic stack realignment (DRAP), used for functions with
over-aligned locals that pass arguments on the stack or use VLAs:
CFA = *(rbp - 8)
rbp = *(rbp)
Common in GCC-built code: 259 functions in Debian's libhs, 345 in
libx265.
- Hand-written OpenSSL perlasm (sha*_block_data_order_avx2 and others):
CFA = *(rsp + offset) + 8
The unwind table builder already recognized the OpenSSL form, but the
rule never reached the eBPF unwinder. Now:
- ehframe: match *(reg + offset) without a bias as well, and
DW_OP_bregN offset as a register location, which describes rbp saved
at rbp;
- unwindtable: emit UNWIND_RULE_REGISTER_DEREF_OFFSET (bias 0) and
UNWIND_RULE_REGISTER_DEREF_OFFSET_PLUS_8. The bias is encoded in the
kind because struct cfa_unwind_rule has no room for it; other biases
stay unsupported. Emit DWARF_UNWIND_RBP_RULE_AT_RBP for rbp saved at
rbp;
- eBPF: evaluate both deref kinds, rejecting a CFA that is not above the
current stack pointer since it comes from memory, and restore rbp from
*(rbp) for DWARF_UNWIND_RBP_RULE_AT_RBP.
UNWIND_RULE_REGISTER_DERREF_OFFSET was unused and is renamed to fix the
typo.
Hand-written assembly keeps its CFA in registers other than rsp and rbp, so the stack stopped at these frames: - openssl aesni_gcm_encrypt/decrypt: CFA = rax + 8 while calling _aesni_ctr32_ghash_6x; - openssl aesni_cbc_encrypt, aesni_ctr32_encrypt_blocks, aesni_xts_*: CFA = r11 + 8 while calling _aesni_encrypt*/_aesni_decrypt*; - other perlasm uses rsi, r9, rcx, rdi, r15; gcc stack realignment uses r10 in its prologue and epilogue. The unwind table already carries such rules; the eBPF unwinder rejected any register but rsp and rbp. Now it keeps the general purpose registers of the sample next to the unwind context: - at the leaf every register is exact. For a sample taken in the kernel, rax, rcx and r11 are dropped when orig_ax shows a syscall: syscall entry overwrites them; - after a DWARF step only caller-saved registers are kept. DWARF treats registers without a rule as having the same value in the caller, and the openssl helpers above rely on it. Callee-saved registers would need DW_CFA_offset rules the table does not carry; - after a frame pointer or JVM step nothing is known. A CFA computed from such a register must be above the current stack pointer, otherwise the register no longer holds the CFA base. arm64 keeps the previous behavior.
✅ All contributors are covered under a CLA with YandexSee CONTRIBUTING.md for more info about Yandex Contributor License Agreement. The following contributors were found: (Only the first commit for a unique contributor is listed) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on #130.
Problem
Hand-written assembly keeps its CFA in registers other than rsp and rbp. The unwind table carries these
rules (
UNWIND_RULE_REGISTER_OFFSETwith any register), but the eBPF unwinder only resolved rsp and rbp,so the stack stopped at such frames. In OpenSSL's libcrypto:
aesni_gcm_encrypt,aesni_gcm_decryptrax + 8_aesni_ctr32_ghash_6x,_aesni_ctr32_6xaesni_cbc_encrypt,aesni_ctr32_encrypt_blocks,aesni_xts_*r11 + 8_aesni_encrypt*,_aesni_decrypt*rsi,r9,rcx,rdi,r15GCC stack realignment also uses
r10in its prologue and epilogue (see #130).In practice this cuts TLS-heavy profiles: samples in AES-GCM, AES-CBC or AES-CTR end at the perlasm
frame, detached from the code that called OpenSSL.
Fix
The eBPF unwinder keeps the general purpose registers of the sample next to the unwind context, indexed
by DWARF register number, with a mask of known ones:
rax,rcxandr11aredropped if
orig_axis not negative: syscall entry overwrites them (raxholds the syscall number,the
syscallinstruction stores rip and rflags in rcx and r11). Exceptions with an error code alsopass that check; for them the three registers are lost, never wrong.
having the same value in the caller, and the OpenSSL helpers above rely on it. Callee-saved registers
would need
DW_CFA_offsetrules, which the unwind table does not carry.no longer holds the CFA base.
arm64 keeps the previous behavior.
Testing
manager_test.go: register-based CFA rules reach the eBPF table unchanged.perforator record -p <pid> -F 999 -d 15s. "Before" is Support CFA saved on the stack and rbp saved at rbp #130, the parent of thischange.
Repro
repro-c.c+repro-c-asm.S,gcc -O2 -g -fno-omit-frame-pointer:repro-c.c
repro-c-asm.S.txt
main -> rax_caller -> rax_frame -> spin_leaf:CFA = rax + 8inrax_frame, andspin_leafpreserves rax, like
aesni_gcm_encrypt -> _aesni_ctr32_ghash_6x. Checks rax kept across a DWARF step.main -> r11_caller -> r11_frame:CFA = r11 + 8, samples land inr11_frameitself, likeaesni_cbc_encrypt. Checks the leaf registers.main -> syscall_caller -> rbx_syscall_frame -> getrandom(2):CFA = rbx + 16in a frame sampledwhile in the kernel. Checks the registers saved on syscall entry.
mainrax_framer11_framerbx_syscall_frameThe kernel leaf
init_module@[nft_reject_inet]in both graphs is fixed separately by #128.