slub debug(linux4.16.1)

文章详细介绍了Linux内核slub调试选项UFPZ的含义,包括跟踪内存属性、开启sanity检查、poisoning功能和redzoning功能。通过这些功能,可以在内存分配和释放时检查非法访问和越界问题。slub_debug在内存分配和释放时检查内存状态,但存在延迟检测的局限性,并可能导致额外的内存开销。文章还提到了如何通过sysfs接口主动触发slab内存检查。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在命令行选项中添加slub_debug=UFPZ,以使能相关检测功能,其含义如下:
(1)U:跟踪该slab内存的相关属性,如其创建时所属的cpu、进程、时间

(2)F:开启sanity检查功能。该功能用于在slab的某些操作中(如slab分配和释放时),检测是否有对该slab的非法内存访问操作

(3)P:开启poisoning功能,它可被用于检测再次访问释放后的内存问题。当slab被释放后,slab object本身内存将被填充为特定值。因此一旦其释放后再次访问该slab,则通过检查slab内存中的值是否被修改,即可检测到

(4)Z:开启redzoning功能,它可被用于检测内存越界访问问题。其会在slab object的前面和后面分别添加red zone,并在其被分配和被释放时,分别用不同的值填充。当发生访问越界时,就可以通过检查redzone的值检测到

如果命令行添加成功后,通过如下命令可以看到

cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-3.13.0-24-generic root=UUID=10426707-be58-4d00-b49b-f1fe35345d57 ro find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US quiet

开启slub debug后,obj被分配出来的时候,整个obj会被初始化为特定的数值

以下的都是假定开启了slub deubg

当内存被分配出来的时候全部的区域都被初始化为了POISON_INUSE(0x5a)

#define SLUB_RED_INACTIVE	0xbb
#define SLUB_RED_ACTIVE		0xcc

/* ...and for poisoning */
#define	POISON_INUSE	0x5a	/* for use-uninitialised poisoning */
#define POISON_FREE	0x6b	/* for use-after-free poisoning */
#define	POISON_END	0xa5	/* end-byte of poisoning */
static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
............................
    if (unlikely(s->flags & SLAB_POISON))
		memset(start, POISON_INUSE, PAGE_SIZE << order);

	kasan_poison_slab(page);

	shuffle = shuffle_freelist(s, page);
	if (!shuffle) {
		for_each_object_idx(p, idx, s, start, page->objects) {
			setup_object(s, page, p);
			if (likely(idx < page->objects))
				set_freepointer(s, p, p + s->size);
			else
				set_freepointer(s, p, NULL);
		}
		page->freelist = fixup_red_left(s, start);
	}

..................

	return page;
}

setup_object-> setup_object_debug

static void setup_object_debug(struct kmem_cache *s, struct page *page,
								void *object)
{
	if (!(s->flags & (SLAB_STORE_USER|SLAB_RED_ZONE|__OBJECT_POISON)))
		return;

	init_object(s, object, SLUB_RED_INACTIVE);
	init_tracking(s, object);
}
static void init_object(struct kmem_cache *s, void *object, u8 val)
{
	u8 *p = object;

	if (s->flags & SLAB_RED_ZONE)
		memset(p - s->red_left_pad, val, s->red_left_pad);

	if (s->flags & __OBJECT_POISON) {
		memset(p, POISON_FREE, s->object_size - 1);
		p[s->object_size - 1] = POISON_END;
	}

	if (s->flags & SLAB_RED_ZONE)
		memset(p + s->object_size, val, s->inuse - s->object_size);
}

下图的是linux3.16的slub deubg可以看到它相比于4. 16就没有left padding这个东西

 

slub分配器将内存刚开始划分出来的时候长这个样子,如下图所示: 

1、fp指向的obj的起始地址。但是看代码应该是这样的

2、上面的red zone有0xbb和0xcc两种情况。bb表示对象被释放了是空闲的,0xcc表示正在被使用

下图也从是kmalloc-64申请了一个地址为0x921a5dc0的slab。

由于申请了之后没有往里面写东西,所以还是6b6b.但是我们可以从地址为0x921a5e80的obj开始分析

