OgUtil.pas in onguard I want to porting to 64 bit
Delphi 64 bit not allow to use ASM with pascal Can I convert this function to work with delphi 64 bit
function LockFile(Handle : THandle;
FileOffsetLow, FileOffsetHigh,
LockCountLow, LockCountHigh : Word) : Boolean;
var
Error : Word;
begin
asm
mov ax,$5C00
mov bx,Handle
mov cx,FileOffsetHigh
mov dx,FileOffsetLow
mov si,LockCountHigh
mov di,LockCountLow
int $21
jc ##001
xor ax,ax
##001:
mov Error,ax
end;
Result := Error = 0;
end;
can convert this code to completely pascal
function UnlockFile(Handle : THandle;
FileOffsetLow, FileOffsetHigh,
UnLockCountLow, UnLockCountHigh : Word) : Boolean;
var
Error : Word;
begin
asm
mov ax, $5C01
mov bx,Handle
mov cx,FileOffsetHigh
mov dx,FileOffsetLow
mov si,UnLockCountHigh
mov di,UnLockCountLow
int $21
jc ##001
xor ax, ax
##001:
mov Error, ax
end;
Result := Error = 0;
end;
Delphi 64 bit not allow to use ASM with pascal Can I convert this function to work with delphi 64 bit
Please help to converting this code to pascal
You are calling the old DOS LockFile and UnlockFile functions via the interruption 21h, you can update and replace these calls by the LockFile and UnlockFile WinApi methods, which are defined in the Windows unit.
function LockFile(hFile: THandle; dwFileOffsetLow, dwFileOffsetHigh: DWORD;
nNumberOfBytesToLockLow, nNumberOfBytesToLockHigh: DWORD): BOOL; stdcall;
function UnlockFile(hFile: THandle; dwFileOffsetLow, dwFileOffsetHigh: DWORD;
nNumberOfBytesToUnlockLow, nNumberOfBytesToUnlockHigh: DWORD): BOOL; stdcall;
The Delphi x64 compiler does indeed support inline assembler. There is nothing to stop you writing inline assembler for the x64 compiler.
However, this is 16 bit code, and you cannot port it to either the 32 bit or 64 bit compiler. I suspect that what you have here is that OnGuard supports both 16 bit and 32 bit code. And it uses conditional compilation in places where there needs to be different implementation for 16 and 32 bit code. I bet that the OnGuard assumes that anything that is not 32 bit code is 16 bit code.
So there will likely be a {$IFDEF WIN32} test somewhere. And the code will not define LockFile and UnlockFile if that condition evaluates to True because the functions are defined in the Windows API now. And when that condition evaluates to False, the code assumes 16 bit and defines the functions. But since you are trying to support x64, the {$IFDEF WIN32} check evaluates False and the code attempts to compile 16 bit code, obviously doomed to fail.
Frankly, the best thing you can do is to remove all of the 16 bit code from this library. That will help you see the wood from the trees. I expect there will be other places in the code which attempt to use the 16 bit code simply because Win32 is not defined in the 64 bit compiler.
Update
And a quick check of the OnGuard repo reveals this code, just as I suspected:
{$IFNDEF Win32}
function LockFile(Handle : THandle; FileOffsetLow, FileOffsetHigh,
LockCountLow, LockCountHigh : Word) : Boolean;
function UnlockFile(Handle : THandle; FileOffsetLow, FileOffsetHigh,
UnLockCountLow, UnLockCountHigh : Word) : Boolean;
function FlushFileBuffers(Handle : THandle) : Boolean;
{$ENDIF}
And there are plenty more tests of Win32 which assume that the lack of that define means that the code is 16 bit. Truly this is 20th century code!
You need to look through the library for all uses of the Win32 conditional. Each and every one that you find will present a porting problem for x64.
The basic strategy you must adopt is that you want to use the Win32 variant for both 32 bit and 64 bit. So if I were you I would simply hunt down every Win32 conditional and remove the conditional. Leave behind the Win32 branch of the conditional.
Related
I want to access the local variables of a Delphi procedure from its nested assembly procedure. Although the compiler does allow the references of the local variables, it compiles wrong offsets which only work if the EBP/RBP values are hacked. In the x86 environment I found a fairly elegant hack, but in x64 I couldn't find yet any decent solution.
In the x86 environment the workaround below seems to work fine:
procedure Main;
var ABC: integer;
procedure Sub;
asm
mov ebp, [esp]
mov eax, ABC
end;
...
In the above code, the compiler treats the variable ABC as it would be in the body of Main, so hacking the value of EBP in the fist assembly line solves the problem. However, the same trick won't work in the x64 environment:
procedure Main;
var ABC: int64;
procedure Sub;
asm
mov rbp, [rsp]
mov rax, ABC
end;
...
In the above code, the compiler adds an offset when it references the variable ABC which isn't correct neither with the original (Main) value of the RBP, nor with its new (Sub) value. Moreover, changing the RBP in a 64-bit code isn't recommended, so I found the workaround below:
procedure Main;
var ABC: int64;
procedure Sub;
asm
add rcx, $30
mov rax, [rcx + OFFSET ABC]
end;
...
As the compiler passes the initial value of RBP in RCX, and the reference to the variable ABC can be hacked to be RCX rather than RBP based, the above code does work. However, the correction of $30 depends on the number of variables of Main, so this workaround is kind of a last resort stuff, and I'd very much like to find something more elegant.
Does anyone have a suggestion on how to do this in a more elegant way?
Note that:
Of course: in my real code there are a large number of local variables to be accessed from the ASM code, so solutions like passing the variables as parameters are ruled out.
I'm adding x64 compatibility to x86 code, and there are dozens of codes like this, so I'd need a workaround which transforms that code with small formal changes only (accessing the local variables in a fundamentally different way would become an inexhaustible source of bugs).
UPDATE:
Found a safe but relatively complicated solution: I added a local variable called Sync to find out the offset between the RBP values of Main and Sub, then I do the correction on the RBP:
procedure Main;
var Sync: int64; ABC: int64;
procedure Sub(var SubSync: int64);
asm
push rbp
lea rax, Sync
sub rdx, rax
add rbp, rdx
mov rax, ABC
pop rbp
end;
begin
ABC := 66;
Sub(Sync);
end;
So far nobody came with a solution, so I consider the code below to be the best known solution:
procedure Main;
var Sync: int64; ABC: int64;
procedure Sub(var SubSync: int64);
asm
push rbp
lea rax, Sync
sub rdx, rax
add rbp, rdx
mov rax, ABC
pop rbp
end;
begin
ABC := 66;
Sub(Sync);
end;
BTW, as this very much looks like a Delphi bug, I posted this to the Embarcadero as a bug report.
I have a Delphi Firemonkey EXIF implementation I'm using in a routine to load image files. I'm trying to determine whether or not the image has been rotated, so I can correct the orientation of the image before displaying it. This routine, in part calls assembly code that executes a BSWAP to determine where header information in the image file is located. Here is a part of the code:
type
TMarker = packed record
Marker : Word; //Section marker
Len : Word; //Length Section
Indefin : Array [0..4] of Char; //Indefiner - "Exif" 00, "JFIF" 00 and ets
Pad : Char; //0x00
end;
TIFDHeader = packed record
pad : Byte; //00h
ByteOrder : Word; //II (4D4D) or MM
i42 : Word; //2A00 (magic number from the 'Hitchhikers Guide'
Offset : Cardinal; //0th offset IFD
Count : Word; // number of IFD entries
end;
function SwapLong(Value: Cardinal): Cardinal;
asm bswap eax end;
procedure TExif.ReadFromFile(const FileName: string);
var
j: TMarker;
ifd: TIFDHeader;
off0: Cardinal; //Null Exif Offset
SOI: Word; //2 bytes SOI marker. FF D8 (Start Of Image)
f: File;
begin
if not FileExists(FileName) then exit;
Init;
System.FileMode:=0; //Read Only open
AssignFile(f,FileName);
reset(f,1);
BlockRead(f,SOI,2);
if SOI=$D8FF then begin //Is this Jpeg
BlockRead(f,j,9);
if j.Marker=$E0FF then begin //JFIF Marker Found
Seek(f,20); //Skip JFIF Header
BlockRead(f,j,9);
end;
//Search Exif start marker;
if j.Marker<>$E1FF then begin
i:=0;
repeat
BlockRead(f,SOI,2); //Read bytes.
inc(i);
until (EOF(f) or (i>1000) or (SOI=$E1FF));
//If we find maker
if SOI=$E1FF then begin
Seek(f,FilePos(f)-2); //return Back on 2 bytes
BlockRead(f,j,9); //read Exif header
end;
end;
if j.Marker=$E1FF then begin //If we found Exif Section. j.Indefin='Exif'.
FValid:=True;
off0:=FilePos(f)+1; //0'th offset Exif header
BlockRead(f,ifd,11); //Read IDF Header
FSwap := ifd.ByteOrder=$4D4D; // II or MM - if MM we have to swap
if FSwap then begin
ifd.Offset := SwapLong(ifd.Offset);
ifd.Count := Swap(ifd.Count);
end;
if ifd.Offset <> 8 then begin
Seek(f, FilePos(f)+abs(ifd.Offset)-8);
end;
This works fine when the application is built for 32-bit Windows, but fails at the SwapLong call under 64-bit Windows. I don't know the first thing about Assembly language and so I'm looking for how to handle the same functionality when building the 64-bit version of the program. Just as a note, in both versions the idf.OffSet value passed to the SwapLong function is 134217728 ($08000000). In the 32-bit version the SwapLong returns a value of 8, but the 64-bit version returns a value of 2694969615 given what appears to be the same input.
I need the 64-bit version to work as I am looking to target 64-bit MAC OSX with the same code. Any help would be greatly appreciated.
The issue exists because the inline assembly assumes the first argument as well as the return value to be using register eax, which is true for Delphi in 32-bit mode as per Delphi's calling convention (and although the inline assembly documentation states that there shouldn't be made any assumptions about registers other than ebp and esp, this always held true even inside of inline assembly statements when they were placed at the top of a function).
However, 64-bit mode uses a different calling convention in which the first argument is in rcx and the return value is using rax. So here you are getting random uninitialized garbage as return value that happened to be in that register (with its bytes swapped) because it's never explicitly set.
The best, portable solution would be to implement the byte swap in pure Pascal without inline assembly:
function SwapLong(Value: Cardinal): Cardinal;
begin
Result := Swap(Value shr 16) or (Cardinal(Swap(Value)) shl 16);
end;
This uses the decades-old Swap function which swaps the lower 2 bytes of a value. This isn't of much use on its own anymore but it can be utilized twice (together with some bit shifting and masking) to shorten code for swapping all 4 bytes of a 32-bit value.
Another way which has more source code but can produce less convoluted assembly code as a result would be accessing the individual bytes in the Cardinal using byte pointers:
function SwapLong(Value: Cardinal): Cardinal; inline;
begin
PByte(#Result)^ := PByte(NativeUInt(#Value) + 3)^;
PByte(NativeUInt(#Result) + 1)^ := PByte(NativeUInt(#Value) + 2)^;
PByte(NativeUInt(#Result) + 2)^ := PByte(NativeUInt(#Value) + 1)^;
PByte(NativeUInt(#Result) + 3)^ := PByte(#Value)^;
end;
64-bit assembly passes parameters in different registers than 32-bit. In this case, parameter will be in ECX register, and return value needs to be in EAX.
That requires different code for 32-bit and 64-bit assembly.
function SwapLong(Value: Cardinal): Cardinal;
{$IFDEF ASSEMBLER}
{$IFDEF CPUX86}
asm
bswap eax
end;
{$ENDIF CPUX86}
{$IFDEF CPUX64}
asm
mov eax, ecx
bswap eax
end;
{$ENDIF CPUX64}
{$ELSE}
begin
// pascal version
end;
{$ENDIF}
Since inline assembly is only available on Windows, other platforms need pure pascal code as shown in CherryDT's answer
I come across a problem while porting Delphi BASM32 code to FPC:
program MulTest;
{$IFDEF FPC}
{$mode delphi}
{$asmmode intel}
{$ELSE}
{$APPTYPE CONSOLE}
{$ENDIF}
function Mul(A, B: LongWord): LongWord;
asm
MUL EAX,EDX
end;
begin
Writeln(Mul(10,20));
Readln;
end.
The above code compiles in Delphi XE and works as expected; FPC outputs compile-time error on MUL EAX,EDX line:
Error: Asm: [mul reg32,reg32] invalid combination of opcode and
operands
I am using Lazarus 1.4.4/FPC2.6.4 for Win32 (the current stable version)
Any workaround or fix for the problem?
FreePascal is correct. There are only 3 forms of MUL:
MUL r/m8
MUL r/m16
MUL r/m32
Performs an unsigned multiplication of the first operand (destination operand) and the second operand (source operand) and stores the result in the destination operand. The destination operand is an implied operand located in register AL, AX or EAX (depending on the size of the operand); the source operand is located in a general-purpose register or a memory location.
In other words, the first operand (used for both input and output) is specified in AL/AX/EAX, and the second input operand is explicitly specified as either a general-purpose register or a memory address.
So, MUL EAX,EDX is indeed an invalid assembly instruction.
If you compile this code in Delphi and use the debugger to look at the generated assembly, you would see that the call to Mul(10,20) generates the following assembly code:
// Mul(10,20)
mov edx,$00000014
mov eax,$0000000a
call Mul
//MUL EAX,EDX
mul edx
So, as you can see, Delphi is actual parsing your source code, sees that the first operand is EAX and strips it off for you, thus producing the correct assembly. FreePascal is not doing that step for you.
The workaround? Write proper assembly code to begin with. Don't rely on the compiler to re-interpret your code for you.
function Mul(A, B: LongWord): LongWord;
asm
MUL EDX
end;
Or, you could simply not write assembly code directly, let the compiler do the work for you. It knows how to multiple two LongWord values together:
function Mul(A, B: LongWord): LongWord;
begin
Result := A * B;
end;
Though Delphi does use IMUL instead of MUL in this case. From Delphi's documentation:
The value of x / y is of type Extended, regardless of the types of x and y. For other arithmetic operators, the result is of type Extended whenever at least one operand is a real; otherwise, the result is of type Int64 when at least one operand is of type Int64; otherwise, the result is of type Integer. If an operand's type is a subrange of an integer type, it is treated as if it were of the integer type.
It also uses some unsightly bloated assembly unless stackframes are disabled and optimizations are enabled. By configuring those two options, it is possible to get Mul() to generate a single IMUL EDX instruction (plus the RET instruction, of course). If you don't want to change the options project-wide, you can isolate them to just Mul() by using the {$STACKFRAMES OFF}/{$W-} and {$OPTIMIZATION ON}/{$O+} compiler instructions.
{$IFOPT W+}{$W-}{$DEFINE SF_Was_On}{$ENDIF}
{$IFOPT O-}{$O+}{$DEFINE O_Was_Off}{$ENDIF}
function Mul(A, B: LongWord): LongWord;
begin
Result := A * B;
end;
{$IFDEF SF_Was_On}{W+}{$UNDEF SF_Was_On}{$ENDIF}
{$IFDEF O_Was_Off}{O-}{$UNDEF O_Was_Off}{$ENDIF}
Generates:
imul edx
ret
MUL always multiplies by AL, AX or EAX (more details), so you should specify only the other operand.
I am trying to convert the Delphi TBits.GetBit to inline assembler for the 64 bit version. The VCL source looks like this:
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}
I started converting the 32 bit ASM code to 64 bit. After some searching, I found out that I need to change the EAX references to RAX for the 64 bit compiler. I ended up with this for the first line:
CMP Index,[RAX].FSize
This compiles but gives an access violation when it runs. I tried a few combinations (e.g. MOV ECX,[RAX].FSize) and get the same access violation when trying to access [RAX].FSize. When I look at the assembler that is generated by the Delphi compiler, it looks like my [RAX].FSize should be correct.
Unit72.pas.143: MOV ECX,[RAX].FSize
00000000006963C0 8B8868060000 mov ecx,[rax+$00000668]
And the Delphi generated code:
Unit72.pas.131: if (Index >= FSize) or (Index < 0) then
00000000006963CF 488B4550 mov rax,[rbp+$50]
00000000006963D3 8B4D58 mov ecx,[rbp+$58]
00000000006963D6 3B8868060000 cmp ecx,[rax+$00000668]
00000000006963DC 7D06 jnl TForm72.GetBit + $24
00000000006963DE 837D5800 cmp dword ptr [rbp+$58],$00
00000000006963E2 7D09 jnl TForm72.GetBit + $2D
In both cases, the resulting assembler uses [rax+$00000668] for FSize. What is the correct way to access a class field in Delphi 64bit Assembler?
This may sound like a strange thing to optimize but the assembler for the 64bit pascal version doesn't appear to be very efficient. We call this routine a large number of times and it takes anything up to 5 times as long to execute depending on various factors.
The basic problem is that you are using the wrong register. Self is passed as an implicit parameter, before all others. In the x64 calling convention, that means it is passed in RCX and not RAX.
So Self is passed in RCX and Index is passed in RDX. Frankly, I think it's a mistake to use parameter names in inline assembler because they hide the fact that the parameter was passed in a register. If you happen to overwrite either RDX, then that changes the apparent value of Index.
So the if statement might be coded as
CMP EDX,[RCX].FSize
JNL TBits.Error
CMP EDX,0
JL TBits.Error
FWIW, this is a really simple function to implement and I don't believe that you will need to use any stack space. You have enough registers in x64 to be able to do this entirely using volatile registers.
I ran into this issue using 64-bit inline assembler in Delphi XE3 that I don't understand.
I tried this, and it works on both 32-bit and 64-bit
function test(a, b: integer): integer; assembler; register;
asm
mov eax, a
add eax, edx
end;
However, this only works on 32-bit but not 64-bit, in 64-bit it compiles, but did not return correct result of the sum of two integers.
function test(a, b: integer): integer; assembler; register;
asm
add eax, edx
end;
I know previous FPU code such as FLD, STP works on 32-bit but it will give compilation error on 64-bit compiler. Any idea how to handle floating numbers in 64-bit ?
It is because 64-bit system uses own calling convention, and these parameters are in RCX and RDX registers. More explanations in MSDN.
About handling floating numbers - read Intel developer documentation.