程序基础

  • 三大结构
    • 顺序结构
    • 选择结构
    • 循环结构
  • 函数

选择结构

  1. if … else if … else
  1. int nDay = 0;
  2. scanf("%d", &nDay);
  3. if( nDay == 1){
  4. printf("星期一");
  5. }else if(nDay==2){
  6. printf("星期二");
  7. }else if(nDay==3){
  8. printf("星期三");
  9. }

汇编版本:

  1. fun proc
  2. push ebp
  3. mov ebp,esp
  4. sub esp , 4 ; 为局部变量分配栈空间.
  5. ; ebp - 4 ==> nDay的位置
  6. lea eax , [ebp-4]; eax= &nDay
  7. push eax
  8. push "%d"
  9. call crt_scanf ; scanf("%d",&nDay)
  10. cmp dword ptr [ebp-4] , 1
  11. ; if(nDay == 1)
  12. jne _IF_1
  13. push "星期一"
  14. call crt_printf
  15. add esp , 4
  16. jmp _ENDIF
  17. _IF_1:
  18. cmp dword ptr [ebp-4] , 2
  19. jne _IF_2
  20. push "星期二"
  21. call crt_printf
  22. add esp , 4
  23. jmp _ENDIF
  24. _IF_2:
  25. cmp dword ptr [ebp-4] , 3
  26. jne _IF_3
  27. push "星期三"
  28. call crt_printf
  29. add esp , 4
  30. jmp _ENDIF
  31. _IF_3:
  32. _ENDIF:
  33. mov esp, ebp
  34. pop ebp
  35. fun endp

总结

  1. 在C语言中, 使用{}来分隔每个语句块
  2. 在汇编指令是自上而下逐条执行, 没有跳转指令是不会跑到其它位置执行代码的.
  3. 在汇编构建一个语句块的方式是:

    1. 在语句块前加入一条条件跳转指令
    2. 在语句块后加入一条无条件跳转指令
      1. _IF_2:
      2. cmp dword ptr [ebp-4] , 3 ;语句块
      3. jne _IF_3 ;的开始
      4. push "星期三"
      5. call crt_printf
      6. add esp , 4
      7. jmp _ENDIF ;语句块的结束
  4. switch … case ```c int nDay=0; scanf(“%d”, &nDay);

switch( nDay ){ case 1: printf(“星期一”);break; case 2: printf(“星期二”);break; case 3: printf(“星期三”);break; }

  1. <br />汇编版本:
  2. 1. 和if的版本一样.
  3. 2. 使用跳转表
  4. ```assembly
  5. .386
  6. .model flat,stdcall
  7. option casemap:none
  8. include msvcrt.inc
  9. includelib msvcrt.lib
  10. .const
  11. pszD db "%d",0
  12. pszTips1 db "星期一",0dh,0ah,0
  13. pszTips2 db "星期二",0dh,0ah,0
  14. pszTips3 db "星期三",0dh,0ah,0
  15. .code
  16. fun proc
  17. push ebp
  18. mov ebp,esp
  19. sub esp , 4 ; 为局部变量分配栈空间.
  20. ; ebp - 4 ==> nDay的位置
  21. lea eax , [ebp-4]; eax= &nDay
  22. push eax
  23. push offset pszD
  24. call crt_scanf ; scanf("%d",&nDay)
  25. cmp dword ptr [ebp-4] , 3
  26. jg _ENDIF
  27. ; nday-=1
  28. ;jmp jmptab[nDay];
  29. mov eax , [ebp-4]; eax = nDay
  30. dec eax ; eax = nDay - 1
  31. ; 通过使用输入的值作为下标
  32. ; 从数组中索引出一个地址. 然后再跳转
  33. ; 到此地址上.
  34. jmp dword ptr [jmptab + eax * 4]
  35. ; 在代码中定义了数据.
  36. ; DwORD jmptab[3]={ _IF_0,_IF_1,_IF_2};
  37. ; 编译之后, _IF_0标签就会编译成代码的地址
  38. jmptab dd _IF_0,_IF_1,_IF_2
  39. _IF_0:
  40. ; if(nDay == 1)``
  41. push offset pszTips1
  42. call crt_printf
  43. add esp , 4
  44. jmp _ENDIF
  45. _IF_1:
  46. push offset pszTips2
  47. call crt_printf
  48. add esp , 4
  49. jmp _ENDIF
  50. _IF_2:
  51. push offset pszTips3
  52. call crt_printf
  53. add esp , 4
  54. jmp _ENDIF
  55. _ENDIF:
  56. mov esp, ebp
  57. pop ebp
  58. ret
  59. fun endp
  60. main:
  61. call fun
  62. ret
  63. end main

循环结构

  1. while
    1. int i=0;
    2. while( i < 100) {
    3. printf("%d\n",i);
    4. ++i;
    5. }


