= Understanding Linker Command Files (Linker Scripts) / MAP files = For most people, linking a program (i.e. the step that takes the compiled object files and creates an executable program) is a little bit like magic. There are a lot of automated tools that handle everything and rarely are there ever any issues. However, in complex embedded systems, it can become necessary to dig in and understand how to control how your program is linked. For example, in the 802.11 Reference design, the upper level MAC code required CPU High to have 256 KB of LMB memory. However, XPS could not build a design that contained a single 256 KB LMB memory. The memory size was too large to meet timing given all the other logic and constraints within the system. Therefore, we had to split the 256 KB memory into two 128 KB physical memories located contiguously within the processor memory map and then use the Linker Command File (Linker Script) to control which sections of the program to load into each memory. This required editing and understanding the Linker Command File (Linker Script) and using the MAP files generated as part of the linker output. This tutorial should give a basic understanding of Linker Command Files (Linker Scripts) and MAP files. You can find additional information on the Linker Command Files (Linker Scripts) and MAP files by searching on the Internet. NOTE: This tutorial is based on [wiki:802.11/Download 802.11 Reference Design v1.3.0] but the concepts should apply generically. == Linker Command Files (Linker Scripts) == '''Opening the Linker Command File (Linker Script):''' Open the Linker Command File (Linker Script) for a project by expanding the project in the "Project Explorer" window and finding the {{{lscript.ld}}} file inside the {{{src}}} folder. By default, double-clicking the {{{lscript.ld}}} file will open the Linker Command File in the SDK "Linker Script Editor". Unfortunately, this editor does not give the granularity of control that is needed for advanced manipulation of Linker Command File (Linker Script). Instead, right-click on the {{{lscript.ld}}} file and select "Open with" --> "Text Editor". This will open the file in a generic text editor within the SDK. [[Image(linker_script_edit.png, 600px)]] If you do not have the SDK open, you can find the Linker Command File (Linker Script) for the 802.11 Reference design v1.3.0 [http://warpproject.org/trac/browser/ReferenceDesigns/w3_802.11/c/wlan_mac_high_ap/lscript.ld?rev=4628 here]. NOTE: The Linker Command File (Linker Script) used in the 802.11 Reference Design was originally generated by the SDK Linker Script Editor and then manually edited. This is why the Linker Command File (Linker Script) is included in SVN and should not be re-generated. The SDK Linker Script Editor is very useful for simple designs. However, for more complicated designs, manual editing is required. The best practice we have found is to let the SDK Linker Script Editor generate the initial Linker Command File (Linker Script) and then manually edit where required. '''Understanding the Linker Command File (Linker Script):''' The Linker Command File (Linker Script) is organized into sections: For this section, we look at the Linker Command File (Linker Script) for the AP project (i.e. wlan_mac_high_ap). 1. The first section defines the sizes for the Stack and the Heap used by the program: {{{ _STACK_SIZE = DEFINED(_STACK_SIZE) ? _STACK_SIZE : 0x1000; _HEAP_SIZE = DEFINED(_HEAP_SIZE) ? _HEAP_SIZE : 0x4000; }}} By default, for the AP project, the 802.11 Reference design allocates a 4 KB stack and a 16 KB heap so that there are no issues with function variables placed on the stack or dynamically allocated memory from the heap. These are larger than what the reference design actually uses so it is easy to extend the design without running into stack or heap issues, which can be difficult to debug. 1. The next section defines the memories in the system that can be used to place code and data. These sections correspond to the physical memories that were defined in the XPS project: {{{ MEMORY { mb_high_ilmb_bram_cntlr_0_mb_high_dlmb_bram_cntlr_0 : ORIGIN = 0x00000050, LENGTH = 0x0001FFB0 mb_high_ilmb_bram_cntlr_1_mb_high_dlmb_bram_cntlr_1 : ORIGIN = 0x00020000, LENGTH = 0x00020000 mb_high_init_bram_ctrl_S_AXI_BASEADDR : ORIGIN = 0x50000000, LENGTH = 0x00001000 pkt_buff_tx_bram_ctrl_S_AXI_BASEADDR : ORIGIN = 0xBF570000, LENGTH = 0x00010000 pkt_buff_rx_bram_ctrl_S_AXI_BASEADDR : ORIGIN = 0xBF560000, LENGTH = 0x00010000 mac_log_bram_ctrl_S_AXI_BASEADDR : ORIGIN = 0x90000000, LENGTH = 0x00002000 mb_high_aux_bram_ctrl_S_AXI_BASEADDR : ORIGIN = 0xBF540000, LENGTH = 0x00010000 ddr3_sodimm_S_AXI_BASEADDR : ORIGIN = 0xC0000000, LENGTH = 0x40000000 } }}} From this, you can see the two 128 KB LMB memories defined for CPU High as well as other memories such as the DDR. 1. The next section specifies the default program entry point: {{{ ENTRY(_start) }}} This should be left alone since it is part of the build configuration of the SDK. 1. The last section defines all the Linker "Sections". A global variable or function can be placed in a give "section" by using the pragma: {{{__attribute__ ((section (".my_data")))}}}, where "my_data" is the name of the section to place teh code or data. By default, the SDK build setup defines a number of default sections that are used to place code and data. Looking at the ".text" section: {{{ .text : { *(.text) *(.text.*) *wlan_mac_common*(.text) *(.gnu.linkonce.t.*) } > mb_high_ilmb_bram_cntlr_0_mb_high_dlmb_bram_cntlr_0 }}} we can see a few thing: 1. The section is placed in the memory: {{{mb_high_ilmb_bram_cntlr_0_mb_high_dlmb_bram_cntlr_0}}} that was defined in the MEMORY section above. 1. The section uses wildcards and pattern matching to assign code to that section. A couple notes on wildcards and pattern matching: a. Wildcards are "greedy" (i.e. they will match all code / data that has not previously been assigned). Therefore, entries like {{{*(.text)}}} or {{{*(.text.*)}}} will allocate any unallocated code and should be used after all other code has been allocated to other sections. b. Section order matters in the Linker Command File (Linker Script). For example, in the example Linker Command File, the {{{.text_bram_1}}} section comes before the {{{.text}}} section so that some of the code can be allocated to {{{mb_high_ilmb_bram_cntlr_1_mb_high_dlmb_bram_cntlr_1}}} vs {{{mb_high_ilmb_bram_cntlr_0_mb_high_dlmb_bram_cntlr_0}}} Next, if we look at a section like ".heap": {{{ .heap (NOLOAD) : { . = ALIGN(8); _heap = .; _heap_start = .; . += _HEAP_SIZE; _heap_end = .; } > mb_high_ilmb_bram_cntlr_1_mb_high_dlmb_bram_cntlr_1 }}} we can see a few things: 1. The heap is not initialized due to the {{{NOLOAD}}} directive (i.e. it is initialized to the default BRAM state). 1. The section can be aligned within the memory map using the {{{ALIGN}}} directive. 1. Symbols can be declared that can be then used in the [http://warpproject.org/trac/browser/ReferenceDesigns/w3_802.11/c/wlan_mac_high_framework/wlan_mac_high.c?rev=4628#L47 C code]. 1. {{{_HEAP_SIZE}}} bytes, declared above, are allocated by incrementing the current point in memory (i.e. the {{{. += _HEAP_SIZE;}}} line). 1. The section is allocated in the {{{mb_high_ilmb_bram_cntlr_1_mb_high_dlmb_bram_cntlr_1}}} memory. By understanding the Linker Command File (Linker Script), it is possible to control how code and data is allocated and placed within memory. This can come in handy when there are hardware limitations to the amount of BRAM that can be allocated to a given physical memory. '''Limitations on DDR:''' Only on-chip BRAM can be initialized by the bitstream that is created from the linked program. Therefore, if the program needs to allocate memory in DDR using the linker, there are a some restrictions that must be placed on that memory: 1. If the memory needs to be initialized, then it must be done so by the program itself. 1. The initial value of the memory must be assumed to be random unless initialized by the program. This is different from BRAM in that the bitstream will initialize BRAM to the value dictated by the program (and zero otherwise). Hence, no code can be placed in DDR, only data that can be initialized by the program. To declare a data section in DDR, it must be specified as "NOLOAD" so that the program that builds the bitstream does not try to do anything with it. For example, a data section for CPU High in the DDR would look like: {{{ .cpu_high_ddr_linker_data (NOLOAD) : { __cpu_high_ddr_linker_data_start = .; *(.my_data) __cpu_high_ddr_linker_data_end = .; } > ddr3_sodimm_S_AXI_BASEADDR }}} The symbols are declared so that it is easy to check programatically that the linked section does not exceed any predefined allocation of DDR, in the case that DDR is being allocated statically within the program. == MAP files == A MAP file is an output of the Linker that gives information about the symbols, addresses, and allocated memory in the generated ELF file. It is extremely useful when trying to understand and debug linker issues related to code size. For example, if you get an error from the linker similar to: {{{ c:/xilinx/14.4/ise_ds/edk/gnu/microblaze/nt64/bin/../lib/gcc/microblaze-xilinx-elf/4.6.2/../../../../microblaze-xilinx-elf/bin/ld.exe: wlan_mac_high_ap.elf section `.abcd' will not fit in region `mb_high_ilmb_bram_cntlr_1_mb_high_dlmb_bram_cntlr_1' c:/xilinx/14.4/ise_ds/edk/gnu/microblaze/nt64/bin/../lib/gcc/microblaze-xilinx-elf/4.6.2/../../../../microblaze-xilinx-elf/bin/ld.exe: region `mb_high_ilmb_bram_cntlr_1_mb_high_dlmb_bram_cntlr_1' overflowed by x bytes }}} then using a MAP file to understand the memory usage of the program is helpful. '''Generating a MAP file:''' By default, MAP files are not generated by an SDK project. To enable MAP file generation, linker flags must be added to the project: 1. Right click on the project to modify in the "Project Expolorer" window and select "Properties". This will open a new dialog box with all of the properties for the project. 1. Under "C/C++ Build" select "Settings" 1. Under "Tool Settings" --> "MicroBlaze gcc linker" select "Miscellaneous" 1. In the "Linker Flags" dialog add: {{{-Wl,-Map=executable.map}}}, where "exectuable.map" is the name of the MAP file to use. 1. Click "OK" and the SDK will re-link your project and create the "exectuable.map" file. [[Image(adding_map_file.png, 600px)]] For all WARP reference designs, we add these linker flags to each project. '''Opening a MAP file:''' Open the MAP file for a project by expanding the project in the "Project Explorer" window and finding the {{{executable.map}}} file inside the {{{Debug}}} folder. By default, double-clicking the {{{executable.map}}} file will result in an error. Instead, right-click on the {{{executable.map}}} file and select "Open with" --> "Text Editor". This will open the file in a generic text editor within the SDK. [[Image(map_file_edit.png, 600px)]] '''Understanding a MAP file:''' The MAP file is organized into sections: For this section, we look at the MAP file for the AP project (i.e. wlan_mac_high_ap). 1. The first section details all of the members included from the various archive files in the system: {{{ Archive member included because of file (symbol) ../../wlan_bsp_cpu_high/mb_high/lib\libxil.a(xil_printf.o) ./wlan_mac_high_framework/wlan_exp_common.o (xil_printf) ... }}} This information is not especially useful, but lets you see all the system functions. 1. The next section shows the names and sizes of global symbols (ie global variables) that have been allocated in the program: {{{ Allocating common symbols Common symbol size file UartLite 0x4c ./wlan_mac_high_framework/wlan_mac_high.o cdma_inst 0x164 ./wlan_mac_high_framework/wlan_mac_high.o async_pkt_enable 0x4 ./wlan_mac_high_framework/wlan_exp_node.o mac_param_chan 0x4 ./src/wlan_mac_ap.o statistics_table 0xc ./src/wlan_mac_ap.o my_bss_info 0x4 ./src/wlan_mac_ap.o ipc_msg_from_low 0x8 ./wlan_mac_high_framework/wlan_mac_high.o ipc_msg_from_low_payload 0x190 ./wlan_mac_high_framework/wlan_mac_high.o ... }}} This is a good place to check that all global variables have expected sizes. A common mistake can be to unknowingly allocate a large global variable that consumes a lot of memory space. 1. The next section show the memory configuration. This should be the same as in the Linker Command File: {{{ Memory Configuration Name Origin Length Attributes mb_high_ilmb_bram_cntlr_0_mb_high_dlmb_bram_cntlr_0 0x00000050 0x0001ffb0 mb_high_ilmb_bram_cntlr_1_mb_high_dlmb_bram_cntlr_1 0x00020000 0x00020000 mb_high_init_bram_ctrl_S_AXI_BASEADDR 0x50000000 0x00001000 ... }}} 1. Finally, the last section details the memory map split out by section defined in the Linker Command file: {{{ Linker script and memory map ... .text_bram_1 0x00020000 0x103a8 *wlan_mac_high_framework*(.text) .text 0x00020000 0x310 ./wlan_mac_high_framework/wlan_exp_common.o 0x00020000 wlan_exp_print_header 0x00020114 wlan_exp_print_mac_address 0x00020190 wlan_exp_set_print_level 0x000201e0 wlan_exp_get_mac_addr 0x00020214 wlan_exp_put_mac_addr 0x0002025c wlan_exp_configure 0x000202b8 get_station_status ... .text 0x00000050 0x183b8 *(.text) .text 0x00000050 0x34 c:/xilinx/14.4/ise_ds/edk/gnu/microblaze/nt64/bin/../lib/gcc/microblaze-xilinx-elf/4.6.2/../../../../microblaze-xilinx-elf/lib/bs/m/le/crt0.o 0x00000050 _start1 0x00000080 _exit .text 0x00000084 0x0 c:/xilinx/14.4/ise_ds/edk/gnu/microblaze/nt64/bin/../lib/gcc/microblaze-xilinx-elf/4.6.2/bs/m/le/crti.o .text 0x00000084 0x118 c:/xilinx/14.4/ise_ds/edk/gnu/microblaze/nt64/bin/../lib/gcc/microblaze-xilinx-elf/4.6.2/bs/m/le/crtbegin.o .text 0x0000019c 0xb0 c:/xilinx/14.4/ise_ds/edk/gnu/microblaze/nt64/bin/../lib/gcc/microblaze-xilinx-elf/4.6.2/../../../../microblaze-xilinx-elf/lib/bs/m/le/crtinit.o 0x0000019c _crtinit .text 0x0000024c 0x604 ./wlan_mac_common/wlan_mac_ipc_util.o 0x0000024c nullCallback 0x00000254 MailboxIntrHandler 0x000002d8 wlan_lib_mailbox_setup_interrupt ... .heap 0x00037ca0 0x4000 0x00037ca0 . = ALIGN (0x8) 0x00037ca0 _heap = . 0x00037ca0 _heap_start = . 0x0003bca0 . = (. + _HEAP_SIZE) *fill* 0x00037ca0 0x4000 00 0x0003bca0 _heap_end = . .stack 0x0003bca0 0x1000 0x0003bca0 _stack_end = . 0x0003cca0 . = (. + _STACK_SIZE) *fill* 0x0003bca0 0x1000 00 0x0003cca0 . = ALIGN (0x8) 0x0003cca0 _stack = . 0x0003cca0 __stack = _stack 0x0003cca0 _end = . ... }}} As you can see, this gives a wealth of information about where everything is mapped in the program. Each top level section, such as {{{.text}}} or {{{.heap}}} has both the starting address in the memory map as well as the size (in bytes) listed. Then each section is broken down into the individual object files and both the starting address and size is listed. Finally, each object file is broken down into the individual functions within the object file and the starting address for each function is listed. This allows you to understand which object files might contain large functions which are not necessary for your program execution. It can also give context when looking at pointer addresses within the program. MAP files are a great source of information when debugging your program. While they are extremely dense and can be a little intimidating, understanding and using MAP files can give you a lot of information that is otherwise difficult to get in standard program debug. If you have any questions, please use the [http://warpproject.org/forums/ forums].