天天看點

常用嵌入式Linux二進制調試工具

Linux

系統中有大量的工具可用于

ELF

檔案的二進制調試,常用的工具在

GNU binutils

包中可以找到,注意你可能需要這些工具的

x86

版本和

arm

版本,以便在調試環境中能夠調試

x86 ELF

檔案和

arm ELF

檔案——與交叉編譯器

arm-linux-gcc

類似,我們需要所謂的“交叉調試工具”,你可以通過網際網路下載下傳别人已經編譯好的

crosstool

,或者自己重新編譯(

configure

時指

--target=arm-linux

)。

包在

GNU

的官方網站提供下載下傳:

<a href="http://www.gnu.org/software/binutils/" target="_blank">http://www.gnu.org/software/binutils/</a>

,特别的,更多跟

相關的資訊和工具可以看看

gnu arm

網站:

<a href="http://www.gnuarm.org/" target="_blank">http://www.gnuarm.org/</a>

我們将常用的

調試工具歸納介紹如下。由于這些工具的

版本使用起來基本沒有差別,這裡也不作區分。讀者在使用的時候請根據使用對象的類型(用

FILE

指令檢視)自行區分。

Ø      

AR

用來建立、修改、提取靜态庫檔案。靜态庫檔案包含多個可重定位目标檔案,其結構保證了可以恢複原始目标檔案内容。比如:

$ gcc –c file1.c file2.c

$ ar rcs libxx.a file1.o file2.o

這裡我們先用

gcc

編譯得到

file1.o file2.o

兩個目标檔案,然後用

ar

指令生成靜态庫

libxx.a

當你希望檢視靜态庫中包含了哪些目标檔案時,可以用選項

-x

解開靜态庫檔案:

$ ar x libxx.a

NM

列出目标檔案的符号表中定義的符号。常見的連結或者運作時發生的

unresolved symbol

類型的錯誤可以用

來輔助調試。比如用

結合

GREP

來檢視變量或函數是否被定義或引用:

$ nm [xx.o, or yy.a, or zz.so] | grep [your symbol]

對于

C++

程式,可以使用選項

-C

來進行所謂的

demangle

——

編譯器一般會将變量名或函數名進行修飾

(mangle)

,加上類資訊、參數資訊等,變成比較難以辨認的符号,而

選項的

則可将其恢複為比較正常的符号。比如下面很簡單的

程式:

#include

int main()

{

std::cout

}

編譯之後用

nm

來檢視:

$ g++ -c hello.cpp

$ nm hello.o

00000094 t _GLOBAL__I_main

0000003e t _Z41__static_initialization_and_destruction_0ii

U _ZNSolsEPFRSoS_E

U _ZNSt8ios_base4InitC1Ev

U _ZNSt8ios_base4InitD1Ev

U _ZSt4cout

U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_

00000000 b _ZSt8__ioinit

U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc

U __cxa_atexit

U __dso_handle

U __gxx_personality_v0

0000007c

t __tcf_0

00000000 T main

這時這些

mangle

之後的

符号是比較難以辨認的,如果使用

nm –C

進行

就好多了:

$ nm -C hello.o

0000003e t __static_initialization_and_destruction_0(int, int)

U

std::basic_ostream

&gt;::operator &gt;&amp; (*)(std::basic_ostream &gt;&amp;))

U std::ios_base::Init::Init[in-charge]()

U std::ios_base::Init::~Init [in-charge]()

U std::cout

std::basic_ostream &gt;&amp;

std::endl

&gt;(std::basic_ostream &gt;&amp;)

00000000 b std::__ioinit

std::operator

&gt;(std::basic_ostream

&gt;&amp;, char const*)

選項在其他一些二進制調試工具中也有提供,使用

開發的讀者可以多加注意,畢竟

之後的符号可讀性要強很多。

OBJDUMP

objdump

是所有二進制工具之母,能夠顯示一個目标檔案中所有的資訊,通常我們用它來反彙編

.text

節中的二進制指令。

比如對上面的

hello.o

反彙編的結果如下:

# objdump -d hello.o

hello.o:    

file format elf32-i386

Disassembly of section .text:

00000000 :

0:  

55                     

push  

%ebp

1:  

89 e5                  

mov   

%esp,%ebp

3:  

83 ec 08               

sub   

$0x8,%esp

6:  

83 e4 f0               

and   

$0xfffffff0,%esp

9:   

b8 00 00 00 00         

$0x0,%eax

e:  

29 c4                  

%eax,%esp

10:  

13:  

68 00 00 00 00         

$0x0

18:  

83 ec 0c               

$0xc,%esp

1b:  

20:  

25:  

e8 fc ff ff ff         

call  

26

2a:  

83 c4 14               

add   

$0x14,%esp

2d:  

50                     

%eax

2e:  

2f

33:  

83 c4 10               

$0x10,%esp

36:  

3b:  

c9                     

leave 

3c:  

c3                     

ret   

3d:  

90                     

nop   

...

注意這裡用的目标檔案

和工具

都是

版本的,生成的反彙編代碼是

Unix

系統上傳統的

AT&amp;T

彙編,而不是多數人更熟悉的

