A shared library is an executable module that is compiled and linked separately. Like all executable modules, a shared library is contained in a PRC file that is installed into either the system storage heap or some type of external storage media. After being installed to the storage heap, a PRC becomes a resource database that contains several resources including:
- The executable module's code segment.
- A copy of the module's initialized static data.
- Relocation information.
Executable modules residing in the system storage heap are executed in place, while those in external storage media must be copied into the storage heap before being executed. In either case, the runtime environment must be set up before a code segment can be executed correctly. The runtime services are responsible for preparing the necessary runtime environment for an executable module. They are also responsible for cleaning up the runtime environment after an executable module has exited.
The Palm OS Cobalt runtime services consist of the following components:
- Program Loader
- Sets up the necessary runtime environment for executable modules. It's also involved in the process of applying patches when loading patchable shared libraries.
- Dynamic Linker
- Resolves function calls across executable module boundaries (for instance, from an application to a shared library).
- Boot/Process Startup Code
- Sets up the initial runtime environment at boot time and each time a new process is started.
Applications can make calls to shared libraries, which are separately compiled and linked executable modules. Shared libraries loaded by an application are executed as subroutines of that application. Consequently, shared libraries are confined by the boundary of the calling application's process.
The operating system is presented to an application as a set of shared libraries. Some operating system shared library functions invoke software interrupts (SWIs) internally, which effectively transfer control to the kernel. For the most part, applications do not invoke SWIs directly. As far as an application is concerned, operating system calls are simply subroutine calls to shared libraries.
Static data of applications and shared libraries have per-process instances. This is also known as process-own data. Process-own data enables the same program to execute in multiple processes simultaneously, each with independent state.
The term "executable module" encompasses applications, shared libraries, and plug-ins—manually-loaded shared libraries. From the runtime services' point of view, applications, shared libraries, and plug-ins are no different. An executable module:
- must have executable instructions in its code segment.
- must have an identifiable main entry point in its code segment. This main entry point must have the following C prototype:
uint32_t PilotMain(uint16_t cmd, void *cmdPBP, uint16_t launchFlags)
- may have zero or more additional exported entry points in its code segment. Exported entry points can have arbitrary C prototypes.
- may have a data segment to hold its state (static data) at runtime.
Each executable module can be given a type, such as "application" or "shared library," when it's placed into a container (PRC file). However, these types are of interest only to higher-level operating system services. Executable modules of all types are treated by the runtime services without regard to type.
The following features also apply to executable modules in Palm OS Cobalt:
- All executable modules can always have static data. C static and global variables can be used freely in programs. The runtime services allocate, initialize and de-allocate memory for such variables automatically.
- Because shared libraries have main entry points, they are "launchable" and so able to register for and receive notifications as applications do.
- Every executable module receives a
sysLaunchCmdInitialize
launch code right after it is loaded and asysLaunchCmdFinalize
launch code right before it is unloaded by the program loader. This gives the program a chance to perform customized initialization and de-initialization.
Exporting Globals
Palm OS Cobalt executable modules cannot export global data directly. However, the runtime services do provide means for exporting data indirectly, at a procedure call level.
An executable module that wants to make all or part of its globals accessible by other modules can do this by putting those globals in a single C structure and defining this structure as part of its external API. The executable module should handle the sysLaunchCmdGetGlobals
launch code in its PilotMain()
function by returning the address of this global structure in the memory location pointed to by the cmdPBP
parameter of PilotMain()
. When, in order to to get an executable's globals, an application calls SysGetModuleGlobals()
and passes the reference number of a module that implements sysLaunchCmdGetGlobals
, the function returns either the address of the global structure or the address of the module's data segment, depending on the value of the wantStructure
parameter.
Whether SysGetModuleGlobals()
is able to return the address of the globals structure depends on whether the module identified by refNum
defines such a structure and returns its address in response to the sysLaunchCmdGetGlobals
launch code. This function returns sysErrNotSupported if wantStructure
is true
and the module doesn't allow the globals structure address to be retrieved. (To prevent globals from being retrieved, simply return NULL
in response to a sysLaunchCmdGetGlobals
launch code.)
If wantStructure
is false
when SysGetModuleGlobals()
is called, the base address of the module's data segment is returned. For executable modules that don't support the sysLaunchCmdGetGlobals
launch code, and for those that return NULL
in response to this launch code, this is the only way to gain access to the shared library's data. However, in this case the caller will have to possess sufficient knowledge on the memory map of the shared library's data segment in order to access data items located at certain offsets.
NOTE: If when
wantStructure
is false
the returned base address of the module's data segment is NULL
, the module has no static data.
Patching Shared Libraries
Patch Configuration Database
When multiple patches exist on the same shared library entry point, calls to that entry point will go through a chain of functions. Each of the functions in the chain can do something interesting and optionally, at some point, invoke the next function in the chain. The patch's configuration information determines the order in which it should be called. The program loader manages this information in a patch configuration database, and provides a set of APIs for manipulating it.
Constructing a Patch
A patch is an executable module with its own code and data segments. Besides the common 'acod'
(code) and 'adat'
(data) resources, each patch has an additional 'amdp'
(ARM Module Patch) resource.
Within the 'acod'
resource of a patch, like that of a shared library, there is an embedded vector table, each entry of which points to a patching function within the same 'acod'
resource. However, the vector table of a patch is different from that of a shared library in that its entries are not arranged in the order of increasing index numbers. The 'amdp'
resource supplies information for the program loader to know which entry in the patch's vector table applies to which entry of which shared library.
An 'amdp'
resource is simply an array of SysPatchTargetType
structures; these structures look like this:
typedef SysPatchTargetType { SysPatchTargetHeaderType header; SysPatchEntryNumType entryNums[]; } SysPatchTargetType;
Each SysPatchTargetType
structure identifies a set of entry points of a shared library that this patch wants to patch. This implies that a single patch can target multiple shared libraries simultaneously. Each element of the entryNums
array corresponds to a patching function whose address is listed in this patch's own vector table. The value of the element is the entry number of the shared library that the corresponding patching function is targeting.
The SysPatchTargetType
structure contains a header field that identifies the shared library being patched. This header is defined as follows:
typedef SysPatchTargetHeaderType { uint32_t type; uint32_t creator; uint16_t rsrcID; uint16_t flags; uint32_t numEntries; } SysPatchTargetHeaderType;
The type
, creator
, and rsrcID
fields identify the type, creator ID, and resource ID of the shared library being patched. The numEntries
field identifies the number of entries in the SysPatchTargetType
structure's entryNums
array. The remaining field, flags
, has two bits defined. These bits indicate whether the patch must be the head or the tail in the call chain. Normally, a patch should work properly regardless of its position within the call chain, so these flags are very rarely used. Use these flags only when absolutely needed, and use them with caution; just because you request that a patch be placed at a particular position doesn't guarantee that it will be placed there.
You build a patch just as you would a normal shared library. Just make sure that you construct the 'amdp'
resource properly. For instance, the following steps show how you might patch SystemMain()
, SysGetEvent()
, and SysHandleEvent()
of SystemLib
.
- Construct the patch's shared library definition file, as shown Listing 6.1.
Listing 6.1 A sample shared library definition file
EXPORTS MySystemMain; name of my patching function MySysGetEvent; name of my patching function MySysHandleEvent; name of my patching function
- Build an
'amdp'
resource. Listing 6.2 shows how to do this.
Listing 6.2 Defining an 'amdp' resource
typedef MyPatchRsrcType { SysPatchTargetHeaderType system; SysPatchEntryNumType systemEntries[3]; } MyPatchRsrcType; const MyPatchRsrcType myPatchRsrc = { { 'prdm', // type of SystemLib 'psys', // creator ID of SystemLib 0, // resource ID of SystemLib 3, // number of entries to patch 0 // flags - no requirement to be head or tail }, { kEntryNumMain, // entry number of SystemMain 3, // entry number of SysGetEvent 7 // entry number of SysHandleEvent } };
When constructing your 'amdp'
resource, note that the entry numbers of the patched shared libraries must appear in exactly the same order as the corresponding patching functions in the shared library definition file. Also, the entry numbers of any patched shared library must appear in the 'amdp'
resource in the order of increasing entry numbers. If the main entry point is patched, it must precede any other entries.
- Package the
'acod'
(code),'adat'
(data), and'amdp'
resources, all with the same resource ID, in a single PRC of type'apch'
using your creator ID.
If this sample patch is installed onto a device, it will be loaded whenever the target shared library (SystemLib
, in this example) is loaded into a process in which this patch is allowed to run.
Registering the Patch
You don't have to register a patch if it is packaged in a PRC of type 'apch'
. The program loader automatically recognizes patches of this type.
If a patch is packaged in a PRC whose type is not 'apch'
, you must call SysRegisterPatch()
to register it with the program loader. Such patches aren't loaded if they are not registered. Note that you don't have to un-register a patch when it is deleted from the storage heap since the program loader automatically un-registers a registered patch if it can't find it in the storage heap.
The Patch Call Chain
Using the 'amdp'
resources, the program loader builds a chain of functions for each patched shared library entry. The program loader maintains such call-chain information for every patched shared library entry point. It also provides a means for each patch to retrieve the address of the next function in the chain.
Each patch has its own main entry point, PilotMain()
. Like the main entry point of any executable module, the PilotMain()
of a patch receives sysLaunchCmdInitialize
and sysLaunchCmdFinalize
launch codes when the patch is loaded and unloaded. As well, a patch receives a third launch code—sysPatchLaunchCmdSetInfo
—once for each of the shared libraries it patches. The launch code's cmdPBP
parameter is a pointer to a SysPatchInfoType
structure, which provides information about the patched shared library that is being loaded.
The sysPatchLaunchCmdSetInfo
launch code tells the patch that one of the shared libraries it wants to patch is being loaded, providing the patch with a good opportunity to retrieve and save addresses of functions in the next patch in the call chain. Taking the patch used to illustrate how you construct a patch in "Constructing a Patch" as an example, its PilotMain()
function might handle the sysPatchLaunchCmdSetInfo
launch code like this (note that this patch is patching two shared libraries: DALLib and SystemLib):
Listing 6.3 Getting and saving function addresses from the next patch in the call chain
// These static variables save pointers to globals of the patched libraries static void *gOriginalDALGlobalsP; static void *gOriginalSystemGlobalsP; // These static variables save function pointers of next patches static KALThreadCreateProcType *gNextKALThreadCreateP; static KALThreadDestroyProcType *gNextKALThreadDestroyP; static KALThreadStartProcType *gNextKALThreadStartP; static SysMainEntryType *gNextSystemMainP; static SysGetEventProcType *gNextSysGetEventP; static SysHandleEventProcType *gNextSysHandleEventP; UInt32 PilotMain(UInt32 cmd, MemPtr cmdPBP, UInt16 launchFlags) { switch(cmd) { case sysPatchLaunchCmdSetInfo: { UInt32 refNum = (SysPatchInfoType*)cmdPBP->refNum; UInt32 myIndex = (SysPatchInfoType*)cmdPBP->index; UInt32 type = (SysPatchInfoType*)cmdPBP->type; UInt32 creator = (SysPatchInfoType*)cmdPBP->creator; UInt32 rsrcID = (SysPatchInfoType*)cmdPBP->rsrcID; Err (*getNextPatchFuncP)(UInt32, UInt32, UInt32, void **) = (SysPatchInfoType*)cmdPBP->sysGetNextPatchP; if(type == 'boot' && creator == 'psys' && rsrcID == 0) { // DALLib is being loaded // Save pointer to original globals so that we can // access them later SysGetModuleGlobals(refNum, &gOriginalDALGlobalsP); // Get and save pointers to next procedures in the call chain (*getNextPatchFuncP)(refNum, kEntryNumKALThreadCreate, myIndex+1, &gNextKALThreadCreateP); (*getNextPatchFuncP)(refNum, kEntryNumKALThreadDestroy, myIndex+1, &gNextKALThreadDestroyP); (*getNextPatchFuncP)(refNum, kEntryNumKALThreadStart, myIndex+1, &gNextKALThreadStartP); } else if(type == 'boot' && creator == 'psys' && rsrcID == 1) { // SystemLib is being loaded // Save pointer to original globals so we can access them later SysGetModuleGlobals(refNum, &gOriginalSystemGlobalsP); // Get and save pointers to next procedures in the call chain (*getNextPatchFuncP)(refNum, kEntryNumMain, myIndex+1, &gNextSystemMainP); (*getNextPatchFuncP)(refNum, kEntryNumSysGetEvent, myIndex+1, &gNextSysGetEventP); (*getNextPatchFuncP)(refNum, kEntryNumSysHandleEvent, myIndex+1, &gNextSysHandleEventP); } break; } } }
For more information on the functions used in the above example, see Chapter 30, "Loader."
Once the call chain function pointers have been saved as illustrated above, when a patching function wants to invoke the next function in the call chain it can simply use the appropriate saved function pointer.
Patches are always loaded and unloaded along with the original shared library. During loading, the original shared library is loaded first, and then the patches are loaded in the reverse of the order that they occur in the call chain (that is, the first patch in the call chain is the last loaded). During unloading, the patches are unloaded in the order in which they occur in the call chain (the first patch in the call chain is the first to be unloaded), after which the original shared library is unloaded. Note that both the original shared library and the patches receive sysLaunchCmdInitialize
and sysLaunchCmdFinalize
launch codes. If the PilotMain()
of a shared library is patched, then the patching function is invoked before that of the shared library, just like any other patched entry.
Adding and Removing Patches
Patches can be added to or removed from the patch configuration database at any time with SysRegisterPatch()
and SysUnregisterPatch()
. The program loader builds the call chains for a shared library only while loading that shared library for the first time in a process. It never tries to modify a shared library's call chains while that shared library is running. Thus, changes made to the patch call chain for a running shared library won't take effect until the shared library is unloaded and then reloaded.
When adding or removing a patch from the patch configuration database, you must take the following into account in order for the new configuration to take effect:
- If the shared library being patched is not yet loaded in any process, nothing further has to be done. The new configuration will take effect automatically when that shared library is loaded.
- If the shared library being patched is already loaded in any process, the new configuration will not take effect in that process until the shared library is unloaded and then reloaded. If it is important that the new configuration take effect immediately, the best thing to do is to restart that process.
- If the shared library being patched is one of the modules involved in the boot sequence (such as
SystemLib
), the whole device must be reset in order for the new configuration to take effect.
After a patch is installed onto the system, unless it can be determined that the target shared libraries are not currently running the patch usually needs to request a soft reset of the device in order for it to be applied.
NOTE: In order to run in a secure process, a patch must be digitally signed by a party that is trusted by that process.
Patch Security
Earlier releases of Palm OS allowed any program to patch operating system entry points without restriction. Palm OS Cobalt restricts the patching of operating system entry points in that:
- Only patches signed by trusted parties are applied in secure processes.
- Not all of the operating system entry points are patchable. Non-patchable entry points include operating system functions that execute in supervisor mode (kernel functions, interrupt service routines, and the like).
The Program Loader
The program loader is responsible for loading and unloading executable modules in the calling process. It also provides a means for retrieving information about executable modules and a means for patching shared library entries.
Program Loader Library Functions
The program loader is implemented as a set of library functions exported by one of the core operating system shared libraries. The library functions perform most of their operations within the context of the calling process, but they also interact with the Application Manager thread (in the System process) to perform certain security-related operations.
See Chapter 30, "Loader," for a complete description of each of the loader APIs.
Shared Libraries and the Program Loader
Programmers can use the program loader in either of the following ways when working with shared libraries:
- The implicit approach
- The client program of a shared library can be statically linked with call stubs of that shared library. The client program can then make shared library calls as if those shared library functions are defined locally. In this scenario the client program does not need to make an operating system call to load that shared library before calling its functions. Whenever the client program makes a call to a shared library function, the stub of that function is entered first. The first time the client program makes a call to a shared library the stub automatically invokes the program loader and dynamic linker to load that shared library and link it with the client, after which it redirects the call to its target location. Once a client program has loaded a shared library, subsequent calls made by that client program are routed to the shared library directly without the involvement of the program loader and dynamic linker. A shared library loaded this way is automatically unloaded when the operating system detects that all client programs that loaded that shared library have exited.
- The explicit approach:
- The client program of a shared library can explicitly call operating system functions (
SysLoadModule()
orSysLoadModuleByDatabaseID()
) to load that shared library and retrieve addresses of exported functions (SysGetEntryAddresses()
). Then, it can make calls to those functions indirectly using the acquired function addresses. After using the shared library, the client program must manually unload it with a call toSysUnloadModule()
. If a shared library is used in this way, the client program does not have to be linked with the call stubs of that shared library. Shared libraries used in this way are also known as plug-ins.