Jump to content

Symbian OS: The Loader


Recommended Posts

The file server process contains a second server, the loader server, whose purpose is to load executables (that is, DLLs and EXEs). It runs in a separate thread from the main file server and is implemented using the Symbian OS client-server framework.

In this chapter I describe the process of loading executables and the management of executable code in Symbian OS. In Section 10.3, I show how the loader server operates: how it handles client requests, how it interacts with the file server and I introduce some of the kernel-side code management services. In Section 10.4, I describe kernel-side code management in more detail.

However, before any of this, we should start by examining the format of the executable files that the loader has to handle.

_________________________________________________________________________________________________________________________________________

E32 image file format

This is the Symbian OS executable file format. Symbian provides tools to convert from standard object file formats into the E32 image format: you would use the PETRAN pre-processing tool to convert PE format files (as output by GCC98r2 for example). Similarly, you would use ELFTRAN to convert from the ELF format files produced by ARM's RealView compiler.

You can configure these conversion tools to produce compressed executable files and it is the responsibility of the loader to detect a compressed executable and decompress it on loading.

E32 image files consist of up of nine sections, in the order specified as follows:

1) The image header The E32ImageHeader. I describe this in Appendix 2, The E32Image-Header.

2) Code section - .text This section contains the executable code.

3) The constant data section - .rdata This section contains constant (read-only) data. It doesn't exist as a separate entity in an E32 image file. It may exist in the PE or ELF format file from which the E32 image file is derived, but the tools then amalgamate it into the .text section.

4) The import address table (IAT) This table contains an entry for each function imported by the executable, as follows:

Offset                               Description

00                               Ordinal of import 1

04                                 Ordinal of import 2

. . .

4(n - 1)                         Ordinal of import n

4n                                     NULL

For each function that the executable imports, the file will contain an import stub function within the code section. When executed, each stub will load the program counter with the value from the corresponding entry of the IAT.

Therefore, when the executable is loaded, the loader has to fix up each entry in the IAT, replacing the ordinal number with the run address of the imported function. It has to calculate the address using the contents of the .idata section of this executable together with the .edata section of the DLL that exports the function requested.

Note that executables originating from ELF format files don't contain an IAT. For these, the tools fix up the import stubs directly - they obtain the location of the stub from the import data section.

The order of the entries in the IAT corresponds precisely with the order that imports are listed in the .idata section.

5) The export directory - .edata The export directory is a table supplying the address of each function exported from this executable. Each entry holds the start address of the function as an offset relative to the start of the code section:

00 Address of 1st function exported from this executable.

04 Address of 2nd function exported from this executable.

. . .

4n - 4 Address of last function exported from this executable.

The order of exports in the table corresponds to the order in the DEF file for this executable. The table is not null terminated. Instead, the number of entries in the table is available from the file's image header.

6) Initialized data section - .data This section contains the initialized data values that are copied to RAM when this executable runs.

7) Import data section - .idata This section contains data on each function that this executable imports. The loader uses this information to identify each referenced DLL that it also needs to load. Additionally, the loader uses this information to fix up each entry in the import address table (IAT).

The format of this section is as follows:

Field                                   Description

Size                                   A word holding the size of this section in bytes (rounded to 4-byte boundary).

Import block for DLL1

Import block for DLL2

. . .                                       . . .

Import block for DLLn

Name of DDL1                NULL terminated ASCII string.

Name of DLL2                 NULL terminated ASCII string.

. . . . . .

Name of DLLn                   NULL terminated ASCII string.

As well as the file name itself, the DLL name string also includes the required third UID. If the file was built with the EKA2 tool set, the name string will also contain the required version number of the DLL (see Section 10.3.1). The loader will have to match all of these when it searches for the imported DLL. The format of the name is as follows, with UID and version in hexadecimal:

<filename>{versionNum}[uid3]<extension>

for example, efsrv{00010000}[100039e4].dll

8) Code relocation section This section contains the relocations to be applied to the code section. The format of the table is shown next:

