But embedded programmers aren't generally finished with the build process at this point. Even if your embedded system includes an operating system, you'll probably still need an absolutely located binary image. In fact, if there is an operating system, the code and data of which it consists are most likely within the relocatable program too. The entire embedded application — including the operating system — is almost always statically linked together and executed as a single binary image.
The tool that performs the conversion from relocatable program to executable binary image is called a locator. It takes responsibility for the easiest step of the three. In fact, you will have to do most of the work in this step yourself, by providing information about the memory on the target board as input to the locator. The locator will use this information to assign physical memory addresses to each of the code and data sections within the relocatable program. It will then produce an output file that contains a binary memory image that can be loaded into the target ROM.
In many cases, the locator is a separate development tool. However, in the case of the GNU tools, this functionality is built right into the linker. Try not to be confused by this one particular implementation. Whether you are writing software for a general-purpose computer or an embedded system, at some point the sections of your relocatable program must have actual addresses assigned to them. In the first case, the operating system does it for you at load time. In the second, you must perform the step with a special tool. This is true even if the locator is a part of the linker, as it is in the case of ld .
The memory information required by the GNU linker can be passed to it in the form of a linker script. Such scripts are sometimes used to control the exact order of the code and data sections within the relocatable program. But here, we want to do more than just control the order; we also want to establish the location of each section in memory.
What follows is an example of a linker script for a hypothetical embedded target that has 512 KB each of RAM and ROM:
MEMORY {
ram : ORIGIN = 0x00000, LENGTH = 512K
rom : ORIGIN = 0x80000, LENGTH = 512K
}
SECTIONS {
data ram : /* Initialized data. */
{
_DataStart = . ;
*(.data)
_DataEnd = . ;
} >rom
bss : /* Uninitialized data. */
{
_BssStart = . ;
*(.bss)
_BssEnd = . ;
}
_BottomOfHeap = . ; /* The heap starts here. */
_TopOfStack = 0x80000; /* The stack ends here. */
text rom : /* The actual instructions. */
{
*(.text)
}
}
This script informs the GNU linker's built-in locator about the memory on the target board and instructs it to locate the data and bss sections in RAM (starting at address 0x00000) and the text section in ROM (starting at 0x80000). However, the initial values of the variables in the data segment will be made a part of the ROM image by the addition of >rom at the end of that section's definition.
All of the names that begin with underscores ( _TopOfStack , for example) are variables that can be referenced from within your source code. The linker will use these symbols to resolve references in the input object files. So, for example, there might be a part of the embedded software (usually within the startup code) that copies the initial values of the initialized variables from ROM to the data section in RAM. The start and stop addresses for this operation can be established symbolically, by referring to the integer variables _DataStart and _DataEnd .
The result of this final step of the build process is an absolutely located binary image that can be downloaded to the embedded system or programmed into a read-only memory device. In the previous example, this memory image would be exactly 1 MB in size. However, because the initial values for the initialized data section are stored in ROM, the lower 512 kilobytes of this image will contain only zeros, so only the upper half of this image is significant. You'll see how to download and execute such memory images in the next chapter.
3.5 Building das Blinkenlights
Unfortunately, because we're using the Arcom board as our reference platform, we won't be able to use the GNU tools to build the examples. Instead we'll be using Borland's C++ Compiler and Turbo Assembler. these tools can be run on any DOS or Windows-based PC. [7] It is interesting to note that Borland's C++ compiler was not specifically designed for use by embedded software developers. It was instead designed to produce DOS and Windows-based programs for PCs that had 80x86 processors. However, the inclusion of certain command-line options allows us to specify a particular 80x86 processor — the 80186, for example — and, thus, use this tool as a cross-compiler for embedded systems like the Arcom board.
If you have an Arcom board to experiment with, this would be a good time to set it up and install the Borland development tools on your host computer. (See Appendix A for ordering information). I used version 3.1 of the compiler, running on a Windows 95-based PC. However, any version of the Borland tools that can produce code for the 80186 processor will do.
As I have implemented it, the Blinking LED example consists of three source modules: led.c , blink.c , and startup.asm . The first step in the build process is to compile these two files. The command-line options we'll need are -c for "compile, but don't link," -v for "include symbolic debugging information in the output," -ml for "use the large memory model," and -1 for "the target is an 80186 processor." Here are the actual commands:
bcc –c –v –ml –1 led.c
bcc –c –v –ml –1 blink.c
Of course, these commands will work only if the bcc.exe program is in your PATH and the two source files are in the current directory. In other words, you should be in the Chapter2 subdirectory. The result of each of these commands is the creation of an object file that has the same prefix as the .c file and the extension .obj . So if all goes well, there will now be two additional files — led.obj and blink.obj — in the working directory.
Although it would appear that there are only these two object files to be linked together in our example, there are actually three. That's because we must also include some startup code for the C program. (See Startup Code earlier in this chapter.) Example startup code for the Arcom board is provided in the file startup.asm , which is included in the Chapter3 subdirectory. To assemble this code into an object file, change to that directory and issue the following command:
tasm /mx startup.asm
The result should be the file startup.obj in that directory. the command that's actually used to link the three object files together is shown here. Beware that the order of the object files on the command line does matter in this case: the startup code must be placed first for proper linkage.
tlink /m /v /s ..\Chapter3\startup.obj led.obj blink.obj, blink.exe, blink.map
Читать дальше