0x921a5e80往前是0xcc这个是left pad,然后开始是obj内容(这个obj前面一部分被写了,后面仍然保留6b,可能是用不掉这么多把)接下来就是最后一字节的0xa5。接下来4自己是fp,然后是32字节的alloc/free trace,+ padding(0x5a)

详细的分析可看文章最后部分slub 分配器(linux3.16)_slub分配器_这个我好像学过的博客-CSDN博客

 

 

red left pad用于检查向左写越界的问题。red zone用于检查向右检查写越界的问题。alloc/free track用于根据释放和申请的调用信息。

slub debug检测时机:

1、申请时

___slab_alloc->alloc_debug_processing

可以看到在申请的完了之后先会进行检查alloc_consistency_checks。

检查完毕了,在重新初始化整个块。同时,这个时候red zone区域的值就从SLUB_RED_INACTIVE被修改为了SLUB_RED_ACTIVE(init_object(s, object, SLUB_RED_ACTIVE);)

static noinline int alloc_debug_processing(struct kmem_cache *s,
					struct page *page,
					void *object, unsigned long addr)
{
	if (s->flags & SLAB_CONSISTENCY_CHECKS) {
		if (!alloc_consistency_checks(s, page, object, addr))
			goto bad;
	}

	/* Success perform special debug activities for allocs */
	if (s->flags & SLAB_STORE_USER)
		set_track(s, object, TRACK_ALLOC, addr);
	trace(s, page, object, 1);
	init_object(s, object, SLUB_RED_ACTIVE);
	return 1;

bad:
	if (PageSlab(page)) {
		/*
		 * If this is a slab page then lets do the best we can
		 * to avoid issues in the future. Marking all objects
		 * as used avoids touching the remaining objects.
		 */
		slab_fix(s, "Marking all objects used");
		page->inuse = page->objects;
		page->freelist = NULL;
	}
	return 0;
}

static inline int alloc_consistency_checks(struct kmem_cache *s,
					struct page *page,
					void *object, unsigned long addr)
{
	if (!check_slab(s, page))
		return 0;

	if (!check_valid_pointer(s, page, object)) {
		object_err(s, page, object, "Freelist Pointer check fails");
		return 0;
	}
	/* 检测对象里面的内存布局的值是否被改变这些 */
	if (!check_object(s, page, object, SLUB_RED_INACTIVE))
		return 0;

	return 1;
}

 检查各个区域的值,是不是我们设定的固定值。如果不是,将错误信息打印出来,并将错误的值恢复回来。

static int check_object(struct kmem_cache *s, struct page *page,
					void *object, u8 val)
{
	u8 *p = object;
	u8 *endobject = object + s->object_size;

	if (s->flags & SLAB_RED_ZONE) {
		/* 检查left padding */
		if (!check_bytes_and_report(s, page, object, "Redzone",
			object - s->red_left_pad, val, s->red_left_pad))
			return 0;

		if (!check_bytes_and_report(s, page, object, "Redzone",
			endobject, val, s->inuse - s->object_size))
			return 0;
	} else {
		if ((s->flags & SLAB_POISON) && s->object_size < s->inuse) {
			check_bytes_and_report(s, page, p, "Alignment padding",
				endobject, POISON_INUSE,
				s->inuse - s->object_size);
		}
	}

	if (s->flags & SLAB_POISON) {
		if (val != SLUB_RED_ACTIVE && (s->flags & __OBJECT_POISON) &&
			(!check_bytes_and_report(s, page, p, "Poison", p,
					POISON_FREE, s->object_size - 1) ||
			 !check_bytes_and_report(s, page, p, "Poison",
				p + s->object_size - 1, POISON_END, 1)))
			return 0;
		/*
		 * check_pad_bytes cleans up on its own.
		 */
		check_pad_bytes(s, page, p);
	}

	if (!s->offset && val == SLUB_RED_ACTIVE)
		/*
		 * Object and freepointer overlap. Cannot check
		 * freepointer while object is allocated.
		 */
		return 1;

	/* Check free pointer validity */
	if (!check_valid_pointer(s, page, get_freepointer(s, p))) {
		object_err(s, page, p, "Freepointer corrupt");
		/*
		 * No choice but to zap it and thus lose the remainder
		 * of the free objects in this slab. May cause
		 * another error because the object count is now wrong.
		 */
		set_freepointer(s, p, NULL);
		return 0;
	}
	return 1;
}

