0
$\begingroup$

When my professor explained how the OS handles memory, he described demand paging, and how when a process starts up, the code page with the first instruction is loaded into memory, and everything else is loaded on-demand.

I haven't been able to find more information on this, but I'm curious if the stack would be allocated at this point and the memory filled with the argument vector for the program. Is this determined by the ABI? Would this be allocated at program start or only done by the OS at first reference to the stack?

Additionally, does the OS zero pages (my professor described these as anonymous pages) when giving them to the program if they do not have a reference on disk? It seems to me it would be best to do so (for security) if the memory was previously mapped by another process, and perhaps unnecessary if it were mapped by the same process.

$\endgroup$

1 Answer 1

1
$\begingroup$

It depends on the operating system, in general. Some don't load the first code page, but let the load-on-demand system handle that!

The ABI does determine the state of the user program at the point where its entry is invoked. There may or may not be a user stack set up.

In the SysV ABI for x86-64 follow, the user state is, roughly, this:

  • There is a user stack set up.
  • *argv and *envp have been pushed on that stack.
  • Four specific registers contain argc, argv, envc, and envp.

It is then up to the runtime for the programming language to set everything else up. We'll use C as our example. The relevant part of the runtime is called crt0 or c0 (short for C runtime, and the 0 means "very first"). The typical jobs that it has to do are:

  • Set up the heap, signal handlers, the handler for atexit, etc.
  • Set up exception handling and run global constructors if this is C++.
  • Invoke main.
  • On return from main, call exit.

One important difference in Windows is that the command-line is a single string; you can see this by looking at WinMain. So if you are writing a program that uses main as its entry point, the C runtime parses the command line for you.

As for zeroing pages, that is generally correct. The program file in a modern operating system is built from a number of segments. They are, typically:

  • text, which is a part of the file that is mapped as read-only and executable. This corresponds to executable code.
  • rodata, a part of the file mapped as read-only. This corresponds to read-only data (e.g. static string data, virtual dispatch tables, etc).
  • data, a part of the file mapped as read-write and copy-on-write. This corresponds to statically initialised read-write data.
  • bss, which is a region of memory mapped as zero-fill. This corresponds to uninitialised read-write data; because it is uninitialised, there does not need to be an image of this data stored in the program file.

The term "bss" was originally a pseudo-assembly instruction for the IBM 704, which meant "block started by symbol". The connection between this and the modern usage is tenuous and not relevant; Peter van der Linden suggested that you can think of it as "better save space".

Stack and allocated heap memory is also zero-fill. This is literally the case in some operating systems, where heap is allocated by memory mapping the special file /dev/zero, but in POSIX-like operating systems you can almost always map anonymous memory directly, or there is a special system call (e.g. VirtualAlloc in Windows, vm_allocate in Mach/macOS). The semantics are always that new regions are zero-filled.

(EDIT: Thanks to Peter Cordes for correcting one mistake in the last paragraph.)

$\endgroup$
5
  • $\begingroup$ For that last bit, is that something you can rely on that new regions are always zero-filled? I had thought the operating system may not bother (if, for example, the memory is not security-prone) and that you can't rely on it being zero'd out. For example, if I increase the program break with sbrk. $\endgroup$ Commented Jun 13, 2023 at 4:36
  • $\begingroup$ Linux supports mmap(MAP_ANONYMOUS), in which case the fd arg is ignored (some other OSes require it to be -1 in that case according to the Linux man page). On some OSes apparently you do actually need to open("/dev/zero"), but Linux isn't one of them. The POSIX man page doesn't mention MAP_ANONYMOUS, but it's not Linux-specific. (en.wikipedia.org/wiki/Mmap says "almost all" POSIX OSes do have that or MAP_ANON). $\endgroup$ Commented Jun 13, 2023 at 4:36
  • $\begingroup$ @user129393192: For security, yes, new anonymous pages are always zeroed, instead of tracking what each physical page last held. A multi-user OS can't let other user's data leak, or files that a process can't read. And in modern systems, isolating processes from each other even under the same UID is often important. Also deallocated kernel stacks could leak KASLR kernel address data. Linux has a MAP_UNINITIALIZED flag for embedded systems to leak data, which is optional in the kernel config and normally not enabled at all (so the kernel will ignore it). $\endgroup$ Commented Jun 13, 2023 at 4:41
  • $\begingroup$ @user129393192: Moving the break within one page probably doesn't re-zero later bytes in the same page, but you already had to get the page the break was in somehow, so the usual mechanism applies. Same if the break moves into a new page. $\endgroup$ Commented Jun 13, 2023 at 4:41
  • $\begingroup$ @PeterCordes Thanks for that. In the interest of full disclosure, I didn't mention the break and sbrk because I honestly don't know how that works in 64-bit operating systems and I didn't want to say anything misleading. $\endgroup$
    – Pseudonym
    Commented Jun 13, 2023 at 4:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.