This article provides a short introduction to using the ELF manipulation library, which is provided as a part of the Solaris operating environment. Programmers with little to medium experience and an interest in knowing about object/library file format will find it useful.

Introduction

Executable and Linkable Format is a portable object file format supported by most UNIX? vendors. It is the dominant file format for UNIX executables, object files and libraries. ELF was originally developed and published by UNIX System Laboratories, and defines the format of an ELF object file.

ELF helps developers by providing a set of binary interface definitions that are cross-platform, and by making it easier for tool vendors to port to multiple platforms. Having a standard object file format also makes porting of object-manipulating programs easier. Compilers, debuggers, and linkers are some examples of tools that use the ELF format.

Executable files - Hold code and data that can be executed on the target operating system

Shared object files - Hold relocatable data that can be statically and dynamically shared with other shared objects

Relocatable files are also called as object files (.o files). They are generated by the compiler during compilation of a source file (such as a C or C++ file). These object files are then processed by the linker to produce either an ELF executable or shared objects (also called shared libraries). Executable ELF objects can then be executed by the user. A shared library is a collection of routines that different object files or executables can share. Shared libraries are made up of several object files with position independent code (PIC).

Every ELF file has an ELF header and a number of sections. An ELF header is present for all ELF objects. This means that the presence of the ELF header identifies the file to be an ELF file. If the ELF object is executable, it has a Program Header Table following the ELF header. ELF objects that are not executable (that is .o object files) have a Section Header Table at the end of the file. The format of the ELF header and sections is part of the ELF specification, and is defined in /usr/include/sys/elf.h .

Table 1 describes the ELF file format. The first column is the field and the second column is the format of that field.

Table 1 Object File Format

Field Format of the field

ELF header typedef struct {

unsigned char e_ident[EI_NIDENT]; /* ident bytes */

Elf32_Half e_type; /* file type */

Elf32_Half e_machine; /* target machine */

Elf32_Word e_version; /* file version */

Elf32_Addr e_entry; /* start address */

Elf32_Off e_phoff; /* phdr file offset */

Elf32_Off e_shoff; /* shdr file offset */

Elf32_Word e_flags; /* file flags */

Elf32_Half e_ehsize; /* sizeof ehdr */

Elf32_Half e_phentsize; /* sizeof phdr */

Elf32_Half e_phnum; /* number phdrs */

Elf32_Half e_shentsize; /* sizeof shdr */

Elf32_Half e_shnum; /* number shdrs */

Elf32_Half e_shstrndx; /* shdr string index */

} Elf32_Ehdr;

Program header table (optional) typedef struct {

Elf32_Word p_type; /* entry type */

Elf32_Off p_offset; /* file offset */

Elf32_Addr p_vaddr; /* virtual address */

Elf32_Addr p_paddr; /* physical address */

Elf32_Word p_filesz; /* file size */

Elf32_Word p_memsz; /* memory size */

Elf32_Word p_flags; /* entry flags */

Elf32_Word p_align; /* memory/file alignment */

} Elf32_Phdr;

Section 1 Each section contains a section header followed by chunks of data. These chunks of data are dependent on the type of the section. For example, if the section is of type SHT_SYMTAB, the section contains an array of symbols. The type of the section dictates the structures contained in the section. The section header is the same for all sections, and is of the type:

typedef struct {

Elf32_Word sh_name; /* section name */

Elf32_Word sh_type; /* SHT_... */

Elf32_Word sh_flags; /* SHF_... */

Elf32_Addr sh_addr; /* virtual address */

Elf32_Off sh_offset; /* file offset */

Elf32_Word sh_size; /* section size */

Elf32_Word sh_link; /* misc info */

Elf32_Word sh_info; /* misc info */

Elf32_Word sh_addralign; /* memory alignment */

Elf32_Word sh_entsize; /* entry size if table */

} Elf32_Shdr;

... ...

Section n Contains the section header and data

... ...

Section header table ...

LibElf and GElf

The Solaris operating environment provides an object file access library, libelf(3LIB), that can be used to manipulate object files, archive files, and archive members. The generic version of this library, which handles both 32 bit and 64 bit objects, is called GElf. Several demo programs are also provided as a part of the SUNWosdem package, and can be found in /usr/demo/ELF.

The Solaris man page for GElf(3ELF) (man gelf ) states that:

GElf is a generic, ELF class independent API, for manipulating ELF object files. GElf provides a single, common interface for handling 32-bit and 64-bit ELF format object files. GElf is a translation layer between the application and the class dependent parts of the ELF library. Thus, the application can use GElf, which in turn, will call the corresponding elf32_ or elf64_ functions on behalf of the application.

