how do (c++) pointers and debugging work in a world of virtualized memory?

Every book I’ve read or class I’ve taken about PC hardware and modern operating systems say that all memory is virtualized. In other words, each application gets it’s own private memory space, and that the addresses of this space are completely independent of the actual memory addresses being used (I assumed each virtual page started at virtual address 1 and ended at whatever number matches the amount of memory the app has requested).

However, this would make debugging and cheating/hacking apps impossible. Also, a in a video about pointers in c++ a simple program that creates an array, then a pointer to that array, and finally prints out the value of that pointer (which is just a memory address) returned different values on successive runs. If each program has a private address space it should return the same results with each run, shouldn’t it? Here’s a link to that video, at the relevant time (11m, 40s) .

Do programs not actually get assigned their own private address space? What’s the straight dope on memory virtualization at the OS level? Specifically Windows, though I imagine Linux uses a similar approach.

Sure, all programs get their own address space, but the memory addresses aren’t re-numbered. You just get a random block with a starting and ending address. When you ask for memory, you get the address of the start of the block. variables are then stored in offsets from that address.

Consecutive runs don’t have to use the same allocated space in memory. It’s a platform specific implementation. Debuggers have to be tied to the C++ implementation to work, they need the capability to locate physical or virtual memory locations with a symbol table that correlates the variable names used in your code with those locations. And of course the language and the system don’t need to make cheating/hacking apps work. Ideally they could not work at all.

Running the same program in the same environment, you would expect to get the same virtual addresses … at least in the olden days.

One possible source of the inconsistency you note is Address space layout randomization — a policy to jitter addresses deliberately to make things more difficult for malware attacks.

Yes they are. Virtual memory is all about numbering addresses. At it’s core, virtual memory is about dividing memory into small fixed-size chunks (called pages) and allowing those chunks to accessed via whatever (virtual) address the OS chooses to assign to that memory.

What should this necessarily be the case? As virtual memory allows the OS to assign an arbitrary address to every page, there’s no reason to expect the same address to be used each time.

Yes, beowulff’s answer is either quite wrong or badly worded. Every byte in memory has a PHYSICAL address, which is its “real” address. It may also have one or more VIRTUAL addresses, which is an address that a program uses to access that memory. The virtual address does not have any relationship with the physical address; the virtual addresses are completely arbitrary. When the OS allocates a page of memory to a virtual address, it is free to use any convenient page of physical memory, at any physical address. Pages that are consecutive in virtual address space need not be consecutive in physical address space, and vice versa. If you run a program several times, you are likely to get completely different pages of physical memory allocated to it, which is why the contents of uninitialized memory will probably not be the same on subsequent runs. Although rereading the OP’s second paragraph, I think the comment is that the address of an array changes with each run, not the contents. As septimus noted, the address space randomization feature in some modern OSes can make that happen. Whether the array is allocated dynamically (via new() or malloc()) rather than statically (as a declared array) is also relevant.

Another key detail: device drivers have access generally to most of the physical address range. And pointers in device drivers, etc, are using real addresses. This is because of legacy requirements to make a driver work. It would be possible to make device drivers not have this kind of access but in windows they do and in Linux they also have access to the memory pages used by the operating system itself (kernel memory).

So many game hacks emulate device drivers OR they are appended to the game binary itself (called injection). Obviously if they are part of the game binary itself then they can use the same virtual addresses used by the game.

I don’t know what makes security keys secret in such a setup: there must be a way to keep them invisible even to device drivers but I do not know how this is accomplished. Well, I sort of do: SOCs like for smartphones have isolated memory and a second operating system that handles the keys. So you can send requests to this second os with say a block of data you want encrypted and you get a reply with the answer without being able to access the memory where this is being done. (This keeps the private key secret)

When a program is using virtual memory it doesn’t know or care about the actual address.

If your debugger is saying what’s in a certain virtual address, it doesn’t matter what the real address is that it’s mapped to.

It’s a lot like mailing addresses. The mailing address you use isn’t actually the physical address of your location. Your county has a system of labeling lots and such. Or there’s the latitude and longitude of your place. Plus the USPS thinks of your location by route number and order on that route. What goes on behind the scenes is not relevant for most everyone.

There’re no new() or malloc() statements, so I assume it’s being done automatically when the array is defined.

