Symbian OS Executable File Format (E32Image)

This article explains the Symbian OS executable file format (E32Image), including the header information and how the executable sections are organized. Note that this article is not based on the official specification from Symbian. They don't release the E32 specification file format and there is not so much information about it. Fortunately Symbian has released the source code of PETRAN that helps us to figure out E32 file format.

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 Windows PE-COFF (Portable Executable Common Object File Format). See the links at the end of this article to find out more about PE-COFF. 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.

Overview

The executable files in Symbian OS, like .app, .exe or .dll, use a special format, called E32 file format. It is a little bit different with Microsoft/Intel's PE (Portable Executable) or UNIX's ELF (Executable and Linking Format), but basically they have some similarities. You might notice that actually Symbian OS uses GCC compiler, it means your source code is actually compiled to PE format. Why do we have E32 then? The answer is simple, because at the end of the Symbian OS build process, there is a tool, called PETRAN, which converts your PE file to E32 file.

E32Image Overview

Figure 1 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. After the header there are some sections, i.e.:

  • Code section, contains three parts, text section, export address table and import address table. The text section contains all the object files (.obj) of all your source code. The export address table lists all the exported functions you have in your program. The import address table lists all the imported functions used in your program.

  • BSS section, contains un-initialized data. This section is usually not available in most Symbian OS applications.

  • Data section, contains initialized data. Since most Symbian OS applications are .app or .dll format, this section usually does not exist either.

  • 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 Information

Now let's start to take a look at the E32 header. The following code is copied from the header file, E32Image.h, distributed by Symbian in their build tools (see the link at the end of this article).

class E32ImageHeader
{

public
:
TUint32 iUid1;
TUint32 iUid2;
TUint32 iUid3;
TUint32 iCheck;
TUint iSignature; // 'EPOC'
TCpu iCpu; // 0x1000 = X86, 0x2000 = ARM, 0x4000 = M*Core
TUint iCheckSumCode; // sum of all 32 bit words in .text
TUint iCheckSumData; // sum of all 32 bit words in .data
TVersion iVersion;
TInt64 iTime;
TUint iFlags; // 0 = exe, 1 = dll, +2 = no call entry points
TInt iCodeSize;
// size of code, import address table, constant data and export dir
TInt iDataSize; // size of initialized 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
TUint iCodeOffset; // file offset to code section
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
TProcessPriority iPriority; // priority of this process
};
  • TUint32 iUid1, TUint32 iUid2, TUint32 iUid3, represent the identifiers that uniquely identify your program.
    The first UID can be thought of as a system level identifier, for example 0x10000079 for DLLs and 0x1000007A for executables.
    The second UID distinguishes between objects having the same iUid1, for example 0x100039CE for polymorphic interface DLLs and 0x1000008d for static interface (shared library).
    The third UID can be thought of as a program identifier. Usually you have to ask the third identifier directly to Symbian OS by sending an email to them. However, there are also some UID for testing, which can be chosen from the range 0x0100000 to 0x0FFFFFF.

  • TUint32 iCheck, is the checksum of the first three UIDs. There is a tool in Symbian OS 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 0x1000079, 0x1000029CE and 0x00DD3103.

	C:\>uidcrc 0x10000079 0x100039ce 0x00dd3103
0x10000079 0x100039ce 0x00dd3103 0xae035303
  • TUint iSignature, is a unique signature of E32 file, always has a value 'EPOC'.

  • TCpu iCpu, the platform of the programs, usually has a value ECpuX86 = 0x1000 for the Intel processor and ECpuArm = 0x2000 for the ARM processor.

  • TUint iCheckSumCode, is checksum of the code section. According to the comments in the header files, this is the sum of all 32 bit words in .text section. However, from my experience it is actually the sum of all 32 bits words in the code section, including import address table and export table (= iCodeSize field).

  • TUint iCheckSumData, is checksum of all 32 bit words in the .data section. Since we usually build our programs to .app or .dll in Symbian OS, this checksum is usually zero.

  • TVersion iVersion, the version of PETRAN used to generate this E32 file. For example, if you are using UIQ 2.1, the version is 1.00(175).

  • TInt64 iTime, is the timestamp when the program was built.

  • TUint iFlags, is a flag with the value 0 for .exe, 1 for .dll and +2 for no call of entry points.

  • TInt iCodeSize, the size of the code section, including import address table, constant data and export address table.

  • TInt iDataSize, the size of the initialized data section. Like iCheckSumData, for most cases we will find that this field has the value 0.

  • TInt iHeapSizeMin, the minimum size of the heap.

  • TInt iHeapSizeMax, the maximum size of the heap.

  • TInt iStackSize, the size of the stack.

  • TInt iBssSize, the size of the un-initialized data section.

  • TUint iEntryPoint, the offset into code of entry point.

  • TUint iCodeBase, where the code is linked for.

  • TUint iDataBase, where the data is linked for.

  • TInt iDllRefTableCount, the number of DLLs imported by this program.

  • TUint iExportDirOffset, the offset of the export address table.

  • TInt iExportDirCount, the number of functions exported by this program.

  • TInt iTextSize, the size of the text section, i.e. the size of all the object files compiled from your source code.

  • TUint iCodeOffset, the file offset to the code section.

  • TUint iDataOffset, the file offset to the data section. As I have explained above, since we usually deal with .app or .dll, this field usually has the value 0.

  • TUint iImportOffset, the file offset to the import section.

  • TUint iCodeRelocOffset, relocation offset for code and constant.

  • TUint iDataRelocOffset, relocation offset for data.

  • TProcessPriority iPriority, the priority of this process, for normal applications the value is EPriorityForeground, it means priority for the foreground tasks.

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 UIQ SDK 2.1 and then take a look at the E32 image header. Firstly, you have to compile the Hello World example with the standard Symbian OS build command.