Programs that need to use LibElf to manipulate an ELF file should do the following:

Check that there are no version inconsistencies.

if (elf_version(EV_CURRENT) == EV_NONE ) {

/* library out of date */

fprintf(stderr, "Elf library out of date!n");

exit(-1);

}

Open the file using the open() system call. For example, if the name of the file is in argv[1], the following code can be used to open the file.

fd = open(argv[1], O_RDONLY);

Indicate to LibElf that this file is to be associated with an ELF object. This is achieved by calling the elf_begin function. elf_begin returns an ELF descriptor (of type Elf* ) that is then used to call other functions in the library.

if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL){

/*error*/

}

Programs get the ELF descriptor, and then read the ELF header. You can then read sections and manipulate them.

Examples

Here are some simple code fragments that demonstrate how to use LibElf to manipulate object files.

This example uses the GElf interface to manipulate both 32-bit and 64-bit ELF objects. Using GElf is very similar to using the LibElf interface, as demonstrated below. (You can download the entire source code.)

static void my_error(const char *fmt, ...);

static void print_usage(char *exename)

{

fprintf(stderr,"Usage: %s filen", exename);

}

int main(int argc, char *argv[])

{

int fd;

int filetype;

Elf *elf;

GElf_Ehdr elfhdr;

char *type, *machine, *kind, *class;

if(argc != 2){

print_usage(argv[0]);

exit(-1);

}

if (elf_version(EV_CURRENT) == EV_NONE) {

/* library out of date */

my_error("ELF library is out of date, Quittingn");

}

fd = open(argv[1],O_RDONLY);

if (fd < 0) {

my_error("%s: Unable to open "%s"n",

argv[0], argv[1]);

}

elf = elf_begin(fd, ELF_C_READ, NULL);

if (gelf_getehdr(elf, &elfhdr) ==0){

my_error("Cannot read elf header. This usually

means that %s is not a valid elf filen", argv[1]);

}

kind = my_elf_kind_of_file(elf);

type = my_elf_type_to_string(elfhdr.e_type);

machine = my_elf_machine_to_string(elfhdr.e_machine);

class = (elfhdr.e_ident[EI_CLASS] == ELFCLASS32) ?

"32-bit" : "64-bit" ;

printf("%s: %s %s %s %sn", argv[1], kind, class,

type, machine);

elf_end(elf);

close(fd);

}

static char* my_elf_type_to_string(GElf_Half t)

{

char *type;

switch(t){

case ET_NONE:

type = "unknown type";

break;

case ET_REL:

type = "relocatable";

break;

case ET_EXEC:

type = "executable";

break;

case ET_DYN:

type = "dynamic lib";

break;

case ET_CORE:

type = "Core file";

break;

case ET_LOPROC:

case ET_HIPROC:

type = "Processor specific";

break;

default:

my_error("Invalid file type");

}

return type;

}

static char* my_elf_machine_to_string(GElf_Half t)

{

char *type;

switch(t){

case ET_NONE:

type = "None";

break;

case EM_SPARC:

type = "SPARC";

break;

case EM_SPARC32PLUS:

type = "SPARC32+";

break;

case EM_SPARCV9:

type = "SPARCV9";

break;

default:

type = "NONSPARC";

break;

}

return type;

}

static char* my_elf_kind_of_file(Elf* elf)

{

char *result;

switch(elf_kind(elf)){

case ELF_K_AR:

result = "Archive";

break;

case ELF_K_COFF:

result = "COFF";

break;

case ELF_K_ELF:

result = "ELF";

break;

case ELF_K_NONE:

result = "Unknown Format";

break;

default:

my_error("Unkown file!n");

}

return result;

}

/******* Sample Output *********************

[pastwatch] elf > ./filetype filetype

filetype: ELF 64-bit executable SPARCV9

[pastwatch] elf > ./filetype /usr/lib/libc.so

/usr/lib/libc.so: ELF 32-bit dynamic lib SPARC

[pastwatch] elf > ./filetype /usr/lib/sparcv9/libc.so

/usr/lib/sparcv9/libc.so: ELF 64-bit dynamic lib SPARCV9

[pastwatch] elf > ./filetype /bin/ls

/bin/ls: ELF 32-bit executable SPARC

[pastwatch] elf > ./filetype ./filetype.o

./filetype.o: ELF 32-bit relocatable SPARC

*********************************************/

Resources

The Solaris man pages for LibElf and GElf are very descriptive and provide useful examples.

About the Author

Neelakanth Nadgir is a software engineer in Sun"s Market Development Engineering organization. He works with tool vendors to develop "best of breed" applications on Sun systems. He also volunteers for the GNU project. In his spare time, he likes to go hiking in Big Basin State Park.