Assembly and C++ with Visual Studio 2015 and MASM x86/64

Asked

Viewed 445 times

1

Good night.

I use Visual Studio 2015 and usually programming in Assembly language via __asm, but I still have doubts about how to use MASM to create code in Assembly for x64. I need help to take the first steps.

How I create Assembly functions in an "asm" format file and call them in a "cpp" format file"?

I ask you to show me how to proceed in the following example, because from it I will search and learn other details. Consider a project with two files, "Header. h" and "Source.cpp", programmed for x86.

The "Header. h" file has the function formula(a,b,ret2), you want to write in Assembly in a source file in Assembly, for example "Asmsource.asm".

// Header.h
// formula(a,b,ret2) = a*a + b*b
// *ret2 = ( a*b )/( a*a/b + b*b/a )

int __cdecl formula( int a , int b , int *ret2 ){
    int sqa , sqb , dv ;
    __asm {
        MOV eax , dword ptr [a]
        MUL eax
        MOV dword ptr [sqa] , eax
        DIV dword ptr [b]
        MOV dword ptr [dv] , eax
        MOV eax , dword ptr [b]
        MUL eax
        MOV dword ptr [sqb] , eax
        DIV dword ptr [a]
        ADD dword ptr [dv] , eax
        MOV eax , dword ptr [a]
        MUL dword ptr [b]
        DIV dword ptr [dv]
        MOV edi , [ret2]
        MOV [edi] , eax
        MOV eax , dword ptr [sqa]
        ADD eax , dword ptr [sqb]
    }
    return ;
}

The "Source.cpp" file has the function main() calling of function that should be written in Assembly.

// Source.cpp
# include <stdio.h>
# include <conio.h>
# include "Header.h"

int main(){
    int ret1 , ret2 ;
    ret1 = formula(12,16,&ret2) ;
    printf(" %i %i\n",ret1,ret2) ;
    _getch() ;
}

If we change "Header. h" for an "Asmsource.asm" file, how should the "Asmsource.asm" and "Source.cpp" files be written so that the same result is obtained? I need to know what changes coming out of __asm to the Assembly code file.

Thank you.

1 answer

1


To create files with functions in Assembly 64 bits using Visual Studio 2015 (Community):

  • Right-click your project, enter the menu Build Dependencies and then in Build Customizations.... On the customization screen, select the option masm(.targets, .props):

Tela de customizações do projeto no VS2015


  • In your project, in the folder Source Files, add a new item Utility of the kind Text File (.txt) (text file). After adding, rename the file from .txt for .asm (example: funcao.txt for funcao.asm):

Solution Explorer mostrando arquivo asm


  • Right-click on the file funcao.asm and select Properties. In the properties dialog, change the Item Type of Text for Microsoft Macro Assembler:

Alteração do tipo do item


  • In the source code C, declare the prototype of the functions as extern. If the program is in C++, declare as extern "C". In your example, the main file looks like this:
#include "stdafx.h"

// delcaração da função em assembly
extern "C" long long int formula(long long int a, long long int b, long long int *r2);

// Função em C que implementa o mesmo cálculo (apenas para teste)
long long int formula2(long long int a, long long int b, long long int *r2)
{
    *r2 = (a*b) / (a*a / b + b*b / a);
    return a*a + b*b;
}

int main(int argc, char *argv[])
{
    long long int ret1;
    long long int ret2;

    printf("inicio\n");
    // Execução em C
    ret1 = formula2(12, 16, &ret2);
    printf("Resultado C: ret1=%lld, ret2=%lld\n", ret1, ret2);

    // Execução em Assembly
    ret1 = formula(12, 16, &ret2);
    printf("Resultado ASM: ret1=%lld, ret2=%lld\n", ret1, ret2);

    printf("fim.\n");
    getchar();
    return 0;
}

The guy long long int defines the variables ret1 and ret2 as 64bit integers.

According to the logic you want to implement, the type can change and the way the Assembly code will be developed, too.


  • In the source code Assembly, declare the functions as public. For your example, the file funcao.asm was like this:
.code
public formula    
;  formula(a,b,ret2) = a*a + b*b
; *ret2 = ( a*b )/( a*a/b + b*b/a )

formula proc
    mov         r11, rdx                ; r11 <- b
    xor         rdx, rdx                ; rdx = 0
    mov         rax, rcx
    imul        rax, rax                        
    mov         r9, rax                 ; r9 <- a^2

    mov         rax, r11
    imul        rax, rax                        
    mov         r10, rax                ; r10 <- b^2

    idiv        rcx
    mov         r12, rax                ; r12 <- b^2/a

    xor         rdx, rdx
    mov         rax, r9
    idiv        r11
    add         rax, r12
    mov         r13, rax                ; r13 <- b^2/a + a^2/b

    xor         rdx, rdx
    mov         rax, rcx
    imul        rax, r11
    idiv        r13
    mov         qword ptr [r8], rax     ; resultado em rax

    mov         rax, r9
    add         rax, r10
    ret
formula endp

end 

In Assembly 64 bits, the passing of parameters, declaration of local variables, return of functions, etc. are different from the Assembly 32-bit.

Here are some links (in English) that talk about the subject (for programming Windows, the main is the first link):


The result, after the implementation of the programme:

inicio
Resultado C: ret1=400, ret2=6
Resultado ASM: ret1=400, ret2=6
fim.

Edit

In response to the comment, to compile a mixed code (32 and 64 bits), according to the target defined in VS2015:

  • Right-click on the file funcao.asm and goes into Properties. In the properties window, change (if necessary) the items: Configuration for All Configurations and Platform for Win32 or Active(Win32) (if this platform is already selected)
  • Change the item Excluded From Build for Yes

Propriedades do arquivo funcao.asm

With this change, the archive funcao.asm will only be compiled when the platform is set to 64 bits.


In the source code, use the macro _WIN64 to define the prototype of the functions as extern for 64bits, or with Assembly inline (for example) for 32bits.

The important point (if possible) is: maintain the prototype of identical functions for both 32 and 64bit.

After the changes, the source code in C is as follows:

#include "stdafx.h"

#ifdef _WIN64
// Código 64 bits
// delcaração da função em assembly
extern "C" long long int formula(long long int a, long long int b, long long int *r2);

#else
// Código 32 bits
// Inline
long long int _cdecl formula(long long int a, long long int b, long long int *ret2) {
    int sqa, sqb, dv;
    int ret_local; // Acrescentei esta variável
    __asm {
        mov     eax, dword ptr[a]
        mul     eax
        mov     dword ptr[sqa], eax
        div     dword ptr[b]
        mov     dword ptr[dv], eax
        mov     eax, dword ptr[b]
        mul     eax
        mov     dword ptr[sqb], eax
        div     dword ptr[a]
        add     dword ptr[dv], eax
        mov     eax, dword ptr[a]
        mul     dword ptr[b]
        div     dword ptr[dv]
        mov     [ret2], eax         ; Aqui, o código estava incorreto
        mov     eax, dword ptr[sqa]
        add     eax, dword ptr[sqb]
        mov     [ret_local], eax    ; Armazena o retorno
    }
    return (long long int) ret_local;
}
#endif // _WIN64

// Função em C que implementa o mesmo cálculo (apenas para teste)
long long int formula2(long long int a, long long int b, long long int *r2)
{
    *r2 = (a*b) / (a*a / b + b*b / a);
    return a*a + b*b;
}

int main(int argc, char *argv[])
{
    long long int ret1;
    long long int ret2;

    printf("inicio\n");
    // Execução em C
    ret1 = formula2(12, 16, &ret2);
    printf("Resultado C: ret1=%lld, ret2=%lld\n", ret1, ret2);

    // Execução em Assembly
    ret1 = formula(12, 16, &ret2);
    printf("Resultado ASM: ret1=%lld, ret2=%lld\n", ret1, ret2);

    printf("fim.\n");
    getchar();
    return 0;
}

Note: I just made a small correction in the code inline and added a local variable to return the function result.

The result of the execution is the same informed in the above answer.


Obs2: There are other, possibly better, ways to organize mixed code (32 and 64bits) within the Solution VS2015. This answer is just a "starting point" to show how to compile mixed code.

  • Thank you. I’ll test it all out.

  • It worked when compiling for x64, but not when I compiled for x86 (even putting "# ifdef _WIN64" conditional to decide whether to use "extern" or "include"). Have some way to make the compiler select which code will be used according to the architecture without giving several errors?

  • Errors were (only) in compiling the file funcao.asm?

  • I edited the answer. Please check if it is ok :)

  • The various errors have been associated with the "file. asm, "was probably interpreting it as C++ code because the first errors referred to post-point and comma comments.

  • One thing I would like to do is use "# ifndef" in the file ". asm" and write the x86 and x64 codes there. It is possible?

  • @RHERWOLF believes that it is not possible, because the formats of the objects generated for x86 and x64 are different and the pre-processor directive will only change the contents of the file and not its format. To analyze the generated format, try entering the directory where the file is funcao.obj and enter the command dumpbin /all funcao.obj. You will see that the file type is 8664 machine (x64), therefore the Linker will not be able to connect the 64bit object with the other objects generated by C (32bits).

  • Okay. Thanks for your help.

  • @RHERWOLF You’re welcome :)

Show 4 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.