zoukankan      html  css  js  c++  java
  • 汇编3栈帧,参数传递,串操作,混合汇编,x64,asm文件

    基础知识2

    1. 选择结构

      1. 通过判断 + 条件跳转指令来实现

    2. 循环结构

      1. 通过判断 + 条件跳转指令来实现(会有一个向上跳转的语句)

    1. 函数调用约定

      1. C调用约定: 由外部平衡栈

      2. 标准调用约定 : 由函数内部平衡栈

      3. 对象调用约定 : 由函数内部平衡栈, 寄存器用于保存对象的首地址(就是this指针)

      4. 快速调用约定 : 由函数内部平衡栈(传参都是从右往左传递.), 用到 ecx , edx 来依次传递前两个参数.

    1. 通过 call 指令, call指令的原理是: 将call指令的下一条指令的地址压入栈中, 然后再进行跳转.

    2. 通过 ret 指令, 来结束函数的调用,回到调用点, 原理: 将保存在栈中的返回地址 pop 到 eip .

    3. 需要在函数内部访问形参的方式:

      1. 通过 esp 来访问, 缺点: esp寄存器是受到一些栈操作指令被改变的(例如:push,call,ret) , 当它被改变之后, 就不能通过一个固定的偏移来定位栈里面的参数了.

      2. 通过 ebp 来访问的 , 原理: 进入到函数内部时, 将esp的值保存到ebp, 然后ebp是不会受到栈操作指令的映 像, 可以使用一个固定的偏移来定位栈里面的参数.

      3. 函数编写的要求:

        1. 保证在函数内部不会修改寄存器的值, 如果要修改,需要保证离开函数之后, 将修改的寄存器的值恢复 回去.

        2. 保证离开函数之后, 栈是平衡的(进入到函数时,栈顶esp指向了哪里, 那么在离开函数之后,esp也必须还 指向了那里),ebp也一样.

      4. 在函数内部如果要使用局部变量.

        1. 打开函数栈帧通过 push ebp; mov ebp,esp;

        2. 开辟局部变量占用的栈空间:

        3. 通过 ebp-4 来定位在栈中的局部变量: ebp-4 是局部变量1, ebp-8 是局部变量2

    int fun( )
    {  
       int nArr[]={1,2,3,4,5};  
       printf("%d",nArr[0]);
    }

     

    fun proc
    push ebp     ;            
    ; 打开栈帧
    mov ebp , esp;/
    sub esp , 20 ; 开辟局部变量的栈空间
    mov [ebp-4] , 1; 初始化局部变量
    mov [ebp-8] , 2; 初始化局部变量
    mov [ebp-0ch] , 3; 初始化局部变量
    mov [ebp-010h] , 4; 初始化局部变量
    mov [ebp-014h] , 5; 初始化局部变量
    mov esp , ebp; 恢复局部变量占用的栈空间
    pop ebp      ; 恢复栈帧 ret fun endp

     

    5.字符串的汇编

    char* pStr = "abcdefg";
    dword ptr [ebp-18h],0BA7B40h
    //dword ptr [pStr],offset string "abcdefg" (0BA7B40h)  

    在内存输入00BA7B40

     

    6.指针

    //函数
    void fun2(char* p) {
     p = "123456";
    }
    //调用时
    fun2(pStr);
    00BA19EC 8B 45 E8             mov         eax,dword ptr [pStr]  
    00BA19EF 50                   push        eax  
    00BA19F0 E8 4F F8 FF FF       call        fun2 (0BA1244h)  
    00BA19F5 83 C4 04             add         esp,4  

    //进函数 p = "123456";
    00BA17D8 mov dword ptr [ebp+8],0BA7B30h      
    //00BA17D8 mov dword ptr [p],offset string "123456" (0BA7B30h)  

    7.二级指针

    //函数
    void fun3(char** pp, char*& pr) {
     *pp = "123456";
     pr = "456789";
    }
    //调用时
    fun3(&pStr, pStr);
    00BA19F8 8D 45 E8             lea         eax,[pStr]  
    00BA19FB 50                   push        eax  
    00BA19FC 8D 4D E8             lea         ecx,[pStr]  
    00BA19FF 51                   push        ecx  
    00BA1A00 E8 02 F9 FF FF       call        fun3 (0BA1307h)  
    00BA1A05 83 C4 08             add         esp,8  
    //进函数
     *pp = "123456";
    00BA1838 8B mov eax,dword ptr [ebp+8]  
    //00BA1838 8B mov eax,dword ptr [pp]
    00BA183B C7    mov         dword ptr [eax],0BA7B30h  
    //00BA183B mov dword ptr [eax],offset string "123456" (0BA7B30h)
     pr = "456789";
    00BA1841 mov eax,dword ptr [ebp+0Ch]  
    //00BA1841 8B mov eax,dword ptr [pr]  
    00BA1844 C7    mov         dword ptr [eax],0BA7B38h  
    00BA1844 C7 mov dword ptr [eax],offset string "456789" (0BA7B38h)

    8.指针 引用 值传递

    //函数
    void fun2(int n, int* p, int& r) {
     n = 10;
     *p = 10;
     r = 10;
    }
    //调用时,先传最右边的
    fun2(n, &n, n);
    //int&
    00BA1A0F 8D 45 DC             lea         eax,[ebp-24h]  
    00BA1A12 50                   push        eax  
    //int*
    00BA1A13 8D 4D DC             lea         ecx,[ebp-24h]  
    00BA1A16 51                   push        ecx  
    //int
    00BA1A17 8B 55 DC             mov         edx,dword ptr [ebp-24h]  
    00BA1A1A 52                   push        edx  
    //进入函数
    00BA1A1B E8 29 F8 FF FF       call        00BA1249  
    //C调用
    00BA1A20 83 C4 0C             add         esp,0Ch  

    //进函数
    n = 10;
    00BA1768 mov dword ptr [ebp+8],0Ah
    //00BA1768 mov dword ptr [n],0Ah
     *p = 10;
    00BA176F mov eax,dword ptr [ebp+0Ch]  
    00BA1772 C7 mov dword ptr [eax],0Ah  
    //00BA176F mov eax,dword ptr [p]  
    //00BA1772 mov dword ptr [eax],0Ah
     r = 10;
    00BA1778 mov eax,dword ptr [ebp+10h]  
    00BA177B mov dword ptr [eax],0Ah  
    //00BA1778 mov eax,dword ptr [r]  
    //00BA1778 mov dword ptr [eax],0Ah

    9.强转

    char* p = (char*)&n;
    *p = 0;
    *(int*)p = 0;
    p = (char*)0;


    char* p = (char*)&n;
    lea         eax,[n]  
    mov         dword ptr [p],eax  

     *p = 0;
    mov         eax,dword ptr [p]  
    mov         byte ptr [eax],0  
     *(int*)p = 0;
    mov         eax,dword ptr [p]  
    mov         dword ptr [eax],0  
     p = (char*)0;
    mov         dword ptr [p],0  

     

     

    串操作指令

     

    • rep 重复操作前缀

      • 默认将重复的次数保存在ecx中. 每重复一次,就递减ecx的值. 当ecx的值等于0时,就不再重复.

      • 不能单独使用 , 也不能和任意的指令搭配使用,只能和串操作指令结合使用,例如:

        mov ecx,10 
        rep inc eax; // 错误
    • stos

      • 默认操作数是 edi 和 al/ax/eax ,

      • Q功能 : 将 al/ax/eax 的值填充到edi指向的内存中. 填充完之后, 会自增/自减(取决于 DF 方向标志) edi 的值 , edi的值在自增的时候有(1/2/4)的可能,实际增加多少,取决于指令的后缀, 它的后缀 stosb , stosw,

      • 和 rep 指令结合使用之后,功能类似于 memset

     

     

    • lods

      • 默认操作数: esi 和 eax

      • 功能: 将esi指向的内存的数据保存到eax中.

    • movs

      • 默认操作数 edi ,

      • 功能 : 将esi指向的内存的数据保存到 edi 中 和 rep 结合之后, 功能类似 memcpy

         

    • cmps

      • 默认操作数是edi,esi

      • 功能: 将esi和edi指向的内存进行比较, 将比较的结果设置到eflags寄存器中. 和 repe 结合,功能类似 memcmp

         

         

    • scas

      • 默认操作数:

      • 功能 : 使用edi指向的字节和eax进行比较, 将比较的结果设置到标志寄存器中 和 repne 结合,功能类似 strlen

    int main(){
    char buff[100];
    char buff2[] = "hello world";
    // 1. 将buff全部填充为0(memset(buff,0,100))
    _asm
    {
    lea edi,[buff]; mov ecx,100; mov al ,0;
    rep stosb;
    }
    // 2. 将buff2的内容拷贝到buff中(memcpy(buff,buu2,11)
    _asm {
    lea esi, [buff2]; lea edi, [buff]; mov ecx, 11;
    rep movsb;
    }

    // 3. 比较buff2和buff的内存是否一样(memcmp(buff2,buff)) int nCmpFlag = 0;
    _asm
    {
    lea esi, [buff2]; lea edi, [buff]; mov ecx, 11;
    repe cmpsb;
    mov [nCmpFlag ],ecx;
    }
    if (nCmpFlag == 0) { printf("内存相等 ");
    }
    else {
    printf("内存不相等 ");
    }

    // 4. 计算buff2字符串长度(strlen(buff2))
     _asm
    {
     lea edi,[buff2];
     mov al,0;
     mov ecx,0xFFFFFFFF;
     repne scasb;
     not ecx;  //按位取反得到原码
     dec ecx;  // ecx存的是补码,-1获取反码
    }

     

     

     

     

    混合编程

    c和汇编一起出现在同一个源文件中

    内嵌汇编

    1. 通过 _asm 关键字来实现

    2. 单行内联汇编

      int main() 
      {  
         int n;
         _asm mov [n] , 10
      }

       

     

    1. 块状内联汇编

    int main() 
    {  
       _asm   {
           mov eax,100;
           add eax, 20;
      }
    }

     

    1. 在内联汇编中定义一个字节的机器码

      int main()
      {
         _asm    
        {  
             jmp _FLAG  
                 _emit 0xe9; 直接在此处定义1个字节的机器码,
            ;初始值是0xe9         _FLAG:  
        }
      }

       

     

    使用asm文件

    在64位程序中,不能使用 _asm 关键字来使用内联汇编了.

    1. 给项目添加一个宏汇编编译器.

    1. 添加一个.asm的文件

    2. 指定这个asm的文件使用汇编编译器来编译(否则默认是不编译的)

       

    1. 在asm中定义汇编代码及结构体

      .model flat,stdcall

      .data

      ; 声明结构体的原型
      MyStruct struct
      cChar db ?
      nNum dd ?
      MyStruct ends


      .code

      ;
      ; 如果在汇编的函数声明中加上了
      ; 参数的声明,汇编编译器会自动
      ; 加上打开栈帧的代码.
      fun proc obj:DWORD; fun(MyStruct* obj)
      ;push ebp
      ;mov ebp ,esp
      ;sub esp , 0
      local obj2:MyStruct; 在函数内部定义局部变量
      ; 访问局部结构体变量的字段
      lea eax,[obj2];
      mov [eax+MyStruct.nNum],0ah
      mov byte ptr [eax+MyStruct.cChar],041h

      ; [ebp+8] => MyStruct* obj
      mov eax,[ebp+8];
      mov [eax+MyStruct.nNum],0ah
      mov byte ptr [eax+MyStruct.cChar],041h

      ;mov esp , ebp
      ;pop ebp
      ret 4
      fun endp

      end

       

    1. 在源文件中调用汇编代码.

    2. 声明函数原型(注意要使用 extern"C"

      extern "C" void __stdcall fun(MyStruct* obj);

       

    3. 直接调用函数即可.

    裸函数

    在函数名中加上关键字 _declspec(naked)

     void _declspec(naked) fun()
    {
        _asm ret;
    }

     

    1. 编译器不会在裸函数的内部生成额外的代码. 写了多少就有多少.

    2. 如果裸函数有形参, 那么需要在函数内部自己编写汇编来打开栈帧.

    3. 如果裸函数有局部变量

    4. 局部变量不能赋初始值

    5. 还需自己编写汇编代码开辟局部变量所占的栈空间

    // 裸函数 
    int _declspec(naked) fun(int n)
    {
       _asm
      {  
           push ebp;  
           mov ebp,esp;  
           sub esp,400  
      }
     int m;
       m = 200; //对应汇编 mov dword ptr [ebp-8],0C8h
       n = 100; //对应汇编 mov dword ptr [ebp+8],64h  
     _asm {
         mov eax, n;
         mov esp,ebp;
         pop ebp;  ret
    }
    }

     

    x64汇编

    函数传参方式: 头4个参数依次使用 rcx,rdx,r8,r9 来传递, 第4个之后的参数使用栈来传递(从右往左) , 栈平衡着是函 数内部.

    反汇编引擎和汇编引擎

     

    反汇编引擎 - 能够将一些机器码翻译成汇编代码. 汇编引擎 - 能够将汇编代码翻译器成机器码.

    关于 RadAsm 的坑

    • RadAsm 中十六进制不能以 0x 开头,需要在末尾添加 h

      正确示范:mov eax, [100h]

      但是执行完在OD里面是 mov eax,100h

      想要实现下面的语义,

      mov eax, dword ptr [0x100]

      需要

      mov eax, DS:[100h]

    • lea eax, [100h] 想实现上面的功能需要写这个代码 mov eax, 100h lea eax, [eax]

    • 栈是什么?栈帧是什么?

      • 线程最少由一个线程内核对象和一个栈组成?

      • 栈: 是 ss 起始的一块特殊的内存空间

      • (栈)帧: 栈帧是栈中的一块区域,栈帧 以函数为单位

    • 栈的操作

      • 通常栈在调试器的增长方向是自上而下

      • 调试器中,栈视图的最上方指向的是 esp

    • 对栈的操作

      • 入栈 push(af) esp-n

      • 出栈 pop(af) esp+4

    • call 和 ret

      • call = push 下一条指令 + jmp 目标地址

      • ret = mov eip, [esp], add esp, 4 + n*(4)

  • 相关阅读:
    算法视频库下载常用网址(转载)
    Python study 1
    $X-Real-Ip和$X-Forwarded-For的区别
    python装饰器
    python迭代器和生成器
    python函数动态参数详解
    python常用模块
    python 正则re模块
    pycharm5新版注册
    老男孩python自动化运维作业2
  • 原文地址:https://www.cnblogs.com/ltyandy/p/11054431.html
Copyright © 2011-2022 走看看