《coredump问题原理探究》Linux x86版5.8节C风格数据结构内存布局之结构体数组结构体coredump

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/8846866

上一节已经阐述完毕结构体的特征了,需要一个coredump例子:

(gdb) bt
#0  0x00000000 in ?? ()
#1  0x080485a1 in result(xuzhina_dump_c05_s3_ex*, int) ()
#2  0x080487f8 in main ()

从栈顶来看,顶层的地址竟然是0,eip的值也是0.会出现eip为0的情况,只可能是调用了函数指针,且函数指针为0.看一下result的汇编:

(gdb) disassemble result
Dump of assembler code for function _Z6resultP22xuzhina_dump_c05_s3_exi:
   0x08048540 <+0>:     push   %ebp
   0x08048541 <+1>:     mov    %esp,%ebp
   0x08048543 <+3>:     push   %ebx
   0x08048544 <+4>:     sub    $0x24,%esp
   0x08048547 <+7>:     movl   $0x0,-0xc(%ebp)
   0x0804854e <+14>:    movl   $0x0,-0x10(%ebp)
   0x08048555 <+21>:    jmp    0x80485a8 <_Z6resultP22xuzhina_dump_c05_s3_exi+104>
   0x08048557 <+23>:    mov    -0x10(%ebp),%edx
   0x0804855a <+26>:    mov    %edx,%eax
   0x0804855c <+28>:    add    %eax,%eax
   0x0804855e <+30>:    add    %edx,%eax
   0x08048560 <+32>:    shl    $0x2,%eax
   0x08048563 <+35>:    mov    %eax,%edx
   0x08048565 <+37>:    mov    0x8(%ebp),%eax
   0x08048568 <+40>:    add    %edx,%eax
   0x0804856a <+42>:    mov    0x8(%eax),%ecx
   0x0804856d <+45>:    mov    -0x10(%ebp),%edx
   0x08048570 <+48>:    mov    %edx,%eax
   0x08048572 <+50>:    add    %eax,%eax
   0x08048574 <+52>:    add    %edx,%eax
   0x08048576 <+54>:    shl    $0x2,%eax
   0x08048579 <+57>:    mov    %eax,%edx
   0x0804857b <+59>:    mov    0x8(%ebp),%eax
   0x0804857e <+62>:    add    %edx,%eax
   0x08048580 <+64>:    mov    0x4(%eax),%ebx
   0x08048583 <+67>:    mov    -0x10(%ebp),%edx
   0x08048586 <+70>:    mov    %edx,%eax
   0x08048588 <+72>:    add    %eax,%eax
   0x0804858a <+74>:    add    %edx,%eax
   0x0804858c <+76>:    shl    $0x2,%eax
   0x0804858f <+79>:    mov    %eax,%edx
   0x08048591 <+81>:    mov    0x8(%ebp),%eax
   0x08048594 <+84>:    add    %edx,%eax
   0x08048596 <+86>:    mov    (%eax),%eax
   0x08048598 <+88>:    mov    %ebx,0x4(%esp)
   0x0804859c <+92>:    mov    %eax,(%esp)

   0x0804859f <+95>:    call   *%ecx
   0x080485a1 <+97>:    add    %eax,-0xc(%ebp)

   0x080485a4 <+100>:   addl   $0x1,-0x10(%ebp)
   0x080485a8 <+104>:   mov    -0x10(%ebp),%eax
   0x080485ab <+107>:   cmp    0xc(%ebp),%eax
   0x080485ae <+110>:   setl   %al
   0x080485b1 <+113>:   test   %al,%al
   0x080485b3 <+115>:   jne    0x8048557 <_Z6resultP22xuzhina_dump_c05_s3_exi+23>
   0x080485b5 <+117>:   mov    -0xc(%ebp),%eax
   0x080485b8 <+120>:   add    $0x24,%esp
   0x080485bb <+123>:   pop    %ebx
   0x080485bc <+124>:   pop    %ebp
   0x080485bd <+125>:   ret    
End of assembler dump.

在上面汇编,指令地址0x08048557-0x080485b3是一个循环,循环条件是判断ebp-0x10单元的值是不是与函数第三个参数(ebp+0xc)相等,由此可知,ebp-0x10存放着循环计数,取名为cnt。而栈顶的返回值0x080485a1也位于这个循环里,

