// 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 #include #include "./centipede/rolling_hash.h" namespace fuzztest::internal { // 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 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 fuzztest::internal #endif // THIRD_PARTY_CENTIPEDE_CALLSTACK_H_