带有英特尔 x86-32 位汇编的 gcc:访问 C 函数参数
gcc with intel x86-32 bit assembly : accessing C function arguments
我正在做操作系统实现工作。
首先是代码:
//generate software interrupt
void generate_interrupt(int n) {
asm("mov al, byte ptr [n]");
asm("mov byte ptr [genint+1], al");
asm("jmp genint");
asm("genint:");
asm("int 0");
}
我在 gcc 中使用 -masm=intel
选项编译上面的代码。还,
这不是生成软件中断的完整代码。
我的问题是我收到错误 n undefined
,我该如何解决,请帮忙?
而且它在link时提示错误,而不是在编译时提示错误,下面是图像
当您使用 GCC 时,您必须使用 GCC-style extended asm
来访问在 C 中声明的变量,即使您使用的是 Intel 汇编语法。将 C 变量名直接写入程序集插入的能力是 MSVC 的一个特性,GCC 没有复制它。
对于这样的构造,使用 单个 装配插入也很重要,而不是连续插入多个; GCC 可以并且将相对于周围代码重新排列汇编插入,包括相对于其他汇编插入,除非您采取特定步骤来阻止它。
应该写这个特定的结构
void generate_interrupt(unsigned char n)
{
asm ("mov byte ptr [1f+1], %0\n\t"
"jmp 1f\n"
"1:\n\t"
"int 0"
: /* no outputs */ : "r" (n));
}
请注意,我已经删除了初始的 mov
和所有涉及 A 寄存器的坚持,而是告诉 GCC 使用 "r"
输入将 n
加载到任何方便的寄存器中约束。汇编插入中最好尽量少做,寄存器的选择尽量交给编译器
我也把n
的类型改成了unsigned char
来匹配INT指令的实际需求,我用的是1f
local label语法所以如果 generate_interrupt
是一个内联函数,这会正常工作。
说了这么多,我恳请您为您的操作系统找到一个不涉及自修改代码的实施策略。好吧,除非你打算 get a whole lot more use out of the self-modifications,无论如何。
这不是您关于将参数传递到内联程序集的特定问题的答案(请参阅@zwol 的回答)。这解决了为此特定任务不必要地使用自修改代码的问题。
如果中断号在编译时已知的宏方法
使用自修改代码的替代方法是创建一个 C 宏来生成您想要的特定中断。一个技巧是您需要一个将数字转换为字符串的宏。 Stringize 宏非常常见并记录在 GCC documentation.
您可以创建一个如下所示的宏 GENERATE_INTERRUPT
:
#define STRINGIZE_INTERNAL(s) #s
#define STRINGIZE(s) STRINGIZE_INTERNAL(s)
#define GENERATE_INTERRUPT(n) asm ("int " STRINGIZE(n));
STRINGIZE
将获取一个数值并将其转换为字符串。 GENERATE_INTERRUPT
简单地获取数字,将其转换为字符串并将其附加到 INT
指令的末尾。
你这样使用它:
GENERATE_INTERRUPT(0);
GENERATE_INTERRUPT(3);
GENERATE_INTERRUPT(255);
生成的指令应如下所示:
int 0x0
int3
int 0xff
如果中断号仅在运行时间
已知,则跳转Table方法
如果您需要调用仅在 运行 时已知的中断,那么可以创建 table 中断调用(使用 int
指令),然后是 ret
. generate_interrupt
然后会简单地从堆栈中检索中断编号,计算 table 中可以找到特定 int
的位置,然后 jmp
到它。
在下面的代码中,我让 GNU 汇编器生成 256 个中断调用的 table,每个中断调用后跟一个 ret
使用 .rept
directive。每个代码片段适合 4 个字节。结果代码生成和 generate_interrupt
函数可能如下所示:
/* We use GNU assembly to create a table of interrupt calls followed by a ret
* using the .rept directive. 256 entries (0 to 255) are generated.
* generate_interrupt is a simple function that takes the interrupt number
* as a parameter, computes the offset in the interrupt table and jumps to it.
* The specific interrupted needed will be called followed by a RET to return
* back from the function */
extern void generate_interrupt(unsigned char int_no);
asm (".pushsection .text\n\t"
/* Generate the table of interrupt calls */
".align 4\n"
"int_jmp_table:\n\t"
"intno=0\n\t"
".rept 256\n\t"
"\tint intno\n\t"
"\tret\n\t"
"\t.align 4\n\t"
"\tintno=intno+1\n\t"
".endr\n\t"
/* generate_interrupt function */
".global generate_interrupt\n" /* Give this function global visibility */
"generate_interrupt:\n\t"
#ifdef __x86_64__
"movzx edi, dil\n\t" /* Zero extend int_no (in DIL) across RDI */
"lea rax, int_jmp_table[rip]\n\t" /* Get base of interrupt jmp table */
"lea rax, [rax+rdi*4]\n\t" /* Add table base to offset = jmp address */
"jmp rax\n\t" /* Do sepcified interrupt */
#else
"movzx eax, byte ptr 4[esp]\n\t" /* Get Zero extend int_no (arg1 on stack) */
"lea eax, int_jmp_table[eax*4]\n\t" /* Compute jump address */
"jmp eax\n\t" /* Do specified interrupt */
#endif
".popsection");
int main()
{
generate_interrupt (0);
generate_interrupt (3);
generate_interrupt (255);
}
如果您查看目标文件中生成的代码,您会发现中断调用 table (int_jmp_table
) 看起来类似于:
00000000 <int_jmp_table>:
0: cd 00 int 0x0
2: c3 ret
3: 90 nop
4: cd 01 int 0x1
6: c3 ret
7: 90 nop
8: cd 02 int 0x2
a: c3 ret
b: 90 nop
c: cc int3
d: c3 ret
e: 66 90 xchg ax,ax
10: cd 04 int 0x4
12: c3 ret
13: 90 nop
...
[snip]
因为我使用 .align 4
每个条目都被填充到 4 个字节。这使得 jmp
的地址计算更容易。
我正在做操作系统实现工作。
首先是代码:
//generate software interrupt
void generate_interrupt(int n) {
asm("mov al, byte ptr [n]");
asm("mov byte ptr [genint+1], al");
asm("jmp genint");
asm("genint:");
asm("int 0");
}
我在 gcc 中使用 -masm=intel
选项编译上面的代码。还,
这不是生成软件中断的完整代码。
我的问题是我收到错误 n undefined
,我该如何解决,请帮忙?
而且它在link时提示错误,而不是在编译时提示错误,下面是图像
当您使用 GCC 时,您必须使用 GCC-style extended asm
来访问在 C 中声明的变量,即使您使用的是 Intel 汇编语法。将 C 变量名直接写入程序集插入的能力是 MSVC 的一个特性,GCC 没有复制它。
对于这样的构造,使用 单个 装配插入也很重要,而不是连续插入多个; GCC 可以并且将相对于周围代码重新排列汇编插入,包括相对于其他汇编插入,除非您采取特定步骤来阻止它。
应该写这个特定的结构
void generate_interrupt(unsigned char n)
{
asm ("mov byte ptr [1f+1], %0\n\t"
"jmp 1f\n"
"1:\n\t"
"int 0"
: /* no outputs */ : "r" (n));
}
请注意,我已经删除了初始的 mov
和所有涉及 A 寄存器的坚持,而是告诉 GCC 使用 "r"
输入将 n
加载到任何方便的寄存器中约束。汇编插入中最好尽量少做,寄存器的选择尽量交给编译器
我也把n
的类型改成了unsigned char
来匹配INT指令的实际需求,我用的是1f
local label语法所以如果 generate_interrupt
是一个内联函数,这会正常工作。
说了这么多,我恳请您为您的操作系统找到一个不涉及自修改代码的实施策略。好吧,除非你打算 get a whole lot more use out of the self-modifications,无论如何。
这不是您关于将参数传递到内联程序集的特定问题的答案(请参阅@zwol 的回答)。这解决了为此特定任务不必要地使用自修改代码的问题。
如果中断号在编译时已知的宏方法
使用自修改代码的替代方法是创建一个 C 宏来生成您想要的特定中断。一个技巧是您需要一个将数字转换为字符串的宏。 Stringize 宏非常常见并记录在 GCC documentation.
您可以创建一个如下所示的宏 GENERATE_INTERRUPT
:
#define STRINGIZE_INTERNAL(s) #s
#define STRINGIZE(s) STRINGIZE_INTERNAL(s)
#define GENERATE_INTERRUPT(n) asm ("int " STRINGIZE(n));
STRINGIZE
将获取一个数值并将其转换为字符串。 GENERATE_INTERRUPT
简单地获取数字,将其转换为字符串并将其附加到 INT
指令的末尾。
你这样使用它:
GENERATE_INTERRUPT(0);
GENERATE_INTERRUPT(3);
GENERATE_INTERRUPT(255);
生成的指令应如下所示:
int 0x0
int3
int 0xff
如果中断号仅在运行时间
已知,则跳转Table方法如果您需要调用仅在 运行 时已知的中断,那么可以创建 table 中断调用(使用 int
指令),然后是 ret
. generate_interrupt
然后会简单地从堆栈中检索中断编号,计算 table 中可以找到特定 int
的位置,然后 jmp
到它。
在下面的代码中,我让 GNU 汇编器生成 256 个中断调用的 table,每个中断调用后跟一个 ret
使用 .rept
directive。每个代码片段适合 4 个字节。结果代码生成和 generate_interrupt
函数可能如下所示:
/* We use GNU assembly to create a table of interrupt calls followed by a ret
* using the .rept directive. 256 entries (0 to 255) are generated.
* generate_interrupt is a simple function that takes the interrupt number
* as a parameter, computes the offset in the interrupt table and jumps to it.
* The specific interrupted needed will be called followed by a RET to return
* back from the function */
extern void generate_interrupt(unsigned char int_no);
asm (".pushsection .text\n\t"
/* Generate the table of interrupt calls */
".align 4\n"
"int_jmp_table:\n\t"
"intno=0\n\t"
".rept 256\n\t"
"\tint intno\n\t"
"\tret\n\t"
"\t.align 4\n\t"
"\tintno=intno+1\n\t"
".endr\n\t"
/* generate_interrupt function */
".global generate_interrupt\n" /* Give this function global visibility */
"generate_interrupt:\n\t"
#ifdef __x86_64__
"movzx edi, dil\n\t" /* Zero extend int_no (in DIL) across RDI */
"lea rax, int_jmp_table[rip]\n\t" /* Get base of interrupt jmp table */
"lea rax, [rax+rdi*4]\n\t" /* Add table base to offset = jmp address */
"jmp rax\n\t" /* Do sepcified interrupt */
#else
"movzx eax, byte ptr 4[esp]\n\t" /* Get Zero extend int_no (arg1 on stack) */
"lea eax, int_jmp_table[eax*4]\n\t" /* Compute jump address */
"jmp eax\n\t" /* Do specified interrupt */
#endif
".popsection");
int main()
{
generate_interrupt (0);
generate_interrupt (3);
generate_interrupt (255);
}
如果您查看目标文件中生成的代码,您会发现中断调用 table (int_jmp_table
) 看起来类似于:
00000000 <int_jmp_table>:
0: cd 00 int 0x0
2: c3 ret
3: 90 nop
4: cd 01 int 0x1
6: c3 ret
7: 90 nop
8: cd 02 int 0x2
a: c3 ret
b: 90 nop
c: cc int3
d: c3 ret
e: 66 90 xchg ax,ax
10: cd 04 int 0x4
12: c3 ret
13: 90 nop
...
[snip]
因为我使用 .align 4
每个条目都被填充到 4 个字节。这使得 jmp
的地址计算更容易。