在OS会发生什么,当我们取消引用在C NULL指针?(What happens in OS when

2019-06-18 09:13发布

比方说,有一个指针,我们用NULL初始化。

int* ptr = NULL;
*ptr = 10;

现在,程序会崩溃,因为ptr没有指向任何地址,我们会分配一个值到,这是一个无效的访问。 所以,问题是,在什么操作系统内部发生了什么? 做一个页面出错/分段故障发生的呢? 将内核甚至在页表搜索? 或之前崩溃发生的?

我知道我不会做这样的事情在任何程序,但是这仅仅是知道在操作系统或编译器在这种情况下内部发生。 它不是一个重复的问题。

Answer 1:

简短的回答 :这取决于很多因素,包括编译器,处理器架构,具体的处理器型号,操作系统,等等。

长一点的回答(x86和x86-64的):让我们下去的最低水平:在CPU。 在x86和x86-64,该代码将通常编译成这样的指令或指令序列:

movl $10, 0x00000000

它说,以“在虚拟内存地址0存放常整数10”。 在英特尔®64和IA-32架构软件开发手册详细描述了当该指令被执行会发生什么,所以我要总结一下你。

CPU可以在几种不同的模式,其中有几个是用于向后兼容老得多的CPU运行。 现代操作系统在一个叫做保护模式模式,它使用运行用户级代码分页到虚拟地址转换成物理地址。

对于每一个过程中,OS保持其规定的地址是如何映射页表 。 页表存储在存储器中以特定的格式(和保护,使得它们不能被用户代码被修改),该CPU理解。 对于出现这种情况每次存储器访问时,CPU根据页面表将其翻译。 如果翻译成功,则执行相应的读/写的物理存储器位置。

当地址转换失败,有趣的事情发生了。 不是所有的地址是有效的,如果有任何存储器存取产生的无效地址,处理器提出了一个缺页异常 。 这触发从(在x86 / x86-64的又名当前特许级(CPL)3) 用户模式的转换到内核模式 (又名CPL 0)到在内核的代码的特定位置,通过中断描述符表定义(IDT) 。

内核重新​​获得控制,并根据从异常和进程的页表中的信息,计算出发生了什么事。 在这种情况下,认识到,用户级进程访问的无效的内存位置,然后将其相应地做出反应。 在Windows中,它会调用结构化异常处理 ,允许用户代码来处理异常。 在POSIX系统,该操作系统将提供一个SIGSEGV信号的过程。

在其他情况下,操作系统会在内部处理页面错误,并从当前位置重新启动的过程,就好像什么都没有发生。 例如, 防护页面放置在堆栈的底部,从而使段落对需求增长起来,而不是预先分配大量内存堆到了一个极限。 类似的机制用于实现写入时复制内存。

在现代操作系统中,页表通常被设置为使地址0无效的虚拟地址。 但有时有可能通过写0到伪文件来改变,例如,在Linux /proc/sys/vm/mmap_min_addr ,在此之后有可能使用mmap(2)来映射虚拟地址0。在这种情况下,解引用空指针不会导致页面错误。

上述讨论是关于当原始代码运行在用户空间会发生什么。 但是,这也可能发生在内核里。 内核可以(并且当然比用户代码以更可能)映射虚拟地址0,所以这样的存储器访问将是正常的。 但是,如果它不映射,那么会发生什么,然后在很大程度上是相似的:在CPU提高其捕获到预定点在内核中的页错误,内核检查发生了什么,并相应地做出反应。 如果内核无法从异常中恢复,这通常会惊慌以某种方式( 内核崩溃内核哎呀 ,或在Windows上,如BSOD)通过打印出一些调试信息发送到控制台或串行端口,然后停止。

另请参见有关NULL庸人自扰:开拓内核NULL取消引用了攻击者如何能够从内核中,以获得在Linux机器上root权限利用一个空指针引用错误的例子。



Answer 2:

作为一个侧面说明,只是为了迫使在结构上的差异,在一定OS开发和众所周知的,他们三个字母的缩写名称,通常被称为一个大的原色有一个最fasicnating NULL确定一个公司维护。

他们利用在一个巨大的“东西”的所有数据(内存和磁盘),128位的线性地址空间。 按照他们的操作系统,一个“有效”指针必须被放置在该地址空间中的128位的边界上。 这,顺便说一句,导致迷人的副作用结构,包装与否,这房子指针。 总之,在每个进程专用页面局促的是,分配一个在一个进程的地址空间,一个有效的指针可以打好每一个有效位置的位图。 在他们的硬件和操作系统,可以生成并返回一个有效的内存地址,并将其分配给一个指针设置,它表示该指针(目标指针)所处的内存地址位ALL操作码。

那么,为什么有人在乎? 出于这个原因很简单:

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.
}

什么是真正有趣的是这一点。

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.
}

它的东西你必须看到相信。 我甚至不能想象做维护的位图,复制指针值或释放动态内存,特别是当管家。



Answer 3:

在CPU支持虚拟mermory,一个页面故障异常通常会在如果你尝试在内存地址读取发行0x0 。 操作系统页面错误处理程序将被调用时,操作系统会再决定该页面无效并终止程序。

请注意,在一些CPU也可以安全地访问内存地址0x0

由于C标准说提领一空指针是不确定的,如果编译器能够在你被提领一空指针就可以为所欲为,就像一个详细的错误信息中止程序编译时(甚至运行)来检测。

(C99,6.5.3.2.p4)“如果无效值已被分配给所述指针,一元运算符*的行为是undefined.87)”

87):“在用于通过一元*运算解引用一个指针的无效值是空指针,用于对象的类型正确对齐指向一个地址,它的寿命结束后的对象的地址。”



Answer 4:

典型的情况下, int *ptr = NULL; 将设置ptr指向地址0 C标准(和C ++标准)是非常小心, 要求,但是这是非常常见不过。

当你做*ptr = 10; 中,CPU通常会产生0上的地址线,并且10在数据线,同时设定了R / W线以指示写(和,如果总线具有这样的事情,断言所述存储器与I / O线以指示内存的写入,而不是I / O)。

假设CPU支持内存保护(和您使用的操作系统,使得它),CPU将检查(未遂)访问它发生之前,虽然。 例如,现代的Intel / AMD CPU将使用该虚拟地址到物理地址的映射分页表。 在典型的情况下,地址0将不被映射到任何物理地址。 在这种情况下,CPU将产生一个访问冲突异常。 对于一个相当典型的例如,Microsoft Windows离开前4兆未映射,所以在这个范围内的任何地址,通常会导致访问冲突。

在旧的CPU(或旧的操作系统不启用的CPU保护功能)的尝试写入往往会取得成功。 例如,在MS-DOS,通过NULL指针写入将简单地写入地址0。 在小型或中型的模型(与数据16位地址),大多数编译器会写一些已知模式的数据段的前几个字节,节目结束后,他们会检查,看看是否能格局保持完好(与做一些事情,表明你通过一个NULL指针书面如果失败)。 在小型或大型模型(20位数据地址),他们会通常只写来解决零没有警告。



Answer 5:

我想,这是平台和编译器相关的。 空指针可以使用空页,在这种情况下,你有一个页面错误来实现,或者也可能是下面的段限制为向下扩展的段,在这种情况下,你就会有一个分段错误。

这不是一个明确的答案,只是我的猜想。



文章来源: What happens in OS when we dereference a NULL pointer in C?