C/C++ Function Definitions Without Assembly

Why does an invalid use of C function compile fine without any warnings?

The compiler should issue a message for this program

int foo() {
return 3;
}

int main() {
int x;
foo(&x);
return x;
}

because the function foo called with an argument though its identifier list is empty. So the program has undefined behavior.

According to the C Standard *6.7.6.3 Function declarators (including prototypes)
)

14 An identifier list declares only the identifiers of the parameters
of the function. An empty list in a function declarator that is part
of a definition of that function specifies that the function has no
parameters.
The empty list in a function declarator that is not part
of a definition of that function specifies that no information about
the number or types of the parameters is supplied.

So the program is invalid.

You could make it a valid program the following way

int foo();

int main() {
int x;
foo(&x);
return x;
}

int foo( int *p ) {
return 3;
}

Though the compiler can issue a warning that the parameter p is not used.

In this case the function declaration that is not its definition means that there is no information about the number and types of parameters.

Opposite to C in C++ this declaration

int foo();

is equivalent to

int foo( void );

Is there a way to define a function with an implementation with a different name?

void foo(bool var) {
bar(var);
}

Either the function is global, or it's not. You have to take a decision; you cannot have both.

If it's just a wrapper in a header, then you can make it static inline:

// bar.h
static inline void foo (bool var)
{
bar (var);
}

It should be clear that you cannot use this together with different definitions / declarations in foo.h.

If you just want a function that's called as foo to use bar as assembly name, then you can use the following C-extension from GNU-C:

// bar.h
void foo (bool) __asm ("bar");

This means bar will be the assembly name of foo. This might avoid an additional call step. Of course, that also cannot be used with a conflicting definition / declaration of foo. Also notice that you might have to adjust the assembly name, for example i386 platforms usually use a leading _ (underscore) so that you'd have to use assembly name _bar. And for C++ you'd need the mangled name.

Bottom line: In each compilation unit (module) you have to decide what
implementation of foo you want to use, and depending on that, include either foo.h or the conflicting bar.h.

C function definition and declaration

The thing you have to remember is that the compile is pretty much sequential when it comes to declarations and definition. When the compiler processes the call for function all it has is the declaration that, as you say, doesn't have any assumptions about the arguments, which means you can call the function with any argument you like. When the compiler sees the definition, it doesn't go back to issue an error about the call, it might issue a warning though.

As for the implicit function, when the compiler first sees it it will assume that the arguments are what you pass in the call when it deduces the declaration. Again it will not know anything else until it later sees the declaration, and may issue a warning then.

Calling a function with to many, or to few, arguments leads to undefined behavior, which is why implicitly declared functions are so dangerous, as well as using an empty argument list when declaring functions.

Issue defining a function in inline assembly and calling from c++

For such tricks, you need to explicitly provide function specification

#include <stdio.h>

asm("one: \n\
movl $1,%eax \n\
ret \n\
");

extern "C" int one();

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

You probably can find more explanations about extern "C" in
What is the effect of extern "C" in C++?

Creating a C function without compiler generated prologue/epilogue & RET instruction?

It's not entirely clear what you want to accomplish. it seems like you want an interrupt handler that does the iret without other pushes and pops by default.


GCC

Using GCC (without NASM) something like this is possible:

/* Make C extern declarations of the ISR entry points */    
extern void isr_test1(void);
extern void isr_test2(void);

/* Define a do nothing ISR stub */
__asm__(".global isr_test1\n"
"isr_test1:\n\t"
/* Other stuff here */
"iret");

/* Define an ISR stub that makes a call to a C function */
__asm__(".global isr_test2\n"
"isr_test2:\n\t"
"cld\n\t" /* Set direction flag forward for C functions */
"pusha\n\t" /* Save all the registers */
/* Other stuff here */
"call isr_test2_handler\n\t"
"popa\n\t" /* Restore all the registers */
"iret");

void isr_test2_handler(void)
{
return;
}

Basic __asm__ statements in GCC can be placed outside of a function. We define labels for our Interrupt Service Routines (ISRs) and make them externally visible with .globl (You may not need global visibility but I show it anyway).

I create a couple of sample interrupt service routines. One that does nothing more than an iret and the other that makes a function call to a C handler. We save all the registers and restore them after. C functions require the direction flag be set forward so we need a CLD before calling the C function. This sample code works for 32-bit targets. 64-bit can be done by saving the registers individually rather than using PUSHA and POPA.

Note: If using GCC on Windows the function names inside the assembly blocks will likely need to be prepended with an _ (underscore). It would look like:

/* Make C extern declarations of the ISR entry points */    
extern void isr_test1(void);
extern void isr_test2(void);

/* Define a do nothing ISR stub */
__asm__(".global _isr_test1\n"
"_isr_test1:\n\t"
/* Other stuff here */
"iret");

/* Define an ISR stub that makes a call to a C function */
__asm__(".global _isr_test2\n"
"_isr_test2:\n\t"
"cld\n\t" /* Set direction flag forward for C functions */
"pusha\n\t" /* Save all the registers */
/* Other stuff here */
"call _isr_test2_handler\n\t"
"popa\n\t" /* Restore all the registers */
"iret");

void isr_test2_handler(void)
{
return;
}

MSVC/MSVC++

Microsoft's C/C++ compilers support the naked attribute on functions. They describe this attribute as:

The naked storage-class attribute is a Microsoft-specific extension to the C language. For functions declared with the naked storage-class attribute, the compiler generates code without prolog and epilog code. You can use this feature to write your own prolog/epilog code sequences using inline assembler code. Naked functions are particularly useful in writing virtual device drivers.

An example Interrupt Service Routine could be done like this:

__declspec(naked) int isr_test(void)
{
/* Function body */
__asm { iret };
}

You'll need to deal with the issues of saving and restoring registers, setting the direction flag yourself in a similar manner to the GCC example above.


GCC 7.x+ introduced Interrupt Attribute on x86/x86-64 Targets

On GCC 7.0+ you can now use __attribute__((interrupt)) on functions. This attribute was only recently supported on x86 and x86-64 targets:

interrupt

Use this attribute to indicate that the specified function is an interrupt handler or an exception handler (depending on parameters passed to the function, explained further). The compiler generates function entry and exit sequences suitable for use in an interrupt handler when this attribute is present. The IRET instruction, instead of the RET instruction, is used to return from interrupt handlers. All registers, except for the EFLAGS register which is restored by the IRET instruction, are preserved by the compiler. Since GCC doesn’t preserve MPX, SSE, MMX nor x87 states, the GCC option -mgeneral-regs-only should be used to compile interrupt and exception handlers.

This method still has deficiencies. If you ever want your C code to access the contents of a register as they appeared at the time of the interrupt, there is currently no reliable way to do it with this mechanism. This would be handy if you were writing a software interrupt and needed access to the registers to determine what actions to take (ie: int 0x80 on Linux). Another example would be to allow an interrupt to dump all the register contents to the display for debug purposes.



Related Topics



Leave a reply



Submit