在没有编译器的情况下创建C函数生成了序言/结尾& RET指令?

时间:2017-04-09 19:03:36

标签: c gcc assembly x86 nasm

考虑这个功能:

void foo(){
    //do something
}

在装配中,它看起来像这样(不准确):

push something

;do stuff

pop something
ret

但我不想要这个生成的代码( RET PUSH POP ...)。我只想要一个代码块的标签,所以我必须回复自己:

void bar(){
    //do something
    asm("iret") //i want to use this function as a ISR
}

并且在汇编中它看起来像这样:

; do something
iret

没有 PUSH POP RET 。是否有任何预处理器指令或关键字可以让我实现这一目标?

我在Windows下使用 GCC NASM ,我正在尝试生成自己的中断服务例程(ISR)。

3 个答案:

答案 0 :(得分:4)

你想要完成什么并不完全清楚。看起来你想要一个中断处理程序来执行iret默认情况下没有其他推送和弹出。

GCC

使用 GCC (不使用 NASM )可能会出现这样的情况:

/* 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;
}
GCC 中的

基本__asm__语句可以放在函数之外。我们为中断服务例程(ISR)定义标签,并使用.globl使其外部可见(您可能不需要全局可见性,但无论如何我都会显示它)。

我创建了几个示例中断服务例程。一个只执行iret而另一个执行函数调用 C 处理程序。我们保存所有寄存器并在之后恢复它们。 C 函数需要向前设置方向标志,因此在调用 C 函数之前我们需要CLD。此示例代码适用于32位目标。 64位可以通过单独保存寄存器来完成,而不是使用PUSHAPOPA

注意:如果在Windows上使用 GCC 内部的功能名称可能需要前面加上_(下划线)。它看起来像是:

/* 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的 C / C ++ 编译器支持函数的naked属性。他们将此属性描述为:

  

裸存储类属性是特定于Microsoft的C语言扩展。对于使用naked storage-class属性声明的函数,编译器生成没有prolog和epilog代码的代码。您可以使用此功能使用内联汇编程序代码编写自己的prolog / epilog代码序列。裸函数在编写虚拟设备驱动程序时特别有用。

示例中断服务例程可以这样做:

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

您需要处理保存和恢复寄存器的问题,自己设置方向标志的方式与上面的 GCC 示例类似。

GCC 7.x +在x86 / x86-64目标上引入了中断属性

在GCC 7.0+上,您现在可以在函数上使用__attribute__((interrupt))。此属性最近仅在x86和x86-64目标上受支持:

  

中断

     

使用此属性指示指定的函数是中断处理程序或异常处理程序(取决于传递给函数的参数,进一步说明)。当存在此属性时,编译器生成适合在中断处理程序中使用的函数入口和出口序列。 IRET指令而不是RET指令用于从中断处理程序返回。除了由IRET指令恢复的EFLAGS寄存器外,所有寄存器都由编译器保留。由于GCC不保留MPX,SSE,MMX和x87状态,因此应使用GCC选项-mgeneral-regs-only来编译中断和异常处理程序。

这种方法仍有不足之处。如果您希望 C 代码访问中断时出现的寄存器内容,则目前没有可靠方法来实现此机制。如果您正在编写软件中断并需要访问寄存器以确定要采取的操作(例如:Linux上的int 0x80),这将非常方便。另一个例子是允许中断将所有寄存器内容转储到显示器以进行调试。

答案 1 :(得分:2)

我找到了一个简洁的解决方法:

在程序集中定义函数但调用extern c函数:

bits 32

global _bar
extern _foo

section .data

section .text

_bar:
    call _foo
    iret

在C:

void foo(){
    //do your stuff here
}

extern void bar();

//bar is now your "naked" function

在windows下用nasm和gcc编译

答案 2 :(得分:0)

我猜你想要一个没有C编译器做任何事情的函数的语法。你可以通过链接到汇编函数来实现这一点(然后它需要将正确的东西复制到堆栈中)。

您的汇编例程只需要C编译器调用的入口点,例如:

程序集部分将包含以下标题:

global my_function 
my_function:
push r1
push r2
; Code 
ret

对应于:

void  my_function ( int arg1, char arg2 );