2、释放时

void kfree(const void *x)
{
	struct page *page;
	void *object = (void *)x;

	trace_kfree(_RET_IP_, x);

	if (unlikely(ZERO_OR_NULL_PTR(x)))
		return;

	page = virt_to_head_page(x);
	if (unlikely(!PageSlab(page))) {
		BUG_ON(!PageCompound(page));
		kfree_hook(object);
		__free_pages(page, compound_order(page));
		return;
	}
	slab_free(page->slab_cache, page, object, NULL, 1, _RET_IP_);
}
static void __slab_free(struct kmem_cache *s, struct page *page,
			void *head, void *tail, int cnt,
			unsigned long addr)

{
	void *prior;
	int was_frozen;
	struct page new;
	unsigned long counters;
	struct kmem_cache_node *n = NULL;
	unsigned long uninitialized_var(flags);

	stat(s, FREE_SLOWPATH);

	if (kmem_cache_debug(s) &&
	    !free_debug_processing(s, page, head, tail, cnt, addr))
		return;

...........................
}

slub分配器在释放obj的时候,在这里进行检查。同时释放的时候会重新初始化obj的魔数字。

static noinline int free_debug_processing(
	struct kmem_cache *s, struct page *page,
	void *head, void *tail, int bulk_cnt,
	unsigned long addr)
{
..............................

	if (s->flags & SLAB_CONSISTENCY_CHECKS) {
		if (!free_consistency_checks(s, page, object, addr))
			goto out;
	}

	if (s->flags & SLAB_STORE_USER)
		set_track(s, object, TRACK_FREE, addr);
	trace(s, page, object, 0);
	/* Freepointer not overwritten by init_object(), SLAB_POISON moved it */
	init_object(s, object, SLUB_RED_INACTIVE);
................................
	return ret;
}
static inline int free_consistency_checks(struct kmem_cache *s,
		struct page *page, void *object, unsigned long addr)
{
	if (!check_valid_pointer(s, page, object)) {//检查obj指针是否正确
		slab_err(s, page, "Invalid object pointer 0x%p", object);
		return 0;
	}

	if (on_freelist(s, page, object)) {
		object_err(s, page, object, "Object already free");
		return 0;
	}

	if (!check_object(s, page, object, SLUB_RED_ACTIVE))
		return 0;

	if (unlikely(s != page->slab_cache)) {
		if (!PageSlab(page)) {
			slab_err(s, page, "Attempt to free object(0x%p) outside of slab",
				 object);
		} else if (!page->slab_cache) {
			pr_err("SLUB <none>: no slab for object 0x%p.\n",
			       object);
			dump_stack();
		} else
			object_err(s, page, object,
					"page slab pointer corrupt.");
		return 0;
	}
	return 1;
}

总结

slub deubg是利用在obj中填充魔数字,在释放和申请的时候都去检查魔数字是否被改变,进而检测到是否有越界改写对象等问题。

内存越界:可以看到obj的左右都填充了固定的数字。如果写越界了,我们检查左右的魔数字,就能被检测出来;

访问已经释放的内存:obj被释放的时候,整个obj的内存布局会被重新初始化,如果我们去访问已经释放的内存,当该obj被再次分配的时候,就能被检测出来;

重复释放:当obj被释放的时候,会去freelist上面找该对象。如果有找到,稍后会去挂到freelist上面。如果找到了,这个就出现了重复释放的问题。

1、但是可以看到slub debug只能在申请和是否的时候去检查。其他时候检查不到。因此该方法具有迟滞性,不能在出问题的时候,就检测出来。

2、另外开启slub debug会让申请的内存块变大,增加内存开销。

3、只能检查到通过slub 分配器分配的内存块。对于全局变量或者栈的问题无法被检测到。

如何主动去触发slub debug检查呢?

 slub debug在发生slab内存非法操作时,不能立即检测出来。而需要手动通过sysfs的validate属性触发检测操作。如要检测128字节kmalloc对象,则可使用以下命令:

echo 1 > /sys/kernel/slab/kmalloc-128/validate

好像是去调用这个函数,去对kmalloc-128池子里面的obj进行检查 

