最近看到一个page的数据比较奇怪:

crash> page ffffea002c239c58
struct page {
flags = ,
_count = {
counter = 34-----------------------被引用34次
},
{
_mapcount = {
counter = -1---------------------没有映射
},
{
inuse = ,
objects =
}
},
{
{
private = ,
mapping = 0x0------------------为NULL,
},
ptl = {
raw_lock = {
slock =
}
},
slab = 0x0,
first_page = 0x0
},
{
index = ,------------index这么大,明显不对
freelist = 0xffff881508fda880------------转成16进制,这个倒是像对的
},
lru = {
next = 0xdead000000100100,
prev = 0xdead000000200200
}
}

查看一下这个地址:

crash> kmem 0xffff881508fda880
CACHE NAME OBJSIZE ALLOCATED TOTAL SLABS SSIZE
ffff880c17fb0d00 mm_struct 8k
SLAB MEMORY TOTAL ALLOCATED FREE
ffff881508fda280 ffff881508fda300
FREE / [ALLOCATED]
ffff881508fda880 PAGE PHYSICAL MAPPING INDEX CNT FLAGS
ffffea00499f77b0 1508fda000 c0000000000080 slab

最后看到的是lru的next和prev成员,一开始才疏学浅,觉得这个 0xdead000000100100 和 0xdead000000200200 很奇怪。

后来搜索之后,才发现这个是将一个entry删除之后,设置的标记值:

/*
* Architectures might want to move the poison pointer offset
* into some well-recognized area such as 0xdead000000000000,
* that is also not mappable by user-space exploits:
*/
#ifdef CONFIG_ILLEGAL_POINTER_VALUE
# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
#else
# define POISON_POINTER_DELTA
#endif /*
* These are non-NULL pointers that will result in page faults
* under normal circumstances, used to verify that nobody uses
* non-initialized list entries.
*/
#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)

删除的代码实现如下:

static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}

不光光是list,hlist也有类似动作:

static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}

在用户态程序中,我们一般通过判断指针是不是是null来判断能否使用这个指针,在将一个entry从list删除后,一般将prev和next设置为NULL,这样做没有什么问题,但是这样就没有

区分这个entry是从list摘除的,还是本身初始化的,所以为了调试方便,将其设置为一些特殊值,是有意义的。

当开启调试之后,即开启 CONFIG_DEBUG_LIST,则在加入链表和删除的时候都会加入调试warn

void __list_del_entry(struct list_head *entry)
{
struct list_head *prev, *next; prev = entry->prev;
next = entry->next; if (WARN(next == LIST_POISON1,
"list_del corruption, %p->next is LIST_POISON1 (%p)\n",
entry, LIST_POISON1) ||
WARN(prev == LIST_POISON2,
"list_del corruption, %p->prev is LIST_POISON2 (%p)\n",
entry, LIST_POISON2) ||
WARN(prev->next != entry,
"list_del corruption. prev->next should be %p, "
"but was %p\n", entry, prev->next) ||
WARN(next->prev != entry,
"list_del corruption. next->prev should be %p, "
"but was %p\n", entry, next->prev))
return; __list_del(prev, next);
}
void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
WARN(next->prev != prev,
"list_add corruption. next->prev should be "
"prev (%p), but was %p. (next=%p).\n",
prev, next->prev, next);
WARN(prev->next != next,
"list_add corruption. prev->next should be "
"next (%p), but was %p. (prev=%p).\n",
next, prev->next, prev);
WARN(new == prev || new == next,
"list_add double add: new=%p, prev=%p, next=%p.\n",
new, prev, next);
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}

当然,如果你摘链之后,还要继续挂链的话,还是要初始化的,这个时候就使用:

static inline void
list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);-----------------------会重新初始化
}
05-08 08:17