前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《coredump问题原理探究》Linux x86版5.3节C风格数据结构内存布局之数组

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

作者头像
血狼debugeeker
发布2018-09-20 14:38:54
4810
发布2018-09-20 14:38:54
举报
文章被收录于专栏:debugeeker的专栏

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1344512

在C语言里,数组就是相同类型变量的集合体。由这个定义,可大致得知数组的特征:

1.有首元素。而首元素的地址和数组地址一样,即有基地址

2.每个元素的大小是一样的。那么每个元素相对基地址的偏移值应该是元素大小和索引值的乘积。

也就是说,基地址和与索引值成比例的偏移值有可能是数组的特征。

还是按照上面的方式来逐个对各类型的数组进行探究。

先看一下char型的数组

代码语言:javascript
复制
 #include <stdio.h>
 int main()
 {
     char buf[16];
     char c = 'a';
 
     printf( "head of array:%x, tail of array:%x", buf, &buf[15] );
 
     for ( int i = 0; i < 16; i++, c++ )
     {
         buf[i] = c;
     }
     buf[15] = '\0';

     printf( "%s\n", buf );
 
     return 0;
}

再看一下它的汇编:

代码语言:javascript
复制
(gdb) disassemble main
Dump of assembler code for function main:
   0x080485a0 <+0>:     push   %ebp
   0x080485a1 <+1>:     mov    %esp,%ebp
   0x080485a3 <+3>:     and    $0xfffffff0,%esp
   0x080485a6 <+6>:     sub    $0x30,%esp
   0x080485a9 <+9>:     movb   $0x61,0x2f(%esp)

   0x080485ae <+14>:    lea    0x18(%esp),%eax
   0x080485b2 <+18>:    add    $0xf,%eax
   0x080485b5 <+21>:    mov    %eax,0x8(%esp)
   0x080485b9 <+25>:    lea    0x18(%esp),%eax
   0x080485bd <+29>:    mov    %eax,0x4(%esp)
   0x080485c1 <+33>:    movl   $0x80486b4,(%esp)
   0x080485c8 <+40>:    call   0x8048460 <printf@plt>

   0x080485cd <+45>:    movl   $0x0,0x28(%esp)
   0x080485d5 <+53>:    jmp    0x80485f2 <main+82>

   0x080485d7 <+55>:    lea    0x18(%esp),%edx
   0x080485db <+59>:    mov    0x28(%esp),%eax
   0x080485df <+63>:    add    %eax,%edx
   0x080485e1 <+65>:    movzbl 0x2f(%esp),%eax
   0x080485e6 <+70>:    mov    %al,(%edx)
   0x080485e8 <+72>:    addl   $0x1,0x28(%esp)
   0x080485ed <+77>:    addb   $0x1,0x2f(%esp)
   0x080485f2 <+82>:    cmpl   $0xf,0x28(%esp)
   0x080485f7 <+87>:    setle  %al
   0x080485fa <+90>:    test   %al,%al
   0x080485fc <+92>:    jne    0x80485d7 <main+55>

   0x080485fe <+94>:    movb   $0x0,0x27(%esp)
   0x08048603 <+99>:    lea    0x18(%esp),%eax
   0x08048607 <+103>:   mov    %eax,(%esp)
   0x0804860a <+106>:   call   0x8048470 <puts@plt>
   0x0804860f <+111>:   mov    $0x0,%eax
   0x08048614 <+116>:   jmp    0x804861e <main+126>
   0x08048616 <+118>:   mov    %eax,(%esp)
   0x08048619 <+121>:   call   0x8048490 <_Unwind_Resume@plt>
   0x0804861e <+126>:   leave  
   0x0804861f <+127>:   ret    
End of assembler dump.

代码语言:javascript
复制
   0x080485ae <+14>:    lea    0x18(%esp),%eax
   0x080485b2 <+18>:    add    $0xf,%eax
   0x080485b5 <+21>:    mov    %eax,0x8(%esp)
   0x080485b9 <+25>:    lea    0x18(%esp),%eax
   0x080485bd <+29>:    mov    %eax,0x4(%esp)
   0x080485c1 <+33>:    movl   $0x80486b4,(%esp)
   0x080485c8 <+40>:    call   0x8048460 <printf@plt>

可以看到:

1.      第二个参数,buf是由eax得来,而eax是由esp+0x18,也就是说,esp+0x18是buf的基地址

2.      第三个参数,&buf15,是由esp+0x18 + 0xf得来的。0xf刚好和buf15相对buf基地址的偏移相等。

3.      由buf15的地址比buf0的高,可知,数组在栈上是递增的。

代码语言:javascript
复制
   0x080485a9 <+9>:     movb   $0x61,0x2f(%esp)

可知,局部变量c是放在esp+0x2f里。

再由

代码语言:javascript
复制
   0x080485d7 <+55>:    lea    0x18(%esp),%edx
   0x080485db <+59>:    mov    0x28(%esp),%eax
   0x080485df <+63>:    add    %eax,%edx
   0x080485e1 <+65>:    movzbl 0x2f(%esp),%eax
   0x080485e6 <+70>:    mov    %al,(%edx)
   0x080485e8 <+72>:    addl   $0x1,0x28(%esp)
   0x080485ed <+77>:    addb   $0x1,0x2f(%esp)
   0x080485f2 <+82>:    cmpl   $0xf,0x28(%esp)
   0x080485f7 <+87>:    setle  %al
   0x080485fa <+90>:    test   %al,%al
   0x080485fc <+92>:    jne    0x80485d7 <main+55>

这个循环里的

代码语言:javascript
复制
   0x080485ed <+77>:    addb   $0x1,0x2f(%esp)

可知

   c递增的步长是1,刚好和char的大小一样。

