New Symbian OS 9 Executable File Format (E32Image)

Symbian has released Symbian OS version 9. A lot of new features and changes have been introduced in this new version. One of them is the new executable file format (E32Image). This article discusses how the new format looks like. If you want to know about pre-Symbian OS 9 format, read my previous article.

Before we start, note that here we are talking about executable file for the target (ARM), not for the emulator. Why? Because the emulator's executable files are actually using different format. If you don't have any knowledge about any executable file format, these links will help give you a better understanding about E32 file format later on.

This article is written based on the public information available from Symbian web site or any other Symbian OS licensees, like Nokia. Although I am currently working for a mobile phone vendor, this article is not endorsed by my company. I cannot guarantee the correctness of this article although I have tried my best to do so.

Introduction to EABI

The ABI (Application Binary Interface) is a standard developed by ARM and its partners that defines how compilers, linkers and other tools should generate object files and executable files. The standard allows object files from different compilers to interoperate, i.e. object files built with one compiler can be combined with object files built with another compiler. The EABI (Embedded Application Binary Interface) points to the same thing; it just highlights the fact that ABI is for embedded world.

At the writing of this article, there are two compilers that can be used to compile in Symbian OS 9, i.e. RVCT (RealView Compilation Tools) and GCCE. RVCT is the compiler developed by ARM and the license costs a few thousand dollars. GCCE, on the other hand, is a free compiler developed by CodeSourcery. As its name implies, GCCE is based on the GNU compiler.

The format of the output of the EABI compiler is ELF (Executable and Linking Format). It is different from pre-Symbian OS 9 world where the object file format is PE (Portable Executable). However, Symbian OS does not use ELF format as it is. Since Symbian OS is usually on the ROM that has size limitation, the size of standard ELF file is simply too big. That's why Symbian converts standard ELF format into Symbian specific format, called E32Image. The tool that is used to convert ELF to E32Image is called elf2e32.exe. If you have one of the Symbian OS SDKs, the tool is located at \epoc32\tools.

Figure 1 shows the new build chain in Symbian OS 9. As you can see here, the final output is converted by elf2e32.exe to Symbian OS specific format, i.e. E32Image. There are more comprehensive pictures at Symbian web site too. Normally you won’t see the process described in Figure 1 because Symbian OS’ build scripts, like bldmake and abld, hide it from you.

ELF to E32 conversion

If you are not familiar with .dso, it is basically similar to .lib file in pre-Symbian OS 9 as well as in other platforms. It provides an interface from your code to the DLLs.

As I said above, the normal ELF file is too big for mobile phone's memory footprint. One way to reduce the size of ELF file is by replacing function name with an ordinal number. For example, MyFunction() can be replaced with ordinal number 1. As you might expect, it will reduce the ELF file sie significantly, especially if the function name is quite long, like ThisIsVeryLongFunction(). How does this mapping work? There is a definition file, .def, that provides mapping between ordinal number and function names.

Overview of E32Image

Now let’s take a look at the overview of E32Image. It looks like other standard executable formats; it contains header, code section, data section, import section, etc.

E32Image overview

Figure 2 shows the overview of E32 file format. It starts with the header, like all other executable format. I will explain what is insider the header in the next section. After the header there are some sections, i.e.:

  • Code section, contains all the object files (.o) of your source code as well as export address table that lists all the exported functions.

  • BSS section, contains un-initialized data.

  • Data section, contains initialized data.

  • Import section, contains the information about all imported functions used by your program.

  • Relocation section, contains relocation table needed by Symbian OS loader to load your program.

Header of E32Image

Header information may be the most interesting part because it contains many information about executable file. The declaration of E32ImageHeader can be found at \epoc32\include\f32image.h. Look at the E32ImageHeaderV class. This is the complete header that is usually used in E32Image file format. If you look at E32ImageHeaderV in more detail, you will find that this class is is derived from E32HeaderComp, which is derived from E32ImageHeader. Look at Figure 3 for better illustration.

E32ImageHeader

The following code snippets below shows the declaration of EImageHeader, EImageHeaderComp and E32ImageHeaderV. Note that I have removed some functions and comments from the original f32image.h to make it clearer.

