linux gdb#039;查找寄存器地址,【Linux跟踪和调试】gdb
8種機械鍵盤軸體對比
本人程序員,要買一個寫代碼的鍵盤,請問紅軸和茶軸怎么選?
gdb是一個由GNU開源組織發布的、UNIX/LINUX操作系統下的、基于命令行的、功能強大的程序調試工具。對于一名Linux下工作的c++程序員,gdb是必不可少的工具。
gdb可以做四種主要的任務,從而幫助我們找到程序中的bug:啟動程序,指定可能影響其行為的任何內容。
讓你的程序在指定條件下停止。
檢查當程序停止時發生了什么。
更改程序的內容,以便你可能糾正一個錯誤的影響后,繼續了解另一個錯誤。
被調試的程序可以用Ada、C、C++、Objective-C、Pascal以及許多其他的語言,其中主要用于C/C++的調試工作。
gdb主要的交互命令:
運行run:簡記為r,其作用是運行程序,當遇到斷點后,程序會在斷點處停止運行,等待用戶輸入下一步的命令。
continue:簡記為c,繼續執行,到下一個斷點處(或運行結束)。
next:簡記為n,單步跟蹤程序,當遇到函數調用時,也不進入此函數體;此命令同step的主要區別是,step遇到用戶自定義的函數,將步進到函數中去運行,而next則直接調用函數,不會進入到函數體內。
step:簡記為s,單步調試如果有函數調用,則進入函數;與命令n不同,n是不進入調用的函數的。
until:當你厭倦了在一個循環體內單步跟蹤時,這個命令可以運行程序直到退出循環體。
until+行號:運行至某行,不僅僅用來跳出循環。
finish:運行程序,直到當前函數完成返回,并打印函數返回時的堆棧地址和返回值及參數值等信息。
call函數(參數):調用程序中可見的函數,并傳遞“參數”,如:call gdb_test(55)。
quit:簡記為q,退出gdb。
設置斷點break n (簡寫b n):在第n行處設置斷點,也可以帶上代碼路徑和代碼名稱:b OAGUPDATE.cpp:578。
b fn1 if a>b:條件斷點設置。
break func(break縮寫為b):在函數func()的入口處設置斷點,如:break cb_button。
delete 斷點號n:刪除第n個斷點。
disable 斷點號n:暫停第n個斷點。
enable 斷點號n:開啟第n個斷點。
clear 行號n:清除第n行的斷點。
info b (info breakpoints):顯示當前程序的斷點設置情況。
delete breakpoints:清除所有斷點。
查看源代碼list:簡記為l,其作用就是列出程序的源代碼,默認每次顯示10行。
list 行號:將顯示當前文件以“行號”為中心的前后10行代碼,如:list 12。
list 函數名:將顯示“函數名”所在函數的源代碼,如:list main。
list:不帶參數,將接著上一次list命令的,輸出下邊的內容。
打印表達式print 表達式:簡記為p,其中“表達式”可以是任何當前正在被測試程序的有效表達式,比如當前正在調試C語言的程序,那么“表達式”可以是任何C語言的有效表達式,包括數字,變量甚至是函數調用。
print a:將顯示整數a的值。
print ++a:將把a中的值加1,并顯示出來。
print name:將顯示字符串name的值。
print gdb_test(22):將以整數22作為參數調用gdb_test()函數。
print gdb_test(a):將以變量a作為參數調用gdb_test()函數。
display 表達式:在單步運行時將非常有用,使用display命令設置一個表達式后,它將在每次單步進行指令后,緊接著輸出被設置的表達式及值。如:display a。
watch 表達式:設置一個監視點,一旦被監視的“表達式”的值改變,gdb將強行終止正在被調試的程序。如:watch a。
whatis:查詢變量或函數。
info function:查詢函數。
擴展info locals:顯示當前堆棧頁的所有變量。
查詢運行信息where/bt:當前運行的堆棧列表。
bt backtrace:顯示當前調用堆棧。
up/down:改變堆棧顯示的深度。
set args 參數:指定運行時的參數。
show args:查看設置好的參數。
info program:來查看程序的是否在運行,進程號,被暫停的原因。
分割窗口layout:用于分割窗口,可以一邊查看代碼,一邊測試。
layout src:顯示源代碼窗口。
layout asm:顯示反匯編窗口。
layout regs:顯示源代碼/反匯編和CPU寄存器窗口。
layout split:顯示源代碼和反匯編窗口。
Ctrl + L:刷新窗口。
實例:
一個簡單的例子:使用一個空指針。
1
2
3
4
5
6
7#includeint main(int argc, char** argv)
{
int *boom = NULL;
printf("hello %d",*boom);
return 0;
}
現在,讓我們用符號來編譯它,這通過在運行gcc時使用-g標志來完成。
1gcc -g main.c -o main
然后我們運行它,并得到一個很討厭的段錯誤:
1
2#./main
[1] 28105 segmentation fault (core dumped) ./main
現在,我們需要找到問題出現在哪里。啟動gdb,語法很簡單:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18#gdb main
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Registered pretty printers for UE4 classes
Reading symbols from main...done.
(gdb)
接下來只需要在gdb內部運行命令。使用run啟動程序
1
2
3
4
5
6
7(gdb) run
Starting program: /home/gjc-2333/code/notes/blog/example/main
Program received signal SIGSEGV, Segmentation fault.
0x0000555555554665 in main (argc=1, argv=0x7fffffffddc8) at main.c:6
6printf("hello %d",*boom);
(gdb)
從上面的信息可以看到,問題出現在第六行,即printf行。
知道問題出現在哪一行,接下來就可以設置斷點了。利用break命令可以設置斷點,它允許你具體指定斷點,或者在自己的函數或有外部庫加載的第三方函數指定斷點,或者在原代碼的特定行指定斷點。同時,我們還可以用info命令來檢查斷點。我們將斷點放在main函數中。
1
2
3
4
5
6(gdb) break main
Breakpoint 1 at 0x555555554659: file main.c, line 5.
(gdb) info breakpoint
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000555555554659 in main at main.c:5
breakpoint already hit 1 time
現在我們再次運行代碼,當執行到main()函數的時候,執行暫停。
1
2
3
4
5
6
7
8(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/gjc-2333/code/notes/blog/example/main
Breakpoint 1, main (argc=1, argv=0x7fffffffddc8) at main.c:5
5int *boom = NULL;
(gdb)
接下來逐步執行,使用next命令。
1
2
3
4
5
6
7
8(gdb) next
6printf("hello %d",*boom);
(gdb) next
Program received signal SIGSEGV, Segmentation fault.
0x0000555555554665 in main (argc=1, argv=0x7fffffffddc8) at main.c:6
6printf("hello %d",*boom);
(gdb)
雖然現在知道了程序在第六行崩潰,但是具體什么原因,還是無法知曉。但問題出現的范圍相較于一開始模糊的段錯誤已經精確到了某一行崩潰,當然大部分程序到達這部分的時候,基本都是邏輯錯誤,需要自己進一步排查到底是什么原因。
以下的內容涉及到匯編,對于隊會便不是非常熟悉的人來說,看匯編找bug并不比直接看代碼找邏輯錯誤明智,甚至可能浪費很多時間而最終還是無法了解代碼究竟是哪里出了問題。但這并不是說不需要學匯編,能夠看懂匯編在許多時候可以幫助我們更快地找到代碼的bug。
反匯編
但是,現階段,代碼什么都沒有告訴我們,我們也無法直接判斷出代碼到底發生了什么。如果只看這行代碼,似乎沒有任何大的問題,或者,我們還無法看到問題的根源。
所以我們在這里使用反匯編命令,它將轉儲(dump)匯編代碼。只需要在gdb中鍵入disassemble,這將轉儲你的代碼使用的匯編指令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20(gdb) disassemble
Dump of assembler code for function main:
0x000055555555464a : push %rbp
0x000055555555464b : mov %rsp,%rbp
0x000055555555464e : sub $0x20,%rsp
0x0000555555554652 : mov %edi,-0x14(%rbp)
0x0000555555554655 :mov %rsi,-0x20(%rbp)
0x0000555555554659 :movq $0x0,-0x8(%rbp)
=> 0x0000555555554661 :mov -0x8(%rbp),%rax
0x0000555555554665 :mov (%rax),%eax
0x0000555555554667 :mov %eax,%esi
0x0000555555554669 :lea 0xa4(%rip),%rdi # 0x555555554714
0x0000555555554670 :mov $0x0,%eax
0x0000555555554675 :callq 0x555555554520
0x000055555555467a :mov $0x0,%eax
0x000055555555467f :leaveq
0x0000555555554680 :retq
End of assembler dump.
(gdb)
左邊是內存地址。第二列顯示內存空間從起始地址開始的增量。第三列顯示助記符。第四列包括實際寄存器和值。
小箭頭指向我們正在執行的內存地址。在偏移量555555554661H處,我們將移動存儲在比本質枕下的8個字節的值到RAX寄存器中。在那只前的一行中,我們將值0移入RBP-8H地址。所以現在RAX寄存器中的值為0。
我們的下一條指令將會導致段錯誤,正如我們之前使用next命令逐步執行代碼時所看到的。
1
20x0000555555554665 :mov (%rax),%eax
0x0000555555554667 :mov %eax,%esi
所以我們需要了解這里為什么會出錯。讓我們來檢查下ESI寄存器,他應該得到這個新的值。我們使用examine或者x命令來做到這一點。
1
2
3
4
5
6
7
8(gdb) x $rax
0x0:Cannot access memory at address 0x0
(gdb) x $eax
0x0:Cannot access memory at address 0x0
(gdb) x $esi
0xffffffffffffddc8:Cannot access memory at address 0xffffffffffffddc8
(gdb)
這里我們得到一個消息:我們不能訪問指定的內存地址。現在問題解決了。我們常釋放文非法內存地址導致了段錯誤。
參考資料:
總結
以上是生活随笔為你收集整理的linux gdb#039;查找寄存器地址,【Linux跟踪和调试】gdb的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux sh 字符截取,shell字
- 下一篇: c语言单词翻译大全,c语言单词翻译