How is text segment made read-only? - c

I know that text segment is a read-only segment and trying to write to it results in "Bus error".
I am curious about how this segment is made read-only.
Since the physical memory is not readonly, this must be done during paging.
Is there a bit for each page of memory for read-only pages which is set for text segment?

An ELF file (a Unix executable or a shared-object) has two main concepts:
Section: An area inside the executable file with a specific role. There may be different sections inside an ELF file (can be seen in man elf). Common sections in an ELF file are:
.text (SHT_PROGBITS): The actual executable code in the ELF file.
.dynsym (SHT_DYNSYM): Holds information about symbols that should be dynamically retrieved.
.rela.dyn and .rela.plt (SHT_RELA): Hold relocation information for the dynamic linker to use when loading the ELF file into memory.
.dynamic (SHT_DYNAMIC): Holds information for the dynamic linker, such as other dependencies, offsets for different sections in run-time, etc.
.symtab (SHT_SYMTAB): Holds a symbol table.
.strtab (SHT_STRTAB): Holds a string table.
There are more sections and the above are just a few common ones.
Using readelf one can see all the sections in an ELF file:
readelf --sections -W <file>
Running this command on a shared-object in my computer results in the following output (simplified):
There are 29 section headers, starting at offset 0x1898:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
...
[ 3] .dynsym DYNSYM 0000000000000230 000230 000168 18 A 4 2 8
[ 4] .dynstr STRTAB 0000000000000398 000398 0000b0 00 A 0 0 1
...
[ 7] .rela.dyn RELA 0000000000000488 000488 0000c0 18 A 3 0 8
[ 8] .rela.plt RELA 0000000000000548 000548 000030 18 AI 3 22 8
...
[12] .text PROGBITS 00000000000005e0 0005e0 000121 00 AX 0 0 16
...
[20] .dynamic DYNAMIC 0000000000200e18 000e18 0001c0 10 WA 4 0 8
...
[23] .data PROGBITS 0000000000201028 001028 000008 00 WA 0 0 8
...
[27] .symtab SYMTAB 0000000000000000 001068 000570 18 28 45 8
[28] .strtab STRTAB 0000000000000000 0015d8 0001c6 00 0 0 1
Segment: An area inside the executable with load instructions for the dynamic linker. Meaning, a segment is just an area inside the ELF file that should be loaded into a preferred address memory, and have specific permissions, alignment, etc.
Every section (which is an area in the ELF file with a logical role) should be part of a segment, with the correct permissions and characteristics. A segment can have more than a single section within, and a section is inside a single segment (one-to-many relation).
Using readelf one can see all segments in an ELF file:
readelf --segments -W <file>
Running this command on a shared-object in my computer results in the following output:
There are 7 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x00079c 0x00079c R E 0x200000
LOAD 0x000e00 0x0000000000200e00 0x0000000000200e00 0x000230 0x000238 RW 0x200000
DYNAMIC 0x000e18 0x0000000000200e18 0x0000000000200e18 0x0001c0 0x0001c0 RW 0x8
NOTE 0x0001c8 0x00000000000001c8 0x00000000000001c8 0x000024 0x000024 R 0x4
GNU_EH_FRAME 0x000718 0x0000000000000718 0x0000000000000718 0x00001c 0x00001c R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x000e00 0x0000000000200e00 0x0000000000200e00 0x000200 0x000200 R 0x1
Section to Segment mapping:
Segment Sections...
00 .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
01 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
02 .dynamic
03 .note.gnu.build-id
04 .eh_frame_hdr
05
06 .init_array .fini_array .jcr .dynamic .got
Here, we can see that all sections related to the executable code, as well as many sections related to the dynamic loading of the file are in segment 00 (PT_LOAD), which has read and executable permissions (R E). Sections that should be modified by the loader are in segment 01 (PT_LOAD) which has read and write permissions (RW). Segment 02 is of type PT_DYNAMIC and holds dynamic linking information - the .dynamic section.
The dynamic linker takes all this information into account when it loads the ELF file into memory. It loads the different segments of the ELF file from disk into memory and protects their pages with the correct permissions. Then, it iterates through the different sections and uses them according to their roles (relocations, resolving dynamic symbols, etc...).
The memory protection itself is being made by the OS and the hardware itself. It is similar to using the Linux methods mprotect(). More information about memory protection can be found here.

Generally, one way this happens is the program loader allocates memory for the program text, reads program text into it from the executable file, and asks the operating system to make that memory read-only. The operating system records this in its databases and conveys the information to the hardware to tell it not to allow writes to that memory.
Another possibility is that the loader does not actually read the program text into memory but merely issues a request to the operating system to mark that the appropriate portion of the executable file is “mapped” into memory, as read-only. When the process actually tries to execute the code in a memory page, the operating system will read it into memory (with write access for itself, so it can do this) and mark it read-only (for the process).
This latter case is an optimization because it allows your program to start without loading all of it first. And parts of it that are never used, such as code to handle rare errors, might never be loaded.

