How do we Load Linux Image to appropiate location in Memory - linux

We are trying to load a linux image into our DRAM at a specific location ,DRAM end address is
0x80000000 which we come to know from boot log which says "mem device ending address is 0x80000000".We are loading our image at address "0x5000000" and before that variuos section in image getting loaded at some address which is greater than "0x80000000",for eaxmple again from boot logs
loading section to address 0xc5000000 from file position 0x1000, size is 0x5ac13e
Whats meaning of "from file position 0x1000" in above line.
first section that loaded is .text section ,below is our vmlinux image dump of section header
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS c5000000 001000 5ac13e 00 AX 0 0 4096
[ 2] .notes NOTE c55ac140 5ad140 000168 00 AX 0 0 4
[ 3] __ex_table PROGBITS c55ac2b0 5ad2b0 000fe0 00 A 0 0 4
[ 4] .rodata PROGBITS c55ae000 5af000 20a930 00 A 0 0 64
[ 5] __bug_table PROGBITS c57b8930 7b9930 0075fc 00 A 0 0 1
[ 6] .pci_fixup PROGBITS c57bff2c 7c0f2c 001a90 00 A 0 0 4
[ 7] .builtin_fw PROGBITS c57c19bc 7c29bc 0000cc 00 A 0 0 4
Its quite a big list, so didn't post full .But one thing we can see here .text section is greater than DRAM end address ,so image should not be loded properly though we are not getting any error after loading first section it keeps on loading other sections but after this message it hangs.
program load complete, entry point: 0x5000000, size: 0x92e7fc
My question is how can I align these different sections address to our DRAM address,
Is objcopy utility could be used here to change address of these Different sections.
Is there any way to set these section addresses before compilation??
Second thing what could be reason for this Hang afer program load complete.

from file position 0x1000 means what it says. You have it in the dump:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
...
[ 1] .text PROGBITS c5000000 001000 5ac13e 00 AX 0 0 4096
It's where the .text section starts in the file, at offset 0x1000.
But one thing we can see here .text section is greater than DRAM end address
Nope, it's not greater (not in the sense of bigger, at least), it's compiled in the expectation that it'll be loaded at address 0xc5000000 in the memory.
so image should not be loded properly though we are not getting any error after loading first section it keeps on loading other sections
The image can be loaded anywhere, it's just data for the purpose of loading.
OTOH, if loading section to address 0xc5000000 means what it says, the file gets loaded into nowhere since your RAM ends at 0x7fffffff.
but after this message it hangs.
And that's expected. Machine code is rarely position-independent and so if you load it at a location different from where it's supposed to be loaded, it'll not work. Or if it doesn't even get loaded, then what are you going to execute? Garbage.
Is there any way to set these section addresses before compilation??
Depending on the system you may have one of the two below options or both:
set up page translation in such a way that virtual addresses from 0xc5000000 and up map to physical addresses from 0x5000000 and up for the entire program
find the linker script that your compiler is using and change the initial section address from 0xc5000000 to 0x5000000, google this up, see compiler/linker documentation
Also, it's a bit odd that the entry point is at 0x5000000. Not that this is necessarily wrong, it's just that it's rarely the case. I'd make sure that the start label (or _start or whatever it is) indeed receives the same address as the beginning of the .text section. If, for some reason, it's not the case, there's something wrong either with the linker script or the compiler/linker command-line options or with the loader.

What loader do you use? What is the form of the "image"? U-boot image, raw one, vmlinux ELF file? I guess the last one judging by the existence of sections, etc. From ELF file you should load not sections but rather so called program headers. For example this is the OpenRISC linux kernel program headers listing (obtained using readelf -l):
Elf file type is EXEC (Executable file)
Entry point 0xc0000000
There are 2 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x002000 0xc0000000 0x00000000 0x231728 0x232000 RWE 0x2000
LOAD 0x234000 0xc0232000 0x00232000 0x17c78c 0x18bfcc RWE 0x2000
Section to Segment mapping:
Segment Sections...
00 .text .rodata __ksymtab __ksymtab_gpl __ksymtab_strings __param __modver
01 .data __ex_table .head.text .init.text .init.data .bss
See the difference between VirtAddr and PhysAddr (dropped c due to usual Linux kernel mapping). Naturally, the physical address should be used for loading.
The reason kernel is using virtual addresses for symbols, sections etc is that during boot you quickly enter the moment when the MMU is initialized, the virtual addresses are then the only valid ones.
And finally, about changing those addresses. Indeed, as pointed by Alexey, linker script is the key. You can find these in arch/(your arch)/kernel/vmlinux.lds.S. But the point is this is IMO not your problem, the problem lies probably in the loader or its options.

