汇编和可执行文件
程序是从源代码通过编译器转化为机器码(汇编,机器只能识别汇编)
java 不是直接编译成汇编,是在执行的时候一步一步的编译成其他语言。
从源代码最终到达的是一个可执行文件,可执行文件和汇编是不同的,当代码进行编译后会按照代码的规则生成与之一一对应的汇编代码,但是并不是可执行的,还需要链接(link).
link 在写代码的时候需要使用各种API,大部分API并不是我们自己写与之对应的汇编代码。
printf("I love mark"); 执行这句话要于显卡交互( 电脑显示器看到的东西都是从通过cpu命令显卡在内存画幅图,展示在显示器上)每一个操作系统有不同的地方,要使用操作系统,就必须与其进行link.
在任何平台上编译首先会产生一个obj文件(程序产生的汇编代码,没有什么含义,不能被执行,不符合操作系统的要求。)经过 link 生成与平台对应的一些文件的格式。
link 相当于加工的过程,windows平台上是加上PE结构。
源代码到编译 成OBJ文件,根据不同的平台link 变成了可执行的文件。
编译 :
把 c/c++代码 翻译成与之对应的 汇编代码 里面的是一一对应的。 如果程序员写了一段废代码,编译器会进行优化,并不会产生汇编代码。在debug下生成的代码和在release时生成的代码是不一样的,因为编译器会进行优化。
编译器就是把复杂逻辑分解成简单逻辑
代码到汇编当中可能就只有 赋值语句 跳转语句 计算语句
汇编代码
最简单的c语言代码
int main()
{
return 0;
}
debug反汇编
int main()
{
00F11650 push ebp 寄存器的名称 栈寄存器
00F11651 mov ebp,esp
00F11653 sub esp,0C0h
00F11659 push ebx
00F1165A push esi
00F1165B push edi
00F1165C lea edi,[ebp-0C0h]
00F11662 mov ecx,30h
00F11667 mov eax,0CCCCCCCCh
00F1166C rep stos dword ptr es:[edi]
return 0;
00F1166E xor eax,eax
}
release反汇编
return 0;
00D01000 xor eax,eax
调试的时候尽量用debug,会对每句代码生成对应的汇编
release会对代码进行优化 发布版 快速 精悍
内存划分
栈 堆 代码区 常量区
更加安全 cpu 内存 都是0101的数据,一门能操作内存的语言,c,c++ ,指针指向代码区并修改了,程序就会崩溃。分区后便于把代码放在一个地方进行保护 代码区(只读不写),常量区也是只读的。
栈区 (1024kb)
栈溢出: 数组超过了1024kb的大小,放在堆上解决。
栈内存储临时变量,临时变量占用一定的内存,每次都要释放。如果每次都分配一块空间, 记录下来,释放的时候再去擦除,会浪费很多性能。因此直接分配了一块内存,就是栈,不进行释放 在栈底部定义一个游标,(ebp寄存器记录),每一次使用临时变量的时候占用一块的内存空间,再用另一个游标(esp)来指示使用的最高点。当使用完成后,就将最高点的游标和最底层的游标想重合(就相当于临时变量被释放了)。所有栈没有被清空,只是反复的使用了。再存入数据时,esp游标上移,将一块空间的值改成要存入的数据。
栈中通过push操作,把先插入的数据放在底部,后插入的放顶部。
栈中不仅存储临时变量,还有跳转后返回地址。
调用函数
例如
printf("I love mark");
printf("I love mark");
程序按顺序执行,最开始通过程序计数器,当遇到call_printf 会跳到另一块去执行printf,执行完后跳回去,这时需要使用栈。
在栈存储了一些东西,这时碰到了call,会把当前的地址放到栈里,esp往上走,再ebp移到同一地方,相当于栈底发生了改变,在改变之前得记录原栈顶,之后会还原。 call执行完后,esp移回来。
push
在esp上添加数据,再把esp上移
pop
根据esp删除删除,esp下移
实例 :栈如何运行
int main()
{
(断点)printf("I love mark");
printf("I love mark");
return 0;
}
转到反汇编
printf("I Love Mark!!!");
009117AE push offset string "I Love Mark!!!" (0916B30h)
009117B3 call _printf (0911316h)
009117B8 add esp,4
printf("I Love Mark too!!!");
009117BB push offset string "I Love Mark too!!!" (0916B44h)
009117C0 call _printf (0911316h)
009117C5 add esp,4
return 0;
寄存器
通用寄存器
EAX = CCCCCCCC
EBX = 7FB7E000
ECX = 00000000
EDX = 00919578
ESI = 00911046
EDI = 00C1FC28
EIP = 009117AE
ESP = 00C1FB5C 栈顶
EBP = 00C1FC28 栈底
EFL = 00000202
(F11)执行下一句时:
寄存器
EIP = 009117B3 指向了下一条语句,说明执行到了这里(009117B3 call _printf (0911316h) )
ESP = 00C1FB58 往上面走了一位 (0x00C1FB58 30 6b 91 00 反着放的 offset string "I Love Mark!!!" (0C36B30h) 地址 )
内存
0x00C1FB58 30 6b 91 00
0x00C1FB5C 46 10 91 00
0x00C1FB60 46 10 91 00
0x00C1FB64 00 e0 b7 7f .
继续往下(F11)指向 esp 继续发生改变
EIP = 00911316 (00911316 jmp printf (0911800h))
ESP = 00C1FB54 发生了改变,说明在之前往后执行了一位,又往栈压入了东西
内存内容
0x00C1FB54 b8 17 91 00 ?.?.
0x00C1FB58 30 6b 91 00 0k?.
0x00C1FB5C 46 10 91 00 F.?.
0x00C1FB60 46 10 91 00 F.?.
0x00C1FB64 00 e0 b7 7f
继续执行(F11)
保存ebp,为了还原
00911800 push ebp EIP = 00911801 ESP = 00C1FB50
内存内容
0x00C1FB50 28 fc c1 00 ebp 放进了内存 EBP = 00C1FC28
0x00C1FB54 b8 17 91 00 .
0x00C1FB58 30 6b 91 00
0x00C1FB5C 46 10 91 00
0x00C1FB60 46 10 91 00
0x00C1FB64 00 e0 b7 7f
00911801 mov ebp,esp ESP = 00C1FB50 EBP =00C1FB50
内存内容
0x00C1FB50 28 fc c1 00 (??.
0x00C1FB54 b8 17 91 00 ?.?.
0x00C1FB58 30 6b 91 00 0k?.
0x00C1FB5C 46 10 91 00 F.?.
0x00C1FB60 46 10 91 00 F.?.
0x00C1FB64 00 e0 b7 7f
00911803 sub esp,0D8h
继续执行
0091185D pop edi 开始pop edi发生改变
0091185E pop esi esi 发生改变
0091185F pop ebx ebx 发生改变
00911860 add esp,0D8h 加上后,栈顶会往下掉 和改变后的栈底持平
ESP = 00C1FB50 EBP =00C1FB50
0x00C1FB50 28 fc c1 00 原来存储的ebp的值
0x00C1FB54 b8 17 91 00 函数的返回地址
0x00C1FB58 30 6b 91 00 . 传进来的参数
0x00C1FB5C 46 10 91 00 .
0x00C1FB60 46 10 91 00 .
0x00C1FB64 00 e0 b7 7f
00911866 cmp ebp,esp
0x00C1FB4C00911868 call __RTC_CheckEsp (091110Eh)
0091186D mov esp,ebp ebp的值给esp 还原到了没有push ebp的状态
0091186F pop ebp 还原到了call的状态
00911870 ret
009117B3 call _printf (0911316h)
009117B8 add esp,4 esp再下移一位 ,此时全部还原 ESP = 00C1FB5C EBP = 00C1FC28
0x00C1FB4C 6d 18 91 00
0x00C1FB50 28 fc c1 00
0x00C1FB54 b8 17 91 00 .
0x00C1FB58 30 6b 91 00 上面的值没清空 下次使用时直接修改
0x00C1FB5C 46 10 91 00 esp
0x00C1FB60 46 10 91 00
0x00C1FB64 00 e0 b7 7f
栈内函数调用的过程:
栈顶 esp 开辟空间 后 ,向里面存储数据 。同时保存原来的栈底ebp(pop)。之后ebp上移,和esp相等。继续执行,esp发生改变,执行完后,调到已经改变的ebp。
根据之前保存的值,将改变的ebp还原(pop),再使esp下移到ebp。 还原后esp上的值依然存在,没有进行释放。下次使用时直接修改即可。