It doesn't have to be. You can make the text segment of a program read write. But you have to instruct the linker about that. The operating system generates pages that generate a page fault when you try to write on them, but not when you try to read. This is part of the multiuser protection system (also called the memory protection system).
The reason of the text segment being read only is a legacy from ancient time. The multiple instances of a single program called by different processes (e.g. different users doing a ls to show their own directory contents) use the same segment as text, so why not share that segment and avoid to load it each time for each instance? That's the reason, but if you want to modify your program's text segment at runtime, you can designate it to be COW ((C)opy (o)n (w)rite) and the pages will be individually copied when you try to write on them, so you have the illussion it is read/write.

Related

Linker Inserts Unnecessary Opcode Padding

I've recently come across a minor issue when linking multiple object files for a Motorola 68000 based system (SEGA Mega Drive). The problem is, when an input section for one object file ends and the next one begins, the linker fills memory addresses with zeros so that the next object file begins aligned on a four byte boundary. The text below is a memory map output by the linker. As you can see, the .text output section contains three object files. The first two (main.o, swap.o), were written in C compiled and assembled using m68k-elf-gcc. The third one (swap_asm.o) was hand written in 68000 assembly and assembled using the vasm. The function at the beginning of swap.o would normally start at address 0x0000001E. But, the linker is *fill*ing the beginning of the swap.o file with two bytes, specifically 0x0000. So, swap.o starts at 0x00000020. But, swap_asm.o is not getting aligned and begins at a non-four-byte-aligned address, 0x00000036. Is there a way to make the linker not add any padding and just start the swap.o right away? I understand there are a few work arounds like filling the space with a NOP, but I was wondering if there is a way to just not do a *fill*?
.text 0x00000000 0x4c
main.o(.text)
.text 0x00000000 0x1e main.o
0x00000000 main
swap.o(.text)
*fill* 0x0000001e 0x2
.text 0x00000020 0x16 swap.o
0x00000020 swap
swap_asm.o(.text)
.text 0x00000036 0x16 swap_asm.o
0x00000036 swap_asm
The 68000 processor requires instructions to be aligned (and this requirement holds also for data). Despite of the CPU requirements (which are unskipable) the linker also uses a script in which the segments are required to have some alignment (normally to provide for this cpu requirements)
While the linker script can be tweakable, It can be the case that changing the alignment makes the linker to produce incorrect code (because of what is said in the above paragraph) but anycase, that's something you can try and test.
Motorola 68000 (and more the 16 bit version of the MegaDrive) triggers a bus error trap when a 16bit transfer is requested on an odd address. The same happens if a 32bit (but this happens also up to the 68030, the 68040 I think already handles this making several bus accesses, like the Intel processors)
So I found my answer. When the assembler detects long (32-bits) data is being dealt with in an assembly file, it automatically aligns the input section along a 4 byte boundary. You can actually override this using SUBALIGN in a linker script. Here's my linker script aligning input sections along a 2 byte boundary.
MEMORY
{
rom : ORIGIN = 0x00000000, LENGTH = 0x00400000
}
SECTIONS
{
.text : SUBALIGN(0x2) {
*(.header)
*(.boot)
obj/main.o(.text)
*(.text)
*(.isr)
*(.vdp)
} > rom
.data : { *(.data) } > rom
.bss : { *(.bss) } > rom
}
New linker map:
.text 0x00000000 0x4a
main.o(.text)
.text 0x00000000 0x1e main.o
0x00000000 main
swap.o(.text)
.text 0x0000001e 0x14 swap.o
0x0000001e swap
swap_asm.o(.text)
.text 0x00000034 0x16 swap_asm.o
0x00000034 swap_asm

Why is the ELF header loaded into memory along with the text segment?

I compiled this program with -m32 -nostdlib into an ELF executable:
void _start() {}
And when I did readelf -l I was surprised to see that the offset on the LOAD segment was 0x000000, since that would mean that the executable header would get loaded into memory at the same time as the text segment. So I checked with GDB, and that is indeed the case:
(gdb) b _start
Breakpoint 1 at 0x8048083
(gdb) r
Starting program: /home/tbodt/ish/build/a.out
Breakpoint 1, 0x08048083 in _start ()
(gdb) x/4c 0x08048000
0x8048000: 127 '\177' 69 'E' 76 'L' 70 'F'
Why is this useful?
I was surprised to see that the offset on the LOAD segment was 0x000000
Why were you surprised?
since that would mean that the executable header would get loaded into memory at the same time as the text segment.
Correct. Why is that a problem?
As this answer explains, the executable is mmaped and mmap works on entire pages; you can't mmap part of the page starting at offset 0x34.
You could build an executable in which .text starts at offset 4096 (leaving a large hole between the ELF header and program headers and the text), and then such executable can have the first PT_LOAD segment with offset 4096. This isn't commonly done: the wasted space in the file usually is not worth saving 52 bytes of memory.

.text section address range of position independent executable

