简体   繁体   English

当我们在C中取消引用NULL指针时,操作系统会发生什么?

[英]What happens in OS when we dereference a NULL pointer in C?

Let's say there is a pointer and we initialize it with NULL. 假设有一个指针,我们用NULL初始化它。

int* ptr = NULL;
*ptr = 10;

Now , the program will crash since ptr isn't pointing to any address and we're assigning a value to that , which is an invalid access. 现在,程序将崩溃,因为ptr没有指向任何地址,我们正在为其分配一个值,这是一个无效的访问。 So , the question is , what happens internally in the OS ? 那么,问题是,操作系统内部会发生什么? Does a page-fault / segmentation-fault occur ? 是否发生页面错误/分段错误? Will the kernel even search in the page table ? 内核甚至会在页面表中搜索吗? Or the crash occur before that? 或者崩溃发生在那之前?

I know I wouldn't do such a thing in any program but this is just to know what happens internally in the OS or Compiler in such a case. 我知道我不会在任何程序中做这样的事情,但这只是为了知道在这种情况下OS或编译器内部发生了什么。 And it is NOT a duplicate question. 这不是一个重复的问题。

Short answer : it depends on a lot of factors, including the compiler, processor architecture, specific processor model, and the OS, among others. 简短回答 :它取决于很多因素,包括编译器,处理器架构,特定处理器型号和操作系统等。

Long answer (x86 and x86-64) : Let's go down to the lowest level: the CPU. 答案很长(x86和x86-64) :让我们下到最低级别:CPU。 On x86 and x86-64, that code will typically compile into an instruction or instruction sequence like this: 在x86和x86-64上,该代码通常会编译成如下所示的指令或指令序列:

movl $10, 0x00000000

Which says to "store the constant integer 10 at virtual memory address 0". 其中说“将常数整数10存储在虚拟内存地址0”。 The Intel® 64 and IA-32 Architectures Software Developer Manuals describe in detail what happens when this instruction gets executed, so I'm going to summarize it for you. 英特尔®64和IA-32架构软件开发人员手册详细描述了执行此指令时会发生什么,因此我将为您总结一下。

The CPU can operate in several different modes, several of which are for backwards compatibility with much older CPUs. CPU可以在几种不同的模式下运行,其中几种模式是为了向后兼容较旧的CPU。 Modern operating systems run user-level code in a mode called protected mode , which uses paging to convert virtual addresses into physical addresses. 现代操作系统以称为保护模式的模式运行用户级代码,该模式使用分页将虚拟地址转换为物理地址。

For each process, the OS keeps a page table which dictates how the addresses are mapped. 对于每个进程,操作系统都会保留一个页表 ,用于指示地址的映射方式。 The page table is stored in memory in a specific format (and protected so that they can not be modified by the user code) that the CPU understands. 页表以特定格式存储在存储器中(并且受到保护,以便CPU不能通过用户代码修改)。 For every memory access that happens, the CPU translates it according to the page table. 对于发生的每次内存访问,CPU根据页表对其进行转换。 If the translation succeeds, it performs the corresponding read/write to the physical memory location. 如果转换成功,它将对物理内存位置执行相应的读/写操作。

The interesting things happen when the address translation fails. 地址转换失败时会发生有趣的事情。 Not all addresses are valid, and if any memory access generates an invalid address, the processor raises a page fault exception . 并非所有地址都有效,并且如果任何内存访问生成无效地址,则处理器会引发页面错误异常 This triggers a transition from user mode (aka current privilege level (CPL) 3 on x86/x86-64) into kernel mode (aka CPL 0) to a specific location in the kernel's code, as defined by the interrupt descriptor table (IDT). 这会触发从用户模式 (在x86 / x86-64上的当前特权级别(CPL)3 )到内核模式 (也称为CPL 0)到内核代码中的特定位置的转换,如中断描述符表 (IDT)所定义。

The kernel regains control and, based on the information from the exception and the process's page table, figures out what happened. 内核重新​​获得控制权,并根据异常和进程页面表中的信息,确定发生了什么。 In this case, it realizes that the user-level process accessed an invalid memory location, and then it reacts accordingly. 在这种情况下,它意识到用户级进程访问了无效的内存位置,然后它会做出相应的反应。 On Windows, it will invoke structured exception handling to allow the user code to handle the exception. 在Windows上,它将调用结构化异常处理以允许用户代码处理异常。 On POSIX systems, the OS will deliver a SIGSEGV signal to the process. 在POSIX系统上,操作系统将向进程发送SIGSEGV信号。

In other cases, the OS will handle the page fault internally and restart the process from its current location as if nothing happened. 在其他情况下,操作系统将在内部处理页面错误,并从当前位置重新启动进程,就好像什么都没发生一样。 For example, guard pages are placed at the bottom of the stack to allow the stack to grow on demand up to a limit, instead of preallocating a large amount of memory for the stack. 例如, 保护页面放置在堆栈的底部,以允许堆栈按需增长到一个限制,而不是预先为堆栈分配大量内存。 Similar mechanisms are used for achieving copy-on-write memory. 类似的机制用于实现写时复制存储器。

