LLVM上的Syscall/sysenter

26
我如何编写LLVM位代码以发出特定于体系结构的系统调用指令?
更具体地说,clang支持内联汇编,并明确支持发出系统调用(否则无法编译libc和vdso)。这个过程是如何进行翻译的,我该如何调整它以再现此行为?
我理解LLVM本身可能不理解各种架构使用的调用接口和寄存器调度在LLVM字节码中以足够高级的方式表达(例如,可能在其他地方填充)。但是,显然存在一个阶段可以添加此类信息。
从“带有内联汇编的C源”之后的任何阶段开始,我要怎么做?
令人满意的答案将包括如何调用五个参数的int 0x80系统调用的示例。我选择五个参数,因为需要将其溢出到堆栈,并且我选择int 0x80因为它易于理解并且在最常见的平台上。

有没有相关的开源示例可以供您学习? - Chris Stratton
为什么不使用内联汇编来进行系统调用? - Ross Ridge
Chris:没有,至少我还没有发现。 - Zach Riggle
Ross:这是一个LLVM代码生成灵活性的练习。与其使用“带有内联汇编的C语言”,我更想使用“带有内联汇编的LLVM位码”。在将“带有内联汇编的C语言”转换为“已组装的机器码”的某个阶段,必须存在同时具有LLVM位码和特定于架构的汇编的中间阶段。我正在寻找一个例子,具体涉及到int 0x80和设置适当的寄存器。 - Zach Riggle
8
在LLVM汇编语言(“比特码”)中,内联汇编语句表示为调用指令,该指令“调用”一个内联汇编表达式(该表达式是一个包含“体系结构特定”汇编指令、约束条件和一些标志的字符串)。在函数中没有其他“体系结构特定”的汇编表示形式,您可以使用C语言中的内联汇编轻松生成此类示例。因此,我不明白您问题的意义所在。 - Ross Ridge
任何快速且正确的实现示例都将是极好的。 - exa
1个回答

8

由于exa提供了悬赏,我在这里发布一个回答。

在Ross Ridge的评论和对clang的一些尝试后,我意识到这是一个有点愚蠢的问题。

让我们假设我们有以下程序,它使用内联汇编直接调用write()函数。

#include <stdio.h>
int main(void)
{
    char *buf = "test\n";
    ssize_t n;
    asm volatile (
        "movl $0x00000002, %%edi\n"  /* first argument == stderr */
        "movl $0x00000006, %%edx\n"  /* third argument == number of bytes */
        "movl $1, %%eax\n"  /* syscall number == write on amd64 linux */
        "syscall\n"
        : "=A"(n)         /* %rax: return value */
        : "S"(buf));      /* %rsi: second argument == address of data to write */
    return n;
}

我们可以使用 gcc 或者 clang 进行编译,得到的结果大致相同。
$ gcc -o syscall.gcc syscall.c
$ clang -o syscall.clang syscall.c
$ ./syscall.gcc
test
$ ./syscall.clang
test

如果我们希望查看生成此代码所使用的精确LLVM指令,我们只需使用-emit-llvm标志即可。如您所见,有一行call i64 asm sideeffect,其中包含完整的内联汇编字符串。
$ clang -S -emit-llvm syscall.c
$ cat syscall.ll
; ModuleID = 'syscall.c'
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

@.str = private unnamed_addr constant [6 x i8] c"test\0A\00", align 1

; Function Attrs: nounwind uwtable
define i32 @main() #0 {
  %1 = alloca i32, align 4
  %buf = alloca i8*, align 8
  %n = alloca i64, align 8
  store i32 0, i32* %1
  store i8* getelementptr inbounds ([6 x i8]* @.str, i32 0, i32 0), i8** %buf, align 8
  %2 = load i8** %buf, align 8
  %3 = call i64 asm sideeffect "movl $$0x00000002, %edi\0Amovl $$0x00000006, %edx\0Amovl $$1, %eax\0Asyscall\0A", "=A,{si},~{dirflag},~{fpsr},~{flags}"(i8* %2) #1, !srcloc !1
  store i64 %3, i64* %n, align 8
  %4 = load i64* %n, align 8
  %5 = trunc i64 %4 to i32
  ret i32 %5
}

attributes #0 = { nounwind uwtable "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nounwind }

!llvm.ident = !{!0}

!0 = metadata !{metadata !"Ubuntu clang version 3.5-1ubuntu1 (trunk) (based on LLVM 3.5)"}
!1 = metadata !{i32 134, i32 197, i32 259, i32 312}

看起来没问题。我没有意识到我可以使用clang得到正确答案的演示。 - exa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接