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 编译器标志和符号表之间的不兼容函数地址的主要内容,如果未能解决你的问题,请参考以下文章
GCC默认导出所有符号与MSVC默认不导出任何东西之间的设计原理是啥?