vendredi 13 avril 2018

Fork a Go process

Today I got a problem with the project I'm working on. Basically file descriptor were messing with each other. And rather than having a proper management of all the file descriptors, I decided to just "contain" the different things I was doing in different process. Each process has its own file descriptor table: problem solved.

However, Go developers seems to really want us NOT to do this. So, forking a Go process is not as simple as a C/C++ one. Here I'm going to explain how I did it anyway :P Also setting up some communication between the two processes: Go usual communication do not encompass this use case.

Edit: Well, after more usage, it appears this is not usable. The reason is we are probably conflicting with how Go manages thread and how that does not work very well with fork. Don't think there a good way to "properly" do that without diving into how the Go runtime is working. And at this point, I'm just better off just compiling another binary and calling it.

TL;DR: Fork using cgo and communication using pipes. Note that you can use any usual inter-process communication. Just not the usual Go ones.
Complete code: https://github.com/Jiliac/go-clone/blob/master/fork.go 

I - Forking

First make a wrapper around libc fork call. This could be done without libc/cgo, but I went for the simple way :-)

//#include 
//
//int cFork() {
//    pid_t pid;
//    pid = fork();
//    return ((int)pid);
//}
import "C"

You can test it works correctly with:

func forkTest() 
        pid := int(C.cFork())
        if pid == 0 {
                fmt.Println("Child")
        } else {
                fmt.Println("Parent")
        }
}

So not very complicated in the end.

II - Communication

Any IPC can be used here. Chose to use pipes because that's I'm comfortable with. Can use networking anything as well...

Problem using pipes is that the ones from os.Pipe are set with FD_CLOEXEC flag. (see https://golang.org/src/os/pipe_linux.go?s=319:360#L1).

So we have to open these pipes by hand; and then 'convert' them into os.File (names are not important) so we can conveniently use them:

func pipe() (r, w *os.File, err error) {
        var p [2]int
        err = syscall.Pipe(p[0:])
        if err != nil {
                return r, w, err
        }

        r = os.NewFile(uintptr(p[0]), "|0")
        w = os.NewFile(uintptr(p[1]), "|1")
        return r, w, err
}
I have a pipeTest function on github to check if these are working correctly. Putting it all together we have:

func forkComTest() {
        r, w, err := pipe()
        if err != nil {
                log.Printf("Failed to create pipes: %v.\n", err)
                return
        }

        pid := int(C.cFork())
        if pid == 0 { // Child
                fmt.Printf("(from child)\tHello.\n")
                err = r.Close()
                if err != nil {
                        log.Printf("Child could not close reading pipe: %v.\n", err)
                        return
                }

                _, err := w.Write([]byte(msg))
                if err != nil {
                        log.Printf("Could not write in pipe: %v.\n", err)
                }

        } else { // Parent
                fmt.Printf("(from parent)\tChild pid = %d.\n", pid)
                err = w.Close()
                if err != nil {
                        log.Printf("Parent could not close writing pipe: %v\n.", err)
                        return
                }

                buf := make([]byte, bufSize)

                _, err := r.Read(buf)
                if err != nil {
                        log.Printf("Could not reat from pipe: %v.\n", err)
                }

                msg := string(buf)
                fmt.Printf("(from parent)\tMessage: '%s'\n", msg)
        }
}
Which should output something like:

(from parent)   Child pid = 47158.
(from child)    Hello.
(from parent)   Message: 'Hello dad, it's your son :-).'
And here we go.

mardi 23 janvier 2018

Compiling Linux Kernel with Clang

Hi everyone. This is my first 'technical' blog post. I saw some people saying it helps growing your explanation skills which I sincerely lack.
Thus, I decided next I struggle doing something because I feel it's quite undocumented, I'll try to make a post and explain how I did it. If even one person reads this and it's even remotely useful to them, I'll consider the job done. Ask any question, I'll be happy to answer.

Now down to the topic. I have trying to compile the kernel with clang ever since I have seen the LWN.net article on the topic. It says that you 'just' need to go and compile your kernel with make CC=clang. So I went into the linux sources I had at this time (something around 4.9.60) and type the command. Obviously it didn't work.

Turns out, it's not that much complicated. There are two requirements to compiling your kernel with clang:
  1.  Get the right source and configuration.
  2.  Get the right version of clang.
Everything is very well summarized on a LKML post from last November:  https://lkml.org/lkml/2017/11/22/943.

Concerning the source, I went for the simplest path: getting the latest stable version of the kernel with default configuration:


git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
cd linux-stable
git checkout v4.14.15
make defconfig

Here I'm on x86. If you want to compile for ARM64, it seems a bit more complicated. I didn't try it but you might want to look for the android source that have not been upstreamed yet. Posting here because it's not linked in the Matthias Kaehlcke's LKML post:
https://android.googlesource.com/kernel/common.git/+log/android-4.9
https://android.googlesource.com/kernel/common.git/+log/android-4.4
There also other documentation made by Google here which shows how to compile the kernel with KASAN and KCOV. Didn't dig into that but could be useful to someone.

If you are looking for other configuration than just the default one, I didn't try that and I think it's really and a case by case basis. You are going to have to dig a little bit more.

And for the version of clang, you need clang 5 or more. I on Debian Stretch for which LLVM has precompiled binary (see here if you are on another OS version). So add them to your source list:
sudo vim /etc/apt/sources.list 
And then add:
deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-6.0 main
deb-src http://apt.llvm.org/stretch/ llvm-toolchain-stretch-6.0 main
Now you can install clang:
sudo apt-get update
sudo apt-get install clang-6.0

If you are not on Debian/Ubuntu, I am not sure whether or not there is another solution than just compiling LLVM directly (see here).

Finally, we have the source and the compiler, so we are ready to compile. Go in your linux-stable/ folder and:
 make CC=clang-6.0 -j4 bzImage
I just wanted the bzImage to boot, but you remove it if you want to compile everything.
-j4 because gain time by making compilation parallel... Now I don't know how you plan to use your clang compiled kernel, but just to check it's usable, I run it with qemu:
qemu-system-x86_64 -kernel arch/x86/boot/bzImage -nographic -serial mon:stdio -append 'console=ttyS0'
And since we didn't provide the -initrd option, it will crash at the end with something like:
---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
There are plenty of guides on how to use your kernel, not the goal here.

That will conclude my first post. Hopefully it was clear enough. Don't hesitate to contact me by any mean if you have any question or problems. See jiliac.com for contact info.