Question

I have a problem. I have following x86 delphi code which is written in ASM. I need to port this to AMD64?

type
 TCPUID = array[1..4] of Longint;

function GetCID : TCPUID; assembler; register;
asm
  push  ebx
  push  edi
  mov   edi, eax
  mov   eax, 1
  dw    $A20F
  stosd
  mov   eax, ebx
  stosd
  mov   eax, ecx
  stosd
  mov   eax, edx
  stosd
  pop   edi
  pop   ebx
end;

I have never programmed in assembly, does anyone know what the port would be or how I would go about changing it.

Was it helpful?

Solution

I am not Win64 assembler guru, but the next translation worked for me (tested on 64-bit free pascal):

program project1;

{$mode delphi}
{$asmmode intel}

type
 TCPUID = array[1..4] of Longint;

function GetCID: TCPUID;
asm
  push  rbx
  push  rdi
  mov   rdi, rcx
  mov   eax, 1
  cpuid
  mov   [rdi],eax
  add   rdi,4
  mov   [rdi],ebx
  add   rdi,4
  mov   [rdi],ecx
  add   rdi,4
  mov   [rdi],edx
  pop   rdi
  pop   rbx
end;

var ID: TCPUID;

begin
  ID:= GetCID;
  Writeln(ID[1], '-', ID[2], '-', ID[3], '-', ID[4]);
  Readln;
end.

OTHER TIPS

Here's my version, for both x86 and x64:

function GetCPUID: TCPUID;
asm
{$IF Defined(CPUX86)}
  push  ebx
  push  edi
  mov   edi, eax
  mov   eax, 1
  xor   ecx,ecx
  cpuid
  mov   [edi+$0], eax
  mov   [edi+$4], ebx
  mov   [edi+$8], ecx
  mov   [edi+$c], edx
  pop   edi
  pop   ebx
{$ELSEIF Defined(CPUX64)}
  mov   r8, rbx
  mov   r9, rcx
  mov   eax, 1
  cpuid
  mov   [r9+$0], eax
  mov   [r9+$4], ebx
  mov   [r9+$8], ecx
  mov   [r9+$c], edx
  mov   rbx, r8
{$IFEND}
end;

One of the nice things about x64 is that there are a lot more registers available, many of which are volatile. So we can make use of that scratch space and avoid touching main memory at all. Well obviously we have to touch main memory to return the result.

Since RBX is nonvolatile we preserve its value. All the other registers that we modify are volatile and so we need not preserve them. I can't think of any way to simplify this further.

This can readily be extended to allow the input to CPUID to be passed as an argument:

function GetCPUID(ID: Integer): TCPUID;
asm
{$IF Defined(CPUX86)}
  push  ebx
  push  edi
  mov   edi, edx
  xor   ecx,ecx
  cpuid
  mov   [edi+$0], eax
  mov   [edi+$4], ebx
  mov   [edi+$8], ecx
  mov   [edi+$c], edx
  pop   edi
  pop   ebx
{$ELSEIF Defined(CPUX64)}
  mov   r8, rbx
  mov   r9, rcx
  mov   eax, edx
  cpuid
  mov   [r9+$0], eax
  mov   [r9+$4], ebx
  mov   [r9+$8], ecx
  mov   [r9+$c], edx
  mov   rbx, r8
{$ELSE}
  {$Message Fatal 'GetCPUID has not been implemented for this architecture.'}
{$IFEND}
end;

This assumes a sub-leaf value of 0, passed in ECX. Again, if you wish to pass that, it is easy enough:

function GetCPUID(Leaf, Subleaf: Integer): TCPUID;
asm
{$IF Defined(CPUX86)}
  push  ebx
  push  edi
  mov   edi, ecx
  mov   ecx, edx
  cpuid
  mov   [edi+$0], eax
  mov   [edi+$4], ebx
  mov   [edi+$8], ecx
  mov   [edi+$c], edx
  pop   edi
  pop   ebx
{$ELSEIF Defined(CPUX64)}
  mov   r9,rcx
  mov   ecx,r8d
  mov   r8,rbx
  mov   eax,edx
  cpuid
  mov   [r9+$0], eax
  mov   [r9+$4], ebx
  mov   [r9+$8], ecx
  mov   [r9+$c], edx
  mov   rbx, r8
{$ELSE}
  {$Message Fatal 'GetCPUID has not been implemented for this architecture.'}
{$IFEND}
end;

I never worked with CPUID so i don't know for sure what it does. But from common sense and Wikipedia (if those sources would suffice) my advices are:

Try to
1) remove "assembler;" keyword - obsolete
1.1) optionally remove "register;" - it is by default and has little value for no-parameters function. Also Wikipedia tells that has no effect in Win64.

2) if possible - rephrase this as procedure GetCID (out data: TCPUID);. If need function - i'd rather made inline wrapper in Pascal - just to keep definition simple and obvious. That is a good advice to author - to keep not-automated things simplistic and leave syntax-sugar automation to Pascal, especially when you have no experience and any not simple trick can confuse you and cause you to type broken code. KISS principle.

3) remove push ebx/pop ebx
3.1) i think push edi/popedi to be removed as well. But to be on safe side - i'd changed them to push rdi and pop rdi

The article does not tell that some registers should be saved or preserved: http://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions
Nor that requirement is told at http://docwiki.embarcadero.com/RADStudio/XE3/en/Assembly_Procedures_and_Functions

4) mov edi, eax -> mov rdi, rcx
4.1) you may add cld command next after it, as an extra safety measure. But it should be overcautious and redundant.

The rest should be the same as it seems x64 has the same convention for CPUID as x86 mode - no mentio nof x64 mode at http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits

To sum it up it should be like

type
 TCPUID = packed array[1..4] of INT32;

function GetCID : TCPUID; inline;
begin
  GetCID_Implementation(Result);
end;

procedure GetCID_Implementation (out buffer: TCPUID);
asm
  mov   rdi, rcx  // mov edi, eax
  // RCX/EAX is Delphi/Windows pointer to 1st parameter
  // RDI/EDI is CPU pointer to output buffer

//  cld  - optionally, should not be needed
//  state of cld should be preserved by all other functions

  xor   eax, eax  // shorter way for eax := 1
  inc   eax

  dw    $A20F     // call CPUID(eax==1),
// output (according to wikipedia) is in eax,edx,ecx,ebx  32-bit registers

  stosd           // *( (INT32*)RDI )++ := eax
  mov   eax, ebx  // xchg eax, ebx would be shorter,on that
  stosd
  mov   eax, ecx  // but Delphi XE2 is broken with xchg eax
  stosd
  mov   eax, edx
  stosd
end;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top