0xdead000000000100是什么-linux 编程
next
和 prev
指针设置成特殊的非法值,方便后续检测非法访问或使用已删除的节点。
一、0xdead000000000100是什么
这是一个 64 位指针地址值,用十六进制表示。
我们把它拆一下看:
0x DEAD 0000 0000 0100
0x
:表示后面是十六进制DEAD
:明显是“中毒”提示(dead = 死掉)0000 0000 0100
:后面可以是为了区分不同用途(比如是 next 还是 prev)
部分 | 含义说明 |
---|---|
0xdead | 经典的调试“毒值”,代表“死的、无效的、不能碰的” |
000000000100 | 可以看作是某种 offset 或标识值,用于区分用途或调试辅助 |
二、案例
通过一段简单的 C 程序模拟一个链表节点被删除后,如果你不小心再访问它的 next
或 prev
,会怎么样崩掉,并且怎么在调试器(比如 GDB)里看到那个标志性的 0xdead...
地址。
示例:使用 LIST_POISON1
模拟非法访问
示例代码(保存为 poison_demo.c
):
struct list_head {
struct list_head *next;
struct list_head *prev;
};
void _list_del(struct list_head *entry)
{
entry->next->prev = entry->prev;
entry->prev->next = entry->next;
}
void list_del(struct list_head *entry)
{
_list_del(entry);
entry->next = (struct list_head *)LIST_POISON1;
entry->prev = (struct list_head *)LIST_POISON2;
}
int main()
{
// 创建链表节点
struct list_head a, b, head;
// 初始化链表头和两个节点
head.next = &a;
head.prev = &b;
a.prev = &head;
a.next = &b;
b.prev = &a;
b.next = &head;
// 删除节点 a
list_del(&a);
printf("Now accessing a.next...\\n");
// 故意访问已经被置为 poison 的 a.next
// 这将导致 SEGFAULT(段错误)
struct list_head *boom = a.next;
// 这行永远不会执行,因为上面会崩溃
printf("boom->next = %p\\n", boom->next);
return 0;
}
编译运行
gcc -m64 -O0 -g poison_demo.c -o poison_demo
./poison_demo
你会看到类似这样的输出:
Now accessing a.next...
Segmentation fault (core dumped)
用 GDB 调试查看崩溃原因
gdb ./poison_demo
(gdb) run
(gdb) print boom
程序崩溃后:
Program received signal SIGSEGV, Segmentation fault.
0xdead000000000100 in ?? ()
是不是很明显?你一眼就能看到 0xdead000000000100,说明你访问了一个已经被删掉的链表节点的 next
指针!
总结流程回顾
你使用了
list_del()
把a.next
设置为0xdead000000000100
程序后续又访问了
a.next
(现在是 boom)GDB 捕捉到了非法访问
出错位置是
boom->next
,GDB 正确提示了出错行用
print boom
确认值是毒地址,定位 bug ✔️
之所以是非法的,是因为它并不是系统实际分配给你的进程的有效内存地址,而是一个人为设置的“哨兵值”,用来 故意触发崩溃 或 帮助定位野指针 bug。
二、非法的原因
它不是操作系统分配的地址。操作系统只允许你访问你当前进程拥有的虚拟地址空间。0xdead000000000100
属于一个很高很高的地址空间,在用户态程序中是不可能合法映射到的内存区域。
所以,当你尝试访问这个地址时:
boom->next; // boom = 0xdead000000000100
CPU 立刻告诉操作系统:“诶!我访问了一个不属于我的内存区域。”
于是操作系统回应:“不准碰!SIGSEGV 伺候!”于是你就收到了段错误(Segmentation Fault)。
这是一个经典的“毒值”(poison value)
这不是随机写的,而是故意写成这个样子的。
0xdead
→ 就像程序在对你说:"It’s dead!"这是一种调试技巧:当你访问这个值的时候,能快速识别这是“悬空指针”或“已释放内存”。
内核和大型项目(比如 Linux、GlusterFS、内存池库)都会用这种办法。
三、为啥不能用 NULL?
很多人会说:那我直接把 next
设成 NULL
不就完了?
其实不行,原因是:
链表实现中,
NULL
通常表示“终止”;有些操作比如
list_move()
、list_del()
、list_splice()
可能依然访问这个指针;所以你需要一个比
NULL
更炸裂的值,让你一旦错用就崩溃,方便发现问题。
四、这个值的使用原理
在现代操作系统中,尤其是 64 位系统,虚拟地址空间的设计通常如下:
32 位系统:
用户空间(User Space):大约 2GB 地址空间(通常是
0x00000000
到0x7FFFFFFF
)。内核空间(Kernel Space):大约 2GB 地址空间(通常是
0x80000000
到0xFFFFFFFF
)。
在 32 位系统中,程序的地址空间通常限制为 2GB 或 4GB。如果没有开启大内存(如 /3GB
或 PAE),用户空间就只能使用其中的 2GB。
64 位系统:
用户空间(User Space):通常是 48 位虚拟地址(
0x0000000000000000
到0x00007FFFFFFFFFFF
),这意味着约 256 TB 的可用空间。内核空间(Kernel Space):高于
0xFFFF000000000000
的地址区段(通常从0xFFFF000000000000
到0xFFFFFFFFFFFFFFFF
)。
64 位系统的地址空间大大扩展,但操作系统通常会限制进程的可用地址范围以提高效率,避免浪费过多的内存。
程序判断和地址设置
用户空间和内核空间是操作系统中虚拟内存的两个主要区域。在用户空间运行的是普通应用程序,而内核空间则是操作系统内核运行的区域。
用户空间和内核空间的划分
用户空间 (User Space)
用户程序运行的地方,包含应用程序、用户级库、动态链接库等。
在 64 位系统中,用户空间通常位于
0x0000000000000000
到0x00007FFFFFFFFFFF
,这意味着用户程序只能访问这个范围内的内存地址。
内核空间 (Kernel Space)
内核和驱动程序运行的地方,包含操作系统内核的代码、硬件驱动、内存管理、系统调用等。
在 64 位系统中,内核空间通常从
0xFFFF000000000000
开始,直到虚拟地址的上限0xFFFFFFFFFFFFFFFF
。
五、如何判断一个地址是用户空间还是内核空间
判断一个地址属于用户空间还是内核空间,可以通过检查该地址是否位于用户空间和内核空间的划分范围内。
用户空间和内核空间地址判断(64 位系统)
在 64 位系统中,用户空间的地址通常是低 48 位地址,内核空间的地址通常是高位地址(如 0xFFFF
开头的地址)。具体地:
用户空间:从
0x0000000000000000
到0x00007FFFFFFFFFFF
(即低 48 位地址)内核空间:从
0xFFFF000000000000
到0xFFFFFFFFFFFFFFFF
(即高 48 位地址)
判断方法:
// 判断一个地址是用户空间还是内核空间
void check_memory_space(uint64_t addr) {
if (addr <= USER_SPACE_END) {
printf("Address 0x%lx is in user space.\\n", addr);
} else if (addr >= KERNEL_SPACE_START) {
printf("Address 0x%lx is in kernel space.\\n", addr);
} else {
printf("Address 0x%lx is in an invalid or undefined space.\\n", addr);
}
}
int main() {
uint64_t user_addr = 0x00007FFFFFFF1234; // 用户空间地址示例
uint64_t kernel_addr = 0xFFFF123456789ABC; // 内核空间地址示例
check_memory_space(user_addr);
check_memory_space(kernel_addr);
return 0;
}
解释:
USER_SPACE_END
定义了用户空间的最大地址。KERNEL_SPACE_START
定义了内核空间的起始地址。check_memory_space
函数通过判断传入地址是否在这两个范围内来判断它是属于用户空间还是内核空间。
32 位系统的地址判断
对于 32 位系统,地址空间通常是 2GB:
用户空间通常是
0x00000000
到0x7FFFFFFF
。内核空间通常是
0x80000000
到0xFFFFFFFF
。
判断方法:
void check_memory_space(uint32_t addr) {
if (addr <= USER_SPACE_END) {
printf("Address 0x%lx is in user space.\\n", addr);
} else if (addr >= KERNEL_SPACE_START) {
printf("Address 0x%lx is in kernel space.\\n", addr);
} else {
printf("Address 0x%lx is in an invalid or undefined space.\\n", addr);
}
}
int main() {
uint32_t user_addr = 0x7FFFFFFF; // 用户空间地址示例
uint32_t kernel_addr = 0x80000000; // 内核空间地址示例
check_memory_space(user_addr);
check_memory_space(kernel_addr);
return 0;
}
注意事项:
32 位系统的用户空间和内核空间范围会受到内存大小限制,因此对于 32 位系统的内存划分更为严格。
在 64 位系统中,内核空间的起始位置和大小可能会根据操作系统的具体实现有所不同。因此,
0xFFFF000000000000
只是一个常见的划分,但并不一定适用于所有 64 位系统。
进程内存空间检查(Linux)
如果你正在调试一个程序并想查看其虚拟内存映射,可以通过 procfs
来查看:
cat /proc/<pid>/maps
这个文件显示了进程的所有虚拟内存地址范围,以及每个地址段的权限、偏移量等信息。你可以从中获取进程的内存布局,并根据地址范围判断哪些是用户空间,哪些是内核空间。
总结
在 64 位系统中,用户空间通常位于
0x0000000000000000
到0x00007FFFFFFFFFFF
,而内核空间通常从0xFFFF000000000000
开始。在 32 位系统中,用户空间通常是
0x00000000
到0x7FFFFFFF
,内核空间是0x80000000
到0xFFFFFFFF
。使用简单的条件判断代码,可以帮助你在程序中检查地址是否属于用户空间或内核空间。
所以说:0x00007FFFFFFFFFFF ****
大于此地址的为内核地址,编写代码的时候将地址设置0xdead000000000100
表示赋予了一个对于程序不可能访问的地址(用户空间),一旦程序访问此地址,就可发生错误,Dead 表示的含义又有“死”的意思,所以设置了此地址。
加入对话