64位BASM学习随笔(一)

来源:互联网 发布:手机淘宝店招怎么设置 编辑:程序博客网 时间:2024/06/05 15:01


64位BASM学习随笔(一)

    Delphi的BASM一直是我最喜爱的内嵌汇编语言,同C/C++的内联汇编相比,它更方便,更具灵活性,因为C/C++的内联汇编只能是或插入式的汇编代码,函数花括号背后隐藏的函数框架,限制了汇编代码的发挥,如无论有无参数和局部变量,总是有个栈框架,更烦人的是只要你在函数中使用了esi,edi,ebx寄存器,就自动给你保存和恢复,使得这些寄存器没法在函数之间传递信息等;而Delphi的BASM可以是插入式的汇编代码,也可是完全的汇编方法,在完全的汇编方法下,怎么发挥就是自己的事了。
    Delphi XE2后就可以编写64位应用程序了,但我一直没时间试一下64位的BASM代码有无变化,春节前后抽时间研究了一下,发现64位的BASM变化还是很大的,不仅仅是简单的寄存器位数更长的问题,而是整个BASM方法的框架(不只是BASM,而是整个64位计算机应用程序框架)都发生了根本性的变化,从编写程序代码的角度而言,这种变化似乎比以前16位程序向32位程序进阶时更大。
    一、64位BASM不支持插入汇编代码,只能写纯BASM方法。如下面的代码是错误的:

function Test(v: Integer): Integer;
var
  i: Integer;
begin
  i := v * v;
  asm
    mov   eax, i
  end;
end;

    只能这样写:

function Test(v: Integer): Integer;
asm
  mov   eax, v
  imul  eax, eax
end;

    二、64位BASM只支持一种调用方式,不论你标明stdcall,pascal,cdecl与否,调用方式都是寄存器参数传递,清栈由调用方法负责(与cdecl类似)。这种变化不只是BASM,好像整个64位程序都是这样,只有一种调用方式。stdcall,pascal,cdecl调用说明只是对32位程序代码的兼容,不会报错。
    三、64位BASM数据类型除指针类型由32位进阶64位外,其它无变化,最常用的Integer、LongWord长度仍然是四字节(前几天据网上传,Delphi XE8的LongWord会改为64位)。
    四、64位寄存器的变化。
    1、寄存器长度增了一倍,由32位进阶为64位,eax,ebx,ecx,edx,esi,edi,ebp,esp等变为rax,rbx,rcx,rdx,rsi,rdi,rbp,rsp,当然e字头的寄存器照样能作32位寄存器使用,同样16位,8位寄存器也可使用。

    这里有一点是要特别注意的,而64位程序中,默认的操作数长度仍然是32位,只有默认地址长度才是64位,这点同16位进阶32位有些不同,操作寄存器的低16位不会不会影响其高16位,而64位下,改变寄存器的低32位,会导致寄存器的高32位清零。如下面代码:

    mov   eax, edx
    shl   rax, 32
    mov   eax, edx

其本意是在rax中形成2个并行的32位数字,结果第三句代码导致rax高32位清零,使用or  eax, edx也是一样会导致高32位清零,只有or  rax, rdx才是正确的(当然要保证rdx的高32位是零)。

    另外,操作64位地址也要注意,虽然64位程序的地址默认是64位,但使用类似[esi+edx]的32位地址操作也不会报错,同样操作结果好像也是正确的,但我认为,应尽量避免这类代码,因为目前看来似乎结果是正确的,主要是因为目前应用程序能操作的数据长度没超过32位,如果以后随着硬件的变化,系统也会发生变化,一旦应用能使用的数据量大于32位,你的代码就有问题了。还有地址的增减也是这样,无论32位还是64位代码,整数长度还是32位,如果增减地址的操作数是32位的,最好转换为64位,除非你能保证其是正数,如下面过程:


function Test(v: Integer): Integer;
asm

    push  rbx
    mov   eax, v
    add   rbx, rax

    .......

    pop   rbx

end;

参数v是32整数,直接用地址rbx去加就很容易出问题,除非你能保证v不为负数。这里可以用cdqe或者使用movsxd  rax, v进行扩展。

    压栈push和出栈pop语句的操作数只能是64位,如push  eax是错误的。
    2、通用寄存器多了r8 - r15等8个寄存器,在BASM方法内,r8 - r11可直接使用,而R12 - R15在使用时同rsi,rdi,rbx一样,应注意保存和恢复。r8 - r15是64位形式,也可表示为32位,16位和8位,如r8,r8d,r8w,r8b分别为64位,32位,16位和8位,而且64位坏境下,rsi, rdi,rbp,rsp也可以用sil,dil,bpl和spl操作低8位。r8 - r15不像rax,rbx,rcx,rdx几个通用寄存器有高低8位寄存器,而且以前通用寄存器的高8位不能和r8 - r15寄存器使用在同一语句中,如mov  ah, r8b; mov  bh, [r8]等都是错误的。
    3、XMM寄存器也多了8个,依次为xmm8 - xmm15,不过,xmm6 - xmm15在使用时应注意保存和恢复(xmm6,xmm7在32位代码中是不需要保护的)。保存SSE寄存器很麻烦,它不能像常规寄存器使用压栈和出栈语句,但BASM中有一个savenv伪指令很方便(我不知道这是BASM独有的,还是其它汇编共有的),如.savenv  xmm7(注意savenv前有个点),Delphi编译器就会在BASM方法中加上保护和恢复xmm7寄存器的语句。
    四、64位BASM方法默认参数传递的变化。无论是32位还是64位BASM方法,默认都使用寄存器传递参数,不同的是32位BASM是前3个参数非浮点数参数使用寄存器传递,从形参左边开始,依次是ecx,edx,ecx,浮点数参数和三个以上非浮点数参数使用栈传递;而64位BASM是前4个参数使用寄存器传递,如果是非浮点数参数,从形参左边开始,依次为rcx,rdx,r8,r9,浮点数则使用xmm0 - xmm3传递。在32位方法中,前3个参数中间夹着浮点数时,寄存器参数会顺延,如方法:

    function Test(v1, v2: Integer; v3: double; v4: Integer): double;

    asm

       fld     v3

    end;

