我使用 C 和 ASM 代码的混合在 STCubeIDE 中对 STM32H745 进行编程。
我有以下结构来确保在两个核心 CM4 和 CM7 之间共享的变量的严格排序:
struct miscSharedMemoryStruct {
volatile uint32_t triggerPtr;
volatile uint32_t bufferHead;
}
__attribute__((section(".sram3"))) struct miscSharedMemoryStruct miscSharedMemory;
.sram3
部分是在链接器脚本中定义的,并且在两个内核的C代码中访问我的变量效果很好。到目前为止,一切都很好。然而...
为了启用
miscSharedMemory
条目的访问,我在 ASM 代码中将变量设置为全局:
.global miscSharedMemory
为了让汇编器能够识别正确的地址,我在标头中添加了以下定义语句,其中定义了上述结构:
#define BUFFER_HEAD_ADR ((uintptr_t)(&miscSharedMemory.bufferHead))
最后,想法是从上面的地址获取数据到我的ASM代码中的寄存器中:
ldr R1, =BUFFER_HEAD_ADR
但是,在编译我的代码时,我得到了
对“BUFFER_HEAD_ADR”的未定义引用
使汇编器识别结构中变量的正确地址的正确方法是什么?我可能不需要执行
.global
语句,只要BUFFER_HEAD_ADR
中存储的地址是正确的并且被汇编器识别,但我只是不知道如何让它工作。
汇编器不知道如何解析 C 源代码并应用结构布局的 ABI 规则来导出成员偏移量。使用像
#define
这样的 CPP 宏进行文本替换会将 ((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");
}
作为 C 或 C++ 程序编译并运行(在 Godbolt 上,对于 x86-64 目标=主机)。输出是:
.equ foo_a, 0
.equ foo_b, 4
这允许
ldr R1, =var + foo_b
或其他什么。或者就你的情况而言,
ldr R1, =miscSharedMemory + miscSharedMemoryStruct_bufferHead
如果需要,您可以对常量使用不同的命名约定,如果您使用不同的宏或不同的格式字符串,甚至可以对不同的结构集使用不同的约定。
如果交叉编译,您需要目标系统的结构布局,而不是例如x86-64 主机。希望您有一个模拟器或可以让您为目标系统运行程序并收集其标准输出的东西。
如果不是
stdout
,那么也许可以按顺序将值分配给volatile size_t sink
全局变量? (并将其与您转储的顺序列表相匹配。)
如果没有,也许可以定义全局变量,如
int foo_a = 0;
等,并从 .o
中提取值,而不是编写打印它们的程序。 (0
值将进入.bss
,非零进入.data
,这并不理想。也许为所有尺寸添加一个常量0x1000
,以便它们都进入.data
?)
如果有一种方法可以为结构体全局变量的每个成员提供 asm 符号名称,您只需定义每个结构体之一并处理目标文件上
nm
的输出即可获取符号地址的偏移量(而不是而不是必须深入研究数据)。
对于以已知顺序分配给
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
}
上帝之箭 - 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
前三个
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
用于解析输出。