compile-time - 如何为 AVR 实现编译时 [dispatch] 表?

标签 compile-time avr-gcc

我有与 How can I implement a dynamic dispatch table in C 中的 Dave Durbin 相同的先决条件...除了我的目标是 AVR。这是我的限制:

  • 模块将在列表中选择,很像 Linux 编译内核模块
  • C(可以是 C++)模块的数量在编译时是已知的
  • 模块将被静态链接(显然)
  • 我想要程序内存中的表,not in SRAM

  • 通常,该表应包含以下类型的项目:
    typedef struct jump_item {
        uint16_t function_id;
        void (*callback)(void);
    } jump_item_t;
    

    我已尝试按照答案中的建议使用自定义部分,但链接器会为未知符号 __start_myownsection 引发错误(尽管我使用的任何部分名称)。当然,因为代码针对的是 Linux/GCC。但我认为我很接近因为avr-gcc居然可以用sections ,只是我还没有弄清楚如何在用户定义的部分中堆叠符号并实际指向表的开头,以及在运行时确定表的长度。

    怎么可能Art's answer适应AVR?

    * 编辑 *

    我可以看到至少有两种方法可以使用部分来实现我想要的效果,要么将函数“附加”到用户定义的部分,要么将结构表(如上定义)全部堆叠在用户定义的部分中。我目前的问题是:
  • 未使用的变量在编译时被优化掉!
  • 由于链接器参数 -gc-sections,未使用的函数在链接时被优化掉,我需要清理未使用的功能。

  • 我更喜欢第二种选择,类似于以下内容:

    模块1.c:
    const jump_item_t module1_table[] __attribute__((__progmem__, section("tbl_dispatch"))) =
    {
        { 0x02, func11 },
        { 0x03, func12 },
        ...
    };
    

    模块2.c:
    const jump_item_t module2_table[] __attribute__((__progmem__, section("tbl_dispatch"))) =
    {
        { 0x12, func21 },
        { 0x13, func22 },
        ...
    };
    

    注意:指数不被认为是相关的。

    当所有模块都定义了这些变量时,它们会被优化掉,因为在任何地方都没有对这些变量的引用。他们需要在 tbl_dispatch 部分堆叠尽管。所以我的问题回到:

    如何告诉编译器删除它“认为”未使用但仅适用于特定 C/C++ 模块的变量?

    到目前为止我使用的全局命令行如下:
    avr-gcc -g -Wall -mcall-prologues -fshort-enums -Os \
        -DF_CPU=8000000 -Wl,-relax -mmcu=... \
        *.cpp *.c -o main
    

    * 编辑 *

    令我失望的是,程序 和自定义部分不在一起。我试图将它们组合起来,但我在程序内存中得到了分散的跳转表......当我完全包含这些时。事实上,甚至并非所有表格都出现在程序存储器中。

    放弃。

    欢迎任何想法。

    最佳答案

    如果您编写自己的自定义链接器脚本,并复制为构造函数和析构函数(ctors 和 dtors)所做的工作,那么您绝对可以创建一个模块系统。下面的链接器脚本基于 AVR GCC 的 avr5.x,但我向其中添加了调度内容。

    如果您在下面的 shell session 中查看构建脚本的输出,您可以看到调度表设置正确,并且有指向它开始和结束的符号。 shell session 包括我用来编译这个示例的所有源代码和构建脚本。

    $ ls
    avr5-x-modules.ld  build.sh  kernel.c  kernel.h  module_foo.c
    
    $ cat avr5-x-modules.ld
    /* Default linker script, for normal executables */
    /* Copyright (C) 2014 Free Software Foundation, Inc.
       Copying and distribution of this script, with or without modification,
       are permitted in any medium without royalty provided the copyright
       notice and this notice are preserved.  */
    OUTPUT_FORMAT("elf32-avr","elf32-avr","elf32-avr")
    OUTPUT_ARCH(avr:5)
    MEMORY
    {
      text   (rx)   : ORIGIN = 0, LENGTH = 128K
      data   (rw!x) : ORIGIN = 0x800060, LENGTH = 0xffa0
      eeprom (rw!x) : ORIGIN = 0x810000, LENGTH = 64K
      fuse      (rw!x) : ORIGIN = 0x820000, LENGTH = 1K
      lock      (rw!x) : ORIGIN = 0x830000, LENGTH = 1K
      signature (rw!x) : ORIGIN = 0x840000, LENGTH = 1K
      user_signatures (rw!x) : ORIGIN = 0x850000, LENGTH = 1K
    }
    SECTIONS
    {
      /* Read-only sections, merged into text segment: */
      .hash          : { *(.hash)           }
      .dynsym        : { *(.dynsym)         }
      .dynstr        : { *(.dynstr)         }
      .gnu.version   : { *(.gnu.version)    }
      .gnu.version_d   : { *(.gnu.version_d)        }
      .gnu.version_r   : { *(.gnu.version_r)        }
      .rel.init      : { *(.rel.init)               }
      .rela.init     : { *(.rela.init)      }
      .rel.text      :
        {
          *(.rel.text)
          *(.rel.text.*)
          *(.rel.gnu.linkonce.t*)
        }
      .rela.text     :
        {
          *(.rela.text)
          *(.rela.text.*)
          *(.rela.gnu.linkonce.t*)
        }
      .rel.fini      : { *(.rel.fini)               }
      .rela.fini     : { *(.rela.fini)      }
      .rel.rodata    :
        {
          *(.rel.rodata)
          *(.rel.rodata.*)
          *(.rel.gnu.linkonce.r*)
        }
      .rela.rodata   :
        {
          *(.rela.rodata)
          *(.rela.rodata.*)
          *(.rela.gnu.linkonce.r*)
        }
      .rel.data      :
        {
          *(.rel.data)
          *(.rel.data.*)
          *(.rel.gnu.linkonce.d*)
        }
      .rela.data     :
        {
          *(.rela.data)
          *(.rela.data.*)
          *(.rela.gnu.linkonce.d*)
        }
      .rel.ctors     : { *(.rel.ctors)      }
      .rela.ctors    : { *(.rela.ctors)     }
      .rel.dtors     : { *(.rel.dtors)      }
      .rela.dtors    : { *(.rela.dtors)     }
      .rel.got       : { *(.rel.got)                }
      .rela.got      : { *(.rela.got)               }
      .rel.bss       : { *(.rel.bss)                }
      .rela.bss      : { *(.rela.bss)               }
      .rel.plt       : { *(.rel.plt)                }
      .rela.plt      : { *(.rela.plt)               }
      /* Internal text space or external memory.  */
      .text   :
      {
        *(.vectors)
        KEEP(*(.vectors))
        /* For data that needs to reside in the lower 64k of progmem.  */
         *(.progmem.gcc*)
        /* PR 13812: Placing the trampolines here gives a better chance
           that they will be in range of the code that uses them.  */
        . = ALIGN(2);
         __trampolines_start = . ;
        /* The jump trampolines for the 16-bit limited relocs will reside here.  */
        *(.trampolines)
         *(.trampolines*)
         __trampolines_end = . ;
         *(.progmem*)
        . = ALIGN(2);
        /* For future tablejump instruction arrays for 3 byte pc devices.
           We don't relax jump/call instructions within these sections.  */
        *(.jumptables)
         *(.jumptables*)
        /* For code that needs to reside in the lower 128k progmem.  */
        *(.lowtext)
         *(.lowtext*)
         __ctors_start = . ;
         *(.ctors)
         __ctors_end = . ;
         __dtors_start = . ;
         *(.dtors)
         __dtors_end = . ;
        KEEP(SORT(*)(.ctors))
        KEEP(SORT(*)(.dtors))
        __dispatch_start = . ;
        *(.dispatch)
        __dispatch_end = . ;
        KEEP(SORT(*)(.dispatch))
        /* From this point on, we don't bother about wether the insns are
           below or above the 16 bits boundary.  */
        *(.init0)  /* Start here after reset.  */
        KEEP (*(.init0))
        *(.init1)
        KEEP (*(.init1))
        *(.init2)  /* Clear __zero_reg__, set up stack pointer.  */
        KEEP (*(.init2))
        *(.init3)
        KEEP (*(.init3))
        *(.init4)  /* Initialize data and BSS.  */
        KEEP (*(.init4))
        *(.init5)
        KEEP (*(.init5))
        *(.init6)  /* C++ constructors.  */
        KEEP (*(.init6))
        *(.init7)
        KEEP (*(.init7))
        *(.init8)
        KEEP (*(.init8))
        *(.init9)  /* Call main().  */
        KEEP (*(.init9))
        *(.text)
        . = ALIGN(2);
         *(.text.*)
        . = ALIGN(2);
        *(.fini9)  /* _exit() starts here.  */
        KEEP (*(.fini9))
        *(.fini8)
        KEEP (*(.fini8))
        *(.fini7)
        KEEP (*(.fini7))
        *(.fini6)  /* C++ destructors.  */
        KEEP (*(.fini6))
        *(.fini5)
        KEEP (*(.fini5))
        *(.fini4)
        KEEP (*(.fini4))
        *(.fini3)
        KEEP (*(.fini3))
        *(.fini2)
        KEEP (*(.fini2))
        *(.fini1)
        KEEP (*(.fini1))
        *(.fini0)  /* Infinite loop after program termination.  */
        KEEP (*(.fini0))
         _etext = . ;
      }  > text
      .data          :
      {
         PROVIDE (__data_start = .) ;
        *(.data)
         *(.data*)
        *(.rodata)  /* We need to include .rodata here if gcc is used */
         *(.rodata*) /* with -fdata-sections.  */
        *(.gnu.linkonce.d*)
        . = ALIGN(2);
         _edata = . ;
         PROVIDE (__data_end = .) ;
      }  > data AT> text
      .bss  ADDR(.data) + SIZEOF (.data)   : AT (ADDR (.bss))
      {
         PROVIDE (__bss_start = .) ;
        *(.bss)
         *(.bss*)
        *(COMMON)
         PROVIDE (__bss_end = .) ;
      }  > data
       __data_load_start = LOADADDR(.data);
       __data_load_end = __data_load_start + SIZEOF(.data);
      /* Global data not cleared after reset.  */
      .noinit  ADDR(.bss) + SIZEOF (.bss)  :  AT (ADDR (.noinit))
      {
         PROVIDE (__noinit_start = .) ;
        *(.noinit*)
         PROVIDE (__noinit_end = .) ;
         _end = . ;
         PROVIDE (__heap_start = .) ;
      }  > data
      .eeprom  :
      {
        /* See .data above...  */
        KEEP(*(.eeprom*))
         __eeprom_end = . ;
      }  > eeprom
      .fuse  :
      {
        KEEP(*(.fuse))
        KEEP(*(.lfuse))
        KEEP(*(.hfuse))
        KEEP(*(.efuse))
      }  > fuse
      .lock  :
      {
        KEEP(*(.lock*))
      }  > lock
      .signature  :
      {
        KEEP(*(.signature*))
      }  > signature
      .user_signatures  :
      {
        KEEP(*(.user_signatures*))
      }  > user_signatures
      /* Stabs debugging sections.  */
      .stab 0 : { *(.stab) }
      .stabstr 0 : { *(.stabstr) }
      .stab.excl 0 : { *(.stab.excl) }
      .stab.exclstr 0 : { *(.stab.exclstr) }
      .stab.index 0 : { *(.stab.index) }
      .stab.indexstr 0 : { *(.stab.indexstr) }
      .comment 0 : { *(.comment) }
      .note.gnu.build-id : { *(.note.gnu.build-id) }
      /* DWARF debug sections.
         Symbols in the DWARF debugging sections are relative to the beginning
         of the section so we begin them at 0.  */
      /* DWARF 1 */
      .debug          0 : { *(.debug) }
      .line           0 : { *(.line) }
      /* GNU DWARF 1 extensions */
      .debug_srcinfo  0 : { *(.debug_srcinfo) }
      .debug_sfnames  0 : { *(.debug_sfnames) }
      /* DWARF 1.1 and DWARF 2 */
      .debug_aranges  0 : { *(.debug_aranges) }
      .debug_pubnames 0 : { *(.debug_pubnames) }
      /* DWARF 2 */
      .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
      .debug_abbrev   0 : { *(.debug_abbrev) }
      .debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end ) }
      .debug_frame    0 : { *(.debug_frame) }
      .debug_str      0 : { *(.debug_str) }
      .debug_loc      0 : { *(.debug_loc) }
      .debug_macinfo  0 : { *(.debug_macinfo) }
      /* SGI/MIPS DWARF 2 extensions */
      .debug_weaknames 0 : { *(.debug_weaknames) }
      .debug_funcnames 0 : { *(.debug_funcnames) }
      .debug_typenames 0 : { *(.debug_typenames) }
      .debug_varnames  0 : { *(.debug_varnames) }
      /* DWARF 3 */
      .debug_pubtypes 0 : { *(.debug_pubtypes) }
      .debug_ranges   0 : { *(.debug_ranges) }
      /* DWARF Extension.  */
      .debug_macro    0 : { *(.debug_macro) }
    }
    
    $ cat build.sh
    CFLAGS="-std=gnu11 -mmcu=atmega328p"
    set -uex
    avr-gcc $CFLAGS -c module_foo.c -o module_foo.o
    avr-gcc $CFLAGS -c kernel.c -o kernel.o
    avr-gcc -T avr5-x-modules.ld kernel.o module_foo.o \
            -o program.elf -Wl,-Map=program.map
    grep dispatch program.map
    
    $ cat kernel.c
    #include "kernel.h"
    #include <avr/pgmspace.h>
    
    extern dispatch_item * __dispatch_start;
    extern dispatch_item * __dispatch_end;
    
    int main()
    {
        while (1)
        {
            for (dispatch_item * item = __dispatch_start; item < __dispatch_end; item++)
            {
                // TODO: Insert code here for reading the contents of the
                // dispatch item from program space and using it.  You
                // probably have to use pgm_read_word avr avr/pgmspace.h,
                // but with GCC 5 you could probably use the new named
                // memory space feature to just access the dispatch item
                // the same way you would access any other struct:
                // https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html
            }
        }
    }
    
    $ cat kernel.h
    #pragma once
    
    #include <stdint.h>
    
    typedef struct dispatch_item {
        uint16_t func_id;
        void (*func)(void);
    } dispatch_item;
    
    #define DISPATCH_ITEM dispatch_item const __attribute__((section (".dispatch")))
    
    
    $ cat module_foo.c
    #include "kernel.h"
    #include <avr/io.h>
    
    // This gets called before main.
    void __attribute__((constructor)) foo_init()
    {
        PINB = 0;
    }
    
    // There is a pointer to this in the dispatch table.
    void foo()
    {
        PINB = 1;
    }
    
    // DISPATHCH_TABLE_ENTRY(0x12, &foo);
    
    DISPATCH_ITEM foo_dispatch = { 0x12, &foo };
    
    DISPATCH_ITEM foo_dispatch2 = { 0x13, &foo };
    
    $ ./build.sh
    ++ avr-gcc -std=gnu11 -mmcu=atmega328p -c module_foo.c -o module_foo.o
    ++ avr-gcc -std=gnu11 -mmcu=atmega328p -c kernel.c -o kernel.o
    ++ avr-gcc -T avr5-x-modules.ld kernel.o module_foo.o -o program.elf -Wl,-Map=program.map
    ++ grep dispatch program.map
                    0x00000002                __dispatch_start = .
     *(.dispatch)
     .dispatch      0x00000002        0x8 module_foo.o
                    0x00000002                foo_dispatch
                    0x00000006                foo_dispatch2
                    0x0000000a                __dispatch_end = .
     SORT(*)(.dispatch)
    

    关于compile-time - 如何为 AVR 实现编译时 [dispatch] 表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39733931/

    相关文章:

    c++ - 在编译时通过用索引折叠指针来分配 C 数组

    c - 寻找最接近的质数

    字符串常量的 C# 编译时连接

    c - 即使声明为(已使用),PROGMEM 变量也会被丢弃

    c - 声明结构指针时出错

    language-agnostic - 运行时与编译时

    c - 如何在编译时检查 `typeof` 的 void 值?

    gcc - 我可以告诉 gcc/ld 排除未使用的模块吗?

    c - WinAVR 没有针对 main.elf 的规则

    c - 为什么 AVR-GCC 编译器在乘法后附加 "clr r1"行?