使用GDB并检查数据的内存布局

时间:2019-01-26 21:01:00

标签: c++ gdb

假设我们有一个简单的C ++代码,如下所示:

#include <iostream>

int main(){
  int a = 5;
}

由于每个内存位置都是8 bits,整数是32 bits,所以我假设a的内存结构是这样的:

0xa      0xb      0xc      0xd 
00000000 00000000 00000000 00000101

其中0xa,0xb,0xc,0xd是示例内存地址。

1)&a指向0xa还是0xd

2)如果我使用GDB并使用x来获取真实的内存地址,则会得到以下信息:

(gdb) p a
$7 = 5
(gdb) p &a
$8 = (int *) 0x7ffeefbffac8
(gdb) x/bt 0x7ffeefbffac8
0x7ffeefbffac8: 00000101
(gdb) x/bt 0x7ffeefbffac8-1
0x7ffeefbffac7: 00000000
(gdb) x/bt 0x7ffeefbffac8-2
0x7ffeefbffac6: 00000000
(gdb) x/bt 0x7ffeefbffac8-3
0x7ffeefbffac5: 01111111
(gdb) 

为什么0x7ffeefbffac8-3填充了01111111而不是00000000?这个地址不等于我们的示例内存地址中的0xa吗?

2 个答案:

答案 0 :(得分:1)

在小字节序的计算机上,&a指向内存的最低有效字节。也就是说,如果&a == 0x7ffeefbffac8,则a驻留在字节中

0x7ffeefbffac8:  101   << least significant byte
0x7ffeefbffac9:  000
0x7ffeefbffaca:  000
0x7ffeefbffacb:  000   << most significant byte.

最好通过分配例如0x0a090705a,然后:

Temporary breakpoint 1, main (argc=3, argv=0x7fffffffdc68) at t.c:2
2     int a = 0x0a090705;
(gdb) n
3     return 0;
(gdb) p &a
$1 = (int *) 0x7fffffffdb7c

检查从&a开始的4个字节:

(gdb) x/4bt 0x7fffffffdb7c
0x7fffffffdb7c: 00000101    00000111    00001001    00001010

或者等效地,一次执​​行一个字节:

(gdb) x/bt 0x7fffffffdb7c
0x7fffffffdb7c: 00000101
(gdb) x/bt 0x7fffffffdb7c+1
0x7fffffffdb7d: 00000111
(gdb) x/bt 0x7fffffffdb7c+2
0x7fffffffdb7e: 00001001
(gdb) x/bt 0x7fffffffdb7c+3
0x7fffffffdb7f: 00001010
  

为什么0x7ffeefbffac8-3填充了01111111而不是00000000

因为您走错了方向:&a-3根本不是a 的一部分(这是其他东西的一部分,或者可能是未初始化的随机垃圾)。

答案 1 :(得分:0)

  

2)如果我使用GDB并使用x获得实际的内存地址,我得到   以下:

在大多数台式机(尤其是Linux)上,显示的地址是虚拟的,而不是“真实的”(不是真实的)。

在嵌入式工具套件(例如vxWorks)中,即使具有虚拟内存,调试器也可以显示硬件地址和值。

注意:我尚未在具有实际硬件地址的系统上使用任何形式的Linux进行访问,但是我已在嵌入式软件上使用了g ++和gdb。


  

1)&a指向0xa还是0xd?

C ++代码片段可以显示十六进制或十进制的整数和字节地址及值。

     int a = 0x0d0c0b0a;
     //  msB---^^    ^^---lsB

     char* a0 = reinterpret_cast<char*>(&a);
     char* a1 = a0+1;
     char* a2 = a0+2;
     char* a3 = a0+3;

     cout  //              Note: vvvvvvvvvvvvv---improves readability 
        << "\n  value of a: " << sop.digiComma(to_string(a))
        << "\n  sizeof(a):  " << sizeof(a) << " bytes   "
        << "\n  address:    " << &a << '\n'
        << "\n  hex value:  " << "0x" << hex << setfill('0') << setw(8) << a << hex
        //
        << "\n              " <<                                   "   | | | |"
        << "\n         a0:  " << setw(2) << static_cast<int>(*a0) << " | | |-^ lsB  " << static_cast<void*>(a0)
        << "\n         a1:  " << setw(2) << static_cast<int>(*a1) << " | |-^        " << static_cast<void*>(a1)
        << "\n         a2:  " << setw(2) << static_cast<int>(*a2) << " |-^          " << static_cast<void*>(a2)
        << "\n         a3:  " << setw(2) << static_cast<int>(*a3) << "-^       msB  " << static_cast<void*>(a3)
        << endl;

典型输出:(罐头的位置会改变)

value of a: 218,893,066
sizeof(a):  4 bytes   
address:    0x7ffee713c1dc

  hex value:  0x0d0c0b0a
                 | | | |
         a0:  0a | | |-^ lsB  0x7ffee713c1dc
         a1:  0b | |-^        0x7ffee713c1dd
         a2:  0c |-^          0x7ffee713c1de
         a3:  0d-^       msB  0x7ffee713c1df

  

为什么0x7ffeefbffac8-3填充了01111111而不是00000000?   这个地址不是我们的示例内存地址中的0xa吗?

另一个答案(指-3)说:“您的方向错误”,我同意。对我来说,这仅仅是您对对象如何在内存中“布置”的误解。

这说明了所有调试器的问题……成功的用户必须知道编译器是如何做的,如何将简单对象“布置”在内存中。我编写的代码片段使用简单的c ++代码显示了一种使编译器说明其布局选择的方法。

摘要:

您可以轻松地添加诊断例程,以显示内存布局以进行检查和内容查看,每种例程都使用c ++的舒适功能(或必须使用c样式)。

您可以轻松地使调试器报告对象的当前地址。

因此,您可以考虑将这两个想法结合起来:

a)我经常创建与上面类似的说明性代码段,以用简单的文本显示我要确认或查看的对象的编译器内存布局。请注意,更改编译器选项可以更改布局选择。

b)通过上述操作,我还创建了一个短名称访问函数,该函数将在调试器命令行上调用。访问功能调用插图代码。

c)如何获得调用说明性代码的功能可能会遇到挑战,但是软件非常灵活,我没有遇到任何问题。

d)有时,我发现将对象地址传递到函数中(作为命令行的一部分)更加容易。其他时候,隐含单个地址。

通常,访问功能是调用说明性代码的唯一代码,因此,两者均从操作代码中删除。即它们对正常操作没有影响(因此很容易移除)