gcc 编译器标志和符号表之间的不兼容函数地址

Posted

技术标签:

【中文标题】gcc 编译器标志和符号表之间的不兼容函数地址【英文标题】:Non-compatible function addresses between gcc compiler flags and symbol table 【发布时间】:2014-10-31 13:26:15 【问题描述】:

我已经用

编译了一个项目(这里是openssh,但这并不重要)
CFLAGS="-ggdb3 -O0 -lm -finstrument-functions"

符号表是(小摘录):

nm -o somepath/sbin/sshd
/mypath/install/sbin/sshd:000f3548 d auth_method
/mypath/install/sbin/sshd:0001a90f t auth_openfile
/mypath/install/sbin/sshd:0001ab90 T auth_openkeyfile
/mypath/install/sbin/sshd:0001ac31 T auth_openprincipals
/mypath/install/sbin/sshd:0001d73a T auth_parse_options
/mypath/install/sbin/sshd:0000e362 T auth_password

我打印__cyg_profile_func_enter|exit中的函数地址时得到的是:

0xb768f8ee
0xb768f66c
0xb768f66c
0xb76d9ae8

显然不是同一范围,在查看了所有数据后确认。

我的理解是nm 提供了偏移地址。当项目中有很多文件时,__cyg_profile_func_enter 呢?是否需要执行额外的转换?

来自gcc documentation,不应该有:

          void __cyg_profile_func_enter (void *this_fn,
                                         void *call_site);
          void __cyg_profile_func_exit  (void *this_fn,
                                         void *call_site);

The first argument is the address of the start of the current function, which may be looked up exactly in the symbol table.

那么,为了让它发挥作用,我错过了什么?

编辑:我的 ptrace.c 我添加了一个互斥锁

#if (__GNUC__>2) || ((__GNUC__ == 2) && (__GNUC_MINOR__ > 95))

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <sys/errno.h>
#include <pthread.h>

#define PTRACE_PIPENAME  "TRACE"

#define REFERENCE_OFFSET "REFERENCE:"
#define FUNCTION_ENTRY   "enter"
#define FUNCTION_EXIT    "exit"
#define END_TRACE        "EXIT"
#define __NON_INSTRUMENT_FUNCTION__    __attribute__((__no_instrument_function__))
#define PTRACE_OFF        __NON_INSTRUMENT_FUNCTION__
#define STR(_x)          #_x
#define DEF(_x)          _x
#define GET(_x,_y)       _x(_y)
#define TRACE __GNU_PTRACE_FILE__

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

/** Initial trace open */
static FILE *__GNU_PTRACE_FILE__;

/** Final trace close */
static void
__NON_INSTRUMENT_FUNCTION__
gnu_ptrace_close(void)

    fprintf(TRACE, END_TRACE " %ld\n", (long)getpid());

        int res = fflush(TRACE);
        if (res < 0) 
            printf("Erreur sur gnu_ptrace_close / fflush\n");
        

    if (TRACE != NULL)
        fclose(TRACE);

        pthread_mutex_destroy(&lock);

    return ;


/** Trace initialization */
static int
__NON_INSTRUMENT_FUNCTION__
gnu_ptrace_init(void)

    struct stat sta;
    __GNU_PTRACE_FILE__ = NULL;

    /* See if a trace file exists */
    if (stat(PTRACE_PIPENAME, &sta) != 0) 
    
        /* No trace file: do not trace at all */
        return 0;
    
    else 
    
        /* trace file: open up trace file */
        if ((TRACE = fopen(PTRACE_PIPENAME, "a")) == NULL)
        
            char *msg = strerror(errno);
            perror(msg);
            printf("[gnu_ptrace error]\n");
            return 0;
        

        #ifdef PTRACE_REFERENCE_FUNCTION
        fprintf(TRACE,"%s %s %p\n",
                  REFERENCE_OFFSET,
                  GET(STR,PTRACE_REFERENCE_FUNCTION),
                  (void *)GET(DEF,PTRACE_REFERENCE_FUNCTION));
        #endif

        /* Tracing requested: a trace file was found */
        atexit(gnu_ptrace_close);
        return 1;
    