In modern OSes, the page tables are usually set up to make the address 0 an invalid virtual address. 在现代操作系统中,页表通常设置为使地址0成为无效的虚拟地址。 But sometimes it's possible to change that, eg on Linux by writing 0 to the pseudofile /proc/sys/vm/mmap_min_addr , after which it's possible to use mmap(2) to map the virtual address 0. In that case, dereferencing a null pointer would not cause a page fault. 但有时可以通过将0写入伪文件/proc/sys/vm/mmap_min_addr来改变它,之后可以使用mmap(2)映射虚拟地址0.在这种情况下,取消引用null指针不会导致页面错误。

The above discussion is all about what happens when the original code is running in user space. 上面的讨论是关于原始代码在用户空间中运行时会发生什么。 But this could also happen inside the kernel. 但这也可能发生在内核中。 The kernel can (and is certainly much more likely than user code to) map the virtual address 0, so such a memory access would be normal. 内核可以(并且当然比用户代码更可能)映射虚拟地址0,因此这样的内存访问是正常的。 But if it's not mapped, then what happens then is largely similar: the CPU raises a page fault error which traps into a predefined point at the kernel, the kernel examines what happened, and reacts accordingly. 但是如果没有映射,那么接下来会发生的情况大致相似:CPU引发页面错误错误,该错误陷入内核的预定义点,内核检查发生了什么,并做出相应的反应。 If the kernel can't recover from the exception, it will typically panic in some fashion ( kernel panic , kernel oops , or a BSOD on Windows, eg) by printing out some debug information to the console or serial port and then halting. 如果内核无法从异常中恢复,它通常会以某种方式发生混乱内核崩溃内核oops或Windows上的BSOD,例如),通过将一些调试信息打印到控制台或串行端口然后暂停。

See also Much ado about NULL: Exploiting a kernel NULL dereference for an example of how an attacker could exploit a null pointer dereference bug from inside the kernel in order to gain root privileges on a Linux machine. 另请参阅关于NULL的很多内容:利用内核NULL取消引用 ,以获取攻击者如何利用内核内部的空指针解除引用错误以获取Linux机器上的root权限的示例。

As a side note, just to compel the differences in architectures, a certain OS developed and maintained by a company known for their three-letter acronym name and often referred to as a large primary color has a most-fasicnating NULL determination. 作为旁注,为了强制体系结构的差异,由一家以三字母缩写名称而闻名的公司开发和维护的某个操作系统通常被称为大型原色,其确定性最强。

They utilize a 128-bit linear address space for ALL data (memory AND disk) in one giant "thing". 它们在一个巨大的“东西”中利用128位线性地址空间来存储所有数据(内存和磁盘)。 In accordance with their OS, a "valid" pointer must be placed on a 128-bit boundary within that address space. 根据它们的OS,“有效”指针必须放在该地址空间内的128位边界上。 This, btw, causes fascinating side effects for structs, packed or not, that house pointers. 这个,顺便说一句,对于那些装有指针的结构而言,会产生令人着迷的副作用。 Anyway, tucked away in a per-process dedicated page is a bitmap that assigns one bit for every valid location in a process address space where a valid pointer can lay. 无论如何,隐藏在每个进程专用页面中的是一个位图,它为进程地址空间中的每个有效位置分配一个 ,其中有效指针可以放置。 ALL opcodes on their hardware and OS that can generate and return a valid memory address and assign it to a pointer will set the bit that represents the memory address where that pointer (the target pointer) is located. 其硬件和操作系统上可以生成并返回有效内存地址并将其分配给指针的所有操作码将设置表示该指针(目标指针)所在的内存地址的位。

So why should anyone care? 那么为什么要关心呢? For this simple reason: 原因很简单:

int a = 0;
int *p = &a;
int *q = p-1;

if (p)
{
// p is valid, p's bit is lit, this code will run.
}

if (q)
{
   // the address stored in q is not valid. q's bit is not lit. this will NOT run.
}

What is truly interesting is this. 真正有趣的是这个。

if (p == NULL)
{
   // p is valid. this will NOT run.
}

if (q == NULL)
{
   // q is not valid, and therefore treated as NULL, this WILL run.
}

if (!p)
{
   // same as before. p is valid, therefore this won't run
}

if (!q)
{
   // same as before, q is NOT valid, therefore this WILL run.
}

Its something you have to see to believe. 它是你必须要相信的东西。 I can't even imagine the housekeeping done to maintain that bit map, especially when copying pointer values or freeing dynamic memory. 我甚至无法想象为维护该位图所做的内务处理,特别是在复制指针值或释放动态内存时。

On CPU which support virtual mermory, a page fault exception will be usually issued if you try to read at memory address 0x0 . 在支持虚拟存储器的CPU上,如果尝试读取内存地址0x0通常会发出页面错误异常。 The OS page fault handler will be invoked, the OS will then decide that the page is invalid and aborts your program. 将调用操作系统页面错误处理程序,操作系统将确定该页面无效并中止您的程序。

Note that on some CPU you can also safely access memory address 0x0 . 请注意,在某些CPU上,您还可以安全地访问内存地址0x0

