.Rodata Section Loaded in Executable Page

.rodata section loaded in executable page

My original guess was that the segments too closely-spaced

You should not call sections segments (ELF has both, and they mean different things).

Sections only matter at static link time, and can be completely removed (are not needed at runtime). Only segments matter at runtime, and a typical ELF binary will have two segments with R-X and RW- permissions.

The .rodata section is usually merged with .text section and put into the executable segment. You can change that with the --rosegment flag if you use gold linker (patch which introduced this).

You can see section to segment mapping in the readelf -Wl a.out output.

Update:

Can there ever be a situation where .rodata needs to be executable, or is it for optimization, or something else?

There are no portable situations where .rodata needs to be executable. It is possible to construct a non-portable program that requires it, as you've done in your question.

Merging of .rodata and .text is an optimization: it requires two mmap calls instead of three (a program linked with --rosegment will have three separate PT_LOAD segments with R-X, R-- and R-W protections) and also fragments the virtual space less. In addition, on Linux there is a system-wide limit on total mappings, so you'll reduce the total number of programs you can run at once by 50% if you link everything with --rosegment.

Update 2:

Recent Linux distributions stopped merging .text and .rodata, and now have three or four separate LOAD segments. See this answer.

Code (.text) not execute-only? .rodata is executable?

Gathered from comments above

It is not possible on several computer architectures, including x86-64, to mark memory as executable but not readable.

While x86 16- and 32-bit did allow segmentation in legacy modes, and memory segments could in theory be used to mark memory as executable-only, the benefits of a flat address space were so great that x86-64 now mostly ignores its segments registers:

3.2.4 Segmentation in IA-32e Mode

In IA-32e mode of Intel 64 architecture, the effects of segmentation depend on whether the processor is running in compatibility mode or 64-bit mode. In compatibility mode, segmentation functions just as it does using legacy 16-bit or 32-bit protected mode semantics.

In 64-bit mode, segmentation is generally (but not completely) disabled, creating a flat 64-bit linear-address space. The processor treats the segment base of CS, DS, ES, SS as zero, creating a linear address that is equal to the effective address. The FS and GS segments are exceptions. These segment registers (which hold the segment base) can be used as additional base registers in linear address calculations. They facilitate addressing local data and certain operating system data structures.

Note that the processor does not perform segment limit checks at runtime in 64-bit mode.

Kernels thus simply set their segments to cover the entire address space and do not rely on segmentation to achieve memory protection.

What they do use is the page table's attributes. Every page that exists in a process's memory map has a page table entry governing access to it. An overview of their format can be seen here, but most crucially there are two bits that control what type of access is permitted:

  • Bit 1 (R/W): 0 indicates read-only, 1 indicates read-write.
  • Bit 63 (XD): 0 indicates executable, 1 indicates non-executable.

It is not possible to indicate an executable-noread-nowrite combination with these flags. If the page is at all present in the memory map, it must be readable.

A solution is fast approaching in Intel's latest microarchitecture, Skylake, which will allow execute-only memory: It is the feature baptized MPK (memory protection keys), support for which landed in Linux kernel 4.6 just recently released. The keys occupy the four bits 62:59 of the page table entry, and areas of memory can be tagged with a key indicating execute-noread-nowrite access.

How does the linker know where a particular section of an executable will be mapped into the address space of a process?

It doesn't. Linker can only propose the executable image be loaded at certain VA, usually 0x00400000 for PE or 0x10000000 for DLL. Virtual adresses of sections (.text, .rodata, .data etc) are aligned by the section alignment (usually 0x00001000) and their proposed VA are therefore 0x00401000, 0x00402000 etc. Linker then fixes adresses of symbols to those assumed VAs.

The default ImageBase address (taken from linker script or linker arguments) is not required by OS loader, but I don't see a reason to change it, its a good habit to see nice rounded addresses during debugging.

In rare cases the Windows loader may find out that part of the proposed address space is occupied, so it will load the image at a different VA and fix position-dependent (absolute) references to their new VA.
Special PE section relocs contains addresses of references to program symbols which need relocation at load-time.

When we run an executable, do all the sections get loaded into memory at once?

With ELF binaries, sections are not what decides how the binary is loaded into memory. They are just useful metadata for debuggers and other tools and there doesn't need to be a correspondence between segments and sections and often multiple sections are subsumed under one segment. A binary can have no section header at all and still load fine.

What actually decides what is getting loaded and where are the program headers. Each program header describes one memory segment and contains the following information:

  • the field p_type tells you what kind of information the program header contains. This is typically just PT_LOAD to mean “loadable segment.”
  • the field p_offset tells you the offset from the beginning of the file where the segment resides. Note that in rare cases, this can lie beyond the end of the file.
  • the field p_vaddr tells you the virtual address at which the segment is mapped. There is also p_paddr to specify a physical address, but it's generally unused.
  • the field p_filesz tells you how long the segment is in the file.
  • the field p_memsz tells you how long the segment is in memory. If this is more than the segment length in the file, the remainder is filled with zeroes.
  • the field p_flags tells you if the segment is readable (PF_R), writable (PF_W), executable (PF_X) or some combination of these three. When loading a segment, the operating system uses these flags to set up write and executable protection.
  • the field p_align tells you how the segment is aligned. This is not really important here.

When the operating system loads your binary or when the runtime link editor loads a shared object, it reads the program headers of your binary and loads or maps each segment in the order they appear. Once this is done, your program is executed.

You can get information about the program headers of a binary by running readelf -l binary.



Related Topics



Leave a reply



Submit