DELPHI内联汇编的一点心得

2018-10-30

DELPHI内联汇编好象有得天独厚的优势,尤其是API的调用,处理好各成员参数后,可以直接CALL API名称。利用内联汇编我们可以为程序添加各种异常,添加反调试代码,添加花指令,还可以令某些难写的注册机简单化等.我把DELPHI内联汇编的一点点使用心得写出来,目的是想与大家交流,挖掘更多的这方面的领域。也希望大家能够指正一些错误。
一、 数据格式
整型数据:8位的用AL返回,16位的用AX返回,32位的用EAX返回。
BYTE(8位):BYTE、CHAR、SHORTINT、BOOLEAN
WORD(16位): SMALLINT、WORD
DWORD(32位):INTEGER、LONGWORD、ANSISTRING、POINTER、CLASS、LONGINT、STRING
ST(0):SINGLE、DOUBLE、EXTENDED、COM

如:
var
ByteVar: Byte;
WordVar: Word;
IntVar: Integer;
begin
asm
MOV AL,ByteVar
MOV BX,WordVar
MOV ECX,IntVar
end;
end;

实型:用ST(0)返回
  指针:用EAX返回
  长字符串:用EAX返回其所在地址
  变量:可用@Result返回

16进制的表示方式:
如果是0-9开头的16进制值直接在后面加H或在前面加$号,如:1AH,$1A
如果是字母开头的前面加0再在后面跟H,或者直接用$号,如:0AH,$A

几个修饰符:
OFFSET 返回内存地址中的立即数
[....] 返回内存地址,与OFFSET相反,如:MOV EAX,OFFSET [XXXX]=MOV EAXX,[OFFSET XXXX]=MOV EAX,XXXX
HIGH 返回高8位的立即数
LOW 返回低8位的立即数
& 防止变量与汇编中的寄存器同名而在前面加& 号,如:
EAX:INTEGER; ...
MOV &EAX,10H 这里的&EAX不是EAX寄存器
. .号的一种用法:
var
STR: Word; ..
MOV DL,STR.Byte或DL,Byte(STR)


二、 嵌入式汇编的格式
  Delphi是使用ASM……END来标志汇编语句
   如:
ASM
   mov al,1
   mov bl,al
   END;

一个简单加法函数:
FUNCTION SUM(X,Y:INTEGER):INTEGER;
BEGIN
ASM
MOV EAX,X
ADD EAX,Y
MOV @RESULT,EAX
END;
END;

Byte转换为16进制字符串:
function ByteToHex(Src: Byte): String;
begin
SetLength(Result, 2);
asm
MOV EDI, [Result]
MOV EDI, [EDI]
MOV AL, Src
MOV AH, AL // Save to AH
SHR AL, 4 // Output High 4 Bits
ADD AL, '0'
CMP AL, '9'
JBE @@OutCharLo
ADD AL, 'A'-'9'-1
@@OutCharLo:
AND AH, $f
ADD AH, '0'
CMP AH, '9'
JBE @@OutChar
ADD AH, 'A'-'9'-1
@@OutChar:
STOSW
end;
end;

三、 可用的寄存器
32位寄存器EAX EBX ECX EDX ESP EBP ESI EDI
 16位寄存器AX BX CX DX SP BP SI DI
 8位寄存器AL BL CL DL(低8位) AH BH CH DH(高8位)
 16位段寄存器CS DS SS ES
32位段寄存器 FS GS
 以及协处理器寄存器堆栈 ST
一个ASM statement 必须保护EDI,ESI,ESP,EBP和EBX寄存器,但是可以自由的修改EAX,ECX和EDX寄存器。
默认情况下,delphi使用“register”方式,若参数在3个以内, 将分别使用eax、edx和ecx,如果超过三个,则用堆栈传递。返回参数的存放视长度而定,例如8位用al返回,16位用ax,32位用eax,64位用用两个32位寄存器edx:eax,其中eax是低位。如果你想用EBX寄存器,接得这样写:
asm
push ebx
mov ebx,2
mov IntVar,ebx
pop ebx
end;

如:FUNCTION(T1,T2,T3:INTEGER):INTEGER,可以默认为T1存在EAX中,T2存在EDX中,T3存在ECX中。
DELPHI的标签名一般都以@开头,比如@exit、@001等

