在 64 位内联汇编程序中访问 Delphi Class 字段
Accessing Delphi Class Fields in 64 bit inline assembler
我正在尝试将 Delphi TBits.GetBit 转换为 64 位版本的内联汇编程序。 VCL 源代码如下所示:
function TBits.GetBit(Index: Integer): Boolean;
{$IFNDEF X86ASM}
var
LRelInt: PInteger;
LMask: Integer;
begin
if (Index >= FSize) or (Index < 0) then
Error;
{ Calculate the address of the related integer }
LRelInt := FBits;
Inc(LRelInt, Index div BitsPerInt);
{ Generate the mask }
LMask := (1 shl (Index mod BitsPerInt));
Result := (LRelInt^ and LMask) <> 0;
end;
{$ELSE X86ASM}
asm
CMP Index,[EAX].FSize
JAE TBits.Error
MOV EAX,[EAX].FBits
BT [EAX],Index
SBB EAX,EAX
AND EAX,1
end;
{$ENDIF X86ASM}
我开始将 32 位 ASM 代码转换为 64 位。经过一些搜索,我发现我需要为 64 位编译器将 EAX 引用更改为 RAX。我在第一行结束了这个:
CMP Index,[RAX].FSize
这可以编译,但在运行时会出现访问冲突。我尝试了一些组合(例如 MOV ECX,[RAX].FSize
)并在尝试访问 [RAX].FSize
时遇到相同的访问冲突。当我查看由 Delphi 编译器生成的汇编程序时,看起来我的 [RAX].FSize
应该是正确的。
Unit72.pas.143: MOV ECX,[RAX].FSize
00000000006963C0 8B8868060000 mov ecx,[rax+[=14=]000668]
和 Delphi 生成的代码:
Unit72.pas.131: if (Index >= FSize) or (Index < 0) then
00000000006963CF 488B4550 mov rax,[rbp+]
00000000006963D3 8B4D58 mov ecx,[rbp+]
00000000006963D6 3B8868060000 cmp ecx,[rax+[=15=]000668]
00000000006963DC 7D06 jnl TForm72.GetBit +
00000000006963DE 837D5800 cmp dword ptr [rbp+],[=15=]
00000000006963E2 7D09 jnl TForm72.GetBit + D
在这两种情况下,生成的汇编程序都使用 [rax+[=19=]000668]
作为 FSize。在 Delphi 64 位汇编程序中访问 class 字段的正确方法是什么?
这听起来像是一件需要优化的奇怪事情,但 64 位 Pascal 版本的汇编程序似乎效率不高。我们多次调用此例程,根据各种因素,它的执行时间最多可达 5 倍。
基本问题是您使用了错误的寄存器。 Self
作为隐式参数传递,先于所有其他参数。在 x64 calling convention 中,这意味着它是在 RCX
中传递的,而不是 RAX
.
所以Self
传入RCX
,Index
传入RDX
。坦率地说,我认为在内联汇编程序中使用参数名称是错误的,因为它们隐藏了参数是在寄存器中传递的事实。如果您碰巧覆盖了 RDX
,那么这会改变 Index
.
的表观值
所以 if
语句可能被编码为
CMP EDX,[RCX].FSize
JNL TBits.Error
CMP EDX,0
JL TBits.Error
FWIW,这是一个实现起来非常简单的函数,我认为您不需要使用任何堆栈 space。您在 x64 中有足够的寄存器,可以完全使用易失性寄存器来完成此操作。
我正在尝试将 Delphi TBits.GetBit 转换为 64 位版本的内联汇编程序。 VCL 源代码如下所示:
function TBits.GetBit(Index: Integer): Boolean;
{$IFNDEF X86ASM}
var
LRelInt: PInteger;
LMask: Integer;
begin
if (Index >= FSize) or (Index < 0) then
Error;
{ Calculate the address of the related integer }
LRelInt := FBits;
Inc(LRelInt, Index div BitsPerInt);
{ Generate the mask }
LMask := (1 shl (Index mod BitsPerInt));
Result := (LRelInt^ and LMask) <> 0;
end;
{$ELSE X86ASM}
asm
CMP Index,[EAX].FSize
JAE TBits.Error
MOV EAX,[EAX].FBits
BT [EAX],Index
SBB EAX,EAX
AND EAX,1
end;
{$ENDIF X86ASM}
我开始将 32 位 ASM 代码转换为 64 位。经过一些搜索,我发现我需要为 64 位编译器将 EAX 引用更改为 RAX。我在第一行结束了这个:
CMP Index,[RAX].FSize
这可以编译,但在运行时会出现访问冲突。我尝试了一些组合(例如 MOV ECX,[RAX].FSize
)并在尝试访问 [RAX].FSize
时遇到相同的访问冲突。当我查看由 Delphi 编译器生成的汇编程序时,看起来我的 [RAX].FSize
应该是正确的。
Unit72.pas.143: MOV ECX,[RAX].FSize
00000000006963C0 8B8868060000 mov ecx,[rax+[=14=]000668]
和 Delphi 生成的代码:
Unit72.pas.131: if (Index >= FSize) or (Index < 0) then
00000000006963CF 488B4550 mov rax,[rbp+]
00000000006963D3 8B4D58 mov ecx,[rbp+]
00000000006963D6 3B8868060000 cmp ecx,[rax+[=15=]000668]
00000000006963DC 7D06 jnl TForm72.GetBit +
00000000006963DE 837D5800 cmp dword ptr [rbp+],[=15=]
00000000006963E2 7D09 jnl TForm72.GetBit + D
在这两种情况下,生成的汇编程序都使用 [rax+[=19=]000668]
作为 FSize。在 Delphi 64 位汇编程序中访问 class 字段的正确方法是什么?
这听起来像是一件需要优化的奇怪事情,但 64 位 Pascal 版本的汇编程序似乎效率不高。我们多次调用此例程,根据各种因素,它的执行时间最多可达 5 倍。
基本问题是您使用了错误的寄存器。 Self
作为隐式参数传递,先于所有其他参数。在 x64 calling convention 中,这意味着它是在 RCX
中传递的,而不是 RAX
.
所以Self
传入RCX
,Index
传入RDX
。坦率地说,我认为在内联汇编程序中使用参数名称是错误的,因为它们隐藏了参数是在寄存器中传递的事实。如果您碰巧覆盖了 RDX
,那么这会改变 Index
.
所以 if
语句可能被编码为
CMP EDX,[RCX].FSize
JNL TBits.Error
CMP EDX,0
JL TBits.Error
FWIW,这是一个实现起来非常简单的函数,我认为您不需要使用任何堆栈 space。您在 x64 中有足够的寄存器,可以完全使用易失性寄存器来完成此操作。