最近写程序的时候越来越多遇到内联汇编的情况了,但是对Delphi的内联汇编机制还是不熟,知道register call,但是究竟怎么用,代码总是写不正确(大多是调试半天才改正确的),自己以前的一点win32汇编功底似乎也没有什么用处,不知道接下来究竟要怎么学,还望高手们赐教,多谢了

解决方案 »

  1.   

    View->Debug Window->Cpu这个很有用,你可以根据自己去的程序去查看对应的汇编代码。Delphi调用子程序默认的调用方式会把参数依次放入eax,edx,ecx超过三个就用入栈的方式进行参数传递,对于在类里的子程序调用时第一个参数edx,第二个放在ecx超过了就使用堆栈,eax存放类的基址可以在子程序里面通过[eax].FieldName的方式去访问类的成员。如果是StdCall参数传递时全部入堆栈不会使用寄存器。写汇编代码比较容易出错,要调试好久是正常的。还有就算懂汇编了,也不一定知道怎么应用,需要有点创造性。Delphi函数库里面好多也是用汇编写的,也可以拿来参考一下。或者不看,自己用汇编去实现。
      

  2.   

    register call之类只是参数的传递,这个是在你的函数/过程定义有参数的情况下定位自己的输入和输出.Delphi的内联汇编跟标准的Win32汇编区别不太大,只是某些指令可能会受到限制.汇编简单地就是:数据+寄存器+指令+状态数据:更多体现为"寻址"
    寄存器:各种分类的寄存器,包括状态寄存器等等(如果牵涉MMX/SSE和调试技术的话相对会复杂很多,特别是新一代的处理器)
    指令:各种运算和转移指令
    状态:这个是在进行过程当中比较重要也比较复杂的,主要是通过检查状态位来控制程序的分支.一开始学习的话还是从简单的入手,比如写一段代码,然后设置断点,再用Ctrl+Alt+C切换过去查看反汇编码.修改那是常有的事.个人建议是要学汇编就单纯地去学习Win32汇编(Win64应该说暂时还不太用得上),有了基础再回过头来看Delphi的东西,你就会发现,除了传参的差异,其实大家都一样.
      

  3.   

    有个专门讲BASM的资料,共有七篇文章,网上找找
      

  4.   

    有汇编基础就够了,在调用的时候加断点,然后ctrl+alt+c看怎么传参数的,有汇编基础就很好办了,我觉得没什么好多说的。我自己写的时候,除非是特别确定的参数传递,一般也是先写一个空函数调用一下看看到底怎么传的,然后再写。这里介绍一下我的一些还没认真总结过的对传delphi参数方式的经验吧。register和pascal调用约定极相似,都是从左向右压栈,由被调用方清理stack,返回值尽可能使用eax。不同的是,register会在允许的情况下依次使用eax、edx、ecx三个通用寄存器。
    能够使用寄存器压栈的参数和返回值使用eax的情况相似,原则基本上都是:首先,数据长度不大于4,这个很容易理解,32位机的寄存器只能放下4字节;其次,该数据类型通常使用通用寄存器运算,这个主要是针对Single类型说的,因为Single类型虽然只有4字节,但一般不用通用寄存器运算浮点数,所以不使用通用寄存器入栈(据说某些c/cpp的编译器的特殊优化会更复杂,可能会使用fpu寄存器或mmx寄存器等)。而长于4字节的,一般是传入变量的地址,少数几个基本类型会压栈(如Int64、Double、Extended等会将数据push进stack,而不把它的地址传入寄存器)。
    第一点要说明的地方稍微多一些,比如record、set of T、TStaticArray=array[m..n]of T 这些数据类型,当它不长于4的时候会使用寄存器,其它情况只传入一个地址。接下来要注意的就是用const/var/out修饰的和没有修饰字的参数类型,编译器对它们处理方式也不同。其中var/out这俩一样,只传入地址,这个很容易理解,就不多讲了(动态数组参数有点儿特殊,下面再说)。而const传入的原则和普通参数的传入原则是相同的,但是入栈后的处理不大一样(这个也放后面讲)。
    对于动态数组参数,首先要理解的是,TDynamicArray=array of T 和 array of T 是不同的,用前者的方式声明的变量是一个指针,指向一个数据结构,该结构和Long String的数据结构可以说是相同的(d2009的string多加了4字节,就有区别了,该指针指向数组的首元素),而首元素偏移量-8的一个32位数是引用计数,-4的一个32位数表示数组长度。而用array of T声明的虽然实际上也是一个指针,但编译器不把这个指针显式给用户看,对于用户来说它只是一个变量名。由于前者是一个指针,也就是4字节,所以它只传入一个指针。而后者的话,会传入两个参数,一个是数组的首地址,另一个是High(DynArrVar),也就是Length(DynArrVar)-1。接下来最复杂的一块就是被调用函数入口对参数的处理,函数入口用begin和asm的处理方式不同,先介绍由begin开始的。
    先从简单的说起,对于用var/out修饰的,这种由于都是传入地址,一般不做处理。但是对array of T这种参数类型,由于它传入的不是指针地址(前面讲的,传入元素首地址和High(),因为指针对用户不可见),所以对其SetLength()不会被编译通过。对于const,不传入地址的不用多说什么,传入地址的不会多做任何处理,这是不作处理是和不带修饰字的参数相对而言的。由于不带修饰字的变量在语法上讲,是对传入数据的一个copy,所以如果传入的参数只是地址的话,函数入口处会对它进行复制(如动态数组类型、record等);而有引用计数的会在入口处增加引用计数。
    另外,由begin入口的,如果传入会由编译器管理生存期的变量(如TArray=array of T等)时,还会隐含的加上try...finally以确保能正确管理其生存期。
    由asm开头的,编译器一般不会进行额外的处理(如复制),但当声明中出现在语义上需要复制的参数时,会生成保护ebp/esp现场的代码(push ebp;mov ebp,esp;...;mov esp,ebp;pop ebp)
    对于对象/类的成员函数(包括d2007支持的record成员),由于对象/类本身是个指针,所以可以把它理解成一个参数,然后就都一样了(这样就很容易理解,为什么注明stdcall的成员函数就会push进stack而不直接用eax传Self指针了)。最后就是函数的返回值,一般是使用eax作为返回值,eax放不下的会用栈返回。而返回string类型的会比较特殊,实际上会多传入一个参数用来放返回值。
    多说一句,从与其它语言的交互的复杂度来讲,返回值应尽量使用标准类型,也就是广义的4字节整数类型(包括整数、Boolean、枚举、指针等)几个类型;对于非标准数据类型的如record、set of T等,应尽量作为用var/out修饰的参数传入(如果注意一下winapi的设计,会发现只返回上述几种eax能放下的基本类型,连Single/Double/Int64都不用,不过好像也没啥用Single/Double的api)。