Check your arch/arm/mach-xxx/Makefile.boot ( You use arm board right ? )
zreladdr-y += 0x80008000
params_phys-y := 0x80000100
initrd_phys-y := 0x80800000
This is from Ti omap3 chip .
As I think you will need the same

Related

Calculate the entry point of an ELF file as a physical address (offset from 0)

I am building a RISC-V emulator which basically loads a whole ELF file into memory.
Up to now, I used the pre-compiled test binaries that the risc-v foundation provided which conveniently had an entry point exactly at the start of the .text section.
For example:
> riscv32-unknown-elf-objdump ../riscv32i-emulator/tests/simple -d
../riscv32i-emulator/tests/simple: file format elf32-littleriscv
Disassembly of section .text.init:
80000000 <_start>:
80000000: 0480006f j 80000048 <reset_vector>
...
Going into this project I didn't know much about ELF files so I just assumed that every ELF's entry point is exactly the same as the start of the .text section.
The problem arose when I compiled my own binaries, I found out that the actual entry point is not always the same as the start of the .text section, but it might be anywhere inside it, like here:
> riscv32-unknown-elf-objdump a.out -d
a.out: file format elf32-littleriscv
Disassembly of section .text:
00010074 <register_fini>:
10074: 00000793 li a5,0
10078: 00078863 beqz a5,10088 <register_fini+0x14>
1007c: 00010537 lui a0,0x10
10080: 43850513 addi a0,a0,1080 # 10438 <__libc_fini_array>
10084: 3a00006f j 10424 <atexit>
10088: 00008067 ret
0001008c <_start>:
1008c: 00002197 auipc gp,0x2
10090: cec18193 addi gp,gp,-788 # 11d78 <__global_pointer$>
...
So, after reading more about ELF files, I found out that the actual entry point address is provided by the Entry entry on the ELF's header:
> riscv32-unknown-elf-readelf a.out -h | grep Entry
Entry point address: 0x1008c
The problem now becomes that this address is not the actual address on the file (offset from 0) but is a virtual address, so obviously if I set the program counter of my emulator to this address, the emulator would crash.
Reading a bit more, I heard people talk about calculations regarding offsets from program headers and whatnot, but no one had a concrete answer.
My question is: what is the actual "formula" of how exactly you get the entry point address of the _start procedure as an offset from byte 0?
Just to be clear my emulator doesn't support virtual memory and the binary is the only thing that is loaded into my emulator's memory, so I have no use for the abstraction of virtual memory. I just want every memory address as physical address on disk.
My question is: what is the actual "formula" of how exactly you get the entry point address of the _start procedure as an offset from byte 0?
First, forget about sections. Only segments matter at runtime.
Second, use readelf -Wl to look at segments. They tell you exactly which chunk of file ([.p_offset, .p_offset + .p_filesz)) goes into which in-memory region ([.p_vaddr, .p_vaddr + .p_memsz)).
The exact calculation of "at which offset in the file does _start reside" is:
Find Elf32_Phdr which "covers" the address contained in Elf32_Ehdr.e_entry.
Using that phdr, file offset of _start is: ehdr->e_entry - phdr->p_vaddr + phdr->p_offset.
Update:
So, am I always looking for the 1st program header?
No.
Also by "covers" you mean that the 1st phdr->p_vaddr is always equal to e_entry?
No.
You are looking for a the program header (describing relationship between in-memory and on-file data) which overlaps the ehdr->e_entry in memory. That is, you are looking for the segment for which phdr->p_vaddr <= ehdr->e_entry && ehdr->e_entry < phdr->p_vaddr + phdr->p_memsz. This segment is often the first, but that is in no way guaranteed. See also this answer.

