26

How do I write the LLVM bitcode required to emit an architecture-specific system call instruction?

More specifically, clang supports inline assembly, and clearly supports emitting system calls (otherwise libc and vdso could not be compiled). How does the translation work for this, and how can I tickle it to reproduce this behavior?

I understand LLVM itself may not understand the calling interface and register schedule used by various architectures in a sufficiently high-level manner to be expressed in LLVM bytecode (e.g. that may be filled in elsewhere). However, there's clearly a stage where this information can be added.

How do I do this, starting at whatever stage comes after "C source with inline assembly"?

A satisfactory answer would include an example of how to invoke a five-argument int 0x80 system call. I choose five since that requires spilling to the stack, and I choose int 0x80 since it's easily understood and on the most common platform.

Zach Riggle
  • 2,975
  • 19
  • 26
  • Is there no relevant open source example you can study? – Chris Stratton Oct 14 '14 at 02:27
  • Why not use inline assembly to make system calls? – Ross Ridge Oct 14 '14 at 02:49
  • Chris: No, not that I've found. – Zach Riggle Oct 14 '14 at 02:56
  • Ross: It's an excercise in LLVM's flexibility for code generation. Rather than "C with inline assembly", I'm looking for "LLVM bitcode with inline assembly". At some point in the transformation of "C with inline assembly" to "assembled machine code", there must be an intermediate stage with both LLVM bitcode **and** arch-specific assembly. I'm looking for an example of this, specifically with an `int 0x80` and setting appropriate registers. – Zach Riggle Oct 14 '14 at 02:59
  • 8
    In LLVM assembly ("bitcode") an inline assembly statement is represented as a call instruction that "calls" an inline assembly expression (which is a string containing the "arch-specifc" assembly instruction(s), plus constraints and a couple of flags). There's no other representation of "arch-specific" assembly within a function, and you can easily generate an example of this using inline assembly in C. So I don't see the point of your question. – Ross Ridge Oct 14 '14 at 06:29
  • Well, any example how to do it quickly and correctly would be great. – exa Dec 25 '14 at 14:16

1 Answers1

8

Posting an answer here since exa has put up a bounty.

I realized this was somewhat a silly question to ask after Ross Ridge's comments, and some playing around with clang.

Let's assume we have the following program, which uses inline assembly to directly call 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;
}

We can compile this with either gcc or clang and get roughly the same result.

$ gcc -o syscall.gcc syscall.c
$ clang -o syscall.clang syscall.c
$ ./syscall.gcc
test
$ ./syscall.clang
test

If we wish to see the exact LLVM instructions which would be used to emit this code, we can simply use the -emit-llvm flag. As you can see, there is a call i64 asm sideeffect line which has the full inline assembly string.

$ 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}
Zach Riggle
  • 2,975
  • 19
  • 26
  • Seems right. I didn't realize I can use clang to get the demo with correct answer :] – exa Jan 07 '15 at 14:32