我将从最终的问题开始:在带有 gcc 的 C 中,是否有可能获得以下值__func__
(或同等地,__FUNCTION__
)存储在除.rodata
(或任何地方-mrodata=
点)或其小节?
完整解释:
假设我有一个日志记录宏:
#define LOG(fmt, ...) log_internal(__FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__)
(字符串连接运算符##
在该一元上下文中使用时会消耗前面的逗号当且仅当__VA_ARGS__
列表为空,因此允许使用带或不带参数的格式字符串。)
然后我就可以正常使用宏了:
void my_function(void) {
LOG("foo!");
LOG("bar: %p", &bar);
}
可能会打印(显然取决于log_internal
):
foo.c:201(my_function) foo!
foo.c:202(my_function) bar: 0x12345678
在这种情况下,格式字符串 ("foo"
and "bar: %p"
) 和预处理器字符串 ("foo.c"
and "my_function"
)是匿名只读数据,它们被放入.rodata
自动部分。
但是假设我希望它们去一个不同的地方(我在一个嵌入式平台上运行几乎所有来自 RAM 的东西以提高速度,但内存限制正在推动将一些东西移动到 ROM 中)。移动起来很“容易”__FILE__
和格式字符串:
#define ROM_STR(str) (__extension__({static const __attribute__((__section__(".rom_data"))) char __c[] = (str); (const char *)&__c;}))
#define LOG(fmt, ...) log_internal(ROM_STR(__FILE__), __LINE__, __func__, ROM_STR(fmt), ##__VA_ARGS__)
你不能把__attribute__
在匿名字符串上,所以ROM_STR
宏给它一个临时名称,将其附加到特定部分,然后计算起始地址,以便它可以干净地替换。如果您尝试通过char *
变量为LOG
作为您的格式字符串,但我愿意排除该用例。
通常,恰好相同的匿名字符串会被编译器组合到单个存储位置中,因此每个实例__FILE__
在一个文件中将共享相同的运行时地址。通过显式命名ROM_STR
,每个实例都会有自己的存储位置,因此使用它实际上可能没有意义__FILE__
.
不过,我想用它__func__
。问题是__func__
与魔法不同__FILE__
。来自 gcc 手册,“函数名称作为字符串”:
标识符__func__
由翻译器隐式声明,就好像紧跟在每个函数定义的左大括号之后,声明
static const char __func__[] = "function-name";
出现,其中 function-name 是词法封闭函数的名称。该名称是函数的原始名称。
...
这些标识符不是预处理器宏。在 GCC 3.3 及更早版本中,并且仅在 C 中,__FUNCTION__
and __PRETTY_FUNCTION__
被视为字符串文字;它们可以用来初始化 char 数组,并且可以与其他字符串文字连接。 GCC 3.4 及更高版本将它们视为变量,例如__func__
.
因此,如果你包装__func__
with ROM_STR
, 你得到
error: invalid initializer
如果您尝试在使用之前或之后放置一个节属性__func__
, 你得到
error: expected expression before ‘__attribute__’
or
error: expected ‘)’ before ‘__attribute__’
因此我们回到开头的问题:是否有可能得到__func__
存储在我选择的部分中?也许我可以用-fdata-sections
并执行一些链接器脚本魔法来获得.rodata.__func__.*
排除在其余的之外.rodata
?如果是这样,链接描述文件中排除通配符的语法是什么?换句话说,在某处你有一个*(.rodata*)
- 我可以放一个*(.rodata.__func__*)
其他地方,但我需要修改原始的 glob 以排除它,这样我就不会得到两个副本。