如何在 ASM 中访问 C 结构体成员

Han*_*sel 2 c assembly struct stm32

我使用 C 和 ASM 代码的混合在 STCubeIDE 中对 STM32H745 进行编程。

我有以下结构来确保在两个核心 CM4 和 CM7 之间共享的变量的严格排序:

struct miscSharedMemoryStruct {
   volatile uint32_t triggerPtr;
   volatile uint32_t bufferHead;
}
__attribute__((section(".sram3"))) struct miscSharedMemoryStruct miscSharedMemory;
Run Code Online (Sandbox Code Playgroud)

该部分.sram3是在链接器脚本中定义的,并且在两个内核的 C 代码中访问我的变量效果很好。到目前为止,一切都很好。然而...

为了允许访问 的条目miscSharedMemory,我在 ASM 代码中将该变量设置为全局变量:

.global miscSharedMemory
Run Code Online (Sandbox Code Playgroud)

为了使汇编器能够识别正确的地址,我在标头中添加了以下定义语句,其中定义了上述结构:

#define BUFFER_HEAD_ADR  ((uintptr_t)(&miscSharedMemory.bufferHead))
Run Code Online (Sandbox Code Playgroud)

最后,想法是从上面的地址获取数据到我的 ASM 代码中的寄存器中:

ldr R1, =BUFFER_HEAD_ADR
Run Code Online (Sandbox Code Playgroud)

但是,当编译我的代码时,我得到了

对“BUFFER_HEAD_ADR”的未定义引用

使汇编器识别结构中变量的正确地址的正确方法是什么?我可能不需要执行该.global语句,只要存储的地址BUFFER_HEAD_ADR是正确的并被汇编器识别即可,但我只是不知道如何让它工作。

Pet*_*des 5

汇编器不知道如何解析 C 源代码并应用结构布局的 ABI 规则来导出成员偏移量。使用 CPP 宏进行文本替换#define会放入((uintptr_t)(&miscSharedMemory.bufferHead))您的 asm 源代码中,就像您在源代码中输入的一样。所以这没有帮助。

作为构建脚本的一部分,您可以有一个 C 程序,它#include包含结构体定义的标头,并用于offsetof(type, member)偏移printf量(至少是您的 asm 使用的成员),也许还有sizeof(struct)总大小,其格式类似于.equ type_member, 12汇编时常量定义,您的 asm 文件可以#include或.include.

例如,如果您使用 make,您将有一个规则来构建一个struct_offsets.inc文件,并依赖于所有相关.h文件。规则是编译并运行该 C 程序,并将其输出重定向到目标.inc文件。.o从使用 asm 源构建的对象文件 ( ).inc获得对其的依赖以及相应的.S.

// #include "foo.h"
// #include "bar.h"

struct foo {   // for example
  int a;
  int b;
};

#include <stddef.h>  // for offsetof
#include <stdio.h>
#include <assert.h>

// C equivalent of C++ sizeof(type::member)
#define member_size(type, member) sizeof(((type *)0)->member)

static const char *offset_fmt = ".equ %s_%s, %d\n";
#define PRINT_OFFSET(T, member) printf(offset_fmt, #T, #member, offsetof(struct T, member))
// stringify the names, passed without the struct keyword.

int main(){
  PRINT_OFFSET(foo, a);
  PRINT_OFFSET(foo, b);

// optional: you'll notice a missing definition from asm error messages and add it to this helper program
// also, this isn't attempting to detect new members in the middle, just a fun idea in case you want something like this
  static_assert( (sizeof(struct foo) == (offsetof(struct foo, b) + member_size(struct foo, b))),
                         "there's a member we didn't print, or padding");
}
Run Code Online (Sandbox Code Playgroud)

作为 C 或 C++ 程序编译并运行(在x86-64 目标=主机的Godbolt 上)。输出是:

.equ foo_a, 0
.equ foo_b, 4
Run Code Online (Sandbox Code Playgroud)

这允许ldr R1, =var + foo_b或其他什么。或者就你而言,

