If a function is reentrant, each of its concurrently active clients must have a separate copy of the data it manipulates for them. The data cannot be associated with the code itself unless the data is read-only. In the ARM shared library architecture, a dedicated register (called sb) is used to address (indirectly) the static data associated with a client.
An ARM shared library is read only, reentrant and usually position independent. A shared library made exclusively from object code compiled by the ARM C compiler will have all three of these attributes. Library components implemented in ARM Assembly Language need not be reentrant and position independent, but in practice, only position independence is inessential.
A library with all three of these attributes in an ideal candidate for packing into a system ROM.
Some shared library mechanisms associate a shared library's data with the library itself and put only a place holder in the stub. At run time, a copy of the library's initialised static data is copied into the client's place holder by the dynamic linker or by library initialisation code.
The ARM shared library mechanism supports these ways of working provided the data is free of values which require link-time (or run time) relocation. In other words, it can be supported provided the input data areas are free of relocation directives.
When a client first calls a proxy function, the call is directed to a dynamic linker. This is a small function (typically about 50-60 ARM instructions) which:
Of course, making an inter-link-unit call like this is more expensive than making a straightforward local procedure call, but not a lot so. It is also the only supported way to call a function more than 32MBytes away.
How the dynamic linker works
The dynamic linker is entered via a proxy call with r0 pointing at the
dynamic linker's 16-byte entry stub. Following this stub code is a copy of
the parameter block for the shared library.
Stored in the parameter block is the identity of the library - perhaps a 32-bit unique identifier or perhaps a string name. Either way, it can be passed to the library location mechanism. You have to decide how to identify your shared libraries and, hence, what to put in their parameter blocks.
The library location function is required to return the address of the start of the library's offset table.
A primitive location mechanism might be to search a ROM for a matching string. This would identify the start of the parameter block of the matching shared library. Immediately preceding it will be negative offsets to library entry points and a non-negative count word containing the number of entry points. By working backwards through memory and counting, you can be sure you have found the entry vector and can return the address of its count word to the dynamic linker.
More sophisticated location schemes are possible, for example:
The first rule is not 100% necessary and is difficult to enforce. The ARM Linker warns you if it finds a non-reentrant code area in the list of objects to be linked into a shared library but it will build the library and its matching stub anyway. You have to decide whether the warning is real, or merely a formality.
The library image file contains, in order:
Obviously, any data included in your shared library must be free of relocation directives. Please refer to ARM shared library format for a full explanation of what kind of data can be included in a shared library.
You specify a parameter block when you describe to the linker how to make a shared library. You might, for example, include the name of the library in its parameter block, to aid its location. An identical copy of the parameter block is included in the library's entry vector in the stub file.
Describing a shared library to the linker
To describe a shared library to the linker you have to prepare a file
which describes:
The name of this file is passed to armlink as the argument to the
-SHL command line option (please refer to the chapter The ARM Linker (armlink) for
further details).
; First, give the name of the file to contain the library -
; strlib - and its parameter block - the single word 0x40000...
> strlib \
0x40000
; ...then include all suitable data areas...
+ ()
; ... finally export all the entry points...
; ... mostly omitted here for brevity of exposition.
memcpy
...
strtok
The header files config.h and interns.h let you compile cl/string.c locally. Little-endian code is assumed. If you want to make a big-endian string library you should edit config.h. Similarly, if you want to alter which functions are included or whether static data is initialised by copying from the library, then you should edit config.h. You do not need to edit interns.h. If you use config.h unchanged you will build a little-endian library which includes a data image and which exports all of its functions.
The -li flag tells armcc to compile for a little-endian
ARM.
armcc -li -apcs /reent -zps1 -c -I. ../../cl/string.c
The -apcs /reent flag tells armcc to compile reentrant code.
The -zps1 flag turns off software stack limit checking and allows the string library to be independent of all other objects and libraries. With software stack limit checking turned on, the library would depend on the stack limit checking functions which, in turn, depend on other sections of the C run time library. While such dependencies do not much obstruct the construction of full scale, production quality shared libraries, they are major impediments to a simple demonstration of the underlying mechanisms.
The -I. flag tells armcc to look for needed header files in the current directory.
strlib's stub will be put in strstub.o as directed by the -o
option.
armlink -o strstub.o -shl strshl -s syms string.o
The file strshl contains instructions for making a shared library called strlib. A shortened version of it was shown in the earlier section "Describing a shared library to the linker."
The option -s syms asks for a listing of symbol values in a file called syms. You may later need to look up the value of EFT$$Offset (it will be 0xA38 if you have changed nothing). As supplied, the dynamic linker expects a library's extenal function table (EFT) to be at the address 0x40000. So, unless you extend the dynamic linker with a library location mechanism (please refer to the discussion in the earlier section How the dynamic linker works), you will have to load strlib at the address 0x40000-EFT$$Offset.
You can extend the test code to probe lots of string functions, but this
is left as an exercise to help you understand what is going on.
armasm -li dynlink.s dynlink.o
armcc -li -c strtest.c
To make the test program you must link together the test code, the dynamic linker, the string library stub and the appropriate ARM C library (so that references to library members other than the string functions can be resolved):
armlink -d -o strtest strtest.o dynlink.o strstub.o ../../lib/armlib.32l
Before starting strtest you must load the shared string library by
using:
A.R.M. Source-level Debugger version ...
ARMulator V1.30, 4 Gb memory, MMU present, Demon 1.1,...
Object program file strtest
armsd: getfile strlib 0x40000-0xa38
armsd: go
strerror(42) returns unknown shared string-library error 0x0000002A
Program terminated normally at PC = 0x00008354 (__rt_exit + 0x24)
+0024 0x00008354: 0xef000011 .... : swi 0x11
armsd: q
Quitting
strlib is the name of the file containing the library; 0x40000 is
the hard wired address at which the dynamic linker expects to find the
external function table; and 0xa38 is the value of EFT$$Offset, the offset
of the external function table from the start of the library.
getfile strlib 0x40000-0xa38
When strtest runs, it calls strerror(42) which causes the dynamic linker to be entered, the static data to be copied, the stub vector to be patched and the call to be resumed. You can watch this is more detail by setting a breakpoint on __rt_dynlink and single stepping.
Your dynamic linker could now search a list of libraries loaded at 0x40000 onwards.