interfacing with elf files an introduction to the executable and linkable format (elf) binary file...

29
Interfacing with ELF files An introduction to the Executable and Linkable Format (ELF) binary file specification standard

Post on 18-Dec-2015

244 views

Category:

Documents


1 download

TRANSCRIPT

Interfacing with ELF files

An introduction to the Executable and Linkable Format (ELF) binary

file specification standard

Library Files

Object Files

Assembly Source

Files

C/C++ Source and Header

Files

Overview of source translation

MakefileC/C++ Source

and HeaderFiles

Assembly Source

Files

Linker Script

File

User-created files

preprocessor

compiler assembler

Make Utility

Object Files

Shared Object

File

Linkable Image File

Executable Image File

Link Map File

Linker and Locator Library Files

Archive Utility

Section-Header Table(optional)

Executable versus Linkable

ELF Header

Section 2 Data

Section 3 Data

…Section n Data

Segment 1 Data

Segment 2 Data

Segment 3 Data

…Segment n Data

Linkable File Executable File

Section-Header Table

Program-Header Table(optional)

Program-Header Table

ELF Header

Section 1 Data

Role of the Linker

ELF Header

Section-Header Table

Section 1 DataSection 2 Data

…Section n Data

ELF Header

Section-Header Table

Section 1 DataSection 2 Data

…Section n Data

ELF Header

Program-Header Table

Segment 1 Data

Segment 2 Data

…Segment n Data

Linkable File

Linkable File

Executable File

ELF Header

e_type e_machine e_version e_entry e_phoff

e_shoff e_flags e_ehsize e_phentsize e_phnum e_shentsize

e_shnum e_shstrndx

e_ident [ EI_NIDENT ]

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 forthe 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

VirtualAddressSpaces(4 GB)

Physicaladdress space

(4 GB)

Portions of physical memory are “mapped” by the CPU into regions of each task’s ‘virtual’ address-space

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 segment-descriptors, 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

.code320x0000 01 D8 add %eax, %ebx0x0002 66 01 D8 add %ax, %bx0x0005 90 nop

.code160x0006 66 01 D8 add %eax, %ebx0x0009 01 D8 add %ax, %bx0x000B 90 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

IVT

ROM-BIOS DATA

BOOT-LOADER

‘tryelf32.b’image

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

0x00010000

0x00018000

0x00000400

0x00007C00 ‘cs630ipl.b’ is read from CS630 disk-partition via ROM-BIOS bootstrap

‘linuxapp.o’ image

hard disk

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, 0x00C0Note: It’s an ‘expand-down’ data-segment!

‘Expand-Down’ segments

base-address

segment limit

Normal ‘Expand-Up’ Data-Segment

base-address

segment limit

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 $userSSpushw $0pushw $userCSpushw $0lret

(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:

.longdo_nothing # for service 0

.longdo_exit # for service 1

.longdo_nothing # for service 2

.longdo_nothing # for service 3

.longdo_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 $0x80, %ebx # table-entry array-indexlea theIDT(, %ebx, 8), %di # descriptor offset-addressmovw $isrSVC, %ss:0(%di) # entry-point offset’s lowordmovw $privCS, %ss:2(%di) # selector for code-segmentmovw $0xEF00, %ss:4(%di) # Gate-Type: 386 Trap-Gatemovw $0x0000, %ss:6(%di) # entry-point offset’s hiword

Using our jump-table

isrSVC: # service-number is found in EAX

cmp $NR_SYS_CALLS, %eax

jb idok

xor %eax, %eax

idok: 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’