C:\Symbian\UIQ_21\examples\HelloWorld>bldmake bldfiles

C:\Symbian\UIQ_21\examples\HelloWorld>abld build armi urel

Now go to \epoc32
elease\armi\urel
directory and use PETRAN tool to read the E32 image header information of your Hello World program. You can of course write your own program and read the header files. Here I use PETRAN to make our life easier.

As I have explained above, PETRAN is basically a tool that converts PE file format (the output of GCC compiler) to Symbian OS specific file format (E32 file format). You can also use PETRAN to display the information of any E32 files, like we are going to do. Simply type the following command to run PETRAN on your Hello World.

C:\Symbian\UIQ_21\epoc32
elease\armi\urel>petran helloworld.app

You will get the following result in your screen. I do not copy the whole output since some of the outputs are not really important for us. The comments in green color explain which fields of E32 image header the information represent of.

PETRAN - PE file preprocessor V01.00 (Build 175)
Copyright (c) 1996-2001 Symbian Ltd.

E32ImageFile 'helloworld.app' // file name (not in E32 image header)
V1.00(175) Time Stamp: 00e0be89,69063b40 // iVersion iTime
EPOC Dll for ARM CPU // iCpu
Entry points are not called // iFlags
Uids: 10000079 100039ce 10008ace (7ec529db) // iUid1, iUid2, iUid3 and iCheck
File Size: 00001368 // file size (not in E32 image header)
Code Size: 00000ed8 // iCodeSize
Data Size: 00000000 // iDataSize
Chk code/data: d4ad460a/00000000 // iCheckSumCode iCheckSumData
Min Heap Size: 00001000 // iHeapSizeMin
Max Heap Size: 00100000 // iHeapSizeMax
Stack Size: 00002000 // iStackSize
Code link addr: 10000000 // iCodeBase
Data link addr: 00000000 // iDataBase
Code reloc offset: 00001194 // iCodeRellocOffset
Data reloc offset: 00000000 // iDataRellocOffset
Dll ref table count: 4 // iDllRefTableCount
Offset Size Relocs NumOfRelocs
Code 00007c 000ed8 // iCodeOffset, iCodeSize
001194 0000e1 +000000 (entry pnt) // iCodeRellocOffset .. iEntryPoint
Data 000000 000000 // iDataOffset iDataSize
Bss 000000 // iBssSize
Export 000f50 000004 (1 entries) // iExportDirOffset iExportDirCount
Import 000f54 // iImportOffset

Code (text size=00000d08) // iTextSize

... // here the dump of the text section

225 relocs

... // here the dump of the relocation section

Idata Size=00000240
Offset of import address table (relative to code section): 00000d08

... // here the import tables information

Figure 2 below shows the sections in Hello World application are structured. On the left side you can see the offset of each section. For example, iCodeOffset (= 0x7C) is the offset of the code section. I won't explain the offsets in more details because all information is stored in the header.

E32Image Example

On the right side you can see the size of each section. The size is a little bit tricky because not all information is stored in the header. The size of E32ImageHeader can be calculated easily using sizeof operator. The size of text section is straightforward as well since the information is available in the header, iTextSize. The same holds for the size of code section, iCodeSize.

Calculating the size of the export table is a little bit more complicated. You need to multiple iExportDirCount to sizeof(UINT). Why? Because each exported function is stored in unsigned integer. In this example you have only 1 exported function, multiplied by sizeof(UINT) and you will get the 4.

Calculating the size of import address table needs some knowledge how the import tables are structured in Symbian OS. Basically you need to find out how many functions are imported by your program, in this example 114. How do we get this number? If you look at iDllRefTableCount, the Hello World example imports 4 DLLs, to get the number 114 you have to go through these DLLs and count the imported functions for each DLL. Like the export table, you need to multiply it by sizeof(UINT). Note that we have to add 1 to the number of imported functions because there is a null terminated sign at the end of the import table.

The size of import section, 0x0240 can be read from the first 32 bits of the import section. If you open E32Image.h, you will find a structure, called E32ImportSection, with one field, iSize.

class E32ImportSection
{
public:
TInt iSize;
// size of this section
// E32ImportBlock[iDllRefTableCount];
};

This class is the first 32 bits in the import section and iSize is the size of the import section I have another article that describes the structure of E32 import section (read also reading E32 import table).

Like import section, the size of relocation section can be read from the first 32 bits of the relocation section. The structure that stores this information is E32RelocSection, with two fields, i.e. iSize and iNumberOfRelocs.

class E32RelocSection
{
public:
TInt iSize;
// size of this relocation section
TInt iNumberOfRelocs; // number of relocations in this section
};

The field iSize in our Hello World example is 0x01CC. Why do we need to add sizeof(E32RelocSection) to the size of relocation section? Because E32RelocSection.iSize does not include the size of the relocation header. It is a little bit different with the import section because E32ImportSection.iSize has already included the import header.

That's all folks! I hope you have a better understanding of E32 file structure after reading this article.

Further Reading

Publications: