在 C 函数中定义一个唯一的全局程序集标签/符号

Posted

技术标签:

【中文标题】在 C 函数中定义一个唯一的全局程序集标签/符号【英文标题】:Define a unique and global assembly label/symbol inside C functions 【发布时间】:2018-10-09 22:07:12 【问题描述】:

我想用某种汇编标签/符号来标记特定的 C 行,它不会占用二进制文件中的任何空间,但是通过检查链接器输出映射文件,我将知道所有此类生成标签的出现,并最终知道 C以这种方式“标记”的代码。所以我希望能够定义这样的标签,并使它们成为全局的,并且 used 所以链接器不会把它扔掉 我还需要一些宏魔法来让这些标签在每次预处理 C 代码时都有一个唯一的名称(以确保函数的每个内联实例都有自己的标签 - 否则我猜我会有重复的符号)

例子:

// my build system will pass -DMYFILE_ID for each file, here I am trying to create a unique literal for each inline instance of the function
#define UN(X) #X
#define UNIQUE(X,Y) UN(X##Y)

void my_func(void)

    _asm("GLOBAL_LABEL_"UNIQUE(MYFILE_ID,__LINE__)":\n\t")
    my_c_code_I_want_to_track();

最后我想要的是在链接器输出符号映射文件中,类似的东西

0xsome_address GLOBAL_LABEL_12_1
0xdifferent_address GLOBAL_LABEL_12_2
0xyeanotheraddress GLOBAL_LABEL_13_1

这基本上应该让我知道 my_c_code_i_want_to_track 实例化的地址

整个想法的灵感来自于组装中的标签实际上是如何放置的“符号”,因此可以检查它们的地址,但它们实际上并不占用自己的空间。

问题: 1.是否可以像这样定义装配标签 2. 如何让这些标签留在输出符号映射文件中并出现 3. UNIQUE 宏有问题,因为我在尝试编译时得到“标签重新定义”

【问题讨论】:

你的问题到底是什么? 用问题定义编辑的问题 不确定内联是如何工作的,它可以很好地重用相同的__LINE__。至于你的地图文件:你是如何创建的? 除非您明确要求,否则不应丢弃、引用或不引用全局变量。 在剥离二进制文件之前提取地址信息可能会更简单。 【参考方案1】:

您可以在Extended-asm template 中使用%=(例如label%=:)来让编译器生成一个唯一编号,以避免在一个编译单元中多次内联包含 inline-asm 的函数时发生名称冲突。

#define STRINGIFY(x) #x
#define STR(x) STRINGIFY(x)
int foo(int x) 
    asm("marker" __FILE__ "_line" STR(__LINE__)  "_uniqueid%=:" :::);
    return x+1;



int caller1(int x) 
    return foo(x);


int caller2(int x) 
    return foo(x);

使用gcc -O3 (on Godbolt) 编译为以下汇编:

foo(int):
        marker/tmp/compiler-explorer-compiler11899-55-1ki0cth.pehm/example.cpp_line4_uniqueid7:
        lea     eax, [rdi+1]
        ret
caller1(int):
        marker/tmp/compiler-explorer-compiler11899-55-1ki0cth.pehm/example.cpp_line4_uniqueid22:
        lea     eax, [rdi+1]
        ret
caller2(int):
        marker/tmp/compiler-explorer-compiler11899-55-1ki0cth.pehm/example.cpp_line4_uniqueid41:
        lea     eax, [rdi+3]
        ret

这当然不会组合,因为/ 在 GAS 中不是有效的标签字符。

使用只包含可以出现在符号名称中的字符的MYFILE_ID,这样可以很好地组合,并且您应该能够在nm 输出中看到所有marker 标签。

【讨论】:

【参考方案2】:

一个问题是,由于内联,您可能会获得同一个标签的多个副本。将以下属性添加到包含这些标签的函数中:

__attribute__((noinline))

另请注意,您需要将符号标记为全局。让我们把它提取到一个宏中,这样我们就可以很好地格式化而不改变__LINE__的值:

#define MAKE_LABEL \
    __asm__( \
        "GLOBAL_LABEL_" UNIQUE(MYFILE_ID, __LINE__) ":" \
        "\n\t.global GLOBAL_LABEL_" UNIQUE(MYFILE_ID, __LINE__) \
    )

但宏扩展已关闭。不幸的是,我无法向您解释为什么会这样。但这里是正确的宏定义:

#define UN(X) #X
#define UNIQUE2(X,Y) UN(X##Y)
#define UNIQUE(X,Y) UNIQUE2(X,Y)

否则,您将得到 __LINE__ 而不是 23。

【讨论】:

GNU C Basic asm(无操作数)隐含为volatile。没有输出操作数的扩展 asm 也是如此。 嗯,很有趣。我不知道这一点。 知道如何为此添加类似 __attribute((used)) 的内容,因此即使要求链接器丢弃任何未引用的符号 - 这些标签仍然存在? @LouisShraga:您具体使用了哪些链接器选项导致了这种情况? @LouisShraga:您将不得不告诉我们更多有关构建工具链的信息。 __attribute__((used)) 的工作方式在不同的系统上是不同的。

以上是关于在 C 函数中定义一个唯一的全局程序集标签/符号的主要内容,如果未能解决你的问题,请参考以下文章

如何定义全局函数指针并分配给特定地址

如何理解函数中的指针

3. 在C语言程序中,在函数内部定义的变量称为全局变量。

单片机C语言 -- 结构体与指针在编程中的应用

全局变量怎么定义

Linux下C/C++动态库在运行时是怎样加载进来的