Intel

彙編。

如果你用

ARM

格式的

,及針對

的交叉調試工具

arm-linux-objdump

,得到的則是

彙編:

$ arm-linux-objdump -d hello.o

file format elf32-littlearm

00000180 :

180: 

e1a0c00d       

mov    

ip, sp

184:  

e92dd800       

stmdb  

sp!, {fp, ip, lr, pc}

188:  

e24cb004       

sub    

fp, ip, #4     

; 0x4

18c:  

e59f0014       

ldr    

r0, [pc, #20]  

; 1a8

190:  

e59f1014       

r1, [pc, #20]  

; 1ac

194:  

ebfffffe       

bl     

194

198:  

e59f1010       

r1, [pc, #16]  

; 1b0

19c:  

19c

1a0:  

e3a00000       

r0, #0 

; 0x0

1a4:  

e89da800       

ldmia  

sp, {fp, sp, pc}

在主機的模拟環境中進行調試時,你可以用

彙編來作為參考,但涉及到與

CPU

體系結構有關的代碼時,最好還是反彙編得到

彙編格式的代碼,這樣更為準确一些。

READELF

readelf

可用來顯示

格式可執行檔案的資訊。比如用

檢視

中的各個

Section

的結果如下:

$ readelf -S hello.o

There are 15 section headers, starting at offset 0x228:

Section Headers:

[Nr] Name             

Type           

Addr    

Off   

Size  

ES Flg Lk Inf Al

[ 0]                  

NULL           

00000000 000000 000000 00     

[ 1] .text            

PROGBITS       

00000000 000034 0000ae 00 

AX 

4

[ 2] .rel.text        

REL            

00000000 000754 000060 08    

13  

[ 3] .data            

00000000 0000e4 000000 00 

WA 

[ 4] .bss             

NOBITS         

00000000 0000e4 000001 00 

[ 5] .rodata          

00000000 0000e4 00000d 00  

1

[ 6] .ctors           

00000000 0000f4 000004 00 

[ 7] .rel.ctors       

00000000 0007b4 000008 08    

[ 8] .eh_frame        

00000000 0000f8 000090 00  

[ 9] .rel.eh_frame    

00000000 0007bc 000028 08    

[10] .note.GNU-stack  

NOTE           

00000000 000188 000000 00     

[11] .comment        

00000000 000188 000034 00     

[12] .shstrtab        

STRTAB         

00000000 0001bc 00006a 00     

[13] .symtab          

SYMTAB         

00000000 000480 000180 10    

14  

[14] .strtab          

STRTAB     

00000000 000600 000153 00     

Key to Flags:

W (write), A (alloc), X (execute), M (merge), S (strings)

I (info), L (link order), G (group), x (unknown)

O (extra OS processing required) o (OS specific), p (processor specific)

SIZE

size

指令可以列出目标檔案每一段的大小以及總體的大小。預設情況下,對于每個目标檔案或者一個歸檔檔案中的每個子產品隻産生一行輸出。

可以用來簡單快速的了解

檔案各個段的情況,比如:

$ size hello.o

text   

data    

bss    

dec    

hex filename

331      

4      

1    

336    

150 hello.o

OBJCOPY

objcopy

用來把一種目标檔案中的内容複制到另一種類型的目标檔案中。一般用來将複制或替換目标檔案中的某些段,或者去掉某些段。

STRINGS

strings

列印某個檔案的可列印字元串,這些字元串最少

個字元長,也可以使用選項

-n

設定字元串的最小長度。預設情況下,它隻列印目标檔案初始化和可加載段中的可列印字元;對于其它類型的檔案它列印整個檔案的可列印字元,這個程式對于了解非文本檔案的内容很有幫助。

STRIP

strip

:丢棄目标檔案中的全部或者特定符号,可以用來減小可執行檔案和庫的大小。具體示例請參見下一章存儲優化部分的相關内容。

ADDR2LINE

addr2line

:把程式位址轉換為檔案名和行号。在指令行中給它一個位址和一個可執行檔案名,它就會使用這個可執行檔案的調試資訊指出在給出的位址上是哪個檔案以及行号。具體示例請參見後面的

Core Dump

分析(

9.5

節)時,如何通過寄存器

pc

的值和

工具找出出錯的

C/C++

源代碼。

LDD

ldd

可用來顯示執行檔案需要哪些共享庫

,

共享庫裝載管理器在哪裡找到了需要的共享庫。比如:

# ldd hello

libstdc++.so.5 =&gt; /usr/lib/libstdc++.so.5 (0x40026000)

libm.so.6 =&gt; /lib/tls/libm.so.6 (0x400d9000)

libgcc_s.so.1 =&gt; /lib/libgcc_s.so.1 (0x400fb000)

libc.so.6 =&gt; /lib/tls/libc.so.6 (0x42000000)

/lib/ld-linux.so.2 =&gt; /lib/ld-linux.so.2 (0x40000000)

最常用的地方是解決運作時找不到庫的錯誤,如程式運作時得到的類似“

error while loading shared libraries: libxxx.so

”的錯誤,這時可以運作

來具體檢視是缺少哪些庫文

繼續閱讀