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

作者:

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

每个函数都接受实际对象的地址和指向对象类型特定调试描述结构的指针。

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

统计信息可通过 /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。它会验证该对象是否不在调用者的堆栈上。如果它在调用者的堆栈上,则会 printk 打印有限数量的警告,包括完整的堆栈跟踪。调用代码必须使用 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 函数,则会调用该函数。修复函数可以在实际对象释放之前纠正问题。例如,它可以停用一个活跃对象,以防止对子系统造成损害。

请注意,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。修复函数可以通过调用 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

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

fixup_init

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

fixup_activate

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

fixup_destroy

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

fixup_free

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

fixup_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() 没有为该对象地址跟踪对象时,会调用 fixup_activate(),对象状态为 ODEBUG_STATE_NOTAVAILABLE。修复函数需要检查这是否是静态初始化对象的合法情况。如果是,它会调用 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,因为这不是一个真正的修复。

已知错误和假设

无(敲木头)。