ldr R1, =miscSharedMemory + miscSharedMemoryStruct_bufferHead
Run Code Online (Sandbox Code Playgroud)

如果需要,您可以对常量使用不同的命名约定,如果使用不同的宏或不同的格式字符串,甚至可以对不同的结构集使用不同的约定。


交叉编译:该程序需要针对目标ABI构建!

更新:解决方法:如何在编译时打印结构成员的偏移量?

您可以使用 GNU C 内联 asm 来格式化文本,该文本甚至不必是有效的 asm 语法,也可以是包含您想要的信息的 asm 注释。(使用 GCC 或clang -no-integrated-as,不检查 asm 语句对于目标而言是否“有效”)。或者正如 Jester 指出的那样,您可以发出asm带有指令的语句.equ。使用 asm 语句的正确操作数,您可以在您想要的位置发出一行仅包含纯数字的文本。


如果交叉编译,您需要目标系统的结构布局,而不是 x86-64 主机。希望您有一个模拟器或可以让您为目标系统运行程序并收集其标准输出的东西。

如果不是stdout,那么也许可以按顺序为volatile size_t sink全局变量赋值?(并将其与您转储的顺序列表相匹配。)

如果没有,也许可以定义诸如此类的全局变量int foo_a = 0;,并从 中提取值,而.o不是编写打印它们的程序。(0值将进入.bss,非零.data,这并不理想。也许0x1000为所有大小添加一个常量,以便它们全部进入.data?)

如果有一种方法可以为结构全局变量的每个成员提供 asm 符号名称,那么您只需定义每个结构之一并处理nm目标文件上的输出即可获取符号地址的偏移量(而不必挖掘到数据中)。

对于以已知顺序分配给 a 的程序volatile int sink,也许可以解析编译器 asm 输出以查看其加载到寄存器中的值?也许甚至gcc -fverbose-asm可以注释说明。调试版本将按程序顺序单独编译每个 C 语句,并且不会跨语句重用任何常量。

struct foo {   // for example
  int a;
  char c[65532];
  int b;
};

#include <stddef.h>  // for offsetof
//#include <stdio.h>
//#include <assert.h>

int main(){
    volatile size_t sink;  // local var to reduce noise of setting up addresses
#define PRINT_OFFSET(T, member) sink = offsetof(struct T, member)

  PRINT_OFFSET(foo, a);
  PRINT_OFFSET(foo, b);
  PRINT_OFFSET(foo, c);

  (void)sink;  // silence unused warning
}
Run Code Online (Sandbox Code Playgroud)

上帝螺栓- GCC-O0 -Wall -xc -mcpu=cortex-m3

main:
        push    {r7}
        sub     sp, sp, #12
        add     r7, sp, #0
        movs    r3, #0           # offsetof(struct foo, a)
        str     r3, [r7, #4]
        mov     r3, #65536       # offsetof(struct foo, b)
        str     r3, [r7, #4]
        movs    r3, #4           # offsetof(struct foo, c)
        str     r3, [r7, #4]
        ldr     r3, [r7, #4]     # (void)sink reload marks that we're done
        movs    r3, #0           # return value
        mov     r0, r3
        adds    r7, r7, #12
        mov     sp, r7
        ldr     r7, [sp], #4
        bx      lr
Run Code Online (Sandbox Code Playgroud)

前三个movs/movw指令是我们分配的三个offsetof值。这是从 Godbolt 编译器资源管理器过滤的 asm,但原始输出arm-none-...-gcc -S不会有更多指令,只有指令。与正则表达式匹配的行 *mov.*#就是您想要的行。

足够新的 Thumb 模式(带有movw)允许宽立即数,例如 16 位。如果offsetof(foo, b)更高,它将是ldr来自附近文字池的 32 位常量,但我惊喜地发现 GCC 使用简单的movs/movw指令来处理高达 65536 的值,因此即使是大型结构也可以简单地工作。

由于我们丢失了 asm 输出中的名称(即使除非gcc -fverbose-asm您为每个要分配的值都有一个命名的全局变量),所以从您将用于解析输出的.c同一列表中以编程方式生成此名称。type : member