class E32ImageHeader
{
public
:
TUint32 iUid1;
TUint32 iUid2;
TUint32 iUid3;
TUint32 iUidChecksum;
TUint iSignature; // 'EPOC'
TUint32 iHeaderCrc; // CRC-32 of entire header
TUint32 iModuleVersion; // Version number for this executable (used in link resolution)
TUint32 iCompressionType; // Type of compression used (UID or 0 for none)
TVersion iToolsVersion; // Version of PETRAN/ELFTRAN which generated this file
TUint32 iTimeLo;
TUint32 iTimeHi;
TUint iFlags; // 0 = exe, 1 = dll, 2 = fixed address exe
TInt iCodeSize; // size of code, import address table, constant data and export dir
TInt iDataSize; // size of initialised data
TInt iHeapSizeMin;
TInt iHeapSizeMax;
TInt iStackSize;
TInt iBssSize;
TUint iEntryPoint; // offset into code of entry point
TUint iCodeBase; // where the code is linked for
TUint iDataBase; // where the data is linked for
TInt iDllRefTableCount; // filling this in enables E32ROM to leave space for it
TUint iExportDirOffset; // offset into the file of the export address table
TInt iExportDirCount;
TInt iTextSize; // size of just the text section, also doubles as the offset for the
// iat w.r.t. the code section
TUint iCodeOffset; // file offset to code section, also doubles as header size
TUint iDataOffset; // file offset to data section
TUint iImportOffset; // file offset to import section
TUint iCodeRelocOffset; // relocations for code and const
TUint iDataRelocOffset; // relocations for data
TUint16 iProcessPriority; // executables priority
TUint16 iCpuIdentifier; // 0x1000 = X86, 0x2000 = ARM
};

class E32ImageHeaderComp : public E32ImageHeader
{
public
:
TUint32 iUncompressedSize; // Uncompressed size of file
// For J format this is file size - sizeof(E32ImageHeader)
// and this is included as part of the compressed data :-(
// For other formats this is file size - total header size
};

class E32ImageHeaderV : public E32ImageHeaderComp
{

public
:
SSecurityInfo iS;

// Use iSpare1 as offset to Exception Descriptor
TUint32 iExceptionDescriptor; // Offset in bytes from start of code section to Exception Descriptor,
// bit 0 set if valid
TUint32 iSpare2;
TUint16 iExportDescSize; // size of bitmap section
TUint8 iExportDescType; // type of description of holes in export table
TUint8 iExportDesc[1]; // description of holes in export table - extend
};

I will explain all the fields above one by one. The number at the left side of the following list is the offset from the beginning of the file. For example, iUid2 has the offset of 0x04 from the beginning of the file. It other words, if you open an executable file with binary editor and go to offset 0x04, you will find iUid2 there. Note that E32Image uses little-endian order.

E32ImageHeader

0x00: iUid1, is the first UID of the executable file. The first UID can be thought of as a system level identifier, for example 0x1000 0079 for DLLs and 0x1000 007A for executables. Please visit this page if you want to know more about Symbian OS' UID.

0x04: iUid2, is the second UID of the executable file. The second UID distinguishes between objects having the same iUid1, for example 0x1000 39CE for polymorphic interface DLLs and 0x1000 008d for static interface (shared library).

0x08: iUid3, is the third UID of the executable file. It is unique for each application. Developers have to request this UID from Symbian Signed service. Symbian OS 9 applications usually have UIDs in the range of 0x200 0000 and 0x2FFF FFFF. Examples from Symbian OS SDKs, like S60 SDK or UIQ SDK, have the UIDs in the range of 0xA000 0000 and 0xAFFF FFFF. There are also some UID available for testing, which can be chosen from the range 0x0100 0000 to 0x0FFF FFFF.

0x0C: iUidChecksum. is the checksum of the first three UIDs. There is a tool in Symbian SDK, called uidcrc.exe, which allows you to generate checksum from the first three UIDs. For example, the following command will generate checksum for the UIDs of 0x1000 007A, 0x1000 39CE and 0xA000 017F.

C:\>uidcrc 0x1000007A 0x100039CE 0xA000017F
0x1000007a 0x100039ce 0xa000017f 0x1e7cca07

0x10: iSignature, is a unique signature of E32 file, always has a value of 'EPOC'.

0x14: iHeaderCrc, is the checksum of entire header, calculated with CCITT CRC-32 algorithm.

0x18: iModuleVersion, is the version number for this executable. This information will be used during linking process. For S60 SDK 3.0, the value of iModuleVersion is 10 (= 0x0000 000A).

