【问题标题】:Forcing certain compiler-generated variables into specific ELF sections (with gcc)将某些编译器生成的变量强制放入特定的 ELF 部分(使用 gcc)
【发布时间】:2011-09-20 19:21:09
【问题描述】:

我将从终极问题开始:在带有 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 是词法封闭函数的名称。该名称是函数的朴素名称。 ... 这些标识符不是预处理器宏。在 GCC 3.3 和更早版本中,以及仅在 C 中,__FUNCTION____PRETTY_FUNCTION__ 被视为字符串文字;它们可用于初始化 char 数组,并且可以与其他字符串 literas 连接。 GCC 3.4 及更高版本将它们视为变量,例如 __func__

因此,如果你用ROM_STR 包裹__func__,你会得到

error: invalid initializer

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

error: expected expression before ‘__attribute__’

error: expected ‘)’ before ‘__attribute__’

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

【问题讨论】:

    标签: c gcc linker elf


    【解决方案1】:

    一种方法可能比您想要的更复杂,它是插入一个脚本来更改编译和汇编之间的部分名称。例如:

    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 的情况下构建脚本,甚至可能会挑选出适当的数据。但是我的实验和对链接器脚本的进一步阅读表明,明确放置这些部分比我想象的要容易得多,所以我在另一个答案中详细说明了这一点。
    • 另外,为 BFD 的链接 +1。这对很多事情都很方便。
    【解决方案2】:

    看起来我最后用-fdata-sections 业务回答了我自己的问题,我只是对 GNU 链接器了解得不够多,无法看到它。只要我首先指定*(.rodata.__func__*) 位,我实际上并不需要排除的通配符。 glob 匹配的任何部分都将被标记为已使用,因此*(.rodata*) 的后续 glob 不会重复计算它们并将它们复制到其他地方。我根本不需要用ROM_STR 标记它们。酷!

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

    【讨论】:

    猜你喜欢
    • 2013-07-22
    • 2016-10-22
    • 1970-01-01
    • 2018-11-07
    • 1970-01-01
    • 2013-11-23
    • 1970-01-01
    • 1970-01-01
    • 2011-03-01
    相关资源
    最近更新 更多