Unixでaddr2lineコマンドを使用しようとしていますが、毎回??:0と同じ出力を提供しています。私はaddr2line -e a.out 0x4005BDC
としてコマンドを与えています。 valgrind
ツールを使用してこのa.out実行可能ファイルを実行しているときにこのアドレスを取得して、メモリリークを見つけました。また、-g
オプションを使用してソースコードをコンパイルしました。
Addr2lineの代わりにgdbを使用して、メモリアドレスを調べることもできます。実行可能ファイルをgdbにロードし、アドレスに保存されているシンボルの名前を出力します。 16シンボルテーブルを調べる 。
(gdb) info symbol 0x4005BDC
チェックしてください:
-g
でコンパイルされているか、addr2line
サポート関数のみがデバッグ情報を持っているか、つまり-g
でコンパイルされているか.text
セクション内のオフセットのみである必要があります。 .text
セクションでは、アドレスはバイナリ内の命令を指す必要があることを意味します以下は、man addr2line
からのメッセージです。
addr2line-アドレスをファイル名と行番号に変換します。
addresses
は、実行可能ファイルのアドレスまたは再配置可能オブジェクトのセクションのオフセットにする必要があります。
出力はFILENAME:LINENO
、ソースファイル名、ファイル内の行番号のようなものです
例としてhelloworld
を取り上げます。
#include <stdio.h>
int main()
{
printf("hello\n");
return 0;
}
gcc -g hello.c
でコンパイルした後、最初にobjdump
を使用して、生成されたa.out
ファイルのオフセット情報に関するアイデアを得ることができます。
ダンプされた逆アセンブリの一部を次に示します。
Disassembly of section .text:
0000000000400440 <_start>:
400440: 31 ed xor %ebp,%ebp
400442: 49 89 d1 mov %rdx,%r9
400445: 5e pop %rsi
400446: 48 89 e2 mov %rsp,%rdx
400449: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
40044d: 50 Push %rax
40044e: 54 Push %rsp
40044f: 49 c7 c0 c0 05 40 00 mov $0x4005c0,%r8
400456: 48 c7 c1 50 05 40 00 mov $0x400550,%rcx
40045d: 48 c7 c7 36 05 40 00 mov $0x400536,%rdi
400464: e8 b7 ff ff ff callq 400420 <__libc_start_main@plt>
400469: f4 hlt
40046a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
...
0000000000400536 <main>:
#include <stdio.h>
int main()
{
400536: 55 Push %rbp
400537: 48 89 e5 mov %rsp,%rbp
printf("hello\n");
40053a: bf d4 05 40 00 mov $0x4005d4,%edi
40053f: e8 cc fe ff ff callq 400410 <puts@plt>
return 0;
400544: b8 00 00 00 00 mov $0x0,%eax
}
400549: 5d pop %rbp
40054a: c3 retq
40054b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
コードの左端の列は、バイナリファイルのオフセットです。 __start
関数は標準Cライブラリに由来し、デバッグ情報なしでプリコンパイルされます。 main
関数は、-g
でファイルをコンパイルするため、デバッグ情報を持つhelloworldコードから取得されます。
addr2line
の出力は次のとおりです。
$ addr2line -e a.out 0x400442 #offset in the `__start` function
??:?
$ addr2line -e a.out 0x400536 #offset in the `main` function
hello.c:21
$ addr2line -e a.out 0x40054b -f #The last instruction of the `main` function
main
??:?
上記の出力からいくつかの結論を出すことができます。
-g
フラグで生成されたコードセグメント(セグメントにデバッグ情報があることを意味する)のみが、ファイル名と行番号の情報を正常に生成できました。-g
フラグでコンパイルされた関数本体のすべてのオフセットが、ファイル名と行番号を正常に出力するとは限りません。オフセット0x40054b
は、ret
関数のmain
命令の後の最後の命令ですが、情報を取得できませんでした。仮想アドレス(VA)ではなく、addr2lineにoffsetを指定する必要があります。おそらく、アドレス空間のランダム化がオフになっている場合、完全なVAを使用できますが、ほとんどの最新のOSでは、新しいプロセスのためにアドレス空間がランダム化されます。
VA 0x4005BDC
valgrindにより、メモリ内のプロセスまたはライブラリのベースアドレスを見つけます。これを行うには、/proc/<PID>/maps
ファイルはプログラムの実行中です。関心のある行は、プロセスのtext
セグメントです。これは、パーミッションr-xp
およびプログラムまたはライブラリの名前。
ベースVAは0x0x4005000
。次に、提供されたvalgrind VAとベースVA:0xbdc
。次に、それをadd2lineに提供します。
addr2line -e a.out -j .text 0xbdc
そして、それがあなたの行番号を取得するかどうか確認してください。
それはまさにあなたがそれを使用する方法です。ただし、お持ちのアドレスがソースコード内の何かに直接対応していない可能性があります。
例えば:
$ cat t.c
#include <stdio.h>
int main()
{
printf("hello\n");
return 0;
}
$ gcc -g t.c
$ addr2line -e a.out 0x400534
/tmp/t.c:3
$ addr2line -e a.out 0x400550
??:0
0x400534
は私の場合のmain
のアドレスです。 0x400408
もa.out
の有効な関数アドレスですが、GCCによって生成/インポートされたコードであり、デバッグ情報はありません。 (この場合、__libc_csu_init
。readelf -a your_exe
で実行可能ファイルのレイアウトを確認できます。)
addr2line
が失敗する他の場合は、デバッグ情報のないライブラリを含めている場合です。
-f
オプションを追加して、関数名を表示してみてください:
addr2line -f -e a.out 0x4005BDC