带英特尔x86-32位汇编的gcc:访问C函数参数
我正在做一个操作系统的实现工作。 以下是代码: 我使用gcc中的-masm=intel选项编译上述代码。而且 这不是生成软件中断的完整代码。 我的问题是我得到的错误为n未定义,我如何解决它,请帮助带英特尔x86-32位汇编的gcc:访问C函数参数,gcc,assembly,x86,inline-assembly,osdev,Gcc,Assembly,X86,Inline Assembly,Osdev,我正在做一个操作系统的实现工作。 以下是代码: 我使用gcc中的-masm=intel选项编译上述代码。而且 这不是生成软件中断的完整代码。 我的问题是我得到的错误为n未定义,我如何解决它,请帮助 此外,它会在链接时而不是在编译时提示错误,下面是一幅图像当您使用GCC时,您必须使用访问用C声明的变量,即使您使用的是英特尔汇编语法。将C变量名直接写入程序集插入的能力是MSVC的一项功能,GCC不会复制它 对于这样的构造,使用单个部件插入件(而不是一行中的多个)也很重要;GCC可以并且将相对周围的代
此外,它会在链接时而不是在编译时提示错误,下面是一幅图像当您使用GCC时,您必须使用访问用C声明的变量,即使您使用的是英特尔汇编语法。将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语法,这样,如果generate_interrupt成为一个内联函数,它就可以正常工作
说到这里,我恳求您为您的操作系统找到一种不涉及自修改代码的实现策略。当然,除非您打算这样做。当您使用GCC时,您必须使用访问用C声明的变量,即使您使用的是英特尔汇编语法。将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语法,这样,如果generate_interrupt成为一个内联函数,它就可以正常工作
说到这里,我恳求您为您的操作系统找到一种不涉及自修改代码的实现策略。好吧,除非你打算这样做。这不是你关于将参数传递到内联程序集的特定问题的答案,请参见@zwol的答案。这解决了不必要地为此特定任务使用自修改代码的问题 如果在编译时中断号已知,则使用宏方法 使用自修改代码的另一种方法是创建一个生成所需特定中断的C宏。其中一个技巧是需要一个将数字转换为字符串的宏。Stringize宏非常常见,并在中进行了说明 您可以创建一个宏GENERATE_中断,如下所示:
#define STRINGIZE_INTERNAL(s) #s
#define STRINGIZE(s) STRINGIZE_INTERNAL(s)
#define GENERATE_INTERRUPT(n) asm ("int " STRINGIZE(n));
GENERATE_INTERRUPT(0);
GENERATE_INTERRUPT(3);
GENERATE_INTERRUPT(255);
STRINGIZE将获取一个数值并将其转换为字符串。GENERATE_中断只需获取数字,将其转换为字符串并将其追加到INT指令的结尾
您可以这样使用它:
#define STRINGIZE_INTERNAL(s) #s
#define STRINGIZE(s) STRINGIZE_INTERNAL(s)
#define GENERATE_INTERRUPT(n) asm ("int " STRINGIZE(n));
GENERATE_INTERRUPT(0);
GENERATE_INTERRUPT(3);
GENERATE_INTERRUPT(255);
生成的指令应如下所示:
int 0x0
int3
int 0xff
/* 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);
}
只有在运行时才知道中断数的跳转表方法
如果您需要调用仅在运行时已知的中断,则可以使用int指令和ret创建一个中断调用表。generate_中断只需从堆栈中检索中断号,计算表中可以找到特定int的位置,并将jmp分配给它
在下面的代码中,我让GNU汇编器生成256个中断调用的表,每个中断调用后跟一个ret,使用。每个代码片段包含4个字节。结果代码生成和生成中断功能如下所示:
int 0x0
int3
int 0xff
/* 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);
}
如果查看对象文件中生成的代码,您会发现中断调用表int_jmp_表类似于以下内容:
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的地址计算变得更容易。这并不是对您关于将参数传递到内联程序集的特定问题的回答,请参见@zwol的答案。这解决了不必要地为此partic使用自修改代码的问题 这是一项艰巨的任务 如果在编译时中断号已知,则使用宏方法 使用自修改代码的另一种方法是创建一个生成所需特定中断的C宏。其中一个技巧是需要一个将数字转换为字符串的宏。Stringize宏非常常见,并在中进行了说明 您可以创建一个宏GENERATE_中断,如下所示:
#define STRINGIZE_INTERNAL(s) #s
#define STRINGIZE(s) STRINGIZE_INTERNAL(s)
#define GENERATE_INTERRUPT(n) asm ("int " STRINGIZE(n));
GENERATE_INTERRUPT(0);
GENERATE_INTERRUPT(3);
GENERATE_INTERRUPT(255);
STRINGIZE将获取一个数值并将其转换为字符串。GENERATE_中断只需获取数字,将其转换为字符串并将其追加到INT指令的结尾
您可以这样使用它:
#define STRINGIZE_INTERNAL(s) #s
#define STRINGIZE(s) STRINGIZE_INTERNAL(s)
#define GENERATE_INTERRUPT(n) asm ("int " STRINGIZE(n));
GENERATE_INTERRUPT(0);
GENERATE_INTERRUPT(3);
GENERATE_INTERRUPT(255);
生成的指令应如下所示:
int 0x0
int3
int 0xff
/* 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);
}
只有在运行时才知道中断数的跳转表方法
如果您需要调用仅在运行时已知的中断,则可以使用int指令和ret创建一个中断调用表。generate_中断只需从堆栈中检索中断号,计算表中可以找到特定int的位置,并将jmp分配给它
在下面的代码中,我让GNU汇编器生成256个中断调用的表,每个中断调用后跟一个ret,使用。每个代码片段包含4个字节。结果代码生成和生成中断功能如下所示:
int 0x0
int3
int 0xff
/* 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);
}
如果查看对象文件中生成的代码,您会发现中断调用表int_jmp_表类似于以下内容:
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的地址计算更容易。请阅读gcc手册中的章节。PS:不幸的是x86 int指令没有寄存器参数,所以您可能希望将函数限制在一些特殊情况下,除非您对自修改代码感到满意。通常情况下,int3和可能的系统调用中断应该足够了。您希望这段代码实际做什么?@sneh\m否,这与使用的语法风格无关。它适用于gcc内联汇编,适用于at&t和intel。不管怎样,正如我所说的,请仔细考虑是否真的需要超通用性,这样才能避免自修改代码。“操作系统实现”和“自修改代码”是排他性的。它不是线程安全的。此外,在整数访问上有一个“byte ptr”限定符。如果您确实必须调用一个直到运行时才知道其编号的中断,请使用一个中断调用和JMP/RET表。最多只有256个。请阅读gcc手册中的章节。PS:不幸的是x86 int指令没有寄存器参数,所以您可能希望将函数限制在一些特殊情况下,除非您对自修改代码感到满意。通常情况下,int3和可能的系统调用中断应该足够了。您希望这段代码实际做什么?@sneh\m否,这与使用的语法风格无关。它适用于gcc内联汇编,适用于at&t和intel。不管怎样,正如我所说的,请仔细考虑是否真的需要超通用性,这样才能避免自修改代码。“操作系统实现”和“自修改代码”是排他性的。它不是线程安全的。此外,在整数访问上有一个“byte ptr”限定符。如果您确实必须调用一个直到运行时才知道其编号的中断,请使用一个中断调用和JMP/RET表。最多只有256个。跳转对于刷新预回迁队列很重要,因为代码本身已修改。由于这是标记为osdev的,我假设他正在编写自己的操作系统。@MichaelFetch Doh,完全正确,我将跳转放回原处。不过,我不认为我写的任何东西会因为OP编写了他们自己的操作系统而失效。这个答案通过解决我的问题而得到了补充。感谢Zwol和@JesterIn在真正的现代x86 CPU上的实践—您不需要jmp,陈旧的指令获取是不可能的。但理论上/理论上是的,jmp甚至是完全序列化的指令都是需要的。跳转对于刷新预取队列很重要,因为代码本身已经修改。由于这是标记为osdev的,我假设他正在编写自己的操作系统。@MichaelPetch Doh,非常正确,我将跳转放回原处。不过,我不认为我写的任何东西会因为OP编写了他们自己的操作系统而失效。这个答案通过解决我的问题而得到了补充。感谢Zwol和@JesterIn在真正的现代x86 CPU上的实践—您不需要jmp,陈旧的指令获取是不可能的。但从理论上/理论上讲,的确需要jmp甚至一条完全序列化的指令。大概,OP所需的中断号直到运行时才知道。@James Martini已经在序言中记下了这一点。我不知道他使用的上下文。我只是举个例子。如果在编译时知道它们,这是一个选项。感谢您推荐了一个更好的替代方法@MichaelPetch,我将在其他时间尝试:@zwol-between-functions GCC不一定保证在源文件的任何时候都设置了哪个节。在GCC邮件中对此进行了讨论
这是几年前的事了。我使用push/pop部分来确保任何全局汇编代码都在文本部分中。这只是预防措施。很可能它已经是文本部分了,但我不认为它不是。通常我只是将这类代码放在一个单独的汇编文件中,并将其与C链接。大概,OP所需的中断号直到运行时才知道。@MartinJames I已经在序言中记下了这一点。我不知道他使用的上下文。我只是举个例子。如果在编译时知道它们,这是一个选项。感谢您推荐了一个更好的替代方法@MichaelPetch,我将在其他时间尝试:@zwol-between-functions GCC不一定保证在源文件的任何时候都设置了哪个节。几年前,GCC邮件列表上就有关于这一点的讨论。我使用push/pop部分来确保任何全局汇编代码都在文本部分中。这只是预防措施。很可能它已经是文本部分了,但我不认为它不是。通常我只是将这种类型的代码放在一个单独的汇编文件中,并将其与C链接。