且由

代码语言:javascript
复制
   0x080485d7 <+55>:    lea    0x18(%esp),%edx
   0x080485db <+59>:    mov    0x28(%esp),%eax
   0x080485df <+63>:    add    %eax,%edx

代码语言:javascript
复制
   0x080485e8 <+72>:    addl   $0x1,0x28(%esp)

可知,数组的元素地址确实是递增的,且每个元素的地址都是esp+0x18+i,即基地址+i

PS:下面的汇编指令

代码语言:javascript
复制
   0x080485e8 <+72>:    addl   $0x1,0x28(%esp)
   0x080485ed <+77>:    addb   $0x1,0x2f(%esp)
   0x080485f2 <+82>:    cmpl   $0xf,0x28(%esp)

的意思,就是c++, i++和i < 15。但把c++放入在i++,i<15之间主要是对c,i两个变量操作的指令之间没有依赖,混编的话,在多核多线程处理器能够同时并发执行。

打一下断点来验证一下上面结论

代码语言:javascript
复制
(gdb) tbreak *0x080485fe
Temporary breakpoint 1 at 0x80485fe
(gdb) r
Starting program: /home/buckxu/work/5/2/xuzhina_dump_c5_s2 

Temporary breakpoint 1, 0x080485fe in main ()
 (gdb) x /16c $esp+0x18
0xbffff468:     97 'a'  98 'b'  99 'c'  100 'd' 101 'e' 102 'f' 103 'g' 104 'h'
0xbffff470:     105 'i' 106 'j' 107 'k' 108 'l' 109 'm' 110 'n' 111 'o' 112 'p'

继续看一下short的数组:

代码语言:javascript
复制
   #include <stdio.h>
   int main()
   {
       short buf[16];
       short s = 'a';
  
       printf( "head of array:%x, tail of array:%x", buf, &buf[15] );
   
       for ( int i = 0; i < 16; i++, s++ )
      {
          buf[i] = s;
      }
  
  
      return buf[15];
  }

看一下相应的汇编:

代码语言:javascript
复制
(gdb) disassemble main
Dump of assembler code for function main:
   0x08048570 <+0>:     push   %ebp
   0x08048571 <+1>:     mov    %esp,%ebp
   0x08048573 <+3>:     and    $0xfffffff0,%esp
   0x08048576 <+6>:     sub    $0x40,%esp
   0x08048579 <+9>:     movw   $0x61,0x3e(%esp)

   0x08048580 <+16>:    lea    0x18(%esp),%eax
   0x08048584 <+20>:    add    $0x1e,%eax
   0x08048587 <+23>:    mov    %eax,0x8(%esp)
   0x0804858b <+27>:    lea    0x18(%esp),%eax
   0x0804858f <+31>:    mov    %eax,0x4(%esp)
   0x08048593 <+35>:    movl   $0x8048674,(%esp)
   0x0804859a <+42>:    call   0x8048440 <printf@plt>

   0x0804859f <+47>:    movl   $0x0,0x38(%esp)
   0x080485a7 <+55>:    jmp    0x80485c2 <main+82>

   0x080485a9 <+57>:    mov    0x38(%esp),%eax
   0x080485ad <+61>:    movzwl 0x3e(%esp),%edx
   0x080485b2 <+66>:    mov    %dx,0x18(%esp,%eax,2)
   0x080485b7 <+71>:    addl   $0x1,0x38(%esp)
   0x080485bc <+76>:    addw   $0x1,0x3e(%esp)
   0x080485c2 <+82>:    cmpl   $0xf,0x38(%esp)
   0x080485c7 <+87>:    setle  %al
   0x080485ca <+90>:    test   %al,%al
   0x080485cc <+92>:    jne    0x80485a9 <main+57>

   0x080485ce <+94>:    movzwl 0x36(%esp),%eax
   0x080485d3 <+99>:    cwtl   
   0x080485d4 <+100>:   jmp    0x80485de <main+110>
   0x080485d6 <+102>:   mov    %eax,(%esp)
   0x080485d9 <+105>:   call   0x8048460 <_Unwind_Resume@plt>
   0x080485de <+110>:   leave  
   0x080485df <+111>:   ret    
End of assembler dump.

按照char型数组类似的分析,可得:

1.      局部变量s存放在esp+0x3e

2.      buf的首地址是esp+0x18,尾元素的地址是esp+0x18+0x1e。可见buf在栈里也是递增的。

3.      buf的空间大小是0x1e+2=0x20 = 32。正好是16个short类型的大小。

4.      由0x080485bc可知,short型数组的递增步长是2,刚好是short的大小。

5.      由0x080485b2可知,对short数组每一个元素的引用,都要用到esp+0x18+2*eax,即基地址+i*sizeof(short)

继续对int, long,float,double,可以得到下表:

类型

特征

char

基地址 + 索引值*1

short

基地址 + 索引值*2

int

基地址 + 索引值*4

long

32-bit:基地址 + 索引值*4 64-bit:基地址 + 索引值*8

float

基地址 + 索引值*4 (因为单精度是占4个字节的),要配合浮点计算的指令确认

double

基地址 + 索引值*8 (双精度占8个字节) ,要配合浮点计算的指令确认

指针

32-bit:基地址 + 索引值*4 64-bit:基地址 + 索引值*8

其实,基地址+索引值*sizeof( element)这些方式,用汇编可以有很多种表示形式。如下面也算是一种

| lea $base,%eax  // 把base地址放到eax里 mov $index,%ecx // 把index放到ecx里。 mul $4, %ecx add %ecx, %eax   //这里eax就存放了index指向的元素地址了。 |

|:----|

详细可以搜索一下“寄存器寻址方式”里的”寄存器变址寻址方式“

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2013年03月14日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档