2016-05-27 97 views
2

下面是hello world MIPS彙編程序的調試會話。該程序使用GCC進行組裝,並使用gdb-multiarch進行調試。 代碼在執行QEMU,GDB連接到QEMUs上8080GDB的break break標籤跳線

當執行break main我期望GDB到第7行(jal hello)打破調試端口,但它在產生線9

(gdb) file proj.out 
Reading symbols from proj.out...done. 
(gdb) target remote 127.0.0.1:8080 
Remote debugging using 127.0.0.1:8080 
0x00400290 in _ftext() 
(gdb) break main 
Breakpoint 1 at 0x400460: file /import/src/main.s, line 9. 
(gdb) list 
1  
2  .text 
3  .globl main 
4  .extern hello 
5  
6  main: 
7   jal hello 
8  
9   li $a0, 0 
10  li $v0, 4001 
斷點

我可以重現這個我添加到程序中的任意標籤。在沒有標籤的情況下斷線時不會發生。但是當使用break main.s:6而不是break main時也會出現這種情況。

我懷疑GDB堅持某種我不知道的慣例。

程序版本:

GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1 
mips-linux-gnu-gcc (Debian 4.3.5-4) 4.3.5 
qemu-mips version 2.0.0 (Debian 2.0.0+dfsg-2ubuntu1.24) 
operating system: ubuntu:14.04.4 docker container 

編譯命令:

mips-linux-gnu-gcc -g -static -mips32r5 -O0 -o 

回答

4

MIPS架構具有 「分支延遲槽」。

考慮一個簡化的視圖。 mips有兩個分開單位:指令取指單位和指令執行單位。

提取單元在執行單元的「一個」之前運行。這允許單元的重疊。也就是說,執行單元能夠與提取並行操作。它執行在前一週期獲取的inst。

因此,在週期0中,取出第一條指令。在週期1中,執行第一條指令,取出第二條指令。在週期2中,執行2nd inst,並取出第3條指令。這看起來像:

cycle  fetch  exec 
0   1   n/a 
1   2   1 
2   3   2 
3   4   3 

這工作得很好,直到我們打任何形式(即jal)的一個分支指令。在你的例子中,我們有7 jal hello9 li $a0,0。你沒有顯示你的C代碼,但我懷疑hello需要一個參數和實際電話是hello(0)

因此,順序是li $a0,0和大多數拱門jal hello

由於取指運行「一前一後」,之後的預取指令將不得不被丟棄並且將被浪費。

所以,mips有分支延遲槽。指令的一個分支是的延時槽。它是總是執行,就好像它出現在分支之前一樣。

所以,從邏輯上講,你的程序是這樣的:

L1:  li  $a0,0    # first arg to hello 
L2:  jal  hello    # call to hello 
L3:  nop       # branch delay slot 

實際的執行順序是L1,L3,L2

編譯器能夠優化這一點,並把有用的指令在分支延遲插槽:

L1:  jal  hello    # call to hello 
L2:  li  $a0,0    # first arg to hello 

執行順序是L2,L1。請記住,對於分支[採取或而不是],分支延遲插槽中的指令始終是,總是執行,就像它先到達一樣。

因此,gdb did把斷點放在正確的位置上:在main的第一條指令上。但是,因爲第一條指令是一個分支,所以放置指令break的正確位置是分支的分支延遲槽。

在您的例子中,是jal線7和它的分支延遲槽是線9


UPDATE:

不幸的是,斷點設置在錯誤的位置不管指令如何:我可以用li $a0, 1代替jal hello,它不會改變任何東西。

對不起。 li應該是一個線索,因爲它是一個僞操作,它可以生成1-2條真正的指令。例如,li $a0,0x01020304將生成:lui $a0,0x0102 ori $a0,$a0,0x0304

但是,您可能仍需注意分支延遲時隙。我不知道qemu,但一些mips模擬器如marsspim允許您配置是否啓用/使用插槽[並且對於它們,插槽默認爲off]。如果關閉,插槽可以被忽略。否則,只需在每個分支後添加一個nop即可。

該代碼是「手動」編寫的,不是從C或任何其他語言編譯的。

再次,抱歉。我看到「用GCC編譯」而不是「用GCC編譯」。問題的


部分是gdb是一種高級語言源調試器。這是它的主要方向。其行號的概念面向HLL(例如C)行號。所以,如果沒有一些幫助,它可能會難以映射到/從asm行號碼。儘管來源是.s,可能來自cc -c -s -o foo.s foo.c ; cc -o foo foo.s

gdb更喜歡該程序已編譯爲-g。這增加了某些asm指令來定義調試信息。要看看這是什麼樣的,採取一個C程序[或幾乎任何.c文件]和[交叉]編譯使用-g [或-gdwarf-2]和-s。然後,看看輸出.s文件。

您可能需要在地方添加類似的指令,以告訴gdb究竟您認爲行號應該是什麼。這當然可以手動完成。但是,我已經知道採取給定.s並通過「元編程」腳本來添加我需要的任何東西。所以,這個輸出是什麼被送到gcc --YMMV


但是,每當我用gdb調試ASM,而且需要精確的控制,我使用的是面向好幾個不同的gdb命令調試彙編程序。

stepi而不是step。這個步驟通過單個asm指令而不是gdb 認爲是源代碼行。

disassemble main而不是list main。這給出了實際的說明,而不是源列表。或者x/i <address>。一個很好的例子是x/i $pc

<address>可以是使用標籤的標籤或簡單表達。

現在,一個大問題:的代替break <function>break <line_number>,我將使用地址形式:break *<address>

所以,如果disassemble main顯示第一條指令是在地址0x00001000,那麼我會做break *0x1000

但是,這將是乏味的。地址表格允許使用符號。所以,你可以做break *main。它也允許地址表達式:break *main+0x4。我認爲「這些你要找的機器人」 :-)


另一種方法是考慮使用marsspim進行仿真。它們是基於GUI的,可以更容易使用(並帶有內置彙編器)。

如果你只是想學習mips asm並做簡單的事情,他們可能是更好的選擇。我在SO上看到的大多數問題都使用它們,或者在真正的硬件上進行調試[通常在linux下啓動]。

我還沒有看到太多使用qemu。所以,如果你沒有操作系統要求,mars/spim可能值得一試。我已經使用了兩個,我更喜歡mars

取決於您的項目[或將成爲]有多大,它們可能仍然是其中一部分的答案(即隔離和調試某個特定功能)。

如果您想嘗試一下,這裏是火星的鏈接:http://courses.missouristate.edu/KenVollmar/MARS/

+0

感謝您的廣泛的答案。 不幸的是,無論指令如何,斷點都被設置在錯誤的位置:我可以用'li $ a0,1'代替'jal hello',它不會改變任何東西。代碼是「手工編寫」的,不是從C或任何其他語言編譯的。 –

+0

你的提示'break * main'是關鍵。 'dissassemble main'是相當有幫助的。非常感謝。 –