I want the address of the .text section of a position independent executable. Using readelf -S:
Name Type Address Offset
Size EntSize Flags Link Info Align
.text PROGBITS 0000000000002700 00002700
0000000000001672 0000000000000000 AX 0 0 16
I learn that it will begin 0x2700 bytes past where library was loaded into memory. But how can I get the load address of the executable?
Is there any other way to get the .text section address range during runtime (from the running program)?
Is there any other way to get the .text section address range during runtime (from the running program)?
Yes: you need to use dl_iterate_phdr and use info->dlpi_addr to locate the PIE binary in memory at runtime. The very first call to your callback will be for the main executable.

where should the .bss section of ELF file take in memory?

It is known that .bss section was not stored in the disk, but the .bss section in memory should be initialized to zero. but where should it take in the memory? Is there any information displayed in the ELF header or the Is the .bss section likely to appear next to the data section, or something else??
The BSS is between the data and the heap, as detailed in this marvelous article.
You can find out the size of each section using size:
cnicutar#lemon:~$ size try
text data bss dec hex filename
1108 496 16 1620 654 try
To know where the bss segment will be in memory, it is sufficient to run readelf -S program, and check the Addr column on the .bss row.
In most cases, you will also see that the initialized data section (.data) comes immediately before. That is, you will see that Addr+Size of the .data section matches the starting address of the .bss section.
However, that is not always necessarily the case. These are historical conventions, and the ELF specification (to be read alongside the platform specific supplement, for instance Chapter 5 in the one covering 32-bit x86 machines) allows for much more sophisticated configurations, and not all of them are supported by Linux.
For instance, the section may not be called .bss at all. The only 2 properties that make a BSS section such are:
The section is marked with SHT_NOBITS (that is, it takes space in memory but none on the storage) which shows up as NOBITS in readelf's output.
It maps to a loadable (PT_LOAD), readable (PF_R), and writeable (PF_W) segment. Such a segment is also shorter on storage than it is in memory (p_filesz < p_memsz).
You can have multiple BSS sections: PowerPC executables may have .sbss and .sbss2 for uninitialized data variables.
Finally, the BSS section is not necessarily adjacent to the data section or the heap. If you check the Linux kernel (more in particular the load_elf_binary function) you can see that the BSS sections (or more precisely, the segment it maps to) may even be interleaved with code and initialized data. The Linux kernel manages to sort that out.

virtual and physical addresses of sections in elf files

How does objdump compute the physical address (LMA) of elf sections? As far as I can tell, elf section headers only contain the virtual address (VMA) of sections [1].
Usually, VMA and LMA are the same. But for initialized data sections (.data), the VMA is the RAM location of the variables and LMA is the ROM location where the initial values are located. Crt0 is responsible for copying the initial values into RAM before main() is called. For example:
$ objdump -h my.elf
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0003c3d0 00080000 00080000 00010000 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
5 .data 000008d0 40000000 000d08d4 00060000 2**3
CONTENTS, ALLOC, LOAD, DATA
-Tom
[1] http://www.ouah.org/RevEng/x430.htm
Find this about LMA:
http://www-zeuthen.desy.de/dv/documentation/unixguide/infohtml/binutils/docs/ld/Basic-Script-Concepts.html#Basic-Script-Concepts
The important is following:
Every loadable or allocatable output section has two addresses. The first is the VMA, or virtual memory address. This is the address the section will have when the output file is run. The second is the LMA, or load memory address. This is the address at which the section will be loaded. In most cases the two addresses will be the same. An example of when they might be different is when a data section is loaded into ROM, and then copied into RAM when the program starts up (this technique is often used to initialize global variables in a ROM based system). In this case the ROM address would be the LMA, and the RAM address would be the VMA
The section header contains a single address. It looks to me like address in the section header is the VMA. The program headers contain the mapping of VMA to LMA.
For example, here's a snippet of what "objdump -x" shows for my elf file:
Program Header:
<a few lines removed>
LOAD off 0x00000240 vaddr 0x00000048 paddr 0x0000018c align 2**0
filesz 0x00000000 memsz 0x00000004 flags rw-
Sections:
Idx Name Size VMA LMA File off Algn
<a few lines removed>
3 .bss 00000004 00000048 0000018c 00000240 2**1
ALLOC
So, .bss has a VMA of 0x48. If you look through the program headers, one entry has a "vaddr" of 0x48 and a paddr of 0x18c, which is the LMA.
Physical address is an attribute of ELF file segment. ELF file section does not have such attribute. It is possible though to map sections to corresponding segment's memory.
The meaning of physical address is architecture dependent and may vary between different OSes and hardware platforms.
From this link:
p_paddr - On systems for which
physical addressing is relevant, this
member is reserved for the segment’s
physical address. Because System V
ignores physical addressing for
application programs, this member has
unspecified contents for executable
files and shared objects.
It looks like your Crt0 makes some assumption about meaning of physical address located in ELF file. This assumption may be true for the particular system, but is not garanteed on another.

Resources