Why data segment starts at a non-page boundary?

I am trying to understand the relation between a compiled program in Linux and how it is loaded in the main memory.
I understand that when a program is loaded in memory, all its virtual pages go in some 'page frames' of the main memory.
Below is the snippet of readelf output for my program.
readelf --segments a.out
Elf file type is DYN (Shared object file)
Entry point 0x1060
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
......
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000600 0x0000000000000600 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x0000000000000fc5 0x0000000000000fc5 R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
0x0000000000000190 0x0000000000000190 R 0x1000
LOAD 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
0x00000000000002cc 0x00000000000002d0 RW 0x1000
.......
Here,
(1) First segment (R) starts at the virtual address '0x0000000000000000' and has size '0x600'. Consumes 1 Page.
(2) Second segment (R,E, text segment) starts at the virtual address '0x0000000000001000' and has size '0xfc5'. Consumes 1 Page.
(3) Third segment (R) starts at the virtual address '0x0000000000002000' and has size '0x190'.
Consumes 1 Page.
(4) Fourth segment (RW, data segment) stars at address '0x0000000000003db8', why?
The fourth segment should have started at '0x0000000000003000' as the third segment has size of only '0x190' bytes and also the Align for this segment is 0x1000(4096), which is a page boundary.
LOAD 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
0x00000000000002cc 0x00000000000002d0 RW 0x1000
After loading this program, the physical memory mappings are,
cat /proc/4125/maps
56028da61000-56028da62000 r--p 00000000 08:05 1442315 a.out
56028da62000-56028da63000 r-xp 00001000 08:05 1442315 a.out
56028da63000-56028da64000 r--p 00002000 08:05 1442315 a.out
56028da64000-56028da65000 r--p 00002000 08:05 1442315 a.out
56028da65000-56028da66000 rw-p 00003000 08:05 1442315 a.out
The linker has put segment 4 starting in a page shared with segment 3, to save 0x0248 bytes of file space. That page is to be mapped into virtual memory in two different places, once at relative address 0x2000 (read-only) and again at relative address 0x3000 (read-write). Since the zeroth page of virtual memory stays unmapped to catch null pointer dereferences, the base address for the program is 0x1000 and the program's virtual memory will contain:
0x3000 - 0x3190: read-only version of segment 3
0x3db8 - 0x4000: read-only version of part of segment 4 (will not be used)
0x4000 - 0x4190: read-write version of segment 3 (will not be used)
0x4db8 - 0x5084: read-write version of segment 4
Now, even though I said the page at 0x4000 would be read-write, the output from /proc/NNN/maps shows it as read-only. It looks like something in the C startup code actually calls mprotect at runtime to change the permissions on that page; you can see it happening with strace.
If you dump the section headers (not segment), you should see that the address range 0x4db8-0x5000 corresponds to the sections .init_array, .fini_array, .dynamic and .got, with the normal writable .data section starting at 0x5000. I haven't looked into it, but I presume that for some reason .init_array and such need to be writable for initialization, but can be read-only during the rest of the program's execution.
The alignment field doesn't refer to the alignment of the first byte of segment 4. Rather, the starting address of segment 4 is rounded down to a multiple of the alignment (to 0x3000), and that address from the file is placed in virtual memory aligned to 0x1000 bytes. As explained at What is p_align in elf header?, the rule is that Offset and VirtAddr must be congruent mod Align. Which they are: when either one is divided by 0x1000, the remainder is 0x02db.

Gnu assembler .data section value corrupted after syscall