static int validate_slab(struct kmem_cache *s, struct page *page,
						unsigned long *map)
{
	void *p;
	void *addr = page_address(page);

	if (!check_slab(s, page) ||
			!on_freelist(s, page, NULL))
		return 0;

	/* Now we know that a valid freelist exists */
	bitmap_zero(map, page->objects);

	get_map(s, page, map);
	for_each_object(p, s, addr, page->objects) {
		if (test_bit(slab_index(p, s, addr), map))
			if (!check_object(s, page, p, SLUB_RED_INACTIVE))
				return 0;
	}

	for_each_object(p, s, addr, page->objects)
		if (!test_bit(slab_index(p, s, addr), map))
			if (!check_object(s, page, p, SLUB_RED_ACTIVE))
				return 0;
	return 1;
}

样例

qemu-system-arm -M vexpress-a9 -m 1024M -kernel arch/arm/boot/zImage -append "rdinit=/linuxrc console=ttyAMA0 loglevel=8 slub_debug" -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic

马上触发slab检测

 / # echo 1 > /sys/kernel/slab/kmalloc-64/validate 

 从kmalloc-64里面申请的内存,释放之后再次去访问

struct obj{
	int a;
	int b;
	char c;
	short d;
	int arr[8];
};
void myCallback(void)
{
    struct obj *p = kmalloc(sizeof(struct obj), GFP_ATOMIC);

	if (!p)
		return;
	kfree(p);
	p->c = 0x88;
}

 用的linux 3.16代码进行演示。。。(没有left red zone)

=============================================================================
BUG kmalloc-64 (Not tainted): Poison overwritten
-----------------------------------------------------------------------------

Disabling lock debugging due to kernel taint
INFO: 0xee057f08-0xee057f08. First byte 0x88 instead of 0x6b
INFO: Allocated in myCallback+0xb8/0xf8 age=1021 cpu=0 pid=0
INFO: Freed in myCallback+0xc4/0xf8 age=1021 cpu=0 pid=0
INFO: Slab 0xef5baae0 objects=32 used=32 fp=0x  (null) flags=0x0080
INFO: Object 0xee057f00 @offset=3840 fp=0xee057680

Bytes b4 ee057ef0: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
Object ee057f00: 6b 6b 6b 6b 6b 6b 6b 6b 88 6b 6b 6b 6b 6b 6b 6b  kkkkkkkk.kkkkkkk
Object ee057f10: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
Object ee057f20: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
Object ee057f30: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5  kkkkkkkkkkkkkkk.
Redzone ee057f40: bb bb bb bb                                      ....
Padding ee057f68: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
Padding ee057f78: 5a 5a 5a 5a 5a 5a 5a 5a                          ZZZZZZZZ
CPU: 0 PID: 758 Comm: linuxrc Tainted: G    B         3.16.0 #144
[<c0013f8c>] (unwind_backtrace) from [<c0010f64>] (show_stack+0x10/0x14)
[<c0010f64>] (show_stack) from [<c044ef5c>] (dump_stack+0x74/0x90)
[<c044ef5c>] (dump_stack) from [<c00c3844>] (check_bytes_and_report+0xb8/0x100)
[<c00c3844>] (check_bytes_and_report) from [<c00c3a14>] (check_object+0x188/0x21c)
[<c00c3a14>] (check_object) from [<c044e094>] (alloc_debug_processing+0x134/0x158)
[<c044e094>] (alloc_debug_processing) from [<c044e390>] (__slab_alloc.constprop.53+0x2d8/0x2f4)
[<c044e390>] (__slab_alloc.constprop.53) from [<c00c4d08>] (kmem_cache_alloc+0xb8/0xe4)
[<c00c4d08>] (kmem_cache_alloc) from [<c03968cc>] (sock_alloc_inode+0x2c/0x98)
[<c03968cc>] (sock_alloc_inode) from [<c00e055c>] (alloc_inode+0x1c/0x9c)
[<c00e055c>] (alloc_inode) from [<c00e2010>] (new_inode_pseudo+0x8/0x4c)
[<c00e2010>] (new_inode_pseudo) from [<c039707c>] (sock_alloc+0x14/0x9c)
[<c039707c>] (sock_alloc) from [<c0397efc>] (__sock_create+0x44/0x198)
[<c0397efc>] (__sock_create) from [<c03980d0>] (sock_create+0x44/0x4c)
[<c03980d0>] (sock_create) from [<c0398594>] (SyS_socket+0x2c/0xac)
[<c0398594>] (SyS_socket) from [<c000e420>] (ret_fast_syscall+0x0/0x30)
FIX kmalloc-64: Restoring 0xee057f08-0xee057f08=0x6b