汇编版本:
通过条件跳转指令来模拟

  1. fun proc
  2. ; 因为没有形参, 所以可以不需要开辟栈帧.
  3. ; 直接使用esp来索引局部变量.
  4. sub esp , 4; 局部变量位置 => [esp]
  5. mov [esp], 0 ; i=0;
  6. ;while( i < 100) {
  7. _WHILE:
  8. cmp dword ptr[esp] , 100
  9. jg _ENDWHILE ; 如果大于就结束循环
  10. ;printf("%d\n",i);
  11. push dword ptr[esp]
  12. push "%d\n"
  13. call crt_printf
  14. add esp , 8
  15. ;++i
  16. inc dword ptr [esp]
  17. jmp _WHILE
  18. _ENDWHILE:
  19. add esp , 4
  20. ret
  21. fun endp;
  1. do…while

    1. int i=0;
    2. do {
    3. printf("%d\n",i);
    4. ++i;
    5. }while( i < 100) ;
    1. fun_dowhile proc
    2. sub esp , 4 ; int i;
    3. mov dword ptr [esp] , 0
    4. _DOWHILE:
    5. push dword ptr [esp];
    6. push "%d\n"
    7. call crt_printf
    8. add esp , 8
    9. inc dword ptr [esp];
    10. cmp dword ptr [esp] , 100
    11. jl _DOWHILE
    12. fun_dowhile endp
  2. for

    1. for( int i = 0; i<100;++i){
    2. printf("%d\n",i);
    3. }


for循环的结构和while循环一样.

  1. fun proc
  2. ; 因为没有形参, 所以可以不需要开辟栈帧.
  3. ; 直接使用esp来索引局部变量.
  4. sub esp , 4; 局部变量位置 => [esp]
  5. mov [esp], 0 ; i=0;
  6. ;while( i < 100) {
  7. _WHILE:
  8. cmp dword ptr[esp] , 100
  9. jg _ENDWHILE ; 如果大于就结束循环
  10. ;printf("%d\n",i);
  11. push dword ptr[esp]
  12. push "%d\n"
  13. call crt_printf
  14. add esp , 8
  15. ;++i
  16. inc dword ptr [esp]
  17. jmp _WHILE
  18. _ENDWHILE:
  19. add esp , 4
  20. ret
  21. fun endp;

函数

调用约定

调用预定 传参方式 平衡栈方式
cdecl(C调用约定) 从右往左 在函数外平衡(调用者平衡),通过add esp,xx的方式平衡
stdcall标准调用约定 从右往左 在函数内平衡(被调用者平衡),通过ret xx的方式平衡.
fastcall(快速调用约定) 前两个参依次使用ecx,edx来传递, 后续函数通过栈传递(从右往左依次入栈) 在函数内平衡(被调用者平衡),通过ret xx的方式平衡.
thiscall(对象调用约定) 通过ecx来保存this指针.参数从右往左依次入栈 在函数内平衡(被调用者平衡),通过ret xx的方式平衡.

总结

  1. 调用约定了制约在代码中传参的方式和平衡栈的方式
  2. 在汇编中调用函数前需要搞清楚, 这个函数使用什么调用约定, 否则参数就无法正确传递.

函数调用总结

  1. 参数是通过栈来传递的. 有时候是直接push到栈中, 有时候也可以先sub esp拉高栈顶, 再使用mov [esp]的方式将实参赋值到栈中, 总之能够将实参放入到栈中合适的位置就可以了.
    • esp栈顶高地址的位置一般都是被使用过的, 不应该随意修改. 而esp低地址的位置一般是没有被使用.可以用来保存新的数据.
  2. 函数调用完毕之后, 需要保证栈的平衡.
  3. 函数的返回值一般是约定使用eax来保存.

函数栈帧

  1. 实参通过栈传递之后, 在函数内部怎么定位? ```assembly ; 定义一个函数 ; void fun(int n1, int n2, int n3) fun proc push ebp ; 因为要使用ebp来保存当前栈顶的值, 因此需要先将寄存器的值备份起来. mov ebp, esp; 将栈顶位置保存到ebp中, 这样即使栈顶发生了浮动, 也不会影响通过ebp+固定偏移的方式去定位到栈内的形参了.

    ; 离开函数前, 需要恢复在函数内部中使用寄存器的值. ; 恢复esp和ebp的值. mov esp , ebp; 因为在函数开头,将esp的值保存到了ebp中, 而ebp在整个函数中是不会被修改的, 所以,esp的值就可以使用ebp来恢复. pop ebp ; 因为在函数开头, ebp的值被push中保存了, 因此, 可以直接从栈中pop出来恢复ebp的值. ret 0ch fun endp

push 3 push 2 push 1 call fun ```


1573002520916.png

使用局部变量

  1. 在打开栈帧之后, 需要给局部变量开辟栈空间
    1. 通过sub esp, 所有局部变量的字节数
    2. esp低地址的位置一般是没有被使用.可以用来保存新的数据, 所以, 没有将栈顶往上抬, 栈空间内的数据就可能被push或者call指令的操作所覆盖
  2. 通过 ebp-4得到第一个局部变量的偏移

易失性寄存器

一般进入函数之后, 函数内部使用寄存器前需要备份寄存器的值, 离开函数时, 会将寄存器的值恢复回去, 但有些寄存器不会被恢复, 这些不会被恢复的寄存器被成为易失性寄存器

  • windows的API的易失性寄存器是eax
  • C运行时库函数(crt_xxx系列函数)的易失性寄存器是eax,ecx,edx,ebx

64位汇编

内联汇编与裸函数

项目

day003.zip

X64混合编程.docx