I just assumed it would be simpler for the OS. Setting the virtual address to 100 (or whatever is the lowest available* number) should take fewer operations than generating a random number, calculating a semi-random offset from the real address, or whatever other method you use to hide the real addresses.

I’ve heard of that, but I thought it randomized what physical addresses (among the available ones) stuff got allocated to, not the range of addresses a program was presented with. Looks like I was wrong, and this is the likely culprit for the unexpected behavior

A lot of the time the descriptions you see of virtual memory are confusing because they tend to describe things in an upside down manner. There is a lot of history under here, and something that is not often appreciated is that virtual memory can and is implemented in many different ways. What we see in the common operating systems - Windows and all the Unix variants harks back to the VAX architecture, and this only because of accidents of history. (The first virtual memory implementation for BSD was done on a VAX, and that propagated out from their, and of course VAX-VMS had a huge influence in Windows via Dave Cutler.) But x86 processors have a different memory setup, but over time accrued virtual memory support more like a VAX. There is a lot more in history, especially the entire segmented memory memory architectures. But they are mostly history.

The initial point of virtual memory as we use it was to present an execution environment (usually called a “process” in these operating systems) with a flat address space that was potentially larger than the physical memory available. Adding address mapping hardware made this possible, and the OS kernel became responsible for managing this. As a consequence it becomes possible to provide secure environments for execution, as an executing program is simply unable to generate an address that can map to an arbitrary physical address, and is unable to access data outside of its own virtual address space. The actual program sees a flat address space all of its own. Hence the “virtual” address space, and virtual memory. In this virtual space there is effectively no notion of other spaces. In reality all sorts of neat tricks a played. Some more brutal than others. The OS has lots of common libraries and data that executing processes can reasonably want to access. It is highly wasteful to make individual copies of these, so each process will typically map parts of their virtual address space to the same in-memory copy of this data. On the VAX there were 4 spaces, which were designated by the top 2 bits of the 32 bit address. User level code and data lived in one of these, and so was limited to 1GB of addressable memory. The kernel lived in another 1GB space. This division was enforced in the VAX hardware and was why the Unix port to the VAX used the same layout. But usefully each process would automatically see important shared data with no overhead. Similarly program code and libraries could be managed with little additional overhead. Modern improvements and needs have made the implementations more featured and complex but the basics persist. It is also possible to have user level processes share a slab of physical memory, which is useful for very high speed inter-process communication. But the kernel is responsible managing this, the only thing the user level processes see is a range of memory that’s contents may change under its feet.

Once some degree of security was expected of operating systems, it became clear that simply providing direct mapping to slabs of physical memory was not enough. That memory needed to be clean. Otherwise it was possible to discover useful bits of data left there by the previous user of the physical memory. Just requesting a large slab of virtual memory for the kernel would deliver all manner of potential goodies and it was trivial to write a program that would look for interesting things. It was actually viable to go looking for passwords. So operating systems zero out the contents of physical memory before mapping them into another process’s virtual address space.

The way the underlying hardware operates to achieve the mapping from virtual address space to physical can be almost arbitrary. The VAX architecture placed the mapping tables in he virtual address space (albeit read only to user level) so you could see what the physical addresses you were using were if you wanted to walk the page tables. Not all architectures do this, so finding out what the physical address space of your virtual address is is not always possible, and in general not an expected part of the OS operation.

In general, from one run of a program to another, the memory layout and contents would be expected to be identical, and indistinguishable. Address space layout randomisation and other tricks are not an effect of the virtual memory, but of additional activities of the OS and its support libraries.

Do you know how device drivers were handled back in the VAX days? Fundamentally they need to access specific physical addresses per memory-mapped I/O models.

However, in reality they don’t need to access all memory and in reality it would be possible to require a device driver to request resources from the OS, and the OS check against a mapping file specific to a computer architecture and only allow access to resources the driver *actually *needs.

For instance a serial port driver in a memory-mapped I/O architecture system will have a specific block of memory that has all the control bits for the port, another block for the DMA hardware that any realistic driver will use, and the DMA will in turn need specific DMA mappable memory (the architectures I have written DMA drivers for, it’s a specific limited range of memory) blocks for the input and output.

But that’s all and the driver needs exclusive access to this memory to work reliably.

As near as I can tell, in Linux it’s a wild west and any device driver can just freely access anywhere in the kernel, and communicate with any other function anywhere in the kernel that someone has exported with the EXPORT SYMBOL macro.