那么coredump指令是由

   0x0804859f <+95>:    call   *%ecx

引起,也就是说ecx为0,如下图。

(gdb) frame 1
#1  0x080485a1 in result(xuzhina_dump_c05_s3_ex*, int) ()
(gdb) i r
eax            0x3      3
ecx            0x0      0
edx            0x24     36
ebx            0x3      3
esp            0xbfac0250       0xbfac0250
ebp            0xbfac0278       0xbfac0278
esi            0x0      0
edi            0x0      0
eip            0x80485a1        0x80485a1 <result(xuzhina_dump_c05_s3_ex*, int)+97>
eflags         0x10296  [ PF AF SF IF RF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51

ecx这个函数指针是由下面的指令来获得:

   0x08048557 <+23>:    mov    -0x10(%ebp),%edx
   0x0804855a <+26>:    mov    %edx,%eax
   0x0804855c <+28>:    add    %eax,%eax
   0x0804855e <+30>:    add    %edx,%eax
   0x08048560 <+32>:    shl    $0x2,%eax
   0x08048563 <+35>:    mov    %eax,%edx
   0x08048565 <+37>:    mov    0x8(%ebp),%eax
   0x08048568 <+40>:    add    %edx,%eax
   0x0804856a <+42>:    mov    0x8(%eax),%ecx

ecx函数指针的第一个参数的获得:

   0x08048583 <+67>:    mov    -0x10(%ebp),%edx
   0x08048586 <+70>:    mov    %edx,%eax
   0x08048588 <+72>:    add    %eax,%eax
   0x0804858a <+74>:    add    %edx,%eax
   0x0804858c <+76>:    shl    $0x2,%eax
   0x0804858f <+79>:    mov    %eax,%edx
   0x08048591 <+81>:    mov    0x8(%ebp),%eax
   0x08048594 <+84>:    add    %edx,%eax
   0x08048596 <+86>:    mov    (%eax),%eax

ecx函数指针的第二个参数的获得:

   0x0804856d <+45>:    mov    -0x10(%ebp),%edx
   0x08048570 <+48>:    mov    %edx,%eax
   0x08048572 <+50>:    add    %eax,%eax
   0x08048574 <+52>:    add    %edx,%eax
   0x08048576 <+54>:    shl    $0x2,%eax
   0x08048579 <+57>:    mov    %eax,%edx
   0x0804857b <+59>:    mov    0x8(%ebp),%eax
   0x0804857e <+62>:    add    %edx,%eax
   0x08048580 <+64>:    mov    0x4(%eax),%ebx

由上面可以看到,这三个值的获取只有最后一条指令是不一样,其它都一样。

先分析一下前面相同的指令:

   0x0804856d <+45>:    mov    -0x10(%ebp),%edx		;把cnt(ebp-0x10)的内容放入edx
   0x08048570 <+48>:    mov    %edx,%eax			;eax = edx
   0x08048572 <+50>:    add    %eax,%eax				;eax *= 2
   0x08048574 <+52>:    add    %edx,%eax				;eax += edx = 3*edx
   0x08048576 <+54>:    shl    $0x2,%eax				;eax <<= 2. eax = 12*cnt
   0x08048579 <+57>:    mov    %eax,%edx			;edx = eax
   0x0804857b <+59>:    mov    0x8(%ebp),%eax		;result函数的第一个参数
   0x0804857e <+62>:    add    %edx,%eax				;eax += edx

由上面的汇编可以看出,取得ecx所指向的函数及它两个参数,都是在result函数的第一参数加一定偏移值来得到,而这个偏移值则是12*cnt。

而cnt在每次循环比较结束条件时,都会:

   0x080485a4 <+100>:   addl   $0x1,-0x10(%ebp)

即cnt++。也就是说,每次循环获取函数指针放入ecx及函数指针两个参数时,计算的偏移值都会比上一次循环的偏移值多上12,是一个固定值。根据数组的特征,可见result函数的第一个参数是一个数组,它每个元素的大小是12

且cnt在循环比较结束条件时,都会

   0x080485ab <+107>:   cmp    0xc(%ebp),%eax

可见,result函数的第二个参数是数组要遍历的个数。

再者,由于函数指针和它两个参数的获得只是最后一条指令不一样,且它们是以数组的元素为基地址的。可以推断它们是结构体。

所以,现在就是要看一下究竟是数组哪个元素的第三个成员为空。

由于cnt是放在ebp-0x10,看一下ebp-0x10的内容就知道:

(gdb) frame 
#1  0x080485a1 in result(xuzhina_dump_c05_s3_ex*, int) ()
(gdb)  x /x $ebp-0x10
0xbfac0268:     0x00000003

可见是数组的第四个元素的第三个成员为空。

看一下代码:

 typedef int (*operation)( int a, int b );
 struct xuzhina_dump_c05_s3_ex
 {
     int a;
     int b;
     operation oper;
 };
 
 int result( struct xuzhina_dump_c05_s3_ex test[], int num )
 {
     int res = 0;
     for ( int i = 0; i < num; i++ )
     {
         res += test[i].oper( test[i].a, test[i].b );
     }
	 
     return res;
 }
 
 int add( int a, int b )
 {
     return a+b;
 }
 int sub( int a, int b )
 {
     return a-b;
 }
 int mul( int a, int b )
 {
     return a*b;
 }
 
 void init( struct xuzhina_dump_c05_s3_ex test[], int num )
 {
      for( int i = 0; i < num; i++ )
      {
          switch( i % 4 )
          {
              case 0:
                  test[i].a = i/4;
                  test[i].b = 0;
                  test[i].oper = add;
                 break;
             case 1:
                 test[i].a = i/4;
                 test[i].b = i%4;
                 test[i].oper = mul;
                 break;
             case 2:
                 test[i].a = i%4;
                 test[i].b = i/4;
                 test[i].oper = sub;
                 break;
             default:
                 test[i].a = i;
                 test[i].b = i%4;
                 test[i].oper = 0;
                 break;
         }
     }
 }
 
 int main()
 {
     struct xuzhina_dump_c05_s3_ex test[15];
     init( test, 15 );
 
     return result( test, 15 );
 }

可见是由于init的这一块代码所导致的

54	             default:
 55	                 test[i].a = i;
 56	                 test[i].b = i%4;
 57	                 test[i].oper = 0;
 58	                 break;

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python小屋

Python中lambda表达式的常见用法

非常抱歉,昨天发的代码中有一处小错误,已通过留言的方式进行了纠正,详情请见【详解Python列表推导式】 lambda表达式常用来声明匿名函数,即没有函数名字的...

37090
来自专栏用户2442861的专栏

NumPy简明教程(二、数组1)

http://blog.csdn.net/sunny2038/article/details/9002531

10610
来自专栏人工智能LeadAI

Python中对字节流/二进制流的操作:struct模块简易使用教程

前言 前段时间使用Python解析IDX文件格式的MNIST数据集,需要对二进制文件进行读取操作,其中我使用的是struct模块。查了网上挺多教程都写的挺好的,...

55950
来自专栏数据结构与算法

计数排序

算法思想 编辑 计数排序对输入的数据有附加的限制条件: 1、输入的线性表的元素属于有限偏序集S; 2、设输入的线性表的长度为n,|S|=k(表示集合S中元素...

303100
来自专栏企鹅号快讯

1.12编程基础之函数与过程抽象/05:统计单词数

总时间限制: 1000ms 内存限制: 65536kB 描述 一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单...

276100
来自专栏赵俊的Java专栏

搜索插入位置

17410
来自专栏数据结构与算法

1341 与3和5无关的数

1341 与3和5无关的数 时间限制: 1 s 空间限制: 64000 KB 题目等级 : 白银 Silver 题目描述 Description ...

28840
来自专栏赵俊的Java专栏

计算最大值

15030
来自专栏猿人谷

成员以其在类中声明的顺序构造

分析以下代码的输出: #include<iostream> using namespace std; class A { public: A(int j):...

20560
来自专栏JavaEdge

Java中类型参数“<T>”和无界通配符“<?>”的区别

List<T>最应该出现的地方,应该是定义一个泛型List容器 但List是库里自带的容器,看看ArrayList的源码头一行:

32110

扫码关注云+社区

领取腾讯云代金券