/** Function called by every function event */
void
__NON_INSTRUMENT_FUNCTION__
gnu_ptrace(char * what, void * p)

    static int first=1;
    static int active=1;
    int res = 0;

    if (active == 0)
        return;

    if (first)
    
        active = gnu_ptrace_init();
            first = 0;

            if (active == 0)
            return;
    

        if (!what || !p) 
            printf("Erreur, what ou p NULL\n");
            return;
        

        if (strcmp(what, FUNCTION_ENTRY) && strcmp(what, FUNCTION_EXIT)) 
            printf("Erreur, what incorrect\n");
            return;
        

        printf("----------------------- what=%s     p=%p\n", what, p);

        res = fprintf(TRACE, "%s %p\n", what, p);
        if (res < 0) 
            printf("Erreur sur gnu_ptrace / fprintf\n");
/*            active = 0;*/
        

        if (res > 0) res = fflush(TRACE);
        if (res < 0) 
            printf("Erreur sur gnu_ptrace / fflush\n");
            active = 0;
        

    return;


/** According to gcc documentation: called upon function entry */
void
__NON_INSTRUMENT_FUNCTION__
__cyg_profile_func_enter(void *this_fn, void *call_site)

        pthread_mutex_lock(&lock);
    gnu_ptrace(FUNCTION_ENTRY, this_fn);
    (void)call_site;
        pthread_mutex_unlock(&lock);


/** According to gcc documentation: called upon function exit */
void
__NON_INSTRUMENT_FUNCTION__
__cyg_profile_func_exit(void *this_fn, void *call_site)

        pthread_mutex_lock(&lock);
    gnu_ptrace(FUNCTION_EXIT, this_fn);
    (void)call_site;
        pthread_mutex_unlock(&lock);


#endif

这是修改后的 ptrace.c 与 JHiant 的想法。我已与 -ldl 链接

#include <dlfcn.h>

_

Dl_info finfo;
if (!dladdr(p, &finfo))
    printf("Erreur, dladdr\n");

    /* res = fprintf(TRACE, "%s %p\n", what, p); */
    res = fprintf(TRACE, "%s %p %s (%s)\t\n", what, p, finfo.dli_sname, finfo.dli_fname);

这是获得的轨迹(第一行):

enter 0xb7494974 OPENSSL_cpuid_setup (/home/laurent/Documents/projet/install/lib/libcrypto.so.1.0.0)    
exit 0xb7494974 OPENSSL_cpuid_setup (/home/laurent/Documents/projet/install/lib/libcrypto.so.1.0.0) 
enter 0xb76ac470 main (/home/laurent/Documents/projet/install/sbin/sshd)    
enter 0xb76b34fe (null) (/home/laurent/Documents/projet/install/sbin/sshd)  
exit 0xb76b34fe (null) (/home/laurent/Documents/projet/install/sbin/sshd)   
enter 0xb7749384 (null) (/home/laurent/Documents/projet/install/sbin/sshd)  
enter 0xb770496e (null) (/home/laurent/Documents/projet/install/sbin/sshd)  
enter 0xb77046ec (null) (/home/laurent/Documents/projet/install/sbin/sshd)  
exit 0xb77046ec (null) (/home/laurent/Documents/projet/install/sbin/sshd)   
enter 0xb774eb68 (null) (/home/laurent/Documents/projet/install/sbin/sshd)  
exit 0xb774eb68 (null) (/home/laurent/Documents/projet/install/sbin/sshd)   
exit 0xb770496e (null) (/home/laurent/Documents/projet/install/sbin/sshd)   
exit 0xb7749384 (null) (/home/laurent/Documents/projet/install/sbin/sshd)   

其他行一直显示null,而不是函数名。我检查了 sshd 映射,使用了正确的带有符号的 libcrypto 构建。在上面的跟踪中令人惊讶的是,它只显示了一次对 libcrypto 的调用,这看起来就像一个简单的设置,正如名称所示。

OpenSSH 编译时使用(在控制台中提取一行 make 执行 - 都具有相同的选项 - 通常没有 -fpic,但生成的跟踪是相同的):