FIX kmalloc-64: Marking all objects used
 

 像这种释放后再访问的问题

1、首先我们知道申请的对象是64字节的

BUG kmalloc-64 (Not tainted): Poison overwritten

 2、我们知道是该对象的第9字节被改写

Object ee057f00: 6b 6b 6b 6b 6b 6b 6b 6b 88 6b 6b 6b 6b 6b 6b 6b  kkkkkkkk.kkkkkkk

3、该对象申请地方myCallback

INFO: Allocated in myCallback+0xb8/0xf8 age=1021 cpu=0 pid=0
INFO: Freed in myCallback+0xc4/0xf8 age=1021 cpu=0 pid=0 

在我这个例子里面知道是申请的 struct obj,再根据第9字节,可以知道修改的是obj的成员c

### SLUB调试技术原理及使用方法 SLUB(Simple Low Fragmentation Allocator)是Linux内核中的一种内存分配器,主要用于管理小型对象的分配和释放。它通过减少内存碎片化来提高性能,并且在调试方面提供了多种工具和技术以帮助开发者检测和修复内存相关的问题。 #### 1. SLUB调试技术原理 SLUB调试技术的核心在于对内存分配和释放过程中的异常行为进行检测。以下是一些关键点: - **影子区域操作**:SLUB调试利用了KASAN(Kernel Address Sanitizer)的技术[^3],通过在内存分配时设置“影子区域”来跟踪每个内存块的状态。如果程序尝试访问未初始化或已释放的内存,系统会捕获并报告错误。 - **内存填充**:为了检测越界访问或使用已释放的内存,SLUB会在分配内存时填充特定的模式(如0x6b),并在释放内存后填充其他模式(如0xdead)。这些模式可以帮助识别非法访问。 - **红色区域(Red Zone)**:SLUB会在分配的内存块前后添加额外的空间(即红色区域),用于检测溢出或下溢问题。如果红色区域被修改,则说明存在越界访问[^3]。 - **对象跟踪**:SLUB可以记录每个分配的对象的调用栈信息,这有助于定位哪些代码路径导致了内存泄漏或其他问题[^3]。 #### 2. 使用方法 以下是使用SLUB调试技术的一些常见方法: - **启用调试选项**: 在编译内核时,可以通过配置选项`CONFIG_SLUB_DEBUG`启用SLUB调试功能。此外,还可以通过传递内核参数(如`slub_debug=P,A,O,F`)来进一步定制调试行为[^3]。 - `P`:启用对象堆栈跟踪。 - `A`:启用红色区域检查。 - `O`:启用内存填充。 - `F`:启用自由列表随机化。 - **分析日志**: 当SLUB检测到问题时,通常会在内核日志中生成详细的错误报告。可以使用`dmesg`命令查看这些日志,并根据报告中的信息定位问题。 - **动态调整调试级别**: 在运行时,可以通过`/sys/kernel/slab/<slab_name>/`目录下的文件动态调整SLUB的调试级别。例如,写入`P`到`/sys/kernel/slab/kmalloc-192/slub_debug`可以为特定的slab启用对象堆栈跟踪。 #### 3. 示例代码 以下是一个简单的示例,展示如何通过内核模块测试SLUB调试功能: ```c #include <linux/module.h> #include <linux/slab.h> static int __init slub_debug_init(void) { char *ptr = kmalloc(10, GFP_KERNEL); if (!ptr) { pr_err("Failed to allocate memory\n"); return -ENOMEM; } // 故意越界访问以触发SLUB调试 ptr[10] = 'X'; kfree(ptr); return 0; } static void __exit slub_debug_exit(void) { pr_info("Module unloaded\n"); } module_init(slub_debug_init); module_exit(slub_debug_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Example Author"); MODULE_DESCRIPTION("SLUB Debug Example"); ``` 在上述代码中,`ptr[10]`的操作将触发SLUB的红色区域检查,并在内核日志中生成相应的错误报告。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值