Gnu assembler .data section value corrupted after syscall - linux

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.

Related

Why does a write system call print a bunch of junk when you mov edx, [msgLen] from db $-msg - same as with the address?

For some reason [msgLen] and msgLen produce the same result. I know that I ought to use equ or something, but I want to know why this isn't working. Is NASM ignoring the dereference? It prints my string and a bunch of junk bc it thinks the string is bigger than it is right? Thanks!
Please see the assembly below:
_start:
mov edx,[msgLen]
mov ecx,msg
mov ebx,1
mov eax,4
int 0x80
mov eax,1
int 0x80
section .data
msg db 'Zoom',0xA
msgLen db $-msg
Use a debugger to look at EDX after the load, or use strace ./a.out to see the length you actually pass to the system call. It will be different (address vs. value loaded). Both ways are buggy in different ways that happen to produce large values, so the effect only happens to be the same. Debugging / tracing tools would have clearly shown you that NASM isn't "ignoring the dereference", though.
mov edx, msgLen is the address, right next to msg. It will be something like 0x804a005 if you link into a standard Linux non-PIE static executable with ld -melf_i386 -o foo foo.o.
mov edx, [msgLen] loads 4 bytes from that address (the width of EDX), where you only assembled the size into 1 byte in the data section. The 3 high bytes come from whatever comes next in the part of the file that's mapped to that memory page.
When I assemble with just nasm -felf32 foo.asm and link with ld -melf_i386, that happens to be 3 bytes of zeros, so I don't get any garbage printed from the dword-load version. (x86 is little-endian).
But it seems your non-zero bytes are debug info. I've found NASM's debug info is the opposite of helpful, sometimes making GDB's disassembly confused (layout reg / layout asm sometimes fail to disassemble a whole block after a label) so I don't use it.
But if I use nasm -felf32 -Fdwarf, then I do get 7173 from that dword load that goes past the end of the .data section. That's a different large number, so this is just wrong in a different way, not the same problem. 7173 is 0x1c05, so it corresponds to db 5, 0x1c, 0, 0. i.e. your calculated length of 5 is the low byte, but there's a 0x1c after it. yasm -gdwarf2 gives me 469762053 = 0x1c000005.
If you'd used db $-msg, 0,0,0 or dd $-msg, you could load a whole dword. (To load and zero-extend a byte into a dword register, use movzx edx, byte [mem])
write() behaviour with a large length
If you give it some very large length, write will go until it reaches an unreadable page, then return the length it actually wrote. (It doesn't check the whole buffer for readability before starting to copy_from_user. And it returns the number of bytes written if that's non-zero before encountering an unreadable page. You can only get -EFAULT if an unreadable page is encountered right away, not if you pass a huge length that includes some unmapped pages later.)
e.g. with mov edx, msgLen (the label address)
$ nasm -felf32 -Fdwarf foo.asm
$ ld -melf_i386 -o foo foo.o
$ strace -o foo.tr ./foo # write trace to a file so it doesn't mix with terminal output
Zoom
foo.asmmsgmsgLen__bss_start_edata_end.symtab.strtab.shstrtab.text.data.debug_aranges.debug_info.debug_abbrev.debug_lin! ' 6& 9B_
$ cat foo.tr
execve("./foo", ["./foo"], 0x7fffdf062e20 /* 53 vars */) = 0
write(1, "Zoom\n\5\34\0\0\0\2\0\0\0\0\0\4\0\0\0\0\0\0\220\4\10\35\0\0\0\0\0"..., 134520837) = 4096
exit(1) = ?
+++ exited with 1 +++
134520837 is the length you passed, 0x804a005 (the address of msgLen). The system call writes 4096 bytes, 1 whole page, before getting to an unmapped page and stopping early.** It doesn't matter number you pass higher than that, because there's only 1 page before the end of the mapping. (And msg: is apparently right at the start of that page.)
On the terminal (where \0 prints as empty), you mostly just see the printable characters; pipe into hexdump -C if you want a better look at the binary data. It includes bits of metadata from the file, because the kernel's ELF program loader works by mmaping the file into memory (with a MAP_PRIVATE read-write no-exec mapping for that part). Use readelf -a and look at the ELF program headers: they tell the kernel which parts of the file to map into memory where, with what permissions.
Fun fact: If I redirect to /dev/null (strace ./foo > /dev/null), the kernel's write handler for that special device driver doesn't even check the buffer for permissions, so write() actually does return 134520837.
write(1, "Zoom\n\5\0\0[...]\0\0\220\4\10"..., 134520837) = 134520837
Using EQU properly
And yes, you should be using equ so you can use the actual length as an immediate. Having the assembler calculate it and then assemble that byte into the data section is less convenient. This prints exactly the right size, and is more efficient than using an absolute address to reference 1 constant byte.
mov edx, msg.len ; mov r32, imm32
...
section .rodata
msg: db 'Zoom',0xA
.len equ $-msg
(Using a NASM . local label is unrelated to using equ; I'm showing that too because I like how it helps organize the global namespace.)
Also semi-related to how ld lays out sections into ELF segments for the program-loader: recent ld pads sections for 4k page alignment or something like that to avoid getting data mapped where it doesn't need to be. Especially out of executable pages.

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)

