【问题标题】:Struct offsets in inline assembly内联汇编中的结构偏移
【发布时间】:2019-04-10 09:50:49
【问题描述】:

我正在做一个项目,其中一些中断服务必须在汇编程序中处理。从中断向量包装器调用处理函数。处理程序主体是用汇编程序编写的,它在特定寄存器中接收单个(指针)参数。

代码目标是 MSP430,它必须使用 MSP430-gcc 和 TI 编译器进行编译。我已经有了 MSP430-gcc 的工作解决方案,它看起来像这样:

static void __attribute__((naked)) _shared_vector_handler(Timer_driver_t *driver) {

__asm__(
    "   MOVX.W %c[iv_register_offset](R12),R14 ; \n"
    "   ADD @R14,PC ; \n"
    "   RETA ; \n"
    "   JMP CCIFG_1_HND ; Vector 2 \n"
    "   JMP CCIFG_2_HND ; Vector 4 \n"
    "   JMP CCIFG_3_HND ; Vector 6 \n"
    "   JMP CCIFG_4_HND ; Vector 8 \n"
    "   JMP CCIFG_5_HND ; Vector 10 \n"
    "   JMP CCIFG_6_HND ; Vector 12 \n"
    "TIFG_HND: \n"
    "   MOVX.A %c[overflow_handle_offset](R12),R14 ; \n"
    "   MOVX.A %c[handler_param_offset](R14),R12 ; \n"
    "   MOVX.A %c[handler_offset](R14),R14 ; \n"
    "   CALLA R14 ; \n"
    "   RETA ; \n"
    "CCIFG_1_HND: \n"
    "   MOVX.A %c[ccr1_handle_offset](R12),R14 ; \n"
    "   MOVX.A %c[handler_param_offset](R14),R12 ; \n"
    "   MOVX.A %c[handler_offset](R14),R14 ; \n"
    "   CALLA R14 ; \n"
    "   RETA ; \n"
    "CCIFG_2_HND: \n"
    "   MOVX.A %c[ccr2_handle_offset](R12),R14 ; \n"
    "   MOVX.A %c[handler_param_offset](R14),R12 ; \n"
    "   MOVX.A %c[handler_offset](R14),R14 ; \n"
    "   CALLA R14 ; \n"
    "   RETA ; \n"
    "CCIFG_3_HND: \n"
    "   MOVX.A %c[ccr3_handle_offset](R12),R14 ; \n"
    "   MOVX.A %c[handler_param_offset](R14),R12 ; \n"
    "   MOVX.A %c[handler_offset](R14),R14 ; \n"
    "   CALLA R14 ; \n"
    "   RETA ; \n"
    "CCIFG_4_HND: \n"
    "   MOVX.A %c[ccr4_handle_offset](R12),R14 ; \n"
    "   MOVX.A %c[handler_param_offset](R14),R12 ; \n"
    "   MOVX.A %c[handler_offset](R14),R14 ; \n"
    "   CALLA R14 ; \n"
    "   RETA ; \n"
    "CCIFG_5_HND: \n"
    "   MOVX.A %c[ccr5_handle_offset](R12),R14 ; \n"
    "   MOVX.A %c[handler_param_offset](R14),R12 ; \n"
    "   MOVX.A %c[handler_offset](R14),R14 ; \n"
    "   CALLA R14 ; \n"
    "   RETA ; \n"
    "CCIFG_6_HND: \n"
    "   MOVX.A %c[ccr6_handle_offset](R12),R14 ; \n"
    "   MOVX.A %c[handler_param_offset](R14),R12 ; \n"
    "   MOVX.A %c[handler_offset](R14),R14 ; \n"
    "   CALLA R14 ; \n" ::
    [iv_register_offset] "i" (offsetof(Timer_driver_t, _IV_register)),
    [overflow_handle_offset] "i" (offsetof(Timer_driver_t, _overflow_handle)),
    [ccr1_handle_offset] "i" (offsetof(Timer_driver_t, _CCR1_handle)),
    [ccr2_handle_offset] "i" (offsetof(Timer_driver_t, _CCR2_handle)),
    [ccr3_handle_offset] "i" (offsetof(Timer_driver_t, _CCR3_handle)),
    [ccr4_handle_offset] "i" (offsetof(Timer_driver_t, _CCR4_handle)),
    [ccr5_handle_offset] "i" (offsetof(Timer_driver_t, _CCR5_handle)),
    [ccr6_handle_offset] "i" (offsetof(Timer_driver_t, _CCR6_handle)),
    [handler_offset] "i" (offsetof(Timer_channel_handle_t, _handler)),
    [handler_param_offset] "i" (offsetof(Timer_channel_handle_t, _handler_param)) :
);
}