As the C Standard says dereferencing a null pointer is undefined, if the compiler is able to detect at compile time (or even runtime) that your are dereferencing a null pointer it can do whatever it wants, like aborting the program with a verbose error message. 正如C标准所说,取消引用空指针是未定义的,如果编译器能够在编译时(或甚至运行时)检测到您正在取消引用空指针,它可以执行任何想要的操作,例如使用详细错误消息中止程序。

(C99, 6.5.3.2.p4) "If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.87)" (C99,6.5.3.2.p4)“如果为指针指定了无效值,则一元*运算符的行为未定义.87)”

87): "Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime." 87):“unary *运算符取消引用指针的无效值是空指针,地址与指向的对象类型不一致,以及对象在其生命周期结束后的地址。”

In a typical case, int *ptr = NULL; 典型的情况下, int *ptr = NULL; will set ptr to point to address 0. The C standard (and the C++ standard) is very careful to not require that, but it's extremely common nonetheless. 将设置ptr指向地址0 C标准(和C ++标准)是非常小心, 要求,但是这是非常常见不过。

When you do *ptr = 10; 当你做*ptr = 10; , the CPU would normally generate 0 on the address lines, and 10 on the data lines, while setting a R/W line to indicate a write (and, if the bus has such a thing, assert the memory vs. I/O line to indicate a write to memory, not I/O). ,CPU通常会在地址线上产生0,在数据线上产生10 ,同时设置R / W线以指示写入(如果总线有这样的话,则断言内存与I / O线表示写入内存,而不是I / O)。

Assuming the CPU supports memory protection (and you're using an OS that enables it), the CPU will check that (attempted) access before it happens though. 假设CPU支持内存保护(并且您正在使用启用它的操作系统),CPU将在它发生之前检查(尝试)访问。 For example, a modern Intel/AMD CPU will use paging tables that map virtual addresses to physical addresses. 例如,现代Intel / AMD CPU将使用将虚拟地址映射到物理地址的分页表。 In a typical case, address 0 won't be mapped to any physical address. 在典型情况下,地址0不会映射到任何物理地址。 In this case, the CPU will generate an access violation exception. 在这种情况下,CPU将生成访问冲突异常。 For one fairly typical example, Microsoft Windows leaves the first 4 megabytes un-mapped, so any address in that range will normally result in an access violation. 对于一个相当典型的示例,Microsoft Windows将未映射的前4兆字节留下,因此该范围内的任何地址通常都会导致访问冲突。

On an older CPU (or an older operating system that doesn't enable the CPUs protection features) the attempted write will often succeed. 在较旧的CPU(或不启用CPU保护功能的较旧操作系统)上,尝试写入通常会成功。 For example, under MS-DOS, writing through a NULL pointer would simply write to address zero. 例如,在MS-DOS下,通过NULL指针写入只会写入地址零。 In small or medium model (with 16-bit addresses for data) most compilers would write some known pattern to the first few bytes of the data segment, and when the program ended, they'd check to see if that pattern remained intact (and do something to indicate that you'd written via a NULL pointer if it failed). 在小型或中型模型(数据的16位地址)中,大多数编译器会将一些已知模式写入数据段的前几个字节,当程序结束时,他们会检查该模式是否保持不变(和做一些事情表明如果失败就通过NULL指针写入。 In compact or large model (20-bit data addresses) they'd generally just write to address zero without warning. 在紧凑型或大型模型(20位数据地址)中,它们通常只是在没有警告的情况下写入地址为零。

I imagine that this is platform and compiler dependent. 我想这是依赖于平台和编译器的。 The NULL pointer could be implemented by using a NULL page, in which case you'd have a page fault, or it could be below the segment limit for an expand-down segment, in which case you'd have a segmentation fault. NULL指针可以通过使用NULL页面来实现,在这种情况下,您会出现页面错误,或者它可能低于扩展段的段限制,在这种情况下,您会遇到分段错误。

This is not a definitive answer, just my conjecture. 这不是一个明确的答案,只是我的猜想。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 当我们取消引用FILE指针时会发生什么? - What happens when we dereference a FILE pointer? 如果取消引用 null 指针,在 CPU 级别会发生什么? - What happens at CPU-Level if you dereference a null pointer? 当您取消引用后增量C时会发生什么 - What happens when you dereference a postincrement C 在C中,将NULL指针传递给strcmp()时会发生什么? - In C, what exactly happens when you pass a NULL pointer to strcmp()? 在C语言中取消引用静态变量时会发生什么情况? - What exactly happens when you dereference a static variable in C? 在MSP430上,当我取消引用空指针时会发生什么? - On MSP430, what will happen when I dereference a null pointer? C中的链表:将当前节点分配给临时节点指针时会发生什么? - Linked List in C: What happens when we assign the current node to a temporary node pointer? 访问字段会导致取消引用 C 中的 null 指针 - Access to field results in dereference of a null pointer in C 当我们将值重新分配给 char 指针时,内存会发生什么? - What happens with memory when we reassign value to char pointer? C语言:当取消引用“指向int的指针的指针”时,应该返回什么? - C Language: When you dereference a “pointer to a pointer to int”, what is supposed to be returned?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM