对象生命周期调试基础设施

作者:

Thomas Gleixner

简介

debugobjects 是一个通用基础设施,用于跟踪内核对象的生命周期并验证对这些对象的操作。

debugobjects 可用于检查以下错误模式

  • 激活未初始化的对象

  • 初始化活动对象

  • 使用已释放/销毁的对象

debugobjects 不会更改实际对象的数据结构,因此可以以最小的运行时影响编译,并根据内核命令行选项按需启用。

如何使用 debugobjects

内核子系统需要提供一个数据结构来描述对象类型,并在适当的位置添加对调试代码的调用。描述对象类型的数据结构至少需要对象类型的名称。可以(并且应该)提供可选函数来修复检测到的问题,以便内核可以继续工作,并且可以从实时系统中检索调试信息,而不是使用串行控制台和监视器的堆栈跟踪记录进行硬核调试。

debugobjects 提供的调试调用是

  • debug_object_init

  • debug_object_init_on_stack

  • debug_object_activate

  • debug_object_deactivate

  • debug_object_destroy

  • debug_object_free

  • debug_object_assert_init

这些函数中的每一个都采用实际对象的地址和指向对象类型特定调试描述结构的指针。

每个检测到的错误都会在统计信息中报告,并且会打印有限数量的错误,包括完整的堆栈跟踪。

统计信息可通过 /sys/kernel/debug/debug_objects/stats 获得。它们提供有关警告数量和成功修复数量的信息,以及有关内部跟踪对象的使用情况和内部跟踪对象池状态的信息。

调试函数

void debug_object_init(void *addr, const struct debug_obj_descr *descr)

调试检查对象何时初始化

参数

void *addr

对象的地址

const struct debug_obj_descr *descr

指向对象特定调试描述结构的指针

每当调用实际对象的初始化函数时,都会调用此函数。

当实际对象已经被 debugobjects 跟踪时,会检查是否可以初始化该对象。不允许初始化活动和已销毁的对象。当 debugobjects 检测到错误时,它会调用对象类型描述结构的 fixup_init 函数(如果调用者提供了)。在实际初始化对象之前,修复函数可以纠正问题。例如,它可以停用活动对象,以防止对子系统造成损害。

当实际对象尚未被 debugobjects 跟踪时,debugobjects 会为实际对象分配一个跟踪器对象,并将跟踪器对象状态设置为 ODEBUG_STATE_INIT。它会验证该对象是否不在调用者的堆栈上。如果它在调用者的堆栈上,则会打印有限数量的警告,包括完整的堆栈跟踪。调用代码必须使用 debug_object_init_on_stack() 并在离开分配该对象的函数之前删除该对象。请参阅下一节。

void debug_object_init_on_stack(void *addr, const struct debug_obj_descr *descr)

调试检查堆栈上的对象何时初始化

参数

void *addr

对象的地址

const struct debug_obj_descr *descr

指向对象特定调试描述结构的指针

每当调用位于堆栈上的实际对象的初始化函数时,都会调用此函数。

当实际对象已经被 debugobjects 跟踪时,会检查是否可以初始化该对象。不允许初始化活动和已销毁的对象。当 debugobjects 检测到错误时,它会调用对象类型描述结构的 fixup_init 函数(如果调用者提供了)。在实际初始化对象之前,修复函数可以纠正问题。例如,它可以停用活动对象,以防止对子系统造成损害。

当实际对象尚未被 debugobjects 跟踪时,debugobjects 会为实际对象分配一个跟踪器对象,并将跟踪器对象状态设置为 ODEBUG_STATE_INIT。它会验证该对象是否在调用者的堆栈上。

在分配对象的函数返回之前,必须通过调用 debug_object_free() 从跟踪器中删除堆栈上的对象。否则,我们将继续跟踪过时的对象。

int debug_object_activate(void *addr, const struct debug_obj_descr *descr)

调试检查对象何时激活

参数

void *addr

对象的地址

const struct debug_obj_descr *descr

指向对象特定调试描述结构的指针。成功返回 0,检查失败返回 -EINVAL。

每当调用实际对象的激活函数时,都会调用此函数。

