数据结构在汇编语言下的表现
数组
栈上数组
main.c
1 2 3 4 5 6 7 8 9 10 11 12 void func () { int local_array[3 ]; int index=2 ; local_array[0 ]=10 ; local_array[1 ]=20 ; local_array[2 ]=30 ; local_array[index]=40 ; } int main () { func(); return 0 ; }
1 2 gcc main.c -O0 -o main.exe ida64 main.exe
func
函数的反汇编:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 .text:0000000000401560 .text:0000000000401560 ; =============== S U B R O U T I N E ======================================= .text:0000000000401560 .text:0000000000401560 ; 64位windows上不再有__fastcall和_cdecl等调用约定的区别,只有一个同一的微软x64调用约定 .text:0000000000401560 ; Attributes: bp-based frame .text:0000000000401560 .text:0000000000401560 ; void __fastcall func() .text:0000000000401560 public func .text:0000000000401560 func proc near ; CODE XREF: main+D↓p .text:0000000000401560 .text:0000000000401560 var_10 = dword ptr -10h .text:0000000000401560 var_C = dword ptr -0Ch .text:0000000000401560 var_8 = dword ptr -8 .text:0000000000401560 var_4 = dword ptr -4 .text:0000000000401560 .text:0000000000401560 push rbp .text:0000000000401561 mov rbp, rsp .text:0000000000401564 sub rsp, 10h ; 栈上开0x10=16字节的空间,用于存放局部变量(int local_array[3] 数组和int index变量,恰好4个int*一个int占用4字节) .text:0000000000401568 mov [rbp+var_4], 2 ; int index=2,可以判断var_4变量对应index .text:000000000040156F mov [rbp+var_10], 0Ah ; 0x0Ah=10,对应local_array[0]=10;可以判断var_10对应local_array[0] .text:0000000000401576 mov [rbp+var_C], 14h ; 同理var_C=local_array[1] .text:000000000040157D mov [rbp+var_8], 1Eh ; 同理var_8=local_array[2] .text:0000000000401584 mov eax, [rbp+var_4] ; 把index变量放在寄存器中,为0x401589的寻址做准备 .text:0000000000401587 cdqe ;将eax寄存器拓展为rax寄存器,该指令只作用于eax寄存器 .text:0000000000401589 mov [rbp+rax*4+var_10], 28h ; local_array[index]=40 .text:0000000000401591 nop .text:0000000000401592 add rsp, 10h ;函数执行完毕,退栈 .text:0000000000401596 pop rbp ;还原rbp原始功能 .text:0000000000401597 retn .text:0000000000401597 func endp .text:0000000000401597
函数栈帧基于rbp帧指针,主函数没有对其传递参数,栈帧中
有返回值,rbp保存值,还有四个变量
image-20220502203420528
image-20220502204300823
源程序中写的数组int local_array[3]
被拆成了3个独立的int变量
只有在.text:0000000000401589 mov [rbp+rax*4+var_10], 28h
这里可以隐约看出实在对数组进行寻址操作
var_10(rbp,rax,4)=M[rbp+rax*4+var_10]
rax
中存放的是index的值,用rax*4是因为一个int占4字节,rbp+var_10
是数组的基地址,rax*4
是偏移量
在分析出var_8,var_C,var_10
同属于一个基地址var_10
的数组结构后,可以在func函数的栈帧视图下高亮这三个变量,使用右键菜单的array
选项将其合并为一个数组
image-20220502205958995
image-20220502205827491
合并后回到反汇编视图
image-20220502210142961
发现三个变量合并为一个var_10
,其地址rbp-10h
,刚好和var_4
相差0xC=12字节
即三个int类型变量
然后可以在反汇编视图下使用右键菜单的rename
功能将变量命名有意义
image-20220502210513069
rename之后后文相关位置都会重新命名
全局数组
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 int global_array[3 ];void func () { int index=2 ; global_array[0 ]=10 ; global_array[1 ]=20 ; global_array[2 ]=30 ; local_array[index]=40 ; } int main () { func(); return 0 ; }
func
函数的反汇编:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 .text:0000000000401560 .text:0000000000401560 ; =============== S U B R O U T I N E ======================================= .text:0000000000401560 .text:0000000000401560 ; Attributes: bp-based frame ;局部变量和参数在栈帧中的地址基于rbp帧指针 .text:0000000000401560 .text:0000000000401560 ; void __fastcall func() .text:0000000000401560 public func .text:0000000000401560 func proc near ; CODE XREF: main+D↓p .text:0000000000401560 .text:0000000000401560 var_4 = dword ptr -4 ;局部变量只有一个,只能对应index .text:0000000000401560 .text:0000000000401560 push rbp .text:0000000000401561 mov rbp, rsp .text:0000000000401564 sub rsp, 10h .text:0000000000401568 mov [rbp+var_4], 2 ;int index=2 .text:000000000040156F lea rax, global_array ;R[rax]=&global_array,将global_array的地址放在rax .text:0000000000401576 mov dword ptr [rax], 0Ah ;寄存器寻址,然后dword ptr指定双字访问内存,global_array[0]=10 .text:000000000040157C lea rax, global_array ;重复R[rax]=&global_array,目的是防止上一次装载和本次之间rax有变化 .text:0000000000401583 mov dword ptr [rax+4], 14h ;寄存器+立即数寻址,双字访问内存 .text:000000000040158A lea rax, global_array .text:0000000000401591 mov dword ptr [rax+8], 1Eh .text:0000000000401598 lea rax, global_array .text:000000000040159F mov edx, [rbp+var_4] ;将var_4的拷贝到edx寄存器中 .text:00000000004015A2 movsxd rdx, edx ;edx有符号拓展到rdx .text:00000000004015A5 mov dword ptr [rax+rdx*4], 28h ; 基址比例变址寻址,然后放入40 .text:00000000004015AC nop .text:00000000004015AD add rsp, 10h .text:00000000004015B1 pop rbp .text:00000000004015B2 retn .text:00000000004015B2 func endp .text:00000000004015B2
问题是global_array
貌似没有体现出声明来就直接使用了,左键双击global_array
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .bss:0000000000407970 public global_array .bss:0000000000407970 global_array db ? ; ; DATA XREF: func+F↑o .bss:0000000000407970 ; func+1C↑o ... .bss:0000000000407971 db ? ; .bss:0000000000407972 db ? ; .bss:0000000000407973 db ? ; .bss:0000000000407974 db ? ; .bss:0000000000407975 db ? ; .bss:0000000000407976 db ? ; .bss:0000000000407977 db ? ; .bss:0000000000407978 db ? ; .bss:0000000000407979 db ? ; .bss:000000000040797A db ? ; .bss:000000000040797B db ? ; .bss:000000000040797C db ? ; .bss:000000000040797D db ? ; .bss:000000000040797E db ? ; .bss:000000000040797F db ? ;
发现global_array
是位于bss段的,确实global_array
在声明的时候并没有初始化,就应该放在bss段
global_array
以db=define byte
即字节为单位,在bss段留了0407970~040797F
一共16个字节的空间
如果在func
函数的反汇编视图下修改global_array
的名字改成空即显示ida给他命的哑名unk_407970
,unk为unknown未知的缩写
如果在源代码中不写全局函数global_array
,改用三个int类型变量
1 2 3 4 5 6 7 8 9 10 11 12 int l0;int l1;int l2;void func () { l0=10 ; l1=20 ; l2=30 ; } int main () { func(); return 0 ; }
此时三个全局变量在bss段中的分布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 .bss:0000000000407970 public l2 .bss:0000000000407970 l2 db ? ; ; DATA XREF: func+1E↑o .bss:0000000000407971 db ? ; .bss:0000000000407972 db ? ; .bss:0000000000407973 db ? ; .bss:0000000000407974 public l0 .bss:0000000000407974 l0 db ? ; ; DATA XREF: func+4↑o .bss:0000000000407975 db ? ; .bss:0000000000407976 db ? ; .bss:0000000000407977 db ? ; .bss:0000000000407978 public l1 .bss:0000000000407978 l1 db ? ; ; DATA XREF: func+11↑o .bss:0000000000407979 db ? ; .bss:000000000040797A db ? ; .bss:000000000040797B db ? ; .bss:000000000040797C db ? ; .bss:000000000040797D db ? ; .bss:000000000040797E db ? ; .bss:000000000040797F db ? ; .bss:0000000000407980 public __native_startup_state ...
很诡异的是三个变量的分布是没有顺序的,l2和l0都分到了4个db即4字节的空间,但是l1却分到了8字节的空间
如果试图使用*(&l0+2)
来得到l2实际上会算得*(0x407974+2*sizeof(int))=*(0x40797C)
而实际上l2
在0x407970
,刚才指针运算得到的值是属于l1"管理"的
堆上数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdlib.h> void func () { int *heap_array=(int *)malloc (3 *sizeof (int )); int index=2 ; heap_array[0 ]=10 ; heap_array[1 ]=20 ; heap_array[2 ]=30 ; heap_array[index]=40 ; free (heap_array); } int main () { func(); return 0 ; }
func
函数反汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 .text:0000000000401560 .text:0000000000401560 ; =============== S U B R O U T I N E ======================================= .text:0000000000401560 .text:0000000000401560 ; Attributes: bp-based frame .text:0000000000401560 .text:0000000000401560 public func .text:0000000000401560 func proc near ; CODE XREF: main+D↓p .text:0000000000401560 .text:0000000000401560 var_C = dword ptr -0Ch ;var_C显然为index .text:0000000000401560 Block = qword ptr -8 ;Block为堆上申请空间的指针 .text:0000000000401560 .text:0000000000401560 push rbp .text:0000000000401561 mov rbp, rsp .text:0000000000401564 sub rsp, 30h ;蜜汁操作,栈上分配了0x30h=48字节的巨大空间,但是只有两个局部变量 .text:0000000000401568 mov ecx, 0Ch ; Size,第一个参数使用ecx寄存器传递,这里0x0C=12字节相当于给出了堆上数组的大小 .text:000000000040156D call malloc ;malloc遵守 微软64位调用约定 .text:0000000000401572 mov [rbp+Block], rax ;rax寄存器带着malloc函数的返回值,即堆上地址的指针,赋值给Block .text:0000000000401576 mov [rbp+var_C], 2 ;int index=2 .text:000000000040157D mov rax, [rbp+Block] ;将堆上指针放到rax中 .text:0000000000401581 mov dword ptr [rax], 0Ah ;字访问rax指向的地址,放入10 .text:0000000000401587 mov rax, [rbp+Block] ;将堆上指针再次放到rax中 .text:000000000040158B add rax, 4 ;rax中的指针副本后移4字节,恰好移过一个int .text:000000000040158F mov dword ptr [rax], 14h ;单字访问rax指向的地址,放入20 .text:0000000000401595 mov rax, [rbp+Block] .text:0000000000401599 add rax, 8 .text:000000000040159D mov dword ptr [rax], 1Eh .text:00000000004015A3 mov eax, [rbp+var_C] ;将index放到eax中 .text:00000000004015A6 cdqe ;拓展eax到rax .text:00000000004015A8 lea rdx, ds:0[rax*4] ;将ds:0+rax*4这个地址放到rdx寄存器 .text:00000000004015B0 mov rax, [rbp+Block] ;rax寄存器获得Block堆指针的拷贝 .text:00000000004015B4 add rax, rdx ;rax指向Block+4*rax即heap_array[index] .text:00000000004015B7 mov dword ptr [rax], 28h ; 单字访问rax指向的地址,放入40 .text:00000000004015BD mov rax, [rbp+Block] ;rax获得Block堆指针拷贝 .text:00000000004015C1 mov rcx, rax ; rcx获得拷贝,准备作为参数传递给free函数 .text:00000000004015C4 call free .text:00000000004015C9 nop .text:00000000004015CA add rsp, 30h .text:00000000004015CE pop rbp .text:00000000004015CF retn .text:00000000004015CF func endp .text:00000000004015CF
The parts of program memory showing a
stack variable pointing to dynamically allocated heap
memory.
堆上数组的特征还是比较明显的
综上,使用变量下标访问数组时更容易察觉数组结构的存在,而使用常量下标访问数组时汇更像访问独立的变量
结构体
对齐
对齐在大多数情况下不是硬性要求,不对齐的话x86-64的硬件也是可以干活的,
但是为了提高性能intel建议对齐,编译器默认情况下是会自动对齐的
基本数据类型的对齐规则:
某种类型的对象,其地址必须是K的倍数
image-20220503010303191
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> void func () { char achar; char bchar; int aint; printf ("%x,%x,%x" , &achar, &bchar, &aint); return ; } int main () { func (); return 0 ; }
1 2 gcc -O0 -g -o main.out gdb -tui -q main.out
使用gdb进行调试,观察运行时的堆栈
1.在func函数处下断点
image-20220503013023486
2.在断点处停下,打印观察
1 2 3 4 5 6 7 8 9 10 11 12 (gdb) r Starting program: /mnt/c/Users/86135/Desktop/reverse/mytest/main.exe Breakpoint 1, func () at main.c:7 (gdb) p $rbp $1 = (void *) 0x7fffffffdc90(gdb) p &achar $2 = 0x7fffffffdc8f "" (gdb) p &bchar $3 = 0x7fffffffdc8e "" (gdb) p &aint $4 = (int *) 0x7fffffffdc88
帧指针rbp
指向0x7fffffffdc90
achar是一个char类型,无对齐要求,必然在紧接着帧指针下方0x7fffffffdc8f
,bchar同理在achar下方0x7fffffffdc8e
但是aint并没有紧挨着bchar在0x7fffffffdc8d
,而是在0x7fffffffdc88
栈向下增长,但是小端模式下,栈中的元素的起始位置是低地址,然后向高地址增长,比如aint就从0x7fffffffdc88
然后向高地址增长直到0x7fffffffdc8b
func
函数的栈帧:
地址
相对rbp的偏移量
存储内容
1
返回值地址
0x7fffffffdc90
0
rbp
帧指针保存地址
0x7fffffffdc8f
-1
achar
0x7fffffffdc8e
-2
bchar
-3
-4
0x7fffffffdc8b
-5
aint
高位
-6
aint
-7
aint
0x7fffffffdc88
-8
aint
低位
-9
-10
...
...
这里aint就没有从-6到-3,而是为了对齐采用-8到-5
小端模式
小端模式:假如int aint=10d=0xAh=0x0000 0000 0000 0000 0000 0000 0000 1010 b
那么0x7fffffffdc88
这个低地址上应该存放aint
的低八位即0000 1010b=0x10h
为了观察这个事情
使用几乎相同的程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> void func () { char achar; char bchar; int aint=10 ; printf ("%x,%x,%x" , &achar, &bchar, &aint); return ; } int main () { func(); return 0 ; }
使用gdb在第7行下断点,然后运行到第七行的时候停下,打印&aint,结果为0x7fffffffe2e8
,然后x/1 0x7fffffffe2e8
打印从该地址开始的第一个单元(单位字节)
image-20220503020706566
结果为10,即证明低地址存放低位,小端模式
结构体的对齐规则
结构体各项目依然满足基本数据类型的对齐方式
结构体自身也有对齐要求,其==基地址必须是最大成员大小的整数倍==
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> typedef struct { char name; int from; int to; }Edge; void func () { char a ; Edge e; char b; printf ("%x,%x,%x" ,&a,&e,&b); return ; } int main () { func(); return 0 ; }
1 2 gcc -O0 -g -o main.exe gdb -tui -q main.exe
在func
函数处下断点然后在该断点处停下,打印观察
1 2 3 4 5 6 7 8 9 10 11 12 13 14 (gdb) p &e $4 = (Edge *) 0x7fffffffdc80(gdb) p &e.name $5 = 0x7fffffffdc80 "" ;e.name的位置(gdb) p &e.from $6 = (int *) 0x7fffffffdc84 ;e.from的位置(gdb) p &e.to $7 = (int *) 0x7fffffffdc88 ;e.to的位置(gdb) p &b $8 = 0x7fffffffdc7f "" ;char b的位置(gdb) p &a $9 = 0x7fffffffdc8f "" ;char a的位置(gdb) p $rbp $10 = (void *) 0x7fffffffdc90 ;帧指针位置
func
函数的栈帧
image-20220503111359404
首先在结构体内部安排对齐 :
name
放在偏移量为0的位置,然后int from
放在偏移量为4的倍数的最小位置,显然是4,然后int to
同理放在8,from和name之间就空出了3字节,原因就是考虑对齐
然后对结构体整体安排对齐 :
为什么要进行整体对齐?
如果不进行此步,只进行内部对齐,那么考虑如下情形
image-20220503112100035
左右两种排列方法均满足结构体内对齐,对于右侧,e.from
的起始地址是0x7fffffffdc85
显然不满足int的对齐规则,而左侧经过整体对齐,e.from
在0x7fffffffdc84
是可以整除4的,满足int的对齐规则
此时sizeof(e)=12,而不是1+4+4=9
整体对齐要求结构体的起始地址必须是最大成员大小的整数倍,在这里是int,4字节
因此结构体e的起始地址应当是能够整除4的并且距离rbp最近的并且能够放得下结构体e的地方,显然是0x7fffffffdc80
整体对齐只是考虑了最大成员大小的整数倍,一定能保证所有成员的对齐吗?
由于任意基本数据类型的大小都是2的幂次,因此最大成员的大小一定是任意成员大小的整数倍,那么起始地址是最大成员大小的整数倍的同时,也一定是任意成员大小的整数倍
#pragma pack(n)指定对齐
如果不写该语句则默认n=8字节对齐,所有对象的对齐方式改为:
地址值是\(min\{自己大小,n\}的整数倍\)
同样的程序在一开始时加入
指定一字节对齐,任意对象的地址是\(min\{自己大小,1\}的整数倍=1的整数倍\) ,即没有对齐
然后使用gdb调试观察
1 2 3 4 5 6 7 8 9 10 11 12 13 14 (gdb) p $rbp $1 = (void *) 0x7fffffffe2f0(gdb) p sizeof(e) $2 = 9(gdb) p &a $3 = 0x7fffffffe2ef "" (gdb) p &b $4 = 0x7fffffffe2e5 "" (gdb) p &e.name $5 = 0x7fffffffe2e6 "" (gdb) p &e.from $6 = (int *) 0x7fffffffe2e7(gdb) p &e.to $7 = (int *) 0x7fffffffe2eb
image-20220503115539724
此时栈帧就非常紧凑了,从rbp向下顺次紧密排列
反汇编观察栈的分布
无对齐时的func函数的反汇编视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 .text:0000000000001139 func proc near ; CODE XREF: main+9↓p .text:0000000000001139 .text:0000000000001139 var_B = byte ptr -0Bh .text:0000000000001139 var_A = byte ptr -0Ah ;var_A放在rbp-10 .text:0000000000001139 var_1 = byte ptr -1 .text:0000000000001139 .text:0000000000001139 ; __unwind { .text:0000000000001139 push rbp .text:000000000000113A mov rbp, rsp .text:000000000000113D sub rsp, 10h ;此处申请了16字节的栈空间 .text:0000000000001141 lea rcx, [rbp+var_B] .text:0000000000001145 lea rdx, [rbp+var_A] .text:0000000000001149 lea rax, [rbp+var_1] .text:000000000000114D mov rsi, rax .text:0000000000001150 lea rax, format ; "%x,%x,%x" .text:0000000000001157 mov rdi, rax ; format .text:000000000000115A mov eax, 0 .text:000000000000115F call _printf .text:0000000000001164 nop .text:0000000000001165 leave .text:0000000000001166 retn .text:0000000000001166 ; } // starts at 1139 .text:0000000000001166 func endp
默认对齐时的func
函数反汇编视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 .text:0000000000001139 func proc near ; CODE XREF: main+9↓p .text:0000000000001139 .text:0000000000001139 var_11 = byte ptr -11h .text:0000000000001139 var_10 = byte ptr -10h ;var_10放在rbp-16 .text:0000000000001139 var_1 = byte ptr -1 .text:0000000000001139 .text:0000000000001139 ; __unwind { .text:0000000000001139 push rbp .text:000000000000113A mov rbp, rsp .text:000000000000113D sub rsp, 20h ;此处申请了32字节的栈空间 .text:0000000000001141 lea rcx, [rbp+var_11] .text:0000000000001145 lea rdx, [rbp+var_10] .text:0000000000001149 lea rax, [rbp+var_1] .text:000000000000114D mov rsi, rax .text:0000000000001150 lea rax, format ; "%x,%x,%x" .text:0000000000001157 mov rdi, rax ; format .text:000000000000115A mov eax, 0 .text:000000000000115F call _printf .text:0000000000001164 nop .text:0000000000001165 leave .text:0000000000001166 retn .text:0000000000001166 ; } // starts at 1139 .text:0000000000001166 func endp
全局结构体
以网络流中可能会用到的边结构体举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 typedef struct { int from; int to; long long dist; long long flow; }Edge; Edge e; void init () { e.from=1 ; e.to=2 ; e.dist=1e12 ; e.flow=1e13 ; } int main () { init(); }
ida观察带gdb调试信息的exe文件
1 2 gcc main.c -g -O0 -o main.exe ida64 main.exe
init
函数的反汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 .text:0000000000401560 ; void __cdecl init() .text:0000000000401560 public init .text:0000000000401560 init proc near ; CODE XREF: main+D↓p .text:0000000000401560 push rbp .text:0000000000401561 mov rbp, rsp .text:0000000000401564 lea rax, e ;把e的地址放在rax中 .text:000000000040156B mov dword ptr [rax], 1 ;把1放在rax指向地址的第一个字 .text:0000000000401571 lea rax, e .text:0000000000401578 mov dword ptr [rax+4], 2 ;把2放在rax指向地址偏移4字节之后的字中 .text:000000000040157F lea rax, e .text:0000000000401586 mov rdx, 0E8D4A51000h ;把1e12放在rdx中 .text:0000000000401590 mov [rax+8], rdx ;把rdx中的值放在rax+8位置,宽度以rdx的64位为准 .text:0000000000401594 lea rax, e .text:000000000040159B mov rcx, 9184E72A000h ;把1e13放在rcx中 .text:00000000004015A5 mov [rax+10h], rcx ;把rcx中的值放在rax+10位置,64位宽 .text:00000000004015A9 nop .text:00000000004015AA pop rbp .text:00000000004015AB retn .text:00000000004015AB init endp
值得庆幸的是e被ida识别为一个Edge结构体的对象,这是因为使用gcc -g
命令,编译形成的exe带有gbd调试信息
可以看出使用e的基地址+成员的偏移量进行结构体成员访问
其中e在bss段:
1 2 3 4 5 6 .bss:0000000000407970 public e .bss:0000000000407970 ; Edge e .bss:0000000000407970 e Edge <?> ; DATA XREF: init+4↑o .bss:0000000000407970 ; init+11↑o ... .bss:0000000000407988 public __native_startup_state ...
e在bss段的地址[0x407970,407987]
共24字节
默认8字节对齐下,最大成员long
long正好8字节,因此任何成员都是按照地址是自己大小倍数进行对齐的
1 2 3 4 5 6 typedef struct { int from; int to; long long dist; long long flow; }Edge;
from和to恰好占用第一个8字节,然后dist占用第二个8字节,然后flow占用第三个8字节,合计24字节
双击Edge可以观察其Structures视图
1 2 3 4 5 6 7 00000000 Edge struc ; (sizeof=0x18, align=0x8, copyof_102) ;大小0x18=24字节,8字节对齐 00000000 ; XREF: .bss:e/r 00000000 from dd ? ;dword双字类型 00000004 to dd ? ;dword双字类型 00000008 dist dq ? ;quad四字类型 00000010 flow dq ? ;quad四字类型 00000018 Edge ends
ida观察不带调试信息的exe
1 2 gcc main.c -O0 -o main.exe ;不使用-g ida64 main.exe
init
函数的反汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .text:0000000000401560 public init .text:0000000000401560 init proc near ; CODE XREF: main+D↓p .text:0000000000401560 push rbp .text:0000000000401561 mov rbp, rsp .text:0000000000401564 lea rax, e .text:000000000040156B mov dword ptr [rax], 1 .text:0000000000401571 lea rax, e .text:0000000000401578 mov dword ptr [rax+4], 2 .text:000000000040157F lea rax, e .text:0000000000401586 mov rdx, 0E8D4A51000h .text:0000000000401590 mov [rax+8], rdx .text:0000000000401594 lea rax, e .text:000000000040159B mov rcx, 9184E72A000h .text:00000000004015A5 mov [rax+10h], rcx .text:00000000004015A9 nop .text:00000000004015AA pop rbp .text:00000000004015AB retn .text:00000000004015AB init endp
到此和刚才带有调试信息的情况几乎相同,但是当我们双击e试图观察e是个什么东西时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 .bss:0000000000407970 public e .bss:0000000000407970 e db ? ; ; DATA XREF: init+4↑o .bss:0000000000407970 ; init+11↑o ... .bss:0000000000407971 db ? ; .bss:0000000000407972 db ? ; .bss:0000000000407973 db ? ; .bss:0000000000407974 db ? ; .bss:0000000000407975 db ? ; .bss:0000000000407976 db ? ; .bss:0000000000407977 db ? ; .bss:0000000000407978 db ? ; .bss:0000000000407979 db ? ; .bss:000000000040797A db ? ; .bss:000000000040797B db ? ; .bss:000000000040797C db ? ; .bss:000000000040797D db ? ; .bss:000000000040797E db ? ; .bss:000000000040797F db ? ; .bss:0000000000407980 db ? ; .bss:0000000000407981 db ? ; .bss:0000000000407982 db ? ; .bss:0000000000407983 db ? ; .bss:0000000000407984 db ? ; .bss:0000000000407985 db ? ; .bss:0000000000407986 db ? ; .bss:0000000000407987 db ? ; .bss:0000000000407988 public __native_startup_state ...
e退化为bss段上的24个字节,看不出结构体的痕迹了,
但是至少我们可以知道有这么一个叫做e的集合,它管理着24个字节
此时的结构体更像是一个鱼龙混杂的数组,有着不同大小的元素
栈上结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct { int from; int to; long long dist; long long flow; }Edge; Edge newEdge (int u,int v,long long d,long long f) { Edge e; e.from=u; e.to=v; e.dist=d; e.flow=f; return e; } int main () { Edge e=newEdge(1 ,2 ,1e12 ,1e13 ); }
带调试信息
newEdge
函数的反汇编视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 .text:0000000000401560 ; Edge *__cdecl newEdge(Edge *__return_ptr __struct_ptr retstr, int u, int v, __int64 d, __int64 f) .text:0000000000401560 public newEdge .text:0000000000401560 newEdge proc near ; CODE XREF: main+38↓p .text:0000000000401560 .text:0000000000401560 e = Edge ptr -20h ;可以识别出结构体e .text:0000000000401560 arg_0 = qword ptr 10h ;蜜汁操作,多一个arg_0参数 .text:0000000000401560 u = dword ptr 18h ;由于带调试信息,ida认识四个参数名 .text:0000000000401560 v = dword ptr 20h .text:0000000000401560 d = qword ptr 28h .text:0000000000401560 f = qword ptr 30h .text:0000000000401560 .text:0000000000401560 push rbp .text:0000000000401561 mov rbp, rsp .text:0000000000401564 sub rsp, 20h .text:0000000000401568 mov [rbp+arg_0], rcx .text:000000000040156C mov [rbp+u], edx .text:000000000040156F mov [rbp+v], r8d .text:0000000000401573 mov [rbp+d], r9 .text:0000000000401577 mov eax, [rbp+u] .text:000000000040157A mov [rbp+e.from], eax .text:000000000040157D mov eax, [rbp+v] .text:0000000000401580 mov [rbp+e.to], eax .text:0000000000401583 mov rax, [rbp+d] .text:0000000000401587 mov [rbp+e.dist], rax .text:000000000040158B mov rax, [rbp+f] .text:000000000040158F mov [rbp+e.flow], rax .text:0000000000401593 mov rcx, [rbp+arg_0] .text:0000000000401597 mov rax, qword ptr [rbp+e.from] .text:000000000040159B mov rdx, [rbp+e.dist] .text:000000000040159F mov [rcx], rax .text:00000000004015A2 mov [rcx+8], rdx .text:00000000004015A6 mov rax, [rbp+e.flow] .text:00000000004015AA mov [rcx+10h], rax .text:00000000004015AE mov rax, [rbp+arg_0] .text:00000000004015B2 add rsp, 20h .text:00000000004015B6 pop rbp .text:00000000004015B7 retn .text:00000000004015B7 newEdge endp
不带调试信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct { int from; int to; long long dist; long long flow; }Edge; Edge newEdge (int u,int v,long long d,long long f) { Edge e; e.from=u; e.to=v; e.dist=d; e.flow=f; return e; } int main () { Edge e=newEdge(1 ,2 ,1e12 ,1e13 ); }
main
函数反汇编视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 .text:00000000004015B8 ; int __cdecl main(int argc, const char **argv, const char **envp) .text:00000000004015B8 public main .text:00000000004015B8 main proc near ; CODE XREF: __tmainCRTStartup+22F↑p .text:00000000004015B8 .text:00000000004015B8 var_30 = qword ptr -30h .text:00000000004015B8 var_20 = byte ptr -20h .text:00000000004015B8 .text:00000000004015B8 push rbp .text:00000000004015B9 mov rbp, rsp .text:00000000004015BC sub rsp, 50h .text:00000000004015C0 call __main .text:00000000004015C5 lea rax, [rbp+var_20] ;&var_20->rax .text:00000000004015C9 mov rdx, 9184E72A000h ;1e13->rdx .text:00000000004015D3 mov [rsp+50h+var_30], rdx ;1e13->rdx->*(var_30+50h) .text:00000000004015D8 mov r9, 0E8D4A51000h ;1e12->r9 .text:00000000004015E2 mov r8d, 2 ;2放在r8 .text:00000000004015E8 mov edx, 1 ;1放在rdx .text:00000000004015ED mov rcx, rax ;rax是var_20的栈地址 .text:00000000004015F0 call newEdge .text:00000000004015F5 mov eax, 0 .text:00000000004015FA add rsp, 50h .text:00000000004015FE pop rbp .text:00000000004015FF retn .text:00000000004015FF main endp