| # Copyright 2021 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """Converts ST generated .icf linker files into basic .ld linker files""" |
| |
| from typing import Dict, Optional, Tuple |
| |
| import pathlib |
| |
| |
| def parse_icf(icf_file: str) -> Tuple[Dict, Dict]: |
| """Parse ICF linker file. |
| |
| ST only provides .icf linker files for many products, so there is a need |
| to generate basic GCC compatible .ld files for all products. |
| This parses the basic features from the .icf format well enough to work |
| for the ST's .icf files that exist in `cmsis_device` |
| |
| Args: |
| icf_file: .icf linker file read into a string |
| |
| Returns: |
| (regions, blocks) where |
| `regions` is a map from region_name -> (start_hex, end_hex) |
| `blocks` is a map from block_name -> {feature_1: val_1,...} |
| |
| Raises: |
| IndexError if .icf is malformed (at least compared to how ST makes them) |
| """ |
| symbols = {} |
| regions = {} # region: (start_addr, end_addr) |
| blocks = {} |
| for line in icf_file.split('\n'): |
| line = line.strip() |
| if line == '' or line.startswith('/*') or line.startswith('//'): |
| continue |
| tokens = line.split() |
| if len(tokens) < 2: |
| continue |
| if tokens[0] == 'define': |
| if tokens[1] == 'symbol': |
| symbols[tokens[2]] = tokens[4].strip(';') |
| elif tokens[1] == 'region': |
| regions[tokens[2].split('_')[0]] = (tokens[5], |
| tokens[7].strip('];')) |
| elif tokens[1] == 'block': |
| blocks[tokens[2]] = { |
| tokens[4]: tokens[6].strip(','), |
| tokens[7]: tokens[9] |
| } |
| parsed_regions = { |
| region: (symbols[start] if start in symbols else start, |
| symbols[end] if end in symbols else end) |
| for region, (start, end) in regions.items() |
| } |
| |
| parsed_blocks = { |
| name: |
| {k: symbols[v] if v in symbols else v |
| for k, v in fields.items()} |
| for name, fields in blocks.items() |
| } |
| |
| return (parsed_regions, parsed_blocks) |
| |
| |
| def icf_regions_to_ld_regions(icf_regions: Dict) -> Dict: |
| """Converts .icf regions to .ld regions |
| |
| The .icf format specifies the beginning and end of each region, while |
| .ld expects the beginning and a length string. |
| |
| Args: |
| icf_regions: icf_regions parsed with `parse_icf()` |
| |
| Returns: |
| A map from `region_name` -> (start_hex, length_str) |
| """ |
| ld_regions = {} |
| for region, (start, end) in icf_regions.items(): |
| start_dec = int(start, 16) |
| end_dec = int(end, 16) |
| length = end_dec - start_dec + 1 |
| length_str = str(length) |
| if length % 1024 == 0: |
| length_str = f'{int(length/1024)}K' |
| |
| # Some icf scripts incorrectly have an exclusive region end. |
| # This corrects for that. |
| elif (length - 1) % 1024 == 0: |
| length_str = f'{int((length-1)/1024)}K' |
| |
| # ST's gcc linker scripts generally use FLASH instead of ROM |
| if region == 'ROM': |
| region = 'FLASH' |
| |
| ld_regions[region] = (start, length_str) |
| |
| return ld_regions |
| |
| |
| def create_ld(ld_regions: Dict, blocks: Dict) -> str: |
| """Create .ld file from template. |
| |
| This creates a barebones .ld file that *should* work for most single core |
| stm32 families. It only contains regions for RAM and FLASH. |
| |
| This template can be bypassed in GN if a more sophisticated linker file |
| is required. |
| |
| Args: |
| ld_regions: generated by `icf_regions_to_ld_regions()` |
| blocks: generated by `parse_icf` |
| |
| Returns: |
| a string linker file with the RAM/FLASH specified by the given reginos. |
| |
| Raises: |
| KeyError if ld_regions does not contain 'RAM' and 'FLASH' |
| """ |
| return f"""\ |
| ENTRY(Reset_Handler) |
| _estack = ORIGIN(RAM) + LENGTH(RAM); |
| |
| _Min_Heap_Size = {blocks['HEAP']['size']}; |
| _Min_Stack_Size = {blocks['CSTACK']['size']}; |
| |
| MEMORY |
| {{ |
| RAM (xrw) : ORIGIN = {ld_regions['RAM'][0]}, LENGTH = {ld_regions['RAM'][1]} |
| FLASH (rx) : ORIGIN = {ld_regions['FLASH'][0]}, LENGTH = {ld_regions['FLASH'][1]} |
| }} |
| |
| SECTIONS |
| {{ |
| |
| /* The ARMv8-M architecture requires this is at least aligned to 128 bytes, |
| * and aligned to a power of two that is greater than 4 times the number of |
| * supported exceptions. 512 has been selected as it accommodates most vector |
| * tables. |
| */ |
| .isr_vector : |
| {{ |
| . = ALIGN(512); |
| KEEP(*(.isr_vector)) |
| . = ALIGN(4); |
| }} >FLASH |
| |
| .text : |
| {{ |
| . = ALIGN(4); |
| *(.text) |
| *(.text*) |
| *(.glue_7) |
| *(.glue_7t) |
| *(.eh_frame) |
| |
| KEEP (*(.init)) |
| KEEP (*(.fini)) |
| |
| . = ALIGN(4); |
| _etext = .; |
| }} >FLASH |
| |
| .rodata : |
| {{ |
| . = ALIGN(4); |
| *(.rodata) |
| *(.rodata*) |
| . = ALIGN(4); |
| }} >FLASH |
| |
| .ARM.extab : {{ |
| . = ALIGN(4); |
| *(.ARM.extab* .gnu.linkonce.armextab.*) |
| . = ALIGN(4); |
| }} >FLASH |
| |
| .ARM : {{ |
| . = ALIGN(4); |
| __exidx_start = .; |
| *(.ARM.exidx*) |
| __exidx_end = .; |
| . = ALIGN(4); |
| }} >FLASH |
| |
| .preinit_array : |
| {{ |
| . = ALIGN(4); |
| PROVIDE_HIDDEN (__preinit_array_start = .); |
| KEEP (*(.preinit_array*)) |
| PROVIDE_HIDDEN (__preinit_array_end = .); |
| . = ALIGN(4); |
| }} >FLASH |
| |
| .init_array : |
| {{ |
| . = ALIGN(4); |
| PROVIDE_HIDDEN (__init_array_start = .); |
| KEEP (*(SORT(.init_array.*))) |
| KEEP (*(.init_array*)) |
| PROVIDE_HIDDEN (__init_array_end = .); |
| . = ALIGN(4); |
| }} >FLASH |
| |
| .fini_array : |
| {{ |
| . = ALIGN(4); |
| PROVIDE_HIDDEN (__fini_array_start = .); |
| KEEP (*(SORT(.fini_array.*))) |
| KEEP (*(.fini_array*)) |
| PROVIDE_HIDDEN (__fini_array_end = .); |
| . = ALIGN(4); |
| }} >FLASH |
| |
| _sidata = LOADADDR(.data); |
| .data : |
| {{ |
| . = ALIGN(4); |
| _sdata = .; |
| *(.data) |
| *(.data*) |
| . = ALIGN(4); |
| _edata = .; |
| }} >RAM AT> FLASH |
| |
| . = ALIGN(4); |
| .bss : |
| {{ |
| _sbss = .; |
| __bss_start__ = _sbss; |
| *(.bss) |
| *(.bss*) |
| *(COMMON) |
| |
| . = ALIGN(4); |
| _ebss = .; |
| __bss_end__ = _ebss; |
| }} >RAM |
| |
| /* The ARMv7-M architecture may require 8-byte alignment of the stack pointer |
| * rather than 4 in some contexts and implementations, so this region is |
| * 8-byte aligned (see ARMv7-M Architecture Reference Manual DDI0403E |
| * section B1.5.7). |
| */ |
| ._user_heap_stack : |
| {{ |
| . = ALIGN(8); |
| PROVIDE ( end = . ); |
| PROVIDE ( _end = . ); |
| . = . + _Min_Heap_Size; |
| . = . + _Min_Stack_Size; |
| . = ALIGN(8); |
| }} >RAM |
| |
| /DISCARD/ : |
| {{ |
| libc.a ( * ) |
| libm.a ( * ) |
| libgcc.a ( * ) |
| }} |
| |
| .ARM.attributes 0 : {{ *(.ARM.attributes) }} |
| }} |
| """ |
| |
| |
| def icf_to_ld(icf_path: pathlib.Path, ld_path: Optional[pathlib.Path]): |
| """Convert icf file into an ld file. |
| |
| Note: This only works for ST generated .icf files. |
| |
| Args: |
| icf_path: path to .icf file to convert |
| ld_path: path to write generated .ld file or None. |
| If None, the .ld file is written to stdout. |
| """ |
| with open(icf_path, 'rb') as icf_file: |
| icf_str = icf_file.read().decode('utf-8', errors='ignore') |
| |
| icf_regions, blocks = parse_icf(icf_str) |
| ld_regions = icf_regions_to_ld_regions(icf_regions) |
| ld_str = create_ld(ld_regions, blocks) |
| |
| if ld_path: |
| with open(ld_path, 'w') as ld_file: |
| ld_file.write(ld_str) |
| else: |
| print(ld_str) |