虚拟存储器的缺页异常分析

Consider this question: Is it possible to pass the address of an object from process A to process B (where there is no relationship between A and B) through a pipe (fifo), and thus access the object of process A from process B?

To put it simply: it is definitely not possible to pass the address of a local object from one process. Modern operating systems have process isolation, with each process having its own independent virtual memory. A valid virtual address needs to be accessed via the MMU to reach the physical address (consider the COW case after fork), but an invalid virtual address will trigger a page fault.

An exception exists in Windows: if the API address from ntdll of process A is sent to process B, process B can call it directly. This is a special case (and it no longer counts as an address unique to process A’s object).

**[Windows Internals,6th]**Because Ntdll.dll is mapped at the same address, randomly generated at boot, into every process’s address space, the current process can simply pass the address of the function it obtains from its own Ntdll.dll mapping.

Now let’s actually experiment with the scenario of passing object addresses between processes:
0. Create a named pipe.

  1. Create an object in process A.
  2. Pass the address of that object from process A to process B through the pipe.
  3. Access the object in process B using the address received from the pipe.
  4. Check the value of that object after process B executes in process A.

First, let’s create a named pipe:

1
mkfifo fifofile

Then we can write two programs, one that writes to the pipe and one that reads from the pipe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// writefifo, process A
int *test=new int(1111);
cout<<*test<<" address is "<<test<<endl;

// Pass object address
int fd=open("fifofile",O_WRONLY);
write(fd,&test,sizeof(&test));

// Wait for process B to finish receiving
sleep(3);
cout<<"B process after "<<*test<<endl;

delete test;
close(fd);

Now for reading:

1
2
3
4
5
6
7
8
9
10
11
12
// readfifo, process B
int fd=open("fifofile",O_RDONLY);
int **x=new int*;

// Read an address from process A
read(fd,x,sizeof(int*));
cout<<*x<<endl<<" "<<**x<<endl;
**x=666;
cout<<*x<<endl<<" "<<**x<<endl;

delete x;
close(fd);

Let’s take a look at the running results:
pass address by fifo

We can see that a segmentation fault occurred.

This is because Linux maintains a separate virtual address space for each process.
The virtual memory of each process roughly looks like this:
virtual address scope
When the MMU attempts to translate a certain virtual address, it uses the virtual address as an index to locate the actual physical address and read it from memory, checking valid bits to determine if the DRAM was hit.
VM page hit
In virtual memory, a DRAM cache miss is referred to as a page fault. A page fault exception will transfer control to the kernel’s page fault handler.

The page fault exception handler has the following three scenarios:

  1. Is the virtual address valid?
  2. Is the attempted memory access valid?
  3. Does the kernel know that the page fault was caused by a valid operation on a valid virtual address?

Starting with the first step: Is the virtual address valid? (Scenario one)
Does the virtual address fall within the defined range of a specific area structure? The page fault handler will search through the linked list of area structures, comparing the virtual address with vm_start and vm_end in each area structure. If the instruction is not valid, the page fault handler will trigger a segmentation fault, thereby terminating the process.
In our code, the virtual address is invalid as this address is not a valid virtual address within process B (not cached in that process’s DRAM), which triggers a segmentation fault that results in the termination of the process.

Second step: Is the attempted memory access valid? (Scenario two)
Does the process have permission to read, write, or execute pages within this area? If the attempted access is not valid, the page fault handler will trigger a protection fault, thus terminating the process.

Third step: Does the kernel know that the page fault was caused by a valid operation on a valid virtual address? (Scenario three)
When the kernel recognizes that the page fault was caused by a valid operation on a valid virtual address, it will handle the page fault by choosing to sacrifice a page. If this sacrificed page has been modified, it will be swapped out. A new page will be brought in, and the page table will be updated.
When the page fault handler returns, the CPU will restart the instruction that caused the page fault, sending that virtual address to the MMU once again. At this point, the MMU will be able to translate that virtual address normally, and no further page fault interrupt will occur.

The description of the three scenarios is as follows:

For more details, please refer to Chapter 9, Virtual Memory in CASPP.

The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:虚拟存储器的缺页异常分析
Author:LIPENGZHA
Publish Date:2017/03/22 01:22
World Count:3.5k Words
Link:https://en.imzlp.com/posts/30933/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!