0x1C: iCompressionType, is the UID of the library used to compress the executable file. It is 0 if the executable is not compressed. From what I have seen so far, there is only one compression algorithm used, i.e. Deflate/Huffman as specified in RFC 1951. The UID for Deflate algorithm is KUidCompressionDeflate (=0x101F 7AFC). Note that there might be some more compression algorithm in the future.

0x20: iToolsVersion, is the version of ELFTRAN which generated this file.

0x24: iTimeLo, is the lowest word of the timestamp when the file is created.

0x28: iTimeHi, is the lowest word of the timestamp when the file is created.

0x2C: iFlags, flags for this executable. There are some flags defined in f32image.h, for example, KImageDll, KImageNoCallEntryPoint, etc. There are some functions defined in E32ImageHeader that are used to interpret these flags.

For example, if we find a flag with the value of 0x1200 002A. The value, 0x1200 002A, comes from 0x1000 0000 + 0x0200 0000 + 0x0000 00020 + 0x0000 0008 + 0x0000 00002. If we look at the constants declaration at f32image.h, we will find:

  • 0x10000000 = KImageImpFmt_PE, executable file uses ELF-derived import.

  • 0x02000000 = KImageHdrFmt_V, header with versioning support.

  • 0x00000020 = KImageEpt_Eka2, EKA2 executable file.

  • 0x00000008 = KImageABI_EABI, executable is EABI image file.

  • 0x00000002 = KImageNoCallEntryPoint, no call to entry point.

0x30: iCodeSize, is the size of code section, import address table, constant data and export dir.

0x34: iDataSize, size of initialised data.

0x38: iHeapSizeMin, the minimum size of the heap.

0x3C: iHeapSizeMax, the maximum size of the heap.

0x40: iStackSize, the size of the stack.

0x44: iBssSize, the size of the un-initialized data section.

0x48: iEntryPoint, offset into code of entry point.

0x4C: iCodeBase, where the code is linked for.

0x50: iDataBase, where the data is linked for.

0x54: iDllRefTableCount, the number of DLLs imported by this program.

0x58: iExportDirOffset, offset into the file of the export address table.

0x5C: iExportDirCount, the offset of the export address table.

0x60: iTextSize, size of just the text section, also doubles as the offset for the iat w.r.t. the code section.

0x64: iCodeOffset, file offset to code section, also doubles as header size.

0x68: iDataOffset, file offset to data section.

0x6C: iImportOffset, file offset to import section.

0x70: iCodeRelocOffset, relocations for code and const.

0x74: iDataRelocOffset, relocations for data.

0x78: iProcessPriority, executables priority.

0x7A: iCpuIdentifier, the identifier of CPU. Look at the following constant for all possible values:

enum TCpu
{
ECpuUnknown=0, ECpuX86=0x1000, ECpuArmV4=0x2000, ECpuArmV5=0x2001, ECpuArmV6=0x2002, ECpuMCore=0x4000
};

E32ImageHeaderComp

0x7C: iUncompressedSize, uncompressed size of file. Remember that E32Image file might be compressed.

E32ImageHeaderV

Before looking into this header, there are are two additional classes that we need to know, i.e. SCapabilitySet and SSSecurityInfo. Both of them are defined in e32cmn.h. They are introduced in Symbian OS 9 to store Platform Security information, such as capability, secure identifier and vendor identifier.

struct SCapabilitySet
{

enum
{ENCapW=2};
TUint32 iCaps[ENCapW];
};

struct SSecurityInfo
{

TUint32 iSecureId;
TUint32 iVendorId;
SCapabilitySet iCaps; // Capabilities re. platform security
};

Let’s go back to E32ImageHeaderV.

0x80: iS.iSecureId, is secure ID of the executable. It is usually the same as the UID3 of the executable.

0x84: iS.iVendorId, is vendor ID of executable. For third party application, it is usually 0.

0x88: iS.iCaps.iCaps, is capabilities needed to run the executable. The definition of all Symbian OS capabilities can be found at e32capability.h.

enum TCapability
{

ECapabilityTCB = 0,
ECapabilityCommDD = 1,
ECapabilityPowerMgmt = 2,
ECapabilityMultimediaDD = 3,
ECapabilityReadDeviceData = 4,
ECapabilityWriteDeviceData = 5,
ECapabilityDRM = 6,
ECapabilityTrustedUI = 7,
ECapabilityProtServ = 8,
ECapabilityDiskAdmin = 9,
ECapabilityNetworkControl = 10,
ECapabilityAllFiles = 11,
ECapabilitySwEvent = 12,
ECapabilityNetworkServices = 13,
ECapabilityLocalServices = 14,
ECapabilityReadUserData = 15,
ECapabilityWriteUserData = 16,
ECapabilityLocation = 17,
ECapabilitySurroundingsDD = 18,
ECapabilityUserEnvironment = 19,
};

