Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support tagging driver handles with a name #8038

Merged
merged 17 commits into from
Aug 24, 2024
2 changes: 2 additions & 0 deletions NEW_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).

## Release notes for next branch cut

- Add a `name` API to Filament objects for debugging handle use-after-free assertions
1 change: 1 addition & 0 deletions filament/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ set(SRCS
src/Engine.cpp
src/Exposure.cpp
src/Fence.cpp
src/FilamentBuilder.cpp
src/FrameInfo.cpp
src/FrameSkipper.cpp
src/Froxelizer.cpp
Expand Down
4 changes: 4 additions & 0 deletions filament/backend/include/private/backend/DriverAPI.inc
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ DECL_DRIVER_API_0(finish)
// reset state tracking, if the driver does any state tracking (e.g. GL)
DECL_DRIVER_API_0(resetState)

DECL_DRIVER_API_N(setDebugTag,
backend::HandleBase::HandleId, handleId,
utils::CString, tag)

/*
* Creating driver objects
* -----------------------
Expand Down
38 changes: 35 additions & 3 deletions filament/backend/include/private/backend/HandleAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
#include <backend/Handle.h>

#include <utils/Allocator.h>
#include <utils/CString.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <utils/Panic.h>

#include <tsl/robin_map.h>

Expand Down Expand Up @@ -173,8 +174,10 @@ class HandleAllocator {
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
auto const pNode = static_cast<typename Allocator::Node*>(p);
uint8_t const expectedAge = pNode[-1].age;
FILAMENT_CHECK_POSTCONDITION(expectedAge == age) <<
"use-after-free of Handle with id=" << handle.getId();
// getHandleTag() is only called if the check fails.
FILAMENT_CHECK_POSTCONDITION(expectedAge == age)
<< "use-after-free of Handle with id=" << handle.getId()
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
}
}

Expand All @@ -201,6 +204,34 @@ class HandleAllocator {
return handle_cast<Dp>(const_cast<Handle<B>&>(handle));
}

void associateTagToHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
// TODO: for now, only pool handles check for use-after-free, so we only keep tags for
// those
if (isPoolHandle(id)) {
// Truncate the tag's age to N bits.
constexpr uint8_t N = 2; // support a history of 4 tags
pixelflinger marked this conversation as resolved.
Show resolved Hide resolved
constexpr uint8_t mask = (1 << N) - 1;

uint8_t const age = (id & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
uint8_t const newAge = age & mask;
uint32_t const key = (id & ~HANDLE_AGE_MASK) | (newAge << HANDLE_AGE_SHIFT);

// This line is the costly part. In the future, we could potentially use a custom
// allocator.
mDebugTags[key] = std::move(tag);
bejado marked this conversation as resolved.
Show resolved Hide resolved
}
}

utils::CString getHandleTag(HandleBase::HandleId id) const noexcept {
if (!isPoolHandle(id)) {
return "(no tag)";
}
if (auto pos = mDebugTags.find(id); pos != mDebugTags.end()) {
return pos->second;
}
return "(no tag)";
}

private:

template<typename D>
Expand Down Expand Up @@ -363,6 +394,7 @@ class HandleAllocator {
// Below is only used when running out of space in the HandleArena
mutable utils::Mutex mLock;
tsl::robin_map<HandleBase::HandleId, void*> mOverflowMap;
tsl::robin_map<HandleBase::HandleId, utils::CString> mDebugTags;
HandleBase::HandleId mId = 0;
bool mUseAfterFreeCheckDisabled = false;
};
Expand Down
3 changes: 3 additions & 0 deletions filament/backend/src/HandleAllocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ HandleAllocator<P0, P1, P2>::HandleAllocator(const char* name, size_t size,
bool disableUseAfterFreeCheck) noexcept
: mHandleArena(name, size, disableUseAfterFreeCheck),
mUseAfterFreeCheckDisabled(disableUseAfterFreeCheck) {
// Reserve initial space for debug tags. This prevents excessive calls to malloc when the first
// few tags are set.
mDebugTags.reserve(512);
}

template <size_t P0, size_t P1, size_t P2>
Expand Down
4 changes: 4 additions & 0 deletions filament/backend/src/metal/MetalDriver.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2061,6 +2061,10 @@
void MetalDriver::resetState(int) {
}

void MetalDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
mHandleAllocator.associateTagToHandle(handleId, std::move(tag));
}

void MetalDriver::runAtNextTick(const std::function<void()>& fn) noexcept {
std::lock_guard<std::mutex> const lock(mTickOpsLock);
mTickOps.push_back(fn);
Expand Down
3 changes: 3 additions & 0 deletions filament/backend/src/noop/NoopDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,7 @@ void NoopDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
void NoopDriver::resetState(int) {
}

void NoopDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
}

} // namespace filament
4 changes: 4 additions & 0 deletions filament/backend/src/opengl/OpenGLDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2210,6 +2210,10 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
// Updating driver objects
// ------------------------------------------------------------------------------------------------

void OpenGLDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
mHandleAllocator.associateTagToHandle(handleId, std::move(tag));
}

void OpenGLDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh,
uint32_t index, Handle<HwBufferObject> boh) {
DEBUG_MARKER()
Expand Down
4 changes: 4 additions & 0 deletions filament/backend/src/vulkan/VulkanDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2032,6 +2032,10 @@ void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, cons
void VulkanDriver::resetState(int) {
}

void VulkanDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
mResourceAllocator.associateHandle(handleId, std::move(tag));
}

// explicit instantiation of the Dispatcher
template class ConcreteDispatcher<VulkanDriver>;

Expand Down
4 changes: 4 additions & 0 deletions filament/backend/src/vulkan/VulkanResourceAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ class VulkanResourceAllocator {
mHandleAllocatorImpl.deallocate(handle, obj);
}

inline void associateHandle(HandleBase::HandleId id, utils::CString&& tag) noexcept {
mHandleAllocatorImpl.associateTagToHandle(id, std::move(tag));
}

private:
AllocatorImpl mHandleAllocatorImpl;

Expand Down
17 changes: 16 additions & 1 deletion filament/include/filament/BufferObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI {
using BufferDescriptor = backend::BufferDescriptor;
using BindingType = backend::BufferObjectBinding;

class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
Expand All @@ -78,6 +78,21 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI {
*/
Builder& bindingType(BindingType bindingType) noexcept;

/**
* Associate an optional name with this BufferObject for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this BufferObject
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited

/**
* Creates the BufferObject and returns a pointer to it. After creation, the buffer
* object is uninitialized. Use BufferObject::setBuffer() to initialize it.
Expand Down
17 changes: 17 additions & 0 deletions filament/include/filament/FilamentAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include <utils/compiler.h>
#include <utils/PrivateImplementation.h>
#include <utils/CString.h>

#include <stddef.h>

Expand Down Expand Up @@ -54,6 +55,22 @@ class UTILS_PUBLIC FilamentAPI {
template<typename T>
using BuilderBase = utils::PrivateImplementation<T>;

void builderMakeName(utils::CString& outName, const char* name, size_t len) noexcept;

template <typename Builder>
class UTILS_PUBLIC BuilderNameMixin {
public:
Builder& name(const char* name, size_t len) noexcept {
builderMakeName(mName, name, len);
return static_cast<Builder&>(*this);
}

utils::CString const& getName() const noexcept { return mName; }

private:
utils::CString mName;
};

} // namespace filament

#endif // TNT_FILAMENT_FILAMENTAPI_H
17 changes: 16 additions & 1 deletion filament/include/filament/IndexBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI {
UINT = uint8_t(backend::ElementType::UINT), //!< 32-bit indices
};

class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
Expand All @@ -83,6 +83,21 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI {
*/
Builder& bufferType(IndexType indexType) noexcept;

/**
* Associate an optional name with this IndexBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this IndexBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited

/**
* Creates the IndexBuffer object and returns a pointer to it. After creation, the index
* buffer is uninitialized. Use IndexBuffer::setBuffer() to initialize the IndexBuffer.
Expand Down
17 changes: 16 additions & 1 deletion filament/include/filament/InstanceBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI {
struct BuilderDetails;

public:
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;

public:
Expand Down Expand Up @@ -70,6 +70,21 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI {
*/
Builder& localTransforms(math::mat4f const* UTILS_NULLABLE localTransforms) noexcept;

/**
* Associate an optional name with this InstanceBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this InstanceBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited

/**
* Creates the InstanceBuffer object and returns a pointer to it.
*/
Expand Down
17 changes: 16 additions & 1 deletion filament/include/filament/MorphTargetBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
struct BuilderDetails;

public:
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
Expand All @@ -63,6 +63,21 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
*/
Builder& count(size_t count) noexcept;

/**
* Associate an optional name with this MorphTargetBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this MorphTargetBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited

/**
* Creates the MorphTargetBuffer object and returns a pointer to it.
*
Expand Down
17 changes: 16 additions & 1 deletion filament/include/filament/SkinningBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI {
struct BuilderDetails;

public:
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
Expand Down Expand Up @@ -69,6 +69,21 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI {
*/
Builder& initialize(bool initialize = true) noexcept;

/**
* Associate an optional name with this SkinningBuffer for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this SkinningBuffer
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited

/**
* Creates the SkinningBuffer object and returns a pointer to it.
*
Expand Down
17 changes: 16 additions & 1 deletion filament/include/filament/Stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class UTILS_PUBLIC Stream : public FilamentAPI {
*
* To create a NATIVE stream, call the <pre>stream</pre> method on the builder.
*/
class Builder : public BuilderBase<BuilderDetails> {
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
public:
Builder() noexcept;
Expand Down Expand Up @@ -136,6 +136,21 @@ class UTILS_PUBLIC Stream : public FilamentAPI {
*/
Builder& height(uint32_t height) noexcept;

/**
* Associate an optional name with this Stream for debugging purposes.
*
* name will show in error messages and should be kept as short as possible. The name is
* truncated to a maximum of 128 characters.
*
* The name string is copied during this method so clients may free its memory after
* the function returns.
*
* @param name A string to identify this Stream
* @param len Length of name, should be less than or equal to 128
* @return This Builder, for chaining calls.
*/
// Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; // inherited

/**
* Creates the Stream object and returns a pointer to it.
*
Expand Down
Loading
Loading