最近看到一个page的数据比较奇怪:
crash> page ffffea002c239c58 struct page { flags = 54043195528445952, _count = { counter = 34-----------------------被引用34次 }, { _mapcount = { counter = -1---------------------没有映射 }, { inuse = 65535, objects = 65535 } }, { { private = 0, mapping = 0x0------------------为NULL, }, ptl = { raw_lock = { slock = 0 } }, slab = 0x0, first_page = 0x0 }, { index = 18446612222659373184,------------index这么大,明显不对 freelist = 0xffff881508fda880------------转成16进制,这个倒是像对的 }, lru = { next = 0xdead000000100100, prev = 0xdead000000200200 } }
查看一下这个地址:
crash> kmem 0xffff881508fda880 CACHE NAME OBJSIZE ALLOCATED TOTAL SLABS SSIZE ffff880c17fb0d00 mm_struct 1408 230 580 116 8k SLAB MEMORY TOTAL ALLOCATED FREE ffff881508fda280 ffff881508fda300 5 2 3 FREE / [ALLOCATED] ffff881508fda880 PAGE PHYSICAL MAPPING INDEX CNT FLAGS ffffea00499f77b0 1508fda000 0 0 1 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 0 #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) ", entry, LIST_POISON1) || WARN(prev == LIST_POISON2, "list_del corruption, %p->prev is LIST_POISON2 (%p) ", entry, LIST_POISON2) || WARN(prev->next != entry, "list_del corruption. prev->next should be %p, " "but was %p ", entry, prev->next) || WARN(next->prev != entry, "list_del corruption. next->prev should be %p, " "but was %p ", 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). ", prev, next->prev, next); WARN(prev->next != next, "list_add corruption. prev->next should be " "next (%p), but was %p. (prev=%p). ", next, prev->next, prev); WARN(new == prev || new == next, "list_add double add: new=%p, prev=%p, next=%p. ", 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);-----------------------会重新初始化 }