寄存器使用:eax=v1,edx=v2,[ebp+8]=v3,ecx=v4,这里ecx是顺延的。而64位方法中不顺延,不论是否浮点数,寄存器的位置是不改变的,如下面的方法:

    procedure Test(v1, v2: Integer; v3: double; v4, v5: Int64);

    asm

    end;

寄存器使用:ecx=v1,edx=v2,xmm2=v3,r9=v4,[rsp+28h](无框架)或者[rsp+30h](有框架)=v5,如果v3是非浮点数,寄存器应该是r8,这里用xmm2表示浮点数参数,r8寄存器没有顺延,同样v3没有使用xmm0,而是严格按位置参数位置安排xmm2。至于参数v5则是使用栈传递的,至于其栈中偏移位置是28h或30h,而不是8和16的原因后面在专门谈及。

    五、64位BASM返回值的变化。64位的BASM方法的返回值也有些变换,常规的返回值还是eax或rax,最明显的是可以用rax返回64位整数类型,而不必使用edx:eax返回了。浮点数的返回值是变化最大的,如前面32位Test函数代码是使用fld  v3通过80x87寄存器来传递的,这句代码用在64位BASM函数中就是错误的,因为64位函数返回浮点数不再使用80x87寄存器,而是使用SSE寄存器xmm0,所以64位代码只能是类似movaps  xmm0, v3或者直接movaps  xmm0, xmm2。

    还有一种特殊的返回值,如下面的方法,返回一个TRect类型:


function GetRect(Left, Top, Right, Bottom: Integer): TRect;
asm

// 32位代码:

    push  ebx

    mov   ebx, Result // 或者mov  ebx, [ebp+8]

    mov   [ebx].TRect.Left, eax

    mov   [ebx].TRect.Top, edx

    mov   [ebx].TRect.Right, ecx

    mov   eax, Bottom // 或者mov  eax, [ebp+12]

    mov   [ebx].TRect.Bottom, eax

    pop    ebx

// 64位代码:
    mov   [rcx].TRect.Left, edx
    mov   [rcx].TRect.Top, r8d
    mov   [rcx].TRect.Right, r9d
    mov   eax, Bottom
    mov   [rcx].TRect.Bottom, eax

end;

通过对比可以看出,对于这种结构形式的返回值,如果是小于或等于通用寄存器的偶数字节结构使用eax或rax返回,这一点32位和64位代码都是相同的,而其它结构返回值就不同了,32位代码用最后一个参数(本例是栈参数),而64位代码则是用第一个参数,即rcx来传递结构地址的,下面是一个调用BASM过程例子:

procedure Test;
var
  r: TRect;
asm
    .params 5

    // r := GetRect(1, 2, 3, 4)
    lea   rcx, r
    mov   edx, 1
    mov   r8d, 2
    mov   r9d, 3
    mov   [rbp+20h], 4
    call  GetRect
end;

用来传递第一个参数的寄存器rec被返回值占用了,或者说,这种结构返回值是作为第一个参数传递的,前面的GetRect函数实际上是下面的形式的变形:

procedure GetRect(var r: TRect; Left, Top, Right, Bottom: Integer);

    在调用例子过程中,有一个伪指令.params用来自动分配参数内存空间,而参数Bottom也是使用栈传递的,但并没有使用压栈指令,具体原因涉及64位函数架构,比较复杂,由于今天时间不早了,明天继续。。。。。。


3 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 幼儿小鸡红肿疼怎么办 宝宝小鸡淹了怎么办 小孩的小鸡肿怎么办 小鸡脚趾歪了怎么办 小鸡脖子歪了怎么办 宝宝的小鸡红怎么办 游戏联不了网络怎么办 光敏印章进水了怎么办 照片打印出来黑怎么办 企业股东签名不符怎么办 电脑签字签不了怎么办 刘海的碎发怎么办 车牌号全是单数怎么办 品正通保车险超市怎么办营业执照 车辆咨询服务门市怎么办营业执照 被互盾科技骗了怎么办 家具生意不好怎么办啊 今年家具店生意不好怎么办 奶茶店位置不好怎么办 早餐店位置不好怎么办 木工做的不好怎么办 淘宝昵称改不了怎么办 淘宝店关门售后怎么办 店里生意不好怎么办?解决方案 淘宝店铺没有生意怎么办 淘宝店做大了应该怎么办 汽车维修没生意怎么办 淘宝买家具安装怎么办 投标时未记主材费结算时怎么办 不敢买自慰棒怎么办 车被扎了个洞怎么办 企业欠税交不起怎么办 组织代码查不到怎么办 u盾电量不足怎么办 对公账户拍照怎么办 个人怎么办对公账户 车辆超过年检日期怎么办 手机cpu负载过高怎么办 移动数据上网慢怎么办 服务器密码忘记了怎么办 网吧电脑卡死了怎么办