带英特尔x86-32位汇编的gcc:访问C函数参数

带英特尔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中的-masm=intel选项编译上述代码。而且 这不是生成软件中断的完整代码。 我的问题是我得到的错误为n未定义,我如何解决它,请帮助


此外,它会在链接时而不是在编译时提示错误,下面是一幅图像

当您使用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链接。