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去加就非常easy出问题,除非你能保证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位函数架构。比較复杂,由于今天时间不早了,明天继续。。。。。

64位BASM学习随笔(一)的更多相关文章

  1. win7+64位+Java学习基本软件安装+环境配置+eclipse(IDE)

    一.下载安装JDK 1.安装包下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk9-downloads-3848520.h ...

  2. PHP在 win7 64位 旗舰版 报错 Call to undefined function curl_init()

    代码在ubuntu下无缝运行OK 转到我的win7 64位 期间 学习机上 报错: Call to undefined function curl_init() 因为用到curl 远程抓取数据. 所以 ...

  3. 【OSG学习笔记之一:】OSG+VS2010+win7 64位环境搭建

    虽然出生的时候,没有说过“Hello World!”,但是自从走上了编程之路,每一次输出“Hello World!”的时候,都觉得好比中了彩票大奖似的: 仔细算算,从2012年暑假到现在,经历了3年半 ...

  4. 关于64位Win7/Win 8 下怎么学习汇编语言

    我看有许多同学用Win 7/Win 8 学习汇编,现在好多人的内存升级了都用64位系统了,但是64位W7没有自带的DEBUG和MASM. 1.首先下载DOSBOX,(下面附带地址)它的作用就是让你在6 ...

  5. VSTO 学习笔记(十一)开发Excel 2010 64位自定义公式

    原文:VSTO 学习笔记(十一)开发Excel 2010 64位自定义公式 Excel包含很多公式,如数学.日期.文本.逻辑等公式,非常方便,可以灵活快捷的对数据进行处理,达到我们想要的效果.Exce ...

  6. VSTO学习笔记(三) 开发Office 2010 64位COM加载项

    原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(A ...

  7. linux内核学习之三:linux中的"32位"与"64位"

    在通用PC领域,不论是windows还是linux界,我们都会经常听到"32位"与"64位"的说法,类似的还有"x86"与"x86 ...

  8. 学习笔记23—window10 64位 python2.7 安装liblinear

    最近在使用pythin,因为要使用libsvm,所以到官网去下载libsvm.官网地址为libsvm(https://www.csie.ntu.edu.tw/~cjlin/libsvm/)结果下载下来 ...

  9. Java串口编程学习1-环境配置(64位Win7)

    最近在做zigbee的课程设计,需要Java实现对串口数据的读写操作. 网上找了很多代码,好像都比较过时了,直接拿来用没法跑通……QAQ……然后自己写个教程留底,如有不当之处还请各位路过的大神赐教. ...

随机推荐

  1. Maven+Docker,发布到Registry

    1.配置Pom.xml <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEnc ...

  2. Spring Cloud (11) Hystrix-监控聚合监控

    上一篇利用Hystrix Dashboard去监控断路器的Hystrix command,当我们有很多服务的时候,就需要聚合所有服务的Hystrix Dashboard数据了,这就需要Hystrix ...

  3. [ Nowcoder Contest 165 #D ] 合法括号序列

    \(\\\) \(Description\) 键盘上有三个键,敲击效果分别是: 在输出序列尾部添加一个左括号 在输出序列尾部添加一个右括号 删除输出序列尾部的第一个元素,若输出序列为空,则什么都不发生 ...

  4. css文本背景样式

    文本样式 文本类 text-transform:uppercase: 全部变为大写 text-transform:lowercase: 全部变为小写 text-transform:capitalize ...

  5. unity3d 各键值对应代码

    KeyCode :KeyCode是由Event.keyCode返回的.这些直接映射到键盘上的物理键.  值        对应键 Backspace     退格键 Delete      Delet ...

  6. jQuery——stop

    为什么要停止动画? 对同一个元素,如果拥有一个以上的动画对其加以作用,那么后面的动画会被放入一个动画队列中.动画队列的动画是在其上一个动画完成以后才会执行. 控制两个参数四种情况 1.第一个参数表示后 ...

  7. python网络编程调用recv函数完整接收数据的三种方法

    最近在使用python进行网络编程开发一个通用的tcpclient测试小工具.在使用socket进行网络编程中,如何判定对端发送一条报文是否接收完成,是进行socket网络开发必须要考虑的一个问题.这 ...

  8. GridView中的日期处理

    数字 {0:N2} 12.36  数字 {0:N0} 13  货币 {0:c2} $12.36  货币 {0:c4} $12.3656  货币  "¥{0:N2}"  ¥12.36 ...

  9. python--(协程 和 I/O多路复用)

    python--(协程 和 I/O多路复用) 一.协程 1. >>>单线程下实现并发, 最大化线程的效率, 检测 IO 并自动切换,程序级别的任务切换, 之前多进程多线程都是系统级别 ...

  10. Django——2 路由分配设置 re_path正则匹配 include总路由 url传参 name使用 模板渲染render方法 模板渲染方法

    Django 路由分配设置 re_path正则匹配 include总路由设置 url额外参数的传递 name的使用 模板的渲染:render方法   路由的分配中, 可以设定相应的转换器加以约束,比如 ...