Offset Description

00H The size of the relocation section in bytes (rounded to 4-byte boundary).

04H Number of relocations.

08H Relocation information.

0CH Relocation information.

. . . . . .

Nn 00000H

The format used for the relocation information block differs slightly to the standard Microsoft PE format. It consists of a number of sub-blocks, each referring to a 4 KB page within the section concerned. Each sub-block is always a multiple of 4 bytes in size and has the following format:

Offset Description

00H The offset of the start of the 4 KB page relative to the section being

relocated.

04H The size of this sub-block in bytes.

08H 2 byte sub-block entry.

The top 4 bits specify the type of relocation:

0 - Not a valid relocation.

1 - Relocate relative to code section.

2 - Relocate relative to data section.

3 - Try to work it out at load time (legacy algorithm).

The bottom 12 bits specify the offset within the 4 K page of the item to be relocated.

0AH 2 byte sub-block entry.

. . . . . .

9) Data relocation section This section contains the relocations to be applied to the data section.

The format of the table is the same as that for code relocations.

The nine sections that I have just listed apply to the structure of the executable files as they are stored on disk. However, once an executable has been loaded by the Symbian OS loader, it consists of two separately relocatable regions (or sections). The first is the code section, which includes the code, import stubs, constant data, IAT (if present) and the export directory (.edata). The second is the data section, which includes the un-initialized data (.bss) and initialized data (.data).

Link to comment
Share on other sites

ROM image file format

The format that I described previously applies to executables that are located on drives which are not execute-in-place (XIP) - for example, executables contained within a NAND Flash ROFS image, on the user-data drive (C: ) or a removable media disk (D: ). On such media, executable code first has to be loaded into RAM (at an address which is not fixed beforehand) before it can be run. However, by definition, for executables stored in XIP memory (such as ROM) there is no need to load the code into RAM for execution.

The ROMBUILD tool assembles the executables and data files destined for the ROM into a ROM image. The base address of the ROM and the address of the data sections are supplied to this tool as part of the ROM specification (that is, the obey file) when it is run. In fact, for certain executables, ROMBUILD itself calculates the address of the data sections. These include fixed processes, variant DLLs, kernel extensions, device drivers and user-side DLLs with writeable static data. With this information, ROMBUILD is able to pre-process the executables, perform the relocations and fix up the import stubs. The result is that on XIP memory, executables have a format that is based on E32 image format but differs in certain ways, which I will now list:

They have a different file header, TRomImageHeader, which is described in [[symbian OS Internals/Appendix 3: The TRomImageHeader 3]], The TRomImageHeader.

They have no IAT; it is removed and each reference to an IAT entry is converted into a reference to the associated export directory entry in the corresponding DLL.

They have no import data (.idata) section; it is discarded.

They have no relocation information; it is discarded.

They include a DLL reference table after the .data section. This is a list of any libraries referenced, directly or indirectly, by the executable that have static data. In other words, libraries with initialized data (.data) or un-initialized data (.bss) sections.

The file header contains a pointer to the start of this table. For each such DLL referenced in the table, the table holds a fixed-up pointer to the image header of the DLL concerned. See the following table:

Offset Description

00H Flags.

02H Number of entries in table.

04H Image header of 1st DLL referenced.

08H Image header of 2nd DLL referenced.

. . . . . .

nn Image header of last DLL referenced.

These differences mean that the size of these files on ROM is smaller than the corresponding E32 image file size.

Another consequence of the pre-processing of the IAT and the removal of the import section for ROM files is that it is not possible to over-ride a statically linked DLL located in ROM by placing a different version of this referenced DLL on a drive checked earlier in the drive search order, such as C:.

Link to comment
Share on other sites

The loader server

The RLoader class provides the client interface to the loader and is contained in the user library, EUSER.DLL. However, user programs have no need to use this class directly - and indeed they must not use it since it is classified as an internal interface. Instead the RLoader class is used privately by various file server and user library methods. These include:

  • RProcess::Create() - starting a new process

    RLibrary::Load() - loading a DLL

    User::LoadLogicalDevice() - loading an LDD

    RFs::AddFileSystem() - adding a file system.