当实际对象已经被 debugobjects 跟踪时,会检查是否可以激活该对象。不允许激活活动和已销毁的对象。当 debugobjects 检测到错误时,它会调用对象类型描述结构的 fixup_activate 函数(如果调用者提供了)。在实际激活对象之前,修复函数可以纠正问题。例如,它可以停用活动对象,以防止对子系统造成损害。

当实际对象尚未被 debugobjects 跟踪时,如果可用,则会调用 fixup_activate 函数。这对于允许合法激活静态分配和初始化的对象是必要的。修复函数会检查对象是否有效,并调用 debug_objects_init() 函数来初始化此对象的跟踪。

当激活合法时,关联的跟踪器对象的状态将设置为 ODEBUG_STATE_ACTIVE。

void debug_object_deactivate(void *addr, const struct debug_obj_descr *descr)

调试检查对象何时停用

参数

void *addr

对象的地址

const struct debug_obj_descr *descr

指向对象特定调试描述结构的指针

每当调用实际对象的停用函数时,都会调用此函数。

当实际对象被 debugobjects 跟踪时,会检查是否可以停用该对象。不允许停用未跟踪或已销毁的对象。

当停用合法时,关联的跟踪器对象的状态将设置为 ODEBUG_STATE_INACTIVE。

void debug_object_destroy(void *addr, const struct debug_obj_descr *descr)

调试检查对象何时销毁

参数

void *addr

对象的地址

const struct debug_obj_descr *descr

指向对象特定调试描述结构的指针

调用此函数以标记对象已销毁。这对于防止使用内存中仍然可用的无效对象很有用:无论是静态分配的对象还是稍后释放的对象。

当实际对象被 debugobjects 跟踪时,会检查是否可以销毁该对象。不允许销毁活动和已销毁的对象。当 debugobjects 检测到错误时,它会调用对象类型描述结构的 fixup_destroy 函数(如果调用者提供了)。在实际销毁对象之前,修复函数可以纠正问题。例如,它可以停用活动对象,以防止对子系统造成损害。

当销毁合法时,关联的跟踪器对象的状态将设置为 ODEBUG_STATE_DESTROYED。

void debug_object_free(void *addr, const struct debug_obj_descr *descr)

当对象被释放时进行调试检查

参数

void *addr

对象的地址

const struct debug_obj_descr *descr

指向对象特定调试描述结构的指针

此函数在对象被释放之前调用。

当实际对象被 debugobjects 跟踪时,会检查该对象是否可以被释放。 活动对象不允许释放。 当 debugobjects 检测到错误时,它会调用对象类型描述结构的 fixup_free 函数(如果调用者提供)。 fixup 函数可以在对象真正释放之前纠正问题。 例如,它可以停用活动对象,以防止对子系统造成损害。

请注意,debug_object_free 会从跟踪器中删除该对象。 稍后对该对象的使用将由其他调试检查检测到。

void debug_object_assert_init(void *addr, const struct debug_obj_descr *descr)

当对象应被初始化时进行调试检查

参数

void *addr

对象的地址

const struct debug_obj_descr *descr

指向对象特定调试描述结构的指针

调用此函数以断言对象已初始化。

当实际对象未被 debugobjects 跟踪时,它会调用调用者提供的对象类型描述结构的 fixup_assert_init,并使用硬编码的对象状态 ODEBUG_NOT_AVAILABLE。 fixup 函数可以通过调用 debug_object_init 和其他特定的初始化函数来纠正问题。

当实际对象已被 debugobjects 跟踪时,它将被忽略。

修复函数

调试对象类型描述结构

struct debug_obj

跟踪对象的表示

定义:

struct debug_obj {
    struct hlist_node               node;
    enum debug_obj_state            state;
    unsigned int                    astate;
    union {
        void *object;
        struct hlist_node       *batch_last;
    };
    const struct debug_obj_descr *descr;
};

成员

node

将对象链接到跟踪器列表的 hlist 节点

state

跟踪对象状态

astate

当前活动状态

{unnamed_union}

匿名

object

指向实际对象的指针

batch_last

指向批次中最后一个 hlist 节点的指针

descr

