考虑这个功能:
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)。
答案 0 :(得分:4)
你想要完成什么并不完全清楚。看起来你想要一个中断处理程序来执行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位可以通过单独保存寄存器来完成,而不是使用PUSHA和POPA。
注意:如果在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;
}
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.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 );