blob: ddd89d676ea5998b4c6c7523331ad0112f1ce006 [file] [log] [blame]
// Copyright 2023 The Centipede Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_CENTIPEDE_CALLSTACK_H_
#define THIRD_PARTY_CENTIPEDE_CALLSTACK_H_
#include <cstddef>
#include <cstdint>
#include "./centipede/rolling_hash.h"
namespace centipede {
// CallStack maintains a function call stack for the current thread.
// It is told when a function is called, via OnFunctionEntry(pc, sp).
// It is not told when a function exits, so every time a new function is called
// it needs to unwind the stack based on the current and recorded sp values.
//
// This does not produce precise call stacks.
//
// For example, at some point the stack is:
// PC: 1, 2, 3
// SP: 10, 9, 8
// Then, functions 2 and 3 exit, and function 4 with a large stack is called:
// PC: 1, 4
// SP: 10, 7
// We will fail to unwind functions 2 and 3 and the stack will look like
// PC: 1, 2, 3, 4
// SP: 10, 9, 8, 7
//
// We currently don't see a reliable way to implement precise call stack by just
// observing function entries (and not exist).
// But for the purposes of Centipede (capturing call stacks as features) this
// implementation should be good enough.
//
// Alternatives that would allow collecting precise calls stacks are
// * add instrumentation to capture function exits
// (fragile in presence of exceptions and longjmp).
// * unwind stack with frame pointers (expensive and also fragile).
// * Wait for hardware shadow call stacks (CET, etc).
//
// Function calls with depth beyond `kMaxDepth` will be ignored.
// Objects of this class must be created as global or TLS.
// The typical non-test usage is to create on TLS.
// There is no CTOR, the objects are zero-initialized.
// We currently do not use a CTOR with absl::ConstInitType so that the objects
// can be declared as __thread.
//
// This code assumes that the stack grows down.
template <size_t kMaxDepth = (1 << 12)>
class CallStack {
public:
// Returns the depth of the call stack.
// May be less than the actual depth if that is greater than kMaxDepth.
size_t Depth() const { return depth_; }
// Returns the PC at `idx`, idx must be less than the current depth.
uintptr_t PC(size_t idx) const {
if (idx >= depth_) __builtin_trap();
return pc_[idx];
}
// Returns the hash of the current call stack.
// Only the last `window_size` frames are used to compute the hash.
// `ResetWindowSize(window_size)` must be called at the initialization time.
uint32_t Hash() const { return depth_ == 0 ? 0 : hashes_[depth_ - 1]; }
// Updates the call stack and its hash on function entry.
// `pc` is the function PC to be recorded.
// `sp` is the current stack pointer value, which grows down.
void OnFunctionEntry(uintptr_t pc, uintptr_t sp) {
// First, unwind until the last record's SP is above `sp`.
while (depth_ && sp_[depth_ - 1] <= sp) {
--depth_;
}
// Ignore this call if we are already too deep.
if (depth_ == kMaxDepth) return;
// Record the frame, compute and remember the hash.
pc_[depth_] = pc;
sp_[depth_] = sp;
uint32_t previous_hash = depth_ == 0 ? 0 : hashes_[depth_ - 1];
uintptr_t previous_pc =
depth_ >= window_size_ ? pc_[depth_ - window_size_] : 0;
hashes_[depth_] = rolling_hash_.Update(previous_hash, pc, previous_pc);
++depth_;
}
// Resets the call stack.
// `window_size` is the number of stack frames used to compute the hash.
void Reset(size_t window_size) {
depth_ = 0;
window_size_ = window_size;
rolling_hash_.Reset(window_size);
}
private:
// All data fields are zero initialized at process or thread startup.
size_t depth_;
uintptr_t pc_[kMaxDepth];
uintptr_t sp_[kMaxDepth];
uint32_t hashes_[kMaxDepth];
RollingHash rolling_hash_;
size_t window_size_;
};
} // namespace centipede
#endif // THIRD_PARTY_CENTIPEDE_CALLSTACK_H_