【问题标题】:Creating a C function without compiler generated prologue/epilogue & RET instruction?在没有编译器生成的序言/结尾和 RET 指令的情况下创建 C 函数?
【发布时间】:2017-09-04 18:16:38
【问题描述】:

考虑这个函数:

void foo(){
    //do something
}

在汇编中它看起来像这样(不准确):

push something

;do stuff

pop something
ret

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

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

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

; do something
iret

没有 PUSHPOPRET。是否有任何预处理器指令或关键字可以让我完成此操作?

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

【问题讨论】:

  • 你试过 - 内联吗?
  • gcc 在某些架构上为此具有 naked 属性 - 但不是 x86 AFAIK。
  • 除非您正在编写自己的操作系统,否则您无需编写 ISR。在 Windows 下,中断由操作系统处理,并根据需要将它们分派给设备驱动程序。
  • @RossRidge :他的帖子历史表明这可能与 OSdev 有关,或者至少我假设他知道自己在做什么。

标签: c gcc assembly x86 nasm


【解决方案1】:

我猜你想要在不让 C 编译器做任何事情的情况下拥有一个函数的语法。您可以通过链接到您的汇编函数来做到这一点(然后它当然需要将正确的东西复制到堆栈上)。

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

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

global my_function 
my_function:
push r1
push r2
; Code 
ret

对应:

void  my_function ( int arg1, char arg2 ); 

【讨论】:

  • 是的,像这样,但我不想在汇编中编码,这就是我的全部问题,我可以这样做,但是我也必须在汇编中对函数进行编码,但我不想那样。
【解决方案2】:

我找到了一个巧妙的解决方法:

在汇编中定义函数但调用外部 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编译

【讨论】:

  • 你是在Windows下编译组装吗? Linux?您使用的是哪个 C 编译器?海合会?微软?叮当声等?
  • @Michael Petch 修复了它
  • @Michael Petch 这有点像“裸骨”或伪代码,只是为了说明如何完成这样的任务
  • 我看到您在 Windows 上使用 GCC 更新了您的问题。谢谢,具体的。我在问题中添加了 GCC 标记。你甚至不需要 NASM,它可以完全在 C 中使用基本的汇编语句来完成。
  • 或者更简单地说,您可以在 .S 文件中以 GAS 语法而不是 NASM 编写中断处理程序,并使用 gcc 构建它。 (如果你愿意,可以使用-m32bits 32.code32 不要更改目标文件格式,它们只是让您将 32 位机器代码放在不属于它的位置(例如 64 位目标文件) ,所以不要使用这些指令。)
【解决方案3】:

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


海合会

使用 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 语言的特定扩展。对于使用裸存储类属性声明的函数,编译器生成的代码没有 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),这将非常方便。另一个示例是允许中断将所有寄存器内容转储到显示器以进行调试。

【讨论】:

    猜你喜欢
    • 2021-11-18
    • 1970-01-01
    • 1970-01-01
    • 2016-06-01
    • 2012-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-02
    相关资源
    最近更新 更多