Assembly x64

Esses dias estive trabalhando em um problema complicado envolvendo o engine do SQL Server rodando assembly .NET. A análise foi feita em um dump de memória x64 e achei que essa seria uma ótima oportunidade para escrever um blog sobre esse assunto.

 

Registradores

A arquitetura x64, também conhecida como AMD64 ou Intel-EMT64, utiliza os mesmos registradores da arquitetura x86 (32-bits: EAX, EBX, ECX, EDX, ESI, EDI) e as versões estendidas 64-bits: RAX, RBX, RCX, RDX, RSI e RDI. Existem 8 novos registradores nomeados R8, R9, .., R15 para serem utilizados livremente.

Register

Status

Use

RAX

Volatile

Return value register

RCX

Volatile

First integer argument

RDX

Volatile

Second integer argument

R8

Volatile

Third integer argument

R9

Volatile

Fourth integer argument

R10:R11

Volatile

Must be preserved as needed by caller; used in syscall/sysret instructions

R12:R15

Nonvolatile

Must be preserved by callee

RDI

Nonvolatile

Must be preserved by callee

RSI

Nonvolatile

Must be preserved by callee

RBX

Nonvolatile

Must be preserved by callee

RBP

Nonvolatile

May be used as a frame pointer; must be preserved by callee

RSP

Nonvolatile

Stack pointer

XMM0

Volatile

First FP argument

XMM1

Volatile

Second FP argument

XMM2

Volatile

Third FP argument

XMM3

Volatile

Fourth FP argument

XMM4:XMM5

Volatile

Must be preserved as needed by caller

XMM6:XMM15

Nonvolatile

Must be preserved as needed by callee.

Ref: MSDN - Register Usage - https://msdn.microsoft.com/en-us/library/9z1stfyw.aspx

Calling Convention

O padrão utilizado no Windows x64 é fast call, no qual os primeiros 4 parâmetros na chamada de função são passados através dos registradores RCX, RDX, R8 e R9. Parâmetros adicionais devem ser passados através da stack.

Stack

A função chamadora (caller) é responsável por reservar o espaço para passagem de parâmetros para a função chamada (callee). Na plataforma x64, essa área é de no mínimo 4 parâmetros (8 bytes) mesmo quando não há parâmetros especificados.

image

Ref: MSDN – Stack Allocation

Exemplo:

 __int64 comando(__int64 a, __int64 b, __int64 c, __int64 d, __int64 e)
{
    return a+b+c+d+e;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    __int64 resultado;
    
    resultado = comando(0x1111111111, 0x2222222222, 
                         0x333333333333, 0x444444444444, 
                         0x55555555); 
 

    return 0;
}

_tmain(): Os 4 primeiros parâmetros são passados através dos registradores RCX, RDX, R8, R9, e o quinto parâmetro é passado pela stack. O valor de retorno da função é feito no registrador RAX.

 00000001`3f992dd6 mov   qword ptr [rsp+20h],55555555h
00000001`3f992ddf mov r9,444444444444h
00000001`3f992de9 mov r8,333333333333h
00000001`3f992df3 mov rdx,2222222222h
00000001`3f992dfd mov rcx,1111111111h
00000001`3f992e07 call    Console64!comando
00000001`3f992e0c mov     qword ptr [rsp+30h],rax

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

comando(): Os parâmetros são armazenados na stack usando a área previamente reservada pelo chamador. Essa área de home de parâmetros é responsabilidade do callee e, por isso, otimizações de código podem utilizar essa área para outras finalidades.

 00000001`3f992d70 mov     qword ptr [rsp+20h],r9
00000001`3f992d75 mov     qword ptr [rsp+18h],r8
00000001`3f992d7a mov     qword ptr [rsp+10h],rdx
00000001`3f992d7f mov     qword ptr [rsp+8],rcx
00000001`3f992d84 push    rdi
00000001`3f992d85 mov     rcx,qword ptr [rsp+18h]
00000001`3f992d8a mov     rax,qword ptr [rsp+10h]
00000001`3f992d8f add     rax,rcx
00000001`3f992d92 add     rax,qword ptr [rsp+20h]
00000001`3f992d97 add     rax,qword ptr [rsp+28h]
00000001`3f992d9c add     rax,qword ptr [rsp+30h]
00000001`3f992da1 pop     rdi
00000001`3f992da2 ret

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Overview of x64 Calling Conventions
https://msdn.microsoft.com/en-us/library/ms235286.aspx

Register Usage
https://msdn.microsoft.com/en-us/library/9z1stfyw.aspx

Parameter Passing
https://msdn.microsoft.com/en-us/library/zthk2dkh.aspx

Stack Allocation
https://msdn.microsoft.com/en-us/library/ew5tede7.aspx