dustland

dustball in dustland

数据结构在汇编语言下的表现

数据结构在汇编语言下的表现

数组

栈上数组

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 #需要将ida的根目录添加到环境变量path

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_arraydb=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)

而实际上l20x407970,刚才指针运算得到的值是属于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;//注意此处给aint赋值10,小端模式下应该存放在&aint的第一个字节单元
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.from0x7fffffffdc84是可以整除4的,满足int的对齐规则

此时sizeof(e)=12,而不是1+4+4=9

整体对齐要求结构体的起始地址必须是最大成员大小的整数倍,在这里是int,4字节

因此结构体e的起始地址应当是能够整除4的并且距离rbp最近的并且能够放得下结构体e的地方,显然是0x7fffffffdc80

整体对齐只是考虑了最大成员大小的整数倍,一定能保证所有成员的对齐吗?

由于任意基本数据类型的大小都是2的幂次,因此最大成员的大小一定是任意成员大小的整数倍,那么起始地址是最大成员大小的整数倍的同时,也一定是任意成员大小的整数倍

#pragma pack(n)指定对齐

如果不写该语句则默认n=8字节对齐,所有对象的对齐方式改为:

地址值是\(min\{自己大小,n\}的整数倍\)

同样的程序在一开始时加入

1
#pragma pack(1)

指定一字节对齐,任意对象的地址是\(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
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