Gcc GDB&x27;s break跳过标签上的换行符

Gcc GDB&x27;s break跳过标签上的换行符,gcc,gdb,mips,Gcc,Gdb,Mips,下面是hello world MIPS汇编程序的调试会话。 该程序使用GCC组装,并使用gdb-multiarch进行调试。 代码在QEMU上执行,GDB连接到8080上的QEMU调试端口 执行breakmain时,我希望GDB在第7行中断(jal hello),但它在第9行创建断点 (gdb) file proj.out Reading symbols from proj.out...done. (gdb) target remote 127.0.0.1:8080 Remote debugg

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

执行
breakmain
时,我希望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
我可以为我添加到程序中的任意标签复制它。如果只是在没有标签的情况下断线,则不会发生这种情况。但是,当使用
breakmain.s:6
而不是
breakmain
时,也会发生这种情况

我怀疑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
mips体系结构具有“分支延迟槽”

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

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

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

cycle       fetch       exec
0           1           n/a
1           2           1
2           3           2
3           4           3
L1:     li      $a0,0               # first arg to hello
L2:     jal     hello               # call to hello
L3:     nop                         # branch delay slot
在我们遇到任何类型的分支指令(即
jal
)之前,这都可以正常工作。在您的示例中,我们有
7jal hello
9li$a0,0
。您没有显示您的C代码,但我怀疑
hello
接受一个参数,而您的实际调用是
hello(0)

因此,在大多数拱门上,顺序是
li$a0,0
jal hello

由于指令提取运行“提前一次”,因此在
jal
之后的预取指令将不得不被丢弃并被浪费

因此,mips有分支延迟槽。分支后的指令位于延迟槽中。它总是被执行,就像它出现在分支之前一样

因此,从逻辑上讲,您的程序如下所示:

cycle       fetch       exec
0           1           n/a
1           2           1
2           3           2
3           4           3
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确实将断点放在了正确的位置:main的第一条指令上。但是,由于第一条指令是一个分支,因此放置
break
指令的正确位置是分支的分支延迟槽

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


更新:

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

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

但是,您可能仍然需要注意分支延迟槽。我不知道
qemu
,但一些mips模拟器,如
mars
spim
允许您配置插槽是否启用/使用[对于它们,插槽默认关闭]。如果禁用,则可以忽略插槽。否则,只需在每个分支后添加一个
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认为的源代码行


反汇编main
而不是
列出main
。这将给出实际的指令,而不是源代码列表。或者
x/i

谢谢你的详尽回答。不幸的是,不管指令如何,断点设置在错误的位置:我可以用
li$a0,1
替换
jal hello
,它不会改变任何东西。代码是“手工”编写的,不是用C或任何其他语言编译的。您对
break*main
的提示是关键
disassembleMain
非常有用。谢谢。