GCC compiled code: why integer declaration needs several statements?

I'm learning AT&T assembly,I know arrays/variables can be declared using .int/.long, or using .equ to declare a symbol, that's to be replaced by assembly.
They're declared insided either .data section(initialzed),or .bss section(uninitialzed).
But when I used gcc to compiled a very simple .c file with '-S' command line option to check the disassembly code, I noticed that:
(1) .s is not using both .data and .bss, but only .data
(2) The declaration of an integer(.long) cost several statements, some of them seems redundant or useless to me.
Shown as below, I've added some comments as per my questions.
$ cat n.c
int i=23;
int j;
int main(){
return 0;
}
$ gcc -S n.c
$ cat n.s
.file "n.c"
.globl i
.data
.align 4
.type i, #object #declare i, I think it's useless
.size i, 4 #There's '.long 23', we know it's 4 bytes, why need this line?
i:
.long 23 #Only this line is needed, I think
.comm j,4,4 #Why j is not put inside .bss .section?
.text
.globl main
.type main, #function
main:
.LFB0: #What does this symbol mean, I don't find it useful.
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0: #What does this symbol mean, I don't find it useful.
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609"
.section .note.GNU-stack,"",#progbits
All my questions are in the comments above, I re-emphasize here again:
.type i, #object
.size i, 4
i:
.long 23
I really think above code is redundant, should be as simple as:
i:
.long 23
Also, "j" doesn't have a symbol tag, and is not put inside .bss section.
Did I get wrong with anything? Please help to correct. Thanks a lot.
(I am guessing you are using some Linux system)
They're declared insided either .data section(initialzed),or .bss section(uninitialzed).
No, you have many other sections, notably .comm (for the "common" section, with initialized data common to several object files, that the linker would "merge") and .rodata for read-only data. The ELF format is flexible enough to permit many sections and many segments (some of which are not loaded -more precisely memory mapped- in memory).
The description of sections in ELF files is much more complex than what you believe. Take time to read more, e.g. Linkers and loaders by Levine. Read also the documentation and the scripts of GNU binutils and also ld(1) & as(1). use objdump(1) and readelf(1) to explore existing ELF executables, object files and shared objects. Read also execve(2) & elf(5)
But when I used gcc to compiled a very simple .c file with -S command line option
When examining the assembler file generated by gcc I strongly recommend passing at least -fverbose-asm to ask gcc to emit some additional and useful comments in the assembler file. I also usually recommend to use some optimization flag -e.g. -O1 at least (or perhaps -Og on recent versions of gcc).
I noticed that:
(1) .s is not using both .data and .bss, but only .data
No, your generated code use the .comm section and put the value of j there.
(2) The declaration of an integer(.long) cost several statements, some of them seems redundant or useless to me.
These are mostly not assembler statements (translated into machine code) but assembler directives; they are very useful (and they don't waste space in the memory segment produced by ld, but the ELF format has information elsewhere). In particular .size and .type are both needed because the symbol tables in ELF files contain more than addresses (it also has a notion of size, and a very primitive notion of type).
The .LFB0 is a gcc (actually cc1-) generated label. GCC does not care about generating useless labels (it is simpler for the assembler generator in the GCC backend), since they don't appear in object files.
There's '.long 23', we know it's 4 bytes,
you might know that a long is 4 bytes, but that information (size of j) should go into the ELF file so requires explicit assembler directives....
(I don't have space or time to explain the ELF format, you need to read many pages about it, and it is a lot more complex and more complete than what you believe)
BTW, Drepper's How To Write Shared Libraries is quite long (more than 40 pages) and explains a good deal about ELF files, focusing on shared libraries.

How do we Load Linux Image to appropiate location in Memory

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

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