I have following code
.data
result: .byte 1
.lcomm input 1
.lcomm cha 2
.text
(some other code, syscalls)
At first everything is fine. When a syscall (eg. read) is called, the value at label 'result' changed to some random trash value.
Anyone know what's wrong?
P.S. Environment
Debian x86_64 latest
Running in virtualbox
Using as -g ld emacs make latest
-----edit-----
(continue)
.global _start
_start:
mov $3,%rax
mov $0,%rbx
mov $input,%rcx
mov $1,%rdx
int $0x80
(sys_exit)
The value of 'input' was changed properly, but the value of 'result' changed to random value as well after
int $0x80
You're looking at 4 bytes starting at result, which includes input as the 2nd or 3rd byte. (That's why the value goes up by a multiple of 256 or 65536, leaving the low byte = 1 if you print (char)result). This would be more obvious if you use p /x to print as hex.
GDB's default behaviour for print result when there was no debug info was to assume int. Now, because of user errors like this, with gdb 8.1 on Arch Linux, print result says 'result' has unknown type; cast it to its declared type
GAS + ld unexpectedly (to me anyway) merge the BSS and data segments into one page, so your variables are adjacent even though you put them in different sections that you'd expect to be treated differently. (BSS being backed by anonymous zeroed pages, data being backed by a private read-write mapping of the file).
After building with gcc -nostdlib -no-pie test.S, I get:
(gdb) p &result
$1 = (<data variable, no debug info> *) 0x600126
(gdb) p &input
$2 = (<data variable, no debug info> *) 0x600128 <input>
Unlike using .section .bss and reserving space manually, .lcomm is free to pad if it wants. Presumably for alignment, maybe here so the BSS starts on an 8-byte boundary. When I built with clang, I got input in the byte after result (at different addresses).
I investigated by adding a large array with .lcomm arr, 888332. Once I realized it wasn't storing literal zeros for the BSS in the executable, I used readelf -a a.out to check further:
(related: What's the difference of section and segment in ELF file format)
...
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000000126 0x0000000000000126 R E 0x200000
LOAD 0x0000000000000126 0x0000000000600126 0x0000000000600126
0x0000000000000001 0x00000000000d8e1a RW 0x200000
NOTE 0x00000000000000e8 0x00000000004000e8 0x00000000004000e8
0x0000000000000024 0x0000000000000024 R 0x4
Section to Segment mapping:
Segment Sections...
00 .note.gnu.build-id .text
01 .data .bss
02 .note.gnu.build-id
...
So yes, the .data and .bss sections ended up in the same ELF segment.
I think what's going on here is that the ELF metadata says to map MemSize of 0xd8e1a bytes (of zeroed pages) starting at virt addr 0x600126. and LOAD 1 byte from offset 0x126 in the file to virtual address 0x600126.
So instead of just an mmap, the ELF program loader has to copy data from the file into an otherwise-zeroed page that's backing the BSS and .data sections.
It's probably a larger .data section that would be required for the linker to decide to use separate segments.

Required alignment of .text versus .data

I've been toying around with the ELFIO library. One of the examples, in particular, allows one to create an ELF file from scratch – defining sections, segments, entry point, and providing binary content to the relevant sections.
I noticed that a program created this way segfaults when the code segment alignment is chosen less than the page size (0x1000):
// Create a loadable segment
segment* text_seg = writer.segments.add();
text_seg->set_type( PT_LOAD );
text_seg->set_virtual_address( 0x08048000 );
text_seg->set_physical_address( 0x08048000 );
text_seg->set_flags( PF_X | PF_R );
text_seg->set_align( 0x1000 ); // can't change this
NB that the .text section is only aligned to multiples of 0x10 in the same example:
section* text_sec = writer.sections.add( ".text" );
text_sec->set_type( SHT_PROGBITS );
text_sec->set_flags( SHF_ALLOC | SHF_EXECINSTR );
text_sec->set_addr_align( 0x10 );
However, the data segment, although loaded separately through the same mechanism, does not have this problem:
segment* data_seg = writer.segments.add();
data_seg->set_type( PT_LOAD );
data_seg->set_virtual_address( 0x08048020 );
data_seg->set_physical_address( 0x08048020 );
data_seg->set_flags( PF_W | PF_R );
data_seg->set_align( 0x10 ); // note here!
Now in this specific case the data fits by design within the page that's already allocated. Not sure if this makes any difference, but I changed its virtual address to 0x8148020 and the result still works fine.
Here's the output of readelf:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000001000 0x0000000008048000 0x0000000008048000
0x000000000000001d 0x000000000000001d R E 1000
LOAD 0x0000000000001020 0x0000000008148020 0x0000000008148020
0x000000000000000e 0x000000000000000e RW 10
Why does the program fail to execute when the alignment of the executable segment is not a multiple of 0x1000 but for data 0x10 is no problem?
Update: Somehow on a second try text_seg->set_align( 0x100 ); works too, text_seg->set_align( 0x10 ); fails. The page size is 0x1000 and interestingly, the working program's VirtAddr does not adhere to it in either segment:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000100 0x08048100 0x08048100 0x0001d 0x0001d R E 0x100
LOAD 0x000120 0x08148120 0x08148120 0x0000e 0x0000e RW 0x10
The SIGSEGV'ing one:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000080 0x08048100 0x08048100 0x0001d 0x0001d R E 0x10
LOAD 0x0000a0 0x08148120 0x08148120 0x0000e 0x0000e RW 0x10
Resulting ELFs are here.
The ELF ABI does not require that VirtAddr or PhysAddr be page-aligned. (I believe) it only requires that
({Virt,Phys}Addr - Offset) % PageSize == 0
That is true for both working binaries, and false for the non-working one.
Update:
I don't see how this fails for the latter.
We have: VirtAddr == 0x08048100 and Offset == 0x80 (and PageSize == 4096 == 0x1000).
(0x08048100 - 0x80) % 0x1000 == 0x80 != 0
has to agree when align == 0x10, doesn't it?
No: it has to agree with the page size (as I said earlier), or the kernel will not be able to mmap the segment.
(sorry, more an extended comment than an answer)
There are some specifications about what an ELF executable should be. Read in particular elf(5) and most importantly the relevant ABI specification (see also this question), e.g. on https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI
AFAIU, these specifications require both code and data segments to be page-aligned, but you need to check that, notably in chapter 5 (program loading and dynamic linking) of the ABI spec.
Current tools generating ELF executables (notably binutils) are working hard to respect these specifications. If you code some ELF generator, you should also try hard to respect these specifications (so testing that the generated ELF apparently works is not enough).
The kernel is implementing execve(2), and dynamic loading is done also with ld-linux(8) using mmap(2). For some reasons (performance probably) it does not check that an executable obeys all the specifications.
(of course, kernel folks want commonly produced ELF executables to be successfully execve-d)
In some corner cases (like the one you observe) the kernel is not failing execve and doing something with ill-constructed ELF files.
But IMHO there is no guarantee about that. Future kernels, and future x86-64 processors, might fail on such ill-constructed ELF files.
My feeling is that you are in some grey area, a bit some "undefined behavior" of execve. If it happens to work, it is by bad luck.
Why does the program fail to execute when the alignment of the executable segment is not a multiple of 0x1000 but for data 0x10 is no problem?
To understand precisely why, you need to dive into the source code (related to execve) of your particular kernel. And I believe that it might perhaps change in the future (future versions of the kernel).
The kernel community is more or less promising past-compatibility, but this is with the specification. It could happen that some ill-formed ELF executable could be execve-d by Linux 3.10 but not by Linux 4.13 or some future Linux 5.
(I did read that some past kernels have been able to execve-d some ill formed ELF executables, but I forgot the details, perhaps something related to the 16 bytes alignment of stack pointers)

What's the difference of section and segment in ELF file format

From wiki Executable and Linkable Format:
The segments contain information that is necessary for runtime execution of the file, while sections contain important data for linking and relocation. Any byte in the entire file can be owned by at most one section, and there can be orphan bytes which are not owned by any section.
But what is the difference between section and segment?
In an executable ELF file, does a segment contain one or more sections?
But what's difference between section and segment?
Exactly what you quoted: the segments contain information needed at runtime, while the sections contain information needed during linking.
does a segment contain one or more sections?
A segment can contain 0 or more sections. Example:
readelf -l /bin/date
Elf file type is EXEC (Executable file)
Entry point 0x402000
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000000d5ac 0x000000000000d5ac R E 200000
LOAD 0x000000000000de10 0x000000000060de10 0x000000000060de10
0x0000000000000440 0x0000000000000610 RW 200000
DYNAMIC 0x000000000000de38 0x000000000060de38 0x000000000060de38
0x00000000000001a0 0x00000000000001a0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x000000000000c700 0x000000000040c700 0x000000000040c700
0x00000000000002a4 0x00000000000002a4 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 8
GNU_RELRO 0x000000000000de10 0x000000000060de10 0x000000000060de10
0x00000000000001f0 0x00000000000001f0 R 1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .ctors .dtors .jcr .dynamic .got
Here, PHDR segment contains 0 sections, INTERP segment contains .interp section, and the first LOAD segment contains a whole bunch of sections.
Further reading with a nice illustration:
Section contains static for the linker, segment dynamic data for the OS
The quote is correct, but to actually understand it the difference, you should try to understand the fields of the section header and program header (segment) entries, and how they are be used by the linker (sections) and operating system (segment).
Particularly important informations are (besides lengths):
section: tell the linker if a section is either:
raw data to be loaded into memory, e.g. .data, .text, etc.
or formatted metadata about other sections, that will be used by the linker, but disappear at runtime e.g. .symtab, .srttab, .rela.text
segment: tells the operating system:
where should a segment be loaded into virtual memory
what permissions the segments have (read, write, execute). Remember that this can be efficiently enforced by the processor: How does x86 paging work?
I have written a tutorial that covers that in more detail at: http://www.cirosantilli.com/elf-hello-world/
Does a segment contain one or more sections?
Yes, and it is the linker that puts sections into segments.
In Binutils, how sections are put into segments by ld is determined by a text file called a linker script. Docs: https://sourceware.org/binutils/docs/ld/Scripts.html
You can get the default one with ld --verbose, and set a custom one with -T.
For example, my default Ubuntu 17.04 linker script contains:
.text :
{
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(.text .stub .text.* .gnu.linkonce.t.*)
}
which tells the linker to put sections named .text.unlikely, .text.*_unlikely, .text.exit, etc. in the .text segment.
OS development is a case where custom scripts are useful, minimal example: https://github.com/cirosantilli/x86-bare-metal-examples/blob/d217b180be4220a0b4a453f31275d38e697a99e0/linker.ld
Once the executable is linked, it is only possible to know which section went to which segment if the linker stores the optional section header in the executable: Where is the "Section to segment mapping" stored in ELF files?
Please correct me if I'm wrong, as I wouldn't consider myself an expert on this topic, but according to my research some statements given in the answers/comments seem to be not fully accurate. To elaborate, I'll quote sentences and comment on them:
Section contains static for the linker, segment dynamic data for the OS
According to this LWN article, the kernel only uses the segment header of type PT_INTERP, PT_LOAD and PT_GNU_STACK to load executables into memory. But there are other segment types, like PHDR, DYNAMIC, NOTE, GNU_EH_FRAME, GNU_PROPERTY, GNU_RELRO, which are ignored.
Afaiu, the GNU_RELRO segment is like a dummy segment; if it is present the loader uses this as a flag to make the relocation data read-only. But the loader is not part of the OS, at least for Linux.
As for the other segment types, I haven't found out what they are actually used for. They seem redundant to me, as there are corresponding sections which basically have the same or more information.
Thus, from my understanding that answer is only a simplified approximation of a more messy truth.
sections are contained with segments
You can have ELF executables with no section header and relocatable (*.o) files usually do not have segment header. Furthermore, in the readelf output in the accepted answer one can see the .interp section in multiple segments. I do not see any containment restriction.
the segments contain information needed at runtime, while the sections contain information needed during linking.
Again this seems like a simplification. The runtime loader (or "interpreter") also needs the sections for loading shared libraries, resolving symbols, doing relocations etc.
To conclude, while the given answers are probably reasonable general approximations, it apparently gets more complicated when looking at the details.

Resources