Murphy的记事本

若教眼底无别离,不信人间有白头
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

BASM基础实例(二)

Posted on 2009-08-28 15:39  Murphy(土豆)  阅读(283)  评论(0编辑  收藏  举报

 BASM基础实例(二)


  { --------- 加法 --------- }
function TTestFrm.Adder(X, Y: Integer): Integer;
begin
  asm
    mov   EAX, X
    add   EAX, Y
    mov   @Result, EAX
  end;
end;

procedure TTestFrm.AddBtnClick(Sender: TObject);
begin
  ShowMessage(IntToStr(Adder(10, 20)));
end;


{ --------- 减法 --------- }
function TTestFrm.Subtracter(X, Y: Integer): Integer;
begin
  asm
    mov   EAX, X
    sub   EAX, Y
    mov   @Result, EAX
  end;
end;

procedure TTestFrm.Button1Click(Sender: TObject);
begin
  ShowMessage(IntToStr(Subtracter(20, 16)));
end;


{ --------- 乘法 ---------
  Mul指令是 14~18 个周期和 5 个吞吐量
}
function TTestFrm.Multiply(X, Y: Integer): Integer;
begin
  asm
    mov EAX, X
    mul EAX, Y
    mov @Result, EAX
  end;
end;

procedure TTestFrm.Button3Click(Sender: TObject);
begin
  ShowMessage(IntToStr(Multiply(20, 20)));
end;


{ --------- 除法 --------- }
function TTestFrm.Division(X, Y: Integer): Integer;
begin
  asm
    xor EDX, EDX      { 32位除法时,如果被除数达到了双精度时,必须将EDX清零,否则可能引起溢出 }
    mov EAX, X        { 被除数 }
    mov ECX, Y        { 除数 }
    div ECX
    mov @Result, EAX  { 相除结果的值数保存在EAX中 }
  end;
end;

procedure TTestFrm.Button4Click(Sender: TObject);
begin
  ShowMessage(IntToStr(Division(20, 3)));
end;


{ --------- 除法取余 --------- }
function TTestFrm.ArithComp(X, Y: Integer): Integer;
begin
  asm
    xor EDX, EDX    
    mov EAX, X
    mov ECX, Y
    div ECX
    mov @Result, EDX  { 相除结果的余数保存在EDX中 }
  end;
end;

procedure TTestFrm.Button5Click(Sender: TObject);
begin
  ShowMessage(IntToStr(ArithComp(20, 3)));
end;


{ --------- 2的阶乘 ---------
 shl左移 是 4 个时钟周期和 1 个吞吐量,在可以使用shl指令完成乘法的情况下,
 shl效率更高
}
function TTestFrm.Factorial(X, FactX: Integer): Integer;
begin
  asm
    mov EAX, X
    mov ECX, FactX              { 初始化计数器的值为:FactX }
  @For:
    shl EAX, 1                  { 左移 }
    sub ECX, 1                  { 计数器累减 }
    cmp ECX, 0                  { 判断计数器值是否为:0 }
    jge @For                    { 跳转 }
    mov @Result, EAX
  end;
end;

procedure TTestFrm.Button2Click(Sender: TObject);
begin
  ShowMessage(IntToStr(Factorial(5, 1)));
end;


{ --------- 右移除法 --------- }
function TTestFrm.RightDispl(X, DispX: Integer): Integer;
begin
  asm
    mov EAX, X
    mov ECX, DispX              { 初始化计数器的值为:DispX }
  @For:
    shr EAX, 1                  { 右移 }
    sub ECX, 1                  { 计数器累减 }
    cmp ECX, 1                  { 判断计数器值是否为:1 }
    jge @For                    { 跳转 }
    mov @Result, EAX
  end;
end;

procedure TTestFrm.Button6Click(Sender: TObject);
begin
  ShowMessage(IntToStr(RightDispl(15, 2)));
end;


{ --------- 无条件跳转 ---------
  jmp指令提供了控制转移,但是它不允许进行任何复杂的判断
}
function TTestFrm.UncondJump: Integer;
begin
  asm
    mov EAX, 10
    jmp @Next2
  @Next0:
    mov EAX, 11
    jmp @Next3
  @Next1:
    mov EAX, 12
    jmp @Next0
  @Next2:
    mov EAX, 13
    jmp @Next1
  @Next3:
    mov @Result, EAX
  end;
end;

procedure TTestFrm.Button7Click(Sender: TObject);
begin
  ShowMessage(IntToStr(UncondJump));
end;


{ --------- 有条件分支 ---------
  一般和 cmp 指令配合使用,条件分支常用的有:
  (JG)大于,(JGE、JNGE)大于等于,(JNG)不大于,(JE)等于,(JNE)不等于,(JNL)不小于,
  (JL、JLE)小于,(JNLE)小于等于
}
procedure TTestFrm.CondJump(CondX: Integer);
var
  reArray: Array[0..4] of String;
begin
  reArray[0] := '大于500';            { 在当前的函数栈内,按顺序申请数组空间 }
  reArray[1] := '大于等于400';        { 基址指针一般保存在EBP中,每个数组单元占用4个字节,内容保存为指向堆的具体指针 }
  reArray[2] := '小于等于300';        { 基于栈是向下增长的,即每压一次栈,栈顶的地址就减少一次 }
  reArray[3] := '不小于200';          { 可以得出:reArray[0] = EBP-$18 }
  reArray[4] := '小于150';            { reArray[4] = EBP-$08 (按数组单元占用字节数递减) }
                                      { EBP-$04 的位置则保存了 CondX 的值 }
  asm
    mov EAX, CondX

    cmp EAX, 500
    jg @Gt500              { 大于500 }

    cmp EAX, 400
    jge @Gt400             { 大于等于400 }

    cmp EAX, 300
    jnle @Gt300            { 小于等于300 }

    cmp EAX, 200
    jnl @Gt200             { 不小于200 }

    cmp EAX, 150
    jl @Gt150              { 小于150 }

  @Gt500:
    mov EAX, [EBP-$18]     { 根据参数调用原则,EAX、EDX、ECX分别保存第1,2,3个参数 }
    jmp @End               { 将指定地址的值传入EAX,再调用Showmessage,相当于调用Showmessage(EAX) }
  @Gt400:
    mov EAX, [EBP-$14]
    jmp @End
  @Gt300:
    mov EAX, [EBP-$10]
    jmp @End
  @Gt200:
    mov EAX, [EBP-$0c]
    jmp @End
  @Gt150:
    mov EAX, [EBP-$08]
    jmp @End
  @End:
    CALL ShowMessage
  end;
end;

procedure TTestFrm.Button9Click(Sender: TObject);
begin
  CondJump(120);
end;


{ --------- 循环 --------- }
function TTestFrm.Circulator(X: Integer): Integer;
begin
  asm
    mov EAX, X                        { 赋初始值 }
    mov ECX, X                        { 初始化计数器值为X }
  @Cyc:
    ADD EAX, X                        { 执行累加操作 }
    loop @Cyc                         { 循环 0-X 次 }
    mov @Result, EAX                 
  end;
end;

procedure TTestFrm.Button8Click(Sender: TObject);
begin
  ShowMessage(IntToStr(Circulator(4)));
end;

 

{ --------- 引用对象方法 --------- }
procedure TTestFrm.FunTest(X, Y: Integer);
begin
  ShowMessage(Format('X: %d, Y: %d', [X, Y]));
end;

procedure TTestFrm.CallFun(X, Y: Integer);
begin
  asm
    //mov EAX, Self      { 在引用对象方法时,Delphi会默认将Self做为参数传入EAX,此行是否添加不影响结果 }
    mov EDX, X           { 使用Register调用规则,传递参数的顺序是:Eax, Edx, Ecx }
    mov ECX, Y
    call FunTest
  end;
end;

procedure TTestFrm.Button10Click(Sender: TObject);
begin
  CallFun(11, 22);
end;

 

{ --------- 引用DLL方法 ---------
  DLL中开放的接口定义如下:
    procedure DllTest(AName, AText: PChar; Var AReMsg: PChar); stdcall;
}
{ 加载DLL的过程 }
procedure TTestFrm.CallDll(AName, AText: PChar; var AReMsg: PChar);
var
  iDLLHandle: THandle;
  pProc: Pointer;
begin
  iDLLHandle := LoadLibrary('Dll_Test.dll');
  try
    if iDLLHandle >= 32 then
    begin
      pProc := GetProcAddress(iDLLHandle, PChar('DllTest'));
      if pProc <> nil then
        DoCallDll(pProc, AName, AText, AReMsg)
    end;
  except
    FreeLibrary(iDLLHandle);
  end;
end;

procedure TTestFrm.DoCallDll(AProc: Pointer; AName, AText: PChar; var AReMsg: PChar);
begin
  asm                              { 使用Stdcall调用规则,参数从右至左的顺序传递 }
    mov EAX, AReMsg                { 外部传入或全局的变量,需要使用MOV,如果是局部变量,必须使用LEA }
    push EAX                       { 对象外方法调用,应该使用入栈的方法传入参数? }
    push AText                     { 未解:为何使用PUSH而不直接MOV寄存器还没有理解清楚?}
    push AName
    call AProc                     { 调用指定的方法指针 }
  end;                             { 未解:如果调用的是Function指针,使用 mov @Result, al时,Result总得不到正确的反回结果 }
end;

procedure TTestFrm.Button15Click(Sender: TObject);
var
  pReMsg: PChar;
begin
  pReMsg := StrAlloc(255);
  try
    CallDll(PChar('TestName'), PChar('TestText'), pReMsg);
    ShowMessage('反馈内容:' + pReMsg);
  finally
    StrDispose(pReMsg);
  end;
end;

 

{ --------- 字符检索 --------- }
function TTestFrm.AsmPos(APosStr: String; ASubChar: String): Integer;
begin
  asm                         { 根据Register调用规则,Eax保存的是Self, Edx保存的是APosStr, Ecx保存的是ASubChar }
    test EDX, EDX             { 测试Edx值是否为0,如果为0,则表明传入的值为nil }
    jz @Exit                  { 如果0标志位被置,则跳转到END }
    mov EDI, [EDX - 4]        { String类型在内存中的结构,前8位为引用计数,前4位为长度,所以EDX-4为APosStr的长度 }
    test ECX, ECX             { 测试ECX的值是否为空,Ecx中保存ASubChar的值 }
    jz @Exit
    mov ESI, 0                { 由于常用的计数器寄存器Ecx被占用,使用ESI做为计数器寄存器并初始化 }
    mov AL, BYTE [ECX]        { 将Ecx中指针指向的字符保存到AL中 }
  @Loop:
    cmp AL, BYTE [EDX + ESI]  { 比较字符 }
    jz @Found                 { 相等则跳出 }
    inc ESI                   { 计数器累加 }
    cmp ESI, EDI              { 比较当前计数是否等于长度 }
    jz @Exit                  { 如果相等,则跳出 }
    jmp @Loop                 { 循环 }
  @Found:
    inc ESI                   { 因为查找的起始值是从0开始,所以反回的位置应该加1 }
    mov EAX, ESI              { 保存计数器到结果 }
    jmp @End
  @Exit:
    mov EAX, -1               { 没找到反回-1 }
  @End:
    mov @Result, EAX
  end;
end;

procedure TTestFrm.Button11Click(Sender: TObject);
begin
  ShowMessage(IntToStr(AsmPos('abcde', 'g')));
end;