首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >GDB的中断跳过了标签上的中断行

GDB的中断跳过了标签上的中断行
EN

Stack Overflow用户
提问于 2016-05-27 08:13:40
回答 1查看 335关注 0票数 2

下面是hello world MIPS程序的调试会话。该程序用GCC编写,用gdb-多重拱进行调试.代码在QEMU上执行,GDB连接到8080上的QEMU调试端口。

在执行break main时,我希望GDB在第7行(jal hello)中中断,但它在第9行创建了断点。

代码语言:javascript
复制
(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会坚持某种我不知道的约定。

程序版本:

代码语言:javascript
复制
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

编译命令:

代码语言:javascript
复制
mips-linux-gnu-gcc -g -static -mips32r5 -O0 -o
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2016-05-27 18:26:58

mips体系结构具有“分支延迟时隙”。

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

获取单元在执行单元的“前面一”运行。这样就可以使单位重叠。也就是说,执行单元能够与获取并行操作。它是对上一个周期获取的inst的执行。

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

代码语言:javascript
复制
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有分支延迟时隙。分支后的指令位于延迟槽中。它总是被执行,就像它出现在树枝前面一样。

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

代码语言:javascript
复制
L1:     li      $a0,0               # first arg to hello
L2:     jal     hello               # call to hello
L3:     nop                         # branch delay slot

实际的执行顺序是L1,L3,L2

编译器能够对此进行优化,并在分支延迟槽中放置了有用的指令:

代码语言:javascript
复制
L1:     jal     hello               # call to hello
L2:     li      $a0,0               # first arg to hello

执行顺序是L2,L1。记住,对于一个分支是否被占用,分支延迟时隙中的指令总是首先执行,就好像它是第一个来的一样。

因此,gdb将断点放在正确的位置:在main的第一个指令上。但是,由于第一条指令是分支,所以正确放置break指令的位置是分支的延迟时隙。

在您的示例中,jal为第7行,其分支延迟时隙为第9行。

更新:

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

真对不起。li应该是一条线索,因为它是一个伪操作,它可以生成1-2个真正的指令。例如,li $a0,0x01020304将生成:lui $a0,0x0102 ori $a0,$a0,0x0304

但是,您可能仍然需要注意分支延迟插槽。我不知道qemu,但是一些mips模拟器(如marsspim )允许您配置是否启用/使用插槽,对于它们来说,插槽是默认关闭的。如果关闭,则可以忽略插槽。否则,只需在每个分支之后添加一个nop

代码是“手工编写的”,不是用C或任何其他语言编译的。

再一次抱歉。我看到的是“与GCC合编”,而不是“与GCC汇合”。

部分问题是gdb是一个高级的语言源调试器。这是它的主要方向。它的线号概念是面向HLL (例如C)行数的。因此,在没有帮助的情况下,可能很难将asm行号映射到/从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>可以是标签,也可以是使用标签的简单表达式。

现在,我将使用address表单:break <function>break <line_number>,而不是break *<address>

因此,如果disassemble main显示第一个指令位于address 0x00001000,那么我将执行break *0x1000

但是,这将是乏味的。地址表单允许符号。所以,你可以做break *main。它还允许地址表达式:break *main+0x4。我想“这些是你要找的机器人的”:-)

另一种方法是考虑使用marsspim进行仿真。它们是基于GUI的,可以更容易地使用(并附带内置的汇编程序)。

如果你只是试着学习mips asm并做一些简单的事情,他们可能是一个更好的选择开始。我看到的大多数问题,所以使用它们或在实际硬件上调试,通常是在linux下引导的。

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

取决于您的项目有多大或将变得多大,它们可能仍然是其中一部分的答案(例如,使用它们隔离和调试特定函数)。

如果你想试一试,这里有一个火星链接:http://courses.missouristate.edu/KenVollmar/MARS/

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/37478266

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档