RLoader is derived from RSessionBase and is a handle on a session with the loader server. Each request is converted into a message that is sent to the loader server.

Unlike the file server, the loader server supports only a small number of services. These include:

  • Starting a process - loading the executable concerned Loading a DLL

    Getting information about a particular DLL. (This includes information on the DLL's UID set, security capabilities and module version)

    Loading a device driver

    Loading a locale

    Loading a file system or file server extension.

Link to comment
Share on other sites

Loading a process

The kernel's involvement in process loading begins after the loader has completed its search for the requested executable image and decided which of the available files should be used to instantiate the new process. The loader will have created an E32Imageobject on its heap, to represent the new image file it is loading. The loader then queries the kernel to discover if the selected image file is already loaded. The E32Loader::CodeSegNext() API, which, like all the E32Loader functions, is a kernel executive call, is used for this purpose. If the selected image file is an XIP image, the loader will already have found the address of the TRomImageHeader describing it. In this case, the kernel will search for a code segment derived from the same TRomImageHeader. If the selected image file is not an XIP image, the loader will know the full path name of the image file and will have read its E32Image header.

The kernel will search for a code segment with the same root name, same UIDs and same version number. In either case, if the kernel finds that the code segment is already loaded, it returns a code segment handle to the loader. This is not a standard Symbian OS handle but is actually just the pointer to the kernel-side DCodeSeg object. The loader then calls E32Loader::CodeSegInfo() on the returned handle; this populates the E32Image object with information about the code segment being loaded, including full path name, UIDs, attributes, version number, security information, code and data sizes, code and data addresses and export information. The loader then calls E32Loader::CodeSegOpen() on the handle. This call checks the EMarkLdr flag on the code segment; if this flag is clear, it sets the flag and increments the reference count of the code segment. The E32Loader::CodeSegOpen() function then performs the same operation recursively on all code segments in the sub-graph below the original one. At the end of this, the loader has a reference on the code segment and on all the code segments upon which it depends, so these will not be deleted.

If the kernel does not find the selected code segment, the loader populates the E32Image object with information read from the E32Image header of the selected file.

The loader then calls E32Loader:: ProcessCreate(), passing in the E32Imageobject; the latter derives from TProcessCreateInfo, which contains all the information the kernel needs to create a new process. Figure 10.2 illustrates the relationship of these classes.

The kernel-side handler, ExecHandler:: ProcessCreate(), verifies that the calling thread belongs to the F32 process and then does some argument marshaling to pass the parameters over to the kernel side. It then calls Kern::ProcessCreate() to do the actual work; this function is also used by the startup extension (EXSTART.DLL) to create the F32 process after the kernel has booted.

After E32Loader:: ProcessCreate() completes, if a new code segment has been created, the loader needs to load and relocate the code (if not XIP) and load all DLLs to which the new EXE code segment implicitly links - see Section 10.4.4 for more details of this. Finally, after it has resolved all dependencies, the loader calls E32Loader:: ProcessLoaded(). This performs the following actions:

If a new EXE code segment was created for the process, the kernel calls DCodeSeg::Loaded() on it. This performs steps 1 to 3 of the CodeSegLoaded() function, described at the end of Section 10.4.4

  • The kernel maps the EXE code segment and all the code segments on which it depends into the process and increments their reference counts

    The kernel sets the EMarkLoaded flag of the EXE code segment to enable it to be reused to launch another instance of the process

    The kernel changes the first thread's M-state to DThread::EReady; it is now ready to be resumed

    The kernel creates a handle from the loader's client to the new process; the client will eventually use this handle to resume the new process.

    After E32Loader:: ProcessLoaded() has completed, the loader copies the new handle to the process back to the client, and cleans up the E32Image object corresponding to the new process. Finally, it completes the client request and control returns to the client.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...