GDB的突破在标签处突破了跳线

时间:2016-05-27 08:13:40

标签: gcc gdb mips

以下是hello world MIPS汇编程序的调试会话。 程序使用GCC汇编并使用gdb-multiarch进行调试。 代码在QEMU上执行,GDB连接到8080上的QEMU调试端口。

执行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

1 个答案:

答案 0 :(得分:4)

mips架构有"分支延迟时隙"。

考虑简化视图。 mips有两个单独的单元:指令 fetch 单元和指令执行单元。

获取单元运行"提前一个"执行单位这允许单元的重叠。也就是说,exec单元能够与获取并行操作。它执行上一周期提取的inst。

因此,在周期0中,获取第一条指令。在周期1中,执行第一条指令,并取出第二条指令。在周期2中,执行第二个inst,并获取第三个指令。这看起来像:

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,0jal hello

因为指令提取运行"提前一步", jal之后的预取指令将不得不被丢弃并且将被浪费。

因此,mips有分支延迟槽。 分支后的指令 in 延迟槽。 始终执行,就好像它已经出现在分支之前。

所以,从逻辑上讲,你的程序看起来像是:

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。请记住,对于分支[take或 not ],分支延迟槽中的指令首先始终执行,就像它首先执行一样。

所以,gdb 确实将断点放在正确的位置:在main的第一条指令上。但是,因为第一条指令是分支,所以放置break指令的正确位置是分支的分支延迟槽。

在您的示例中,jal是第7行,其分支延迟位置是第9行。

<强>更新

  

不幸的是,无论指令如何,断点都设置在错误的位置:我可以用jal hello替换li $a0, 1并且它不会改变任何东西。

很抱歉。 li应该是一个线索,因为它是一个伪操作,它可以生成1-2个实际指令。例如,li $a0,0x01020304会生成:lui $a0,0x0102 ori $a0,$a0,0x0304

但是,您可能仍然需要注意分支延迟槽。我不了解qemu,但有些mips模拟器(例如marsspim)允许您配置是否已启用/使用广告位[并且对于他们来说,插槽是默认关闭]。如果禁用,则可以忽略插槽。否则,只需在每个分支后添加nop

  

代码是用手写的#34;并且不是用C或任何其他语言编译的。

再次,抱歉。我看到&#34;编译与GCC&#34;而不是&#34;与GCC&#34;组装。

部分问题是gdb是一个高级语言源调试器。这是它的主要方向。其行号的概念面向HLL(例如C)行号。因此,在没有一些帮助的情况下,可能难以映射到/来自asm行号。即使来源为.s,也可能来自cc -c -s -o foo.s foo.c ; cc -o foo foo.s

gdb更喜欢该程序已使用-g进行编译。这会添加某些asm指令来定义调试信息。要查看相同的内容,请使用.c [或-g]和{{1}来使用C程序[或任何-gdwarf-2文件]和[交叉]编译它}}。然后,查看输出-s文件。

您可能需要在地方添加类似的指令,以告诉.s 认为行号应该是什么。当然,这可以手动完成。但是,我知道我会接受一个给定的gdb并通过一个&#34;元编程&#34;脚本添加我需要的东西。因此,这是输出到.s - YMMV

的输出

但是,每当我使用gcc调试asm并需要精确控制时,我会使用一些不同的gdb命令来更好地调试汇编程序。

gdb代替stepi。单个asm指令而不是gdb 认为是源代码行的步骤。

step代替disassemble main。这给出了实际的指令而不是源列表。或list main。一个很好的例子是x/i <address>

x/i $pc可以是标签或使用标签的简单表达。

现在,一个大问题:而不是<address>break <function>,我将使用地址表单:{{1} }。

因此,如果break <line_number>显示第一条指令位于地址break *<address>,那么我会disassemble main

但是,这将是乏味的。地址表格允许使用符号。所以,你可以做0x00001000。它还允许地址表达式:break *0x1000。我认为&#34;这些您正在寻找的机器人&#34; : - )

另一种方法是考虑使用break *mainbreak *main+0x4进行模拟。它们基于GUI,可以更容易使用(并且内置汇编程序)。

如果你只是想学习mips asm并做一些简单的事情,他们可能是一个更好的选择。我在SO上看到的大多数问题都使用它们或在真实硬件上进行调试[通常在linux下启动]。

我没见过太多使用mars的人。因此,如果您没有操作系统要求,spim可能值得一试。我使用了两者,我更喜欢qemu

根据您的项目[或将成为]的大小,它们可能仍然是其中一部分的答案(即使用它们隔离和调试特定功能)。

如果您想尝试一下,请点击以下链接:http://courses.missouristate.edu/KenVollmar/MARS/