将某些编译器生成的变量强制转换为特定的ELF节(使用gcc)

我将从最终的问题开始:在C中使用gcc,是否有可能获得__func__ (或等效地, __FUNCTION__ )的值存储在除.rodata (或者任何地方-mrodata= points)或子部分之外的部分中其?

完整的解释:

假设我有一个记录宏:

 #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宏给它一个临时名称,将它ROM_STR到一个特定的部分,然后计算到起始地址,这样它就可以干净地替换。 如果您尝试将char *变量作为格式字符串传递给LOG ,则此方法无效,但我愿意排除该用例。

通常,碰巧相同的匿名字符串会被编译器合并到一个存储位置,因此一个文件中的__FILE__每个实例都将共享相同的运行时地址。 通过ROM_STR的显式命名,每个实例都将获得自己的存储位置,因此在__FILE__上使用它可能实际上没有意义。

但是,我想在__func__上使用它。 问题是__func____FILE__不是同一种魔法。 从gcc手册,“函数名称为字符串”:

标识符__func__由翻译器隐式声明,就像紧跟在每个函数定义的__func__大括号后面的声明一样

 static const char __func__[] = "function-name"; 

出现了,其中function-name是词法封闭函数的名称。 这个名字是该function的简单名称。 …这些标识符不是预处理器宏。 在GCC 3.3及更早版本中,仅在C中, __FUNCTION____PRETTY_FUNCTION__被视为字符串文字; 它们可以用来初始化char数组,它们可以与其他字符串文字连接起来。 GCC 3.4及更高版本将它们视为变量,如__func__

因此,如果你用ROM_STR包装ROM_STR ,你就得到了

 error: invalid initializer 

如果你试图在使用__func__之前或之后放置一个section属性,你就得到了

 error: expected expression before '__attribute__' 

要么

 error: expected ')' before '__attribute__' 

因此我们回到开头问题:是否有可能将__func__存储在我选择的部分中? 也许我可以使用-fdata-sections并使用一些链接器脚本魔法来获取.rodata.__func__.*.rodata的其余部分中.rodata ? 如果是这样,在链接描述文件中使用排除的globbing的语法是什么? 换句话说,在某个地方你有一个*(.rodata*) – 我可以把*(.rodata.__func__*)放在其他地方,但是我需要修改原始的glob来排除它所以我没有得到两份。

一种可能比你想要的更黑的方法是插入一个脚本来改变编译和汇编之间的段名。 例如:

 gcc -fdata-sections -S -o test.s test.c sed 's/^\t.section\t\.rodata\.__func__\.[0-9]*/\t.section .rom_data/' -i test.s gcc -c test.s 

您还可以尝试编写一个clang转换传递,将__func__声明放在您选择的部分中,或者使用libbfd编写一个目标文件操作程序 。

看起来我最后用-fdata-sections业务回答了我自己的问题,我只是不明白GNU Linker足以看到它。 只要我先指定*(.rodata.__func__*)位,我实际上并不需要排除*(.rodata.__func__*) 。 glob匹配的任何部分都将标记为已使用,因此*(.rodata*)的后续glob不会对它们进行双重计数并将它们复制到其他位置。 我根本不需要用ROM_STR标记它们。 凉!

重要的是要注意-fdata-sections确实将每个函数字符串放入它自己的.rodata.__func__.1234部分(我不确定数字跟随的模式)。 我不知道匿名字符串是否也有自己的部分; 如果是这样,我可以使用相同的链接器技巧捕获所有匿名字符串而不是ROM_STR部分属性宏,但它可能是一个坏主意。 ROM_STRLOG宏中使用,因此保证仅应用于日志记录格式字符串。 如果我使用链接器技巧强制所有匿名字符串进入ROM,那将包括正常的消息数据,并且我将支付运行时性能损失以从闪存访问它。 所以我不知道它是否可能,但它的可行性取决于您的具体系统要求。