您的位置:首页 > 理论基础 > 数据结构算法

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

2013-03-14 21:24 344 查看
在C语言里,数组就是相同类型变量的集合体。由这个定义,可大致得知数组的特征:

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

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

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

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

先看一下char型的数组

#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;
}


再看一下它的汇编:

(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.



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.      第三个参数,&buf[15],是由esp+0x18 + 0xf得来的。0xf刚好和buf[15]相对buf基地址的偏移相等。

3.      由buf[15]的地址比buf[0]的高,可知,数组在栈上是递增的。

 



0x080485a9 <+9>:     movb   $0x61,0x2f(%esp)

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

 

再由

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>

这个循环里的

0x080485ed <+77>:    addb   $0x1,0x2f(%esp)

可知

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

且由

0x080485d7 <+55>:    lea    0x18(%esp),%edx
0x080485db <+59>:    mov    0x28(%esp),%eax
0x080485df <+63>:    add    %eax,%edx



0x080485e8 <+72>:    addl   $0x1,0x28(%esp)

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

 

 

PS:下面的汇编指令

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两个变量操作的指令之间没有依赖,混编的话,在多核多线程处理器能够同时并发执行。

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

(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的数组:

#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];
}

看一下相应的汇编:

(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指向的元素地址了。

详细可以搜索一下“寄存器寻址方式”里的”寄存器变址寻址方式“
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