翻译成英文:驱动结构包含IV寄存器在某个特定偏移处的地址。该地址上的内容被添加到 PC,因此跳转到特定标签(取决于设置的中断标志)发生。这是 TI 在user's guide,第 653 页中描述的推荐用法。所有标签的作用相同:它们从特定偏移量的驱动程序结构中获取指向某个句柄的指针。句柄再次具有某些特定的偏移函数指针(中断服务处理程序)和指向某个参数的指针,这些参数应传递给处理程序。简而言之,结构:

typedef struct Timer_driver {
// enable dispose(Timer_driver_t *)
Disposable_t _disposable;
// base of HW timer registers, (address of corresponding TxCTL register)
uint16_t _CTL_register;
...
// interrupt vector register
uint16_t _IV_register;
// stored mode control
uint8_t _mode;
// amount of CCRn registers
uint8_t _available_handles_cnt;

// main (CCR0) handle
Timer_channel_handle_t *_CCR0_handle;
// up to six (CCRn) handles sharing one interrupt vector
Timer_channel_handle_t *_CCR1_handle;
Timer_channel_handle_t *_CCR2_handle;
...
}

struct Timer_channel_handle {
// vector wrapper, enable dispose(Timer_channel_handle_t *)
Vector_handle_t vector;
// HW timer driver reference
Timer_driver_t *_driver;
// capture / compare control register
uint16_t _CCTLn_register;
// capture / compare register
uint16_t _CCRn_register;

// vector interrupt service handler
void (*_handler)(void *);
// vector interrupt service handler parameter
void *_handler_param;
...
}

现在的问题。

  • 直到编译时才知道偏移量
  • 我无法将一些 offsetof(s, m) 传递给汇编器
  • 偏移量取决于使用的内存模型(指针大小为 16 位或 32 位)
  • 偏移量取决于两个结构的第一个成员的大小,而这个大小取决于预处理器定义(1 个指针或 4 个指针)
  • 无法预先计算偏移量,因为每个编译器都会在第一个成员结构中添加一些对齐和填充
  • 第一个成员必须是第一个成员(不允许重新排序)
  • TI 编译器不支持将编译时变量传递给内联汇编代码

目标:

  • 支持两种编译器
  • 不要重复代码,不要硬编码偏移量
  • 如果可能,请避免将整个处理程序提取到 asm 文件并通过 .cdecls(或在 gcc 的情况下为 #include)包含标头。两种编译器都以不同的方式处理包含 C 头文件,结构偏移量也以不同的方式定义,并且需要对头文件进行一些重要的重组,我认为这几乎是不可能的。

当我使用 TI 编译器进行编译时,出现以下错误:

"../module/driver/src/timer.c", line 274: error #18: expected a ")"
"../module/driver/src/timer.c", line 285: warning #12-D: parsing restarts here after previous syntax error
1 error detected in the compilation of "../module/driver/src/timer.c".
gmake: *** [module/driver/src/timer.obj] Error 1

我的构建由 CMake 处理,我可以想到一种解决方案 - 只是将这些偏移量预生成到某些头文件中,该头文件应包含在驱动程序中。如何做到这一点的方法在here 中进行了描述。但如果可能的话,我也想避免这一步,因为它需要在 Code Composer Studio 中编译,所以不会运行 cmake。

那么如何创建 CMake 目标来预生成这些偏移量?还是有其他想法?

【问题讨论】:

  • 错误信息仅仅是因为在 asm 语句的结束之前有一个无关的 : )。
  • 使用 gcc 你可以这样做,因为它是extended assembly。使用 TI 编译器这是不可能的,内联汇编仅限于 __asm("assembler string"),因此不能将编译时变量传递给它。
  • 请注意,您的扩展 asm 缺少它所踩到的所有寄存器的 clobbers,因此即使您使用的是支持 GNU C 扩展的编译器,这也不是一个安全的例子。哦,nvm,你是在一个裸函数中做的,所以这很好,但会阻止它内联。
  • 只有当各种中断源的处理程序不同时,add-IV-to-PC 机制才有用。在这种情况下,您可以将句柄设为数组,并将 IV 值用作该数组的偏移量。而CALLA+RETA可以优化成跳转。无论如何,您为什么认为有必要使用汇编程序?编译器生成的代码是不是差很多?
  • 是否可以重新排序结构字段?

标签: c assembly cmake cross-compiling msp430


【解决方案1】:

感谢大家,特别感谢@CL。我一直认为这必须在汇编程序中完成,原因有很多,我只需要以某种方式获得这些偏移量。解决方法很简单:

static void _shared_vector_handler(Timer_driver_t *driver) {
    uint16_t interrupt_source;
    Timer_channel_handle_t *handle;

    if ( ! (interrupt_source = hw_register_16(driver->_IV_register))) {
        return;
    }

    handle = *((Timer_channel_handle_t **) 
        (((uintptr_t)(&driver->_CCR0_handle)) + (interrupt_source * _POINTER_SIZE_ / 2)));

    (*handle->_handler)(handle->_handler_param);
}

