Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

64-bit Equivalent to BSWAP in X86 Image Handling Routine

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.

like image 753
Gordon Turner Avatar asked Oct 19 '21 21:10

Gordon Turner


Video Answer


2 Answers

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;
like image 66
CherryDT Avatar answered Oct 19 '22 23:10

CherryDT


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

like image 32
Dalija Prasnikar Avatar answered Oct 20 '22 00:10

Dalija Prasnikar