The value of this capability indicates position of the bit. For example, ECapabilityTCB means the least significant bit (LSB). If LSB is set, then the executable has TCB capability.

0x90: iExceptionDescriptor, is offset in bytes from start of code section to Exception Descriptor, bit 0 set if valid

0x94: iSpare2, reserved.

0x98: iExportDescSize, size of bitmap section.

0x9A: iExportDescType[1], type of description of holes in export table.

0x9B: iExportDesc[1], is description of holes in export table.

Example

To have a better understanding what the above fields are all about, I will give a simple example. We will compile Hello World example of S60 SDK 3.0 and then take a look at the E32Image header. Firstly, you have to compile the Hello World Basic example with the standard Symbian OS build command.

C:\Symbian\9.1\S60_3rd\S60Ex\helloworldbasic>bldmake bldfiles

C:\Symbian\9.1\S60_3rd\S60Ex\helloworldbasic>abld build gcce urel

Now go to \epoc32
elease\gcce\urel
directory and use Elf2E32 tool to read the E32 image header information of your Hello World program. How to do that? Simply type the following command to run Elf2E32 on your Hello World.

C:\Symbian\9.1\S60_3rd\S60Ex\helloworldbasic>elf2e32 --e32input=helloworldbasic.exe

The command above will display header information, code section and import section of helloworldbasic.exe. The output might be quite long, so it’s good idea to dump it to a file so that you can analyze it easily:

C:\Symbian\9.1\S60_3rd\S60Ex\helloworldbasic>elf2e32 --e32input=helloworldbasic.exe > helloworldbasic.txt

If you open helloworldbasic.txt, at the beginning of the file, you will see header information. I have put some comments in green color to show which field of E32ImageHeaderV is referred by a specific line.

E32ImageFile 'helloworldbasic.exe'
V2.00(505) Time Stamp: 00e0eb0a,d2525b80 // iTimeStampHi, iTimeStampLo
EPOC Exe for ARMV5 CPU // iCpuIdentifier = 0x20001 (ARMv5)
Flags: 1200002a // iFlags
Priority Foreground
Entry points are not called
Image header is format 2
Image is compressed using the DEFLATE algorithm // iCompressionType
Uncompressed size 0000b788
Image FPU support : Soft VFP
Secure ID: a000017f // iSecureId
Vendor ID: 00000000 // iVendorId
Capabilities: 00000000 00000000 // iSs.iCaps.iCaps
Exception Descriptor Offset: 00002561 // iExceptionDescriptor
Exception Index Table Base: 00012dfc
Exception Index Table Limit: 000130bc
RO Segment Base: 00008001
RO Segment Limit: 0000a77c
Export Description: Size=000, Type=01 // iExportDescSize = 000 iExportDescType = 01

Export description consistent
Module Version: 10.0 // iModuleVersion
Imports are ELF-style
ARM EABI
Built against EKA2
Uids: 1000007a 100039ce a000017f (1e7cca07)
// iUid1 = 1000007a, iUid2 = 100039ce, iUid3 = a000017f, iUidChecksum = 1e7cca07
Header CRC: 023aca0d // iHeaderCrc
File Size: 0000b788 // iUncompressedSize
Code Size: 0000b0bc // iCodeSize
Data Size: 00000000 // iDataSize
Compression: 101f7afc // iCompressionType
Min Heap Size: 00001000 // iHeapSizeMin
Max Heap Size: 00100000 // iHeapSieMax
Stack Size: 00005000 // iStackSize
Code link addr: 00008000 // iCodeBase
Data link addr: 00400000 // iDataBase
Code reloc offset: 0000b650 // iCodeRelocOffset
Data reloc offset: 00000000 // iDataRelocOffset
Dll ref table count: 10 // iDllRefTableCount
Offset Size Relocs #Relocs
Code 00009c 00b0bc 00b650 00007d +002504 (entry pnt)
// iCodeOffset = 00009c iCodeSize = 00b0bc iCodeRelocOffset = 00b650
Data 000000 000000
// iDataOffset iDataSize
Bss 000000 // iBssSize
Import 00b158 // iImportOffset

That's all folks! I hope you enjoy this article.

Further Reading

Publications: