Mezcla de archivos C y de ensamblaje

Quiero usar un naked function in my C++ program using g++. Unfortunately g++, unlike VC++, does not support naked functions and the only way to manage this is to write your own assembly code in a separate file and link with your C++ files. I tried to find some good tutorial for x86 to mix assembly and C/C++ files but couldn't find any good one.

Kindly let me know if you know about any. Note that I'm not asking about inline assembly but linking C and assembly files and ways to declare extern variables of C in assembly and vice versa besides using them in either C or assembly, and also ways to link the C and asm files using Makefile.

preguntado el 08 de noviembre de 11 a las 17:11

Try and make this more specific - it's rather broad and vague as it stands - otherwise it will probably be closed as "not a real question". Also recall the earlier advice in your earlier questions about using gcc -S ... to produce asm templates etc. -

gcc in the same vein has __attribute__((naked)) but not for x86 :( -

@MetallicPriest did you ever find out how to accomplish what you asked? I'm having the same problems. -

@YuriyF: gcc has supported __attribute__((naked)) on x86 for a couple versions, maybe since gcc6. -

4 Respuestas

In C++ file:

extern "C" void foo(); // Stop name mangling games

int main() {
  foo();
}

in "naked" asm file, for x86:

# modified from http://asm.sourceforge.net/howto/hello.html

.text                   # section declaration
    .global foo

foo:

# write our string to stdout

    movl    $len,%edx   # third argument: message length
    movl    $msg,%ecx   # second argument: pointer to message to write
    movl    $1,%ebx     # first argument: file handle (stdout)
    movl    $4,%eax     # system call number (sys_write)
    int $0x80       # call kernel

# and exit

    movl    $0,%ebx     # first argument: exit code
    movl    $1,%eax     # system call number (sys_exit)
    int $0x80       # call kernel

.data                   # section declaration

msg:
    .ascii  "Hello, world!\n"   # our dear string
    len = . - msg           # length of our dear string

Compile, assemble and link (with g++ rather than ld because it's much easier to do it that way for C++) and run:

ajw@rapunzel:/tmp > g++ -Wall -Wextra test.cc -c -o test.o
ajw@rapunzel:/tmp > as -o asm.o asm.S
ajw@rapunzel:/tmp > g++ test.o asm.o
ajw@rapunzel:/tmp > ./a.out
Hello, world!

If you want to pass arguments to your function or return anything you need to respect the calling conventions.

contestado el 03 de mayo de 18 a las 08:05

Here's an example of a trick to achieve the "naked function" effect.

#include <stdio.h>

extern "C" int naked_func ();

static void
dummy ()
{
  __asm__ __volatile__
    (
     "  .global naked_func\n"
     "naked_func:\n"
     "  movq    $3141569, %rax\n"
     "  ret\n"
     );
}

int
main ()
{
  printf ("%d\n", naked_func ());
  return 0;
}

respondido 12 nov., 11:00

You don't even need the dummy function as a wrapper as basic inline assembly can be placed at global scope. - Michael Petch

This is my way to define a function in assembly, this does not need to have a seperate assembler-file nor you need to escape every newline. You can just copy the contents of the assembly files into the string-literal. Nota: La raw multiline string literal is a C++11 feature (you also tagged C++). This is useful, if you want to compile everything in a single .c-/.cpp-archivo.

extern"C" int rand_byte(void);
asm (R"(
    .globl rand_byte
rand_byte:
    call rand
    and eax, 255
    ret
)");

You can only use a basic assembly-statement without additional parameters at global scope. When using GCC or Clang and an arm processor, you are able to use [[gnu::naked]]/__attribute__((naked)).

    [[gnu::naked]]
int rand_byte(void) {
    asm volatile (R"(
        push {lr}
        bl rand
        and r0, #255
        pop {pc}
    )");
};

The first way always allows to define naked functions. This also helps to make more portable code.

    extern"C" int _cdecl rand_byte(void);
    #if defined __x86__
        // NOTE: the names are decorated with a '_' on windows 32-bit
        // You can use extern"C" int _cdecl rand_byte() asm("rand_byte");
        // to make the asm symbol name always be rand_byte, without an _
        asm volatile (R"(
            .globl _rand_byte
        _rand_byte:
            call rand
            and eax, 255
            ret
        )");
    #elif defined __x86_64__
        asm volatile (R"(
            .globl rand_byte
        rand_byte:
            call rand
            and rax, 255    # eax works here, too.  x86-32 and -64 could share the same source.
            ret
        )");
    #elif defined __arm__
        asm (R"(
            .global rand_byte
        rand_byte:
            push {lr}
            bl rand
            and r0, #255
            pop {pc}
        )");
    #else
        #error There is no code for your platform yet...
    #endif

Respondido el 10 de enero de 18 a las 15:01

You can use a basic ASM statement at global scope in GCC/G++. You can't use extended inline assembly at global scope. A basic assembly statement is always implicitly volatile per the documentation. - Michael Petch

Your x86 vs. x86-64 example is a bit silly. and eax, 255 does what you want on both x86-32 and x86-64, and is one byte shorter than and rax, 255. También, movzx eax, al is slightly better on both, because it's shorter. Are you sure this even assembles? The gas directive is .globl or .globalno, global. Utilizando gcc -masm=intel still uses GAS directives, not NASM. (It will compile fine, but not assemble. On godbolt, use "binary" mode to make sure it assembles as well as compiles.) Also, ARM doesn't have eax, tiene r0. - Peter Cordes

Of course, asm for rand_byte is silly anyway. Much better to just make uint8_t rand_byte a weak alias for rand when the calling convention allows narrow return values to have high garbage (like on x86 but not ARM), and let the caller inline the zero-extension if it needs it. It's nearly impossible to come up with examples for inline asm where it wouldn't be better to simply gcc.gnu.org/wiki/DontUseInlineAsm, though, but you might want to at least mention it. (Except maybe privileged instructions that don't have builtin / intrinsic wrappers). - Peter Cordes

Still doesn't assemble: godbolt.org/g/fP3p1j. You can't use NASM directives like global in GNU C inline-asm. It seems you only fixed it for ARM. - Peter Cordes

I think this answer is correct now, but you should test it on godbolt. I'm not sure R"(...)" works in C11, so the suggestion to use it in both .c y .cpp files might be bogus. Use -xc -std=gnu11 on Godbolt to compile as C. - Peter Cordes

I just want to add one thing to previous post. Imagine you want a function, that accept arguments: (something like

int add(int,int);

prototipo)

segment .text
global add

   add:
   enter 0,0
   mov eax,[ebp+8]  ; first argument
   mov ebx,[ebp+12]  ; second argument
   add eax,ebx
   leave
   ret

respondido 09 nov., 11:02

yes, except don't use the slow enter instrucción. agner.org/optimize. There's no reason to build a stack frame here at all. Just mov eax, [esp+4] / add eax, [esp+8] / ret. Also, don't clobber ebx; it's a call-preserved register in all the usual calling conventions. Downvoting for a dangerous (and inefficient) example, and that you didn't show how to build + link this with NASM. - Peter Cordes

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.