指向对象类型特定的调试描述结构的指针

struct debug_obj_descr

对象类型特定的调试描述结构

定义:

struct debug_obj_descr {
    const char              *name;
    void *(*debug_hint)(void *addr);
    bool (*is_static_object)(void *addr);
    bool (*fixup_init)(void *addr, enum debug_obj_state state);
    bool (*fixup_activate)(void *addr, enum debug_obj_state state);
    bool (*fixup_destroy)(void *addr, enum debug_obj_state state);
    bool (*fixup_free)(void *addr, enum debug_obj_state state);
    bool (*fixup_assert_init)(void *addr, enum debug_obj_state state);
};

成员

name

对象类型的名称

debug_hint

返回地址的函数,该地址具有关联的内核符号,以允许识别对象

is_static_object

如果 obj 是静态的,则返回 true,否则返回 false

fixup_init

修复函数,当初始化检查失败时调用。 如果修复成功,则所有修复函数必须返回 true,否则返回 false

fixup_activate

修复函数,当激活检查失败时调用

fixup_destroy

修复函数,当销毁检查失败时调用

fixup_free

修复函数,当释放检查失败时调用

fixup_assert_init

修复函数,当 assert_init 检查失败时调用

fixup_init

每当检测到 debug_object_init 中存在问题时,调试代码都会调用此函数。 该函数接受对象的地址和跟踪器中当前记录的状态。

当对象状态为时从 debug_object_init 调用

  • ODEBUG_STATE_ACTIVE

当修复成功时,该函数返回 true,否则返回 false。 返回值用于更新统计信息。

请注意,该函数需要在修复损坏后再次调用 debug_object_init() 函数,以保持状态一致。

fixup_activate

每当检测到 debug_object_activate 中存在问题时,调试代码都会调用此函数。

当对象状态为时从 debug_object_activate 调用

  • ODEBUG_STATE_NOTAVAILABLE

  • ODEBUG_STATE_ACTIVE

当修复成功时,该函数返回 true,否则返回 false。 返回值用于更新统计信息。

请注意,该函数需要在修复损坏后再次调用 debug_object_activate() 函数,以保持状态一致。

静态初始化对象的激活是一种特殊情况。 当 debug_object_activate() 没有此对象地址的跟踪对象时,则以对象状态 ODEBUG_STATE_NOTAVAILABLE 调用 fixup_activate()。 修复函数需要检查这是否是静态初始化对象的合法情况。 如果是,则调用 debug_object_init()debug_object_activate(),以使该对象对跟踪器已知并标记为活动。 在这种情况下,该函数应返回 false,因为这不是真正的修复。

fixup_destroy

每当检测到 debug_object_destroy 中存在问题时,调试代码都会调用此函数。

当对象状态为时从 debug_object_destroy 调用

  • ODEBUG_STATE_ACTIVE

当修复成功时,该函数返回 true,否则返回 false。 返回值用于更新统计信息。

fixup_free

每当检测到 debug_object_free 中存在问题时,调试代码都会调用此函数。 此外,当从 debug_check_no_obj_freed() 健全性检查中检测到活动对象时,可以在 kfree/vfree 中的调试检查中调用它。

当对象状态为时,从 debug_object_free() 或 debug_check_no_obj_freed() 调用

  • ODEBUG_STATE_ACTIVE

当修复成功时,该函数返回 true,否则返回 false。 返回值用于更新统计信息。

fixup_assert_init

每当检测到 debug_object_assert_init 中存在问题时,调试代码都会调用此函数。

当在调试桶中未找到对象时,从 debug_object_assert_init() 调用,并使用硬编码状态 ODEBUG_STATE_NOTAVAILABLE。

当修复成功时,该函数返回 true,否则返回 false。 返回值用于更新统计信息。

请注意,此函数应确保在返回之前调用 debug_object_init()

静态初始化对象的处理是一种特殊情况。 修复函数应检查这是否是静态初始化对象的合法情况。 在这种情况下,仅应调用 debug_object_init(),以使该对象对跟踪器已知。 然后该函数应返回 false,因为这不是真正的修复。

已知错误和假设

无(但愿如此)。