The emmc driver is a spaghetti mess as a result of this and it’s almost indecipherable.

Some of the comments, including the above, ignore that physical (“real”) addresses are don’t-cares to the application, and it may be difficult or impossible for an application (or malware) to know (or care) what those addresses are. It is only the virtual (non-real) addresses that seem “real” to the application. :slight_smile:

~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~

On the matter of physical memory mapping to two or more different virtual addresses, this will typically arise when the mappings are in different address spaces (i.e. with different STO’s in IBM/370 language) so the same physical memory will not be accessed with different addresses from a single program.

Are there exceptions to this? I know that an application might be able to arrange this, with OS assistance, but are there any useful applications where a unit of physical memory has two different virtual addresses in the same address space?

[From the trivia history desk:] Mapping the same physical page to two different virtual pages can provoke an extremely bizarre firmware “bug” on a certain 45-year old mainframe.
(Details are too tedious to fit in the margin of this post.)

Just for quick entertainment I tried it on two Linux machines and two Windowses:

#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv) {
char s[256];
char *p = malloc (1024 * 1024);
printf ("main = %016p
", main);
printf ("argv = %016p
", argv);
printf ("s = %016p
", s);
printf ("p = %016p
", p);
exit (0);
Typical result on Linux:

main = 0x000000004005c0
argv = 0x007ffe6779a3d8
s = 0x007ffe6779a1e0
p = 0x007f00cbfab010


On a given machine the code (main) is always exactly the same. Not seeing ASLR, I guess that’s only for dynamic libraries (certainly have seen it in real life).
The heap § and stack (argv and s) are in the 0x7F… range and the distance between them varies every consecutive run by some multiple of 4GB, up to 0xFF (a terabyte).


On Windows ALL of the numbers are exactly the same from run to run and even across machines!! Exactly what the OP expected but I did not!! The heap on Windows is high up (0x6ff… times 4GB) and the stack is low down around the 4GB region.

ASLR is after my time, but I think you’re seeing it, with the heap and stack addresses.

Changing code addresses is more difficult and Wikipedia says “The PIE feature [ASLR for code] is in use only for the network facing daemons – the PIE feature cannot be used together with the prelink feature for the same executable.”

A limiting case in a way, but read only (or more likely copy on write) zero filled memory can use the one page of physical memory many times.

In VMS I think all of physical memory was mapped into one area of physical memory, and in other parts of the address space there were actual page maps used to provide areas of virtual memory.

Which does lead to a use for multiple mapping, something close to code I have written. If you have a single address space and want to synthesise some interesting properties of that address space (for me it was persistent memory) you can have code residing one one area responsible for managing the contents of memory as needed for the persistence abstraction (which can include reading and writing pages to disk, but may also include “pointer swizzling” or other dynamic modification of the page contents) and then have the pages mapped into the area where your application code can see them, oblivious to how the data got there. Mach (and now Darwin/MacOS) provided the “external pager” as a nicer way of managing these sorts of things. But you can hack it up with ordinary page mapping and some evil tricks.

From vague understanding of VMS, I doubt it was much better in terms of protecting writers of device drivers from their own folly. In the end the code is running in kernel mode, and if it wants it gets. That said, VMS was very tightly bound to the VAX architecture, and had a very controlled idea of how a device driver worked. Th system provided a lot of library routines that were expected to be used to mediate the flow of control, and a lot of this was bound up in a limited set of abstractions about what devices there were, and how they worked. This was the days when a disk driver stuffed values on control registers to move the heads.

The VAX had fully memory mapped IO, which is a good start. The Unibus was carried over from the PDP machines, and it was mapped. As were the more modern IO busses. The kernel was, in part, running in its own version of virtual memory, which is pretty impressive.

Device drivers were just code modules that were dynamically loaded and linked at boot time (but no on demand hot loading, just a dynamic check on configured hardware). That step probably meant that the system could severely limit the symbols that could be referenced, and is probably what you might like. I don’t think the linker was very smart, and the set of symbols that could be bound at load was likely very constrained to be those that the overarching OS model saw fit to provide.

I’ve probably forgotten too much, so nothing at all definitive here.

You have to compile your code with special flags to get a Position Independent Executable (PIE) in order for ASLR to work on program addresses. By default the compiler will generate code that depends on program addresses being constant, and the linker assigns a fixed address to every program symbol.