我先从最核心的问题开始:在使用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"
和"bar:%p"
)以及预处理器字符串("foo.c"
和"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,其中function-name是词法上封闭函数的名称。这个名称是函数的未装饰名称。 ... 这些标识符不是预处理器宏。在GCC 3.3及更早版本以及仅限于C语言中,
__FUNCTION__
和__PRETTY_FUNCTION__
被视为字符串字面量;它们可以用来初始化char数组,并且可以与其他字符串字面量连接。GCC 3.4及更高版本将它们视为变量,就像__func__
一样。
因此,如果您将__func__
与ROM_STR
包装在一起,您将得到:
error: invalid initializer
如果你试图在使用__func__
之前或之后添加 section 属性,你会得到:
error: expected expression before ‘__attribute__’
或者error: expected ‘)’ before ‘__attribute__’
那么我们又回到了最初的问题:是否有可能将__func__
存储在我选择的某个部分中?也许我可以使用-fdata-sections
以及一些链接脚本魔法来排除.rodata
之外的.rodata.__func__.*
?如果可以,那么链接脚本中用于排除的glob语法是什么?换句话说,在某个地方你会有一个*(.rodata*)
- 我可以将*(.rodata.__func__*)
放在其他地方,但我需要修改原始的glob以将其排除,这样我就不会得到两份副本。