gcc -ggdb3 -O0 -lm -finstrument-functions -ldl -fpic -Wall -Wpointer-arith -Wuninitialized -Wsign-compare -Wformat-security -Wno-pointer-sign -Wno-unused-result -fno-strict-aliasing -D_FORTIFY_SOURCE=2 -ftrapv -fno-builtin-memset -fstack-protector-all -fPIE  -I. -I. -I/home/laurent/Documents/projet/install/include  -DSSHDIR=\"/home/laurent/Documents/projet/install/etc\" -D_PATH_SSH_PROGRAM=\"/home/laurent/Documents/projet/install/bin/ssh\" -D_PATH_SSH_ASKPASS_DEFAULT=\"/home/laurent/Documents/projet/install/libexec/ssh-askpass\" -D_PATH_SFTP_SERVER=\"/home/laurent/Documents/projet/install/libexec/sftp-server\" -D_PATH_SSH_KEY_SIGN=\"/home/laurent/Documents/projet/install/libexec/ssh-keysign\" -D_PATH_SSH_PKCS11_HELPER=\"/home/laurent/Documents/projet/install/libexec/ssh-pkcs11-helper\" -D_PATH_SSH_PIDDIR=\"/var/run\" -D_PATH_PRIVSEP_CHROOT_DIR=\"/var/empty\" -DHAVE_CONFIG_H -c ssh-keyscan.c

OpenSSL 编译时使用(在控制台中提取一行 make 执行 - 都具有相同的选项):

gcc -I.. -I../.. -I../modes -I../asn1 -I../evp -I../../include  -ggdb3 -O0 -lm -finstrument-functions -ldl  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -Wa,--noexecstack -DL_ENDIAN -DTERMIO -fomit-frame-pointer -Wall -DOPENSSL_BN_ASM_PART_WORDS -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DRMD160_ASM -DAES_ASM -DVPAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM   -c -o cms_lib.o cms_lib.c

为什么没有更多来自 OpenSSL/libcrypto 的踪迹?

为什么跟踪中除了“main”之外没有函数名称?

【问题讨论】:

【参考方案1】:

几个想法:

    创建 helloworld.c 并检查它的 nm 是否与您看到的内容同步。在您的个人资料代码中。这可能是 sshd 构建方式的问题,而不是您的代码。 尝试将 -fpic 添加到您的 CFLAGS。 强制性:只是为了确保您是从源代码编译 sshd,而不是链接它,因为函数检测是在编译步骤而不是链接步骤中插入的。否则它只会打印不是来自 sshd 库的地址,例如 main() 或一些辅助函数的地址。您可以考虑通过以下方式在调试输出中添加函数名称:

 

#include <dlfcn.h>
__cyg_profile_func_enter(void *func, void *site)

    DL_info finfo;
    dladdr(fun, &finfo);
    fprintf(log, "Entered &s", finfo.dli_sname);

【讨论】:

我已经 (1) 成功了,跟踪和 nm 地址直接匹配。我尝试了etrace project 提供的示例,从那里我得到了方法和 ptrace.c 文件。回到办公室后,我会尝试(2)和(3)。谢谢。 我还不能测试你的解决方案,但它是最有希望的。所以我奖励你。在列表点不会丢失。干杯。 干杯。随时通知我们。我看到的一件事是 nm 中的数字与发布的数字之间的增量,但没有相关性。会很好奇会发生什么。让我知道 DL_info 标志如何提供帮助。您可以相对于 nm 中的 main 对其进行逆向工程。不确定,但如果你能做到这一点,也许可以以某种方式查找主地址作为运行时挂钩。 我已根据您的建议编辑了 OP。那我就一点痕迹都没有了。 我又有踪迹了。这是由于题外话问题。【参考方案2】:

当您加载并执行可执行文件(或共享库)时,它将被加载到任意地址,并且链接器将所有符号(如您的函数)的地址重新定位到相对于可执行文件的加载地址。由于Address space layout randomization,这些地址可能因可执行文件的一次运行而不同,您可以将 nm 显示的地址视为相对地址(朝向可执行文件的加载位置)。

为了打印像 nm 这样的地址,您是否需要知道加载地址并将其从分析函数中给出的地址中减去。由于我不熟悉您正在使用的分析 API,我不知道是否有 API 可以找到该重定位偏移量。

【讨论】:

/proc/$PID/maps 是我们的朋友。看看Mickael Pankov solution。

以上是关于gcc 编译器标志和符号表之间的不兼容函数地址的主要内容,如果未能解决你的问题,请参考以下文章

ELF 动态链接 - so 的 重定位表

如何忽略“有符号和无符号整数表达式之间的比较”?

GCC默认导出所有符号与MSVC默认不导出任何东西之间的设计原理是啥?

-symbolic 和 -shared GCC 标志之间有啥区别?

未解决符号表,导出符号表和地址重定向表

O2 中导致未定义符号的 gcc 优化标志