四、 CALL的应用
在汇编中写代码要保存寄存器现场(保存使用前的寄存器状态,使用Push压栈和Pop从栈中弹出),不过这一切对于Delphi的嵌入式汇编是没有必要的(除非你自己要使用Push和Pop),因为Delphi已经帮你做了,不必担心会使数据丢掉。
DELPHI内联汇编中同样可以用汇编中CALL的功能。
比如:
asm
CALL @1
JMP @EXIT
@1:
MOV EAX,1
MOV SN,EAX
RETN
@exit:
end;

call的第二种用法,在汇编代码中直接调用FUNCTION函数,如:
function cacl(eax: integer):integer;
var
...
begin
....
Result:=code;
end;
调用时可以直接CALL CACL:
ASM
CALL CACL
...
END

编语句中的Call语句,可以用于调用其它过程,既可以是其它汇编程序段也可以是Delphi中的标准过程:
  例如:假设新建一个窗体并在上面加了一个按钮,在Click事件中写入以下代码
  procedure TForm1.Button1Click(Sender: TObject);
  begin
   showmessage(`ok');
  end;
  再写一个过程_X
  function TForm1._x(var i:smallint):integer;
  asm
   call button1click
  end;
  执行_x的结果就可以显示消息框。

五、调用API函数
DELPHI内联汇编中调用API函数函数非常简单,比如调用PostQuitMessage:
Asm
Push 0
Call PostQuitMessage(或Call SYSTEM.PostQuitMessage)
End

调用MessageBox函数:
procedure TForm1.Button2Click(Sender: TObject);
  var
   szTitle:string;
   szCaption:string;
  begin
   szTitle:='您好!';
   szCaption:='这是一个在内嵌汇编中调用stdcall类型函数的例子.';
   asm
    PUSH MB_OK+MB_ICONINFORMATION
    PUSH szTitle
    PUSH szCaption
    PUSH 0
    CALL MessageBox
   end;
  end;

调用GetFileSize函数
function stdcalldemo: Integer;
var
FH: THandle;
begin
FH:= FileOpen(’c: oot.ini’,fmOpenRead);
asm
push 0
push FH
call GetFileSize
mov @Result,eax
end;
Result:= GetFileSize(FH,0);//此句就相当于上面的汇编调用方式
FileClose(FH);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
showmessage(IntToStr(stdcalldemo));
end;

六、汇编的调试。
如果发现汇编通不过,就要注意停下后的位置的代码:如变量的跟踪、断点、堆栈查看……可以在关键代码处下断,停下后打开View菜单的Debug Windows的CPU VIEW窗口,F8单步跟踪。如:
Unit1.pas.62:     PUSH MB_OK+MB_ICONINFORMATION
00455E8F 6A40 push $40
Unit1.pas.63:     PUSH szTitle
00455E91 FF75FC push dword ptr [ebp-$04]
Unit1.pas.64:     PUSH szCaption
00455E94 FF75F8 push dword ptr [ebp-$08]
Unit1.pas.65:     PUSH 0
00455E97 6A00 push $00
Unit1.pas.66:     CALL MessageBox
00455E99 E8FA0DFBFF call MessageBox
Unit1.pas.69:   end;
00455E9E 33C0 xor eax,eax

七、常见易错语句。
MOV ESI,DOWRD PTR SS:[TEXT] 对(TEXT为STRING)
MOV ESI,DOWRD PTR [TEXT] 对(段寄存器CS,DS,SS,ES可省略)
MOV AL,DOWRD PTR SS:[TEXT] 错(DWORD为32位)
MOV AL,BYTE PTR [TEXT] 对
MOV EAX,BYTE PTR [TEXT] 错(DWORD为8位)
MOV AL,WORD PTR [TEXT] 错(DWORD为16位)
MOV EAX,WORD PTR [TEXT] 错
MOV AX,WORD PTR [TEXT] 对
MOV [A],EAX
MOV BYTE PTR[EDI],'A'
MOV AL,[EDI]
MOV EAX,X //X指向的值赋值给EAX
MOV EAX,[EAX] //X指向的地址赋值给EAX
MOV DOWRD PTR[ESP],EAX 对
MOV DOWRD PTR[ECX],EAX 对
MOV DOWRD PTR[ECX+4],EAX 对
MOV DOWRD PTR[ECX+EDI],EAX 对
MOV DOWRD PTR[ESI],EAX 错
MOV DOWRD PTR[EDX],EAX 错
MOV DOWRD PTR[EBX],EAX 错
.....

比较详细的例子可以看一下
http://bbs.pediy.com/showthread.php?s=&threadid=26382
http://bbs.pediy.com/showthread.php?s=&postid=222986#post222986
里的注册机代码,更多的内容还需要你补充…。
阅读28