翻译成汇编器(TI编译器,内存模型大):

        _shared_vector_handler():
011ef6:   4C1F 0008           MOV.W   0x0008(R12),R15
011efa:   4F2F                MOV.W   @R15,R15
011efc:   930F                TST.W   R15
011efe:   240D                JEQ     (0x1f1a)
231         (*handle->_handler)(handle->_handler_param);
011f00:   F03F 3FFF           AND.W   #0x3fff,R15
011f04:   025F                RLAM.W  #1,R15
011f06:   4F0F                MOV.W   R15,R15
011f08:   00AC 000C           ADDA    #0x0000c,R12
011f0c:   0FEC                ADDA    R15,R12
011f0e:   0C0F                MOVA    @R12,R15
011f10:   0F3C 003E           MOVA    0x003e(R15),R12
011f14:   00AF 003A           ADDA    #0x0003a,R15
011f18:   0F00                BRA     @R15
        $C$L12:
011f1a:   0110                RETA    

原来的汇编器需要 7 条指令来执行处理程序,但是 add-IV-to-PC 会破坏管道。这里我们有 13 条指令,因此效率几乎相等。

顺便说一句,实际提交是here

【讨论】:

    【解决方案2】:

    对于在 C 预处理器运行时以数字形式提供的常量,您可以使用 #define 宏将它们与 inline-asm 字符串进行字符串化和连接,例如 asm("blah blah " stringify(MYCONST) "\nblah blah");,但这不适用于 @987654324 @,这需要适当的编译器将其评估为数字。


    FIXME:这在交叉编译时不会那么容易。您必须解析编译器生成的 asm,或从 .o 转储静态数据 这两种方法都可以作为对该方法的微小修改,但有点难看。我将把这个答案留在这里,以防它对非交叉编译用例有用。


    但是,由于您标记了此 cmake,因此您拥有一个可以处理一系列依赖项的构建系统。 您可以编写一个程序,使用offsetof创建一个具有类似内容的.h,使用一些简单的printf 语句

    // already stringified to simplify
    // if you want them as numeric literals, leave out the double quotes and use a STR() macro
    #define OFFSET_Timer_driver_t__CCR1_handle  "12"
    #define OFFSET_Timer_driver_t__CCR2_handle  "16"
    ...
    

    然后你可以在需要它的文件中#include "struct_offsets.h",并将其用于内联asm之类的

    asm("insn " OFFSET_Timer_driver_t__CCR1_handle "\n\t"
        "insn blah, blah \n\t"
        "insn foo " OFFSET_Timer_driver_t__CCR2_handle "\n\t"
       );
    

    或者使用纯 asm 而不是裸函数,因为你是。

    使用 CMake 构建依赖项,以便任何需要 struct_offsets.h 的文件在发生更改时重新构建。

    【讨论】:

    • @CL.:哦,好点子,我认为它不会像我描述的那样在构建过程中在编译目标上运行代码。您可以创建一个简单的.c 来定义像long offset_foo_bar = offsetof(blah, blah) 这样的全局变量,然后将其编译为asm (.s) 并查看标记为.long 12 的代码,或者编译为.o 和hexdump 之类的。当然有一种更好/更清洁的方法,而不必为任何目标解析 asm,或者为该目标从 .o 转储静态数据,但我想不出一个。
    • @Peter Cordes 这是我最初的想法——预先生成一些头文件的偏移量并将其包含在驱动程序中。但问题是 - 我如何用 cmake 做到这一点? configure_file() 似乎不是最好的工具,我似乎找不到其他合适的工具。
    • @mutant-industries:您需要一个实际的 C 编译器来解析结构并了解 ABI 规则以获取偏移量。你不能用纯 CMake 来做到这一点,除非你想使用某种预处理器自己重新实现 ABI 的结构对齐/填充规则。
    • 解决交叉编译情况的一个丑陋技巧是在您知道编译器将输出它的某些上下文中使用 offsetof 的值,例如,作为错误消息的输出。例如,使用该值的负数作为数组大小,并且编译器可能会输出-123 is not a valid array size 或其他任何内容(您可以通过赋值执行类似的操作以获取有关“常量值截断等”的消息)。然后您可以从错误消息中解析结果并生成您的.h,而无需“运行”外部二进制文件。写这个让我觉得很脏……
    • @BeeOnRope:我想到了“令人愉快的可怕”这个短语。
    猜你喜欢
    • 2015-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-05
    • 1970-01-01
    • 2023-03-25
    • 2012-10-20
    • 1970-01-01
    相关资源
    最近更新 更多