Interfacing with ELF files An introduction to the Executable file specification standard

advertisement
Interfacing with ELF files
An introduction to the Executable
and Linkable Format (ELF) binary
file specification standard
Overview of source translation
User-created files
Makefile
Make Utility
C/C++
C/C++Source
Source
and
Header
and Header
Files
Files
assembler
Object
Object
Files
Files
Archive Utility
Shared
Object
File
Linker
Script
File
preprocessor
compiler
Library
Library
Files
Files
Assembly
Assembly
Source
Source
Files
Files
Linker and Locator
Linkable
Image File
Executable
Image File
Link Map
File
Executable versus Linkable
ELF Header
ELF Header
Program-Header Table
(optional)
Program-Header Table
Section 1 Data
Segment 1 Data
Section 2 Data
Segment 2 Data
Section 3 Data
Segment 3 Data
…
Section n Data
…
Segment n Data
Section-Header Table
Section-Header Table
(optional)
Linkable File
Executable File
Role of the Linker
ELF Header
Section 1 Data
Section 2 Data
…
Section n Data
ELF Header
Program-Header Table
Segment 1 Data
Section-Header Table
Linkable File
Segment 2 Data
ELF Header
…
Segment n Data
Section 1 Data
Section 2 Data
…
Section n Data
Section-Header Table
Linkable File
Executable File
ELF Header
e_ident [ EI_NIDENT ]
e_type
e_machine
e_shoff
e_version
e_flags
e_entry
e_phoff
e_ehsize e_phentsize e_phnum e_shentsize
e_shnum e_shstrndx
Section-Header Table: e_shoff, e_shentsize, e_shnum, e_shstrndx
Program-Header Table: e_phoff, e_phentsize, e_phnum, e_entry
NOTE: The sizes of these fields, and their arrangement, is slightly different for
the ELF64 files that are produced by default on our x86_64 Linux workstations.
Section-Headers
sh_name
sh_type
sh_flags
sh_addr
sh_offset
sh_size
sh_link
sh_info
sh_addralign
sh_entsize
NOTE: These are for the ELF32 file-format.
Program-Headers
p_type
p_offset
p_vaddr
p_paddr
p_filesz
p_memsz
p_flags
p_align
NOTE: These are for the ELF32 file-format.
Official ELF documentation
• The official document that describes ELF
file-formats for both the ‘linkable’ and the
‘executable’ files is available online on our
CS630 course website (see ‘Resources’)
• (Be aware that this document has been
revised to accommodate programs that
will be run on platforms which implement
64-bit addresses and processor registers)
Memory: Physical vs. Virtual
Portions of physical memory
are “mapped” by the CPU
into regions of each task’s
‘virtual’ address-space
Physical
address space
(4 GB)
Virtual
Address
Spaces
(4 GB)
Linux ‘Executable’ ELF files
• An Executable ELF32 file produced by the
Linux linker is configured to execute in a
private ‘virtual’ address space, whereby
every program gets loaded at the identical
virtual memory-address (i.e., 0x08048000)
• We will soon study the x86 CPU’s paging
mechanism which makes this possible
(i.e., after we have finished Project #1)
Linux ‘Linkable’ ELF files
• It is possible that some ‘linkable’ ELF files
are self-contained (i.e., they may not need
to be linked with any other object-files, or
with any shared libraries)
• Our ‘manydots.o’ is one such example
• So we can write our own system-code that
can execute the instructions contained in a
stand-alone ‘linkable’ object-module, using
the CPU’s ‘segmented’ physical memory
Our ‘loadmap.cpp’ utility
• We created a tool that ‘parses’ a linkable
ELF file, to identify each section’s length,
type, and location within the object-module
• For those sections containing the ‘text’ and
‘data’ for the program, we build segmentdescriptors, based on where the linkable
image-file will reside in physical memory
• Then we jump to the ‘_start’ entry-point
32-bit versus 16-bit code
• Linux’s compilers, and the ‘as’ assembler, can
produce object-files that are intended to reside
in ’32-bit’ memory-segments (i.e., the D-bit in a
code-segment descriptor is set to 1)
• This affects the CPU’s interpretation of all the
machine-instructions it subsequently fetches
• Our ‘as’ assembler can produce both16-bit and
32-bit code (although its default is 64-bit code)
• We employ ‘.code32’ or ‘.code16’ directives
Example: ‘as’ Listing
0x0000
01 D8
0x0002 66 01 D8
0x0005
90
.code32
add %eax, %ebx
add %ax, %bx
nop
0x0006 66 01 D8
0x0009
01 D8
0x000B
90
.code16
add %eax, %ebx
add %ax, %bx
nop
.end
Demo-program
• We created a Linux program (‘linuxapp.s’) that
invokes two system-calls (‘write’ and ‘exit’)
• We assembled it with the ‘as’ assembler:
$ as --32 linuxapp.s –o linuxapp.o
• This linkable ELF object-file ‘linuxapp.o’ should
then be written to our hard-disk partition
(‘/dev/sda4’) at sector 65, using the ‘dd’ utility:
$ dd if=linuxapp.o of=/dev/sda4 seek=65
• So it will get loaded into memory by ‘cs630ipl’
Memory-Map
Both ‘tryelf32.b’ and
‘linuxapp.o’ will get
loaded into ram
from sectors 1..127
of the disk-partition
by our ‘cs630ipl.b’
program-loader
hard disk
‘cs630ipl.b’ is read from
CS630 disk-partition via
ROM-BIOS bootstrap
‘linuxapp.o’ image
0x00018000
‘tryelf32.b’
image
0x00010000
BOOT-LOADER
0x00007C00
ROM-BIOS DATA
IVT
0x00000400
Segment Descriptors
• We created 32-bit segment-descriptors for
the ‘text’ and ‘data’ sections of ‘linuxapp.o’
(in a Local Descriptor Table) with DPL=3
• For the ‘.text’ section:
offset in ELF file = 0x34 size = 0x24
• So its segment-descriptor is:
.word 0x0023, 0x8034, 0xFA01, 0x0040
(base-address = load-address + file-offset)
Descriptors (continued)
• For the ‘.data’ section:
offset in ELF file = 0x58 size = 0x16
• So its segment-descriptor is:
.word 0x0015, 0x8058, 0xF201, 0x0040
(base-address = load-address + file-offset)
• For our ring3 stack (not part of ELF file):
.word 0x0000, 0x0000, 0xF602, 0x00C0
Note: It’s an ‘expand-down’ data-segment!
‘Expand-Down’ segments
segment
limit
segment
limit
base-address
base-address
Normal ‘Expand-Up’
Data-Segment
Special ‘Expand-Down’
Data-Segment
Task-State Segment
• Because any system-calls (via int 0x80)
will cause privilege-level transitions, we
will need to setup a Task-State Segment
(to store a ring0 stack-pointer SS0:ESP0)
theTSS: .long 0, 0, 0 # 3 longwords
• Its segment-descriptor goes into our GDT:
.word 0x000B, theTSS, 0x8901, 0x0000
Transition to Ring 3
• Recall that we use ‘lret’ to enter ring-3:
pushw
$userSS
pushw
$0
pushw
$userCS
pushw
$0
lret
(NOTE: This assumes we are coming from
a 16-bit code-segment in protected-mode)
System-Call Dispatcher
• All system-calls get ‘vectored’ through our
IDT’s interrupt-gate number 0x80
• For ‘linuxapp.o’ we only need to implement
two system-calls: ‘exit’ and ‘write’
• But to simplify future enhancements, we
use a ‘jump-table’ anyway (although for
now it has a few ‘dummy’ entries, which
can easily be modified later on)
System-Call ID-numbers
• System-call ID #0 (it will never be needed)
• System-call ID #1 is for ‘exit’ (required)
• System-call ID #2 is for ‘fork’ (deferred)
• System-call ID #3 is for ‘read’ (deferred)
• System-call ID #4 is for ‘write’ (required)
• System-call ID #5 is for ‘open’ (deferred)
• System-call ID #6 is for ‘close’ (deferred)
(NOTE: over 300 system-calls exist in Linux)
Defining our jump-table
sys_call_table:
.long do_nothing
# for service 0
.long do_exit
# for service 1
.long do_nothing
# for service 2
.long do_nothing
# for service 3
.long do_write
# for service 4
.equ NR_SYS_CALLS, ( . - sys_call_table)/4
Setting up IDT Gate 0x80
• The Descriptor Privilege Level must be 3
• The Gate-Type should be ‘386 Trap-Gate’
• The entry-point will be our ‘isrSVC’ label
# Interrupt Descriptor Table’s entry for SuperVisor Call (int $0x80)
mov
lea
movw
movw
movw
movw
$0x80, %ebx
theIDT(, %ebx, 8), %di
$isrSVC, %ss:0(%di)
$privCS, %ss:2(%di)
$0xEF00, %ss:4(%di)
$0x0000, %ss:6(%di)
# table-entry array-index
# descriptor offset-address
# entry-point offset’s loword
# selector for code-segment
# Gate-Type: 386 Trap-Gate
# entry-point offset’s hiword
Using our jump-table
isrSVC:
idok:
# service-number is found in EAX
cmp $NR_SYS_CALLS, %eax
jb
idok
xor %eax, %eax
jmp *sys_call_table(, eax, 4)
Our ‘exit’ service
• When the application invokes the ‘exit’
system-call, our mini ‘operating system’
should leave protected-mode and return
back to our boot-loader program
• The ‘exit-code’ parameter (in %ebx) may
just as well be discarded (since this isn’t
yet a multitasking operating-system)
Our ‘write’ service
• We only implement writing to the STDOUT
device (i.e., the video display console)
• For most characters in the user’s buffer,
we just write the ascii-code (and standard
display-attribute) directly to video memory
at the current cursor-location and advance
the cursor (scrolling the screen if needed)
• Special ascii control-codes (‘\n’, ‘\r’, ‘\b’)
are treated differently, as on a TTY device
In-Class Exercise
• The ‘manydots.s’ demo (to be used with Project
#1) uses the ‘read’ system-call (in addition to the
‘write’ and ‘exit’ services)
• However, you could still ‘execute’ it, using our
‘tryelf32.s’ mini operating-system, by letting the
‘read’ service simply “do nothing” (or return with
some kind of “hard-coded” buffer-contents)
• You just need to modify the LDT descriptors so
they’ll conform to ELF sections in ‘manydots.o’
Download