链接器产生这种输出
/var/tmp/ccITB4j2.o: In function `main':
/var/tmp/ccITB4j2.o(.text+0x4): undefined reference to `myFunction(void)'
如何在 .text+0x4 处找到实际调用该函数的指令对应的源代码行?
首先,您的问题的另一个答案是错误的:在 Linux 上,您确实从链接器获取文件和行号:
$ cat foo.cc
extern int myFunction(void);
int main()
{
return myFunction();
}
$ g++ -g foo.cc
/tmp/cc3twlhL.o: In function `main':
/tmp/foo.cc:5: undefined reference to `myFunction()'
collect2: ld returned 1 exit status
上面的输出来自gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
和 linker GNU ld (GNU Binutils for Ubuntu) 2.22
,但对于更旧版本的 GCC 和 ld 也是如此。
你没有得到文件/行的原因一定是
-g
标志,或者ld
,或者ld
不支持调试(我不确定这是否可能)。但是,即使您ld
拒绝告诉您文件和行,也不会丢失所有内容。您可以将源代码编译为对象,然后用于objdump -rdS foo.o
获取相同的信息:
g++ -g -c foo.cc
objdump -rdS foo.o
Disassembly of section .text:
0000000000000000 <main>:
extern int myFunction(void);
int main()
{
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
return myFunction();
4: e8 00 00 00 00 callq 9 <main+0x9>
5: R_X86_64_PC32 _Z10myFunctionv-0x4
}
9: 5d pop %rbp
a: c3 retq
在上面的输出中,您可以清楚地看到哪个源代码行导致在目标文件中发出引用_Z10myFunctionv
(这是 的C++
损坏名称)。myFunction(void)
理解链接器错误的关键是了解声明和定义之间的区别。
这是一个声明:
int myFunction();
这是一个定义:
int myFunction() {
// do something
return val;
}
当您声明某些内容时,编译器会将其视为您的承诺,您最终也会定义它(可能稍后在同一个翻译单元中,或者可能在不同的翻译单元中)。这些承诺实际上是由链接器在链接时检查的。
因此,链接器错误(例如这个)实际上是在抱怨您违反了定义某些内容的承诺。由于这是在编译之后发生的,并且由于它处理的是“不存在”的东西,所以询问“不存在的行号”是没有意义的。
希望这有助于解释为什么您不会将行号附加到来自链接器的“未解析符号”错误消息上。
我有 3 个答案(到目前为止),以及关于我需要知道如何执行此操作的特定案例的分析
addr2line
可以做到,前提是编译器生成的调试信息与工具兼容。
~ cat asdf.txt
/var/tmp/ccITB4j2.o: In function `main':
/var/tmp/ccITB4j2.o(.text+0x4): undefined reference to `myFunction(void)'
~ cat asdf.txt | addr2line -e /var/tmp/ccITB4j2.o
... it should print src:line info here
然后是objdump
:
objdump --dwarf=decodedline test.o
test.o: file format elf64-x86-64
Decoded dump of debug contents of section .debug_line:
CU: test.cc:
File name Line number Starting address
test.cc 2 0x23
test.cc 3 0x84
为了回答“调试器如何做到这一点”这个问题,这里有一篇关于该主题的好文章:调试器如何工作:第 3 部分 - 调试信息,这是objdump
选项的来源。
如果链接断开,我会提前为后代道歉。
我在 Linux 中使用 Solaris Studio 12.3 时遇到了这些问题。它看起来好像它生成的信息(无论是在.debug_line
还是其他部分)调试信息与 不兼容addr2line
,但仅在使用优化构建时。以下代码将引发类似的链接错误:
~ cat test.cc
struct Test {int x; Test(); };
inline void test() { Test *t = new Test(); }
void blah() { test(); }
~ CC -g -Kpic test.cc -shared -o libtest.so -Wl,--unresolved-symbols=ignore-in-shared-libs
(...)
test.cc:2: undefined reference to `void operator delete(void*)'
(...)
~ CC -g -Kpic test.cc -shared -o libtest.so -Wl,--unresolved-symbols=ignore-in-shared-libs -O0
(...)
test.cc:(.text+0x45): undefined reference to `void operator delete(void*)'
(...)
~ CC -g -Kpic test.cc -c
~ addr2line -e test.o +0x45
test.cc:2
~ CC -g -Kpic test.cc -O0 -c
~ addr2line -e test.o +0x45
??:?
解决这种情况下的链接错误需要针对编译器库进行链接libCrun
。作为反对者评论的一个反例,即知道行号是无用的:它确实让我摸不着头脑,不知道在哪里delete
引用。事实证明,编译器正在插入额外的代码来分配和删除它。如果它正确地打印了行号(函数的右括号),那么编译器正在做一些不寻常的事情会更加明显。