字幕表 動画を再生する
So yes, today I'm going to talk about eBPF, as Sven said, I work at Aqua Security where we build tools to help enterprises with their cloud-native, securing their cloud-native deployments, and eBPF is one of the technologies that we're starting to leverage, in particular with a project called Tracy.
スヴェンが言ったように、私はAqua Securityで働いていて、企業のクラウド・ネイティブなデプロイメントをセキュアにするためのツールを作っている。
Now, I have done a talk about eBPF before, but today I'm going to do something new in that I am writing my code, or at least my user space code, with Go today, it's the first time I've done the talk using Go, so that's the new twist that we're going to tackle today.
以前にもeBPFについての講演をしたことがあるのですが、今日はGoを使ってコードを書くという新しいことをします。
So, before I get into the point where I'm going to start using Go and start writing some code, I should probably start by talking a little bit about what eBPF is.
さて、これからGoを使ってコードを書き始める前に、eBPFとは何かということから少し話しておこう。
Key thing is, it lets you run custom programs of your choice in the kernel, so it's a Linux kernel feature technology that lets you, on the fly, add and change code programs that are going to run in response to events, and you can change them dynamically without having to reboot the machine, so much, much more powerful than writing a kernel module.
重要なのは、カーネル内で好きなカスタム・プログラムを実行できることだ。つまり、Linuxカーネルの機能技術で、イベントに応じて実行するコード・プログラムをその場で追加・変更でき、マシンをリブートすることなく動的に変更できるので、カーネル・モジュールを書くよりもずっとずっと強力なのだ。
We've seen eBPF becoming really a hot technology over the last few years, because it's so powerful and because you can hook it into so many different events, we're seeing it used in lots of observability tools, we're starting to see it used for security as well.
eBPFは非常に強力で、様々なイベントにフックすることができるため、ここ数年で本当にホットなテクノロジーになってきている。
Today, I'm going to talk more about how it works and hopefully give you enough grounding that you can go away and start writing your own eBPF code.
今日は、eBPFがどのように機能するのかについて詳しく説明し、あなたがeBPFのコードを書き始めるのに十分な基礎知識を与えたいと思う。
Now, if we're going to run some code in the kernel, but as developers, we're usually writing applications in user space, so how do we communicate between user space and kernel?
さて、カーネルでコードを実行するとしても、開発者としては通常、ユーザー空間でアプリケーションを書くわけで、ユーザー空間とカーネルの間でどのように通信するのだろうか?
The answer is system calls.
答えはシステムコールだ。
System calls provide the interface between user space and kernel.
システムコールは、ユーザー空間とカーネル間のインターフェースを提供する。
So, I can make a pretty good guess that there would be a system call related to eBPF, and there certainly is.
だから、eBPFに関連するシステムコールがあるだろうと、私はかなり推測できる。
If we look up the man page for BPF, we'll find lots of really helpful information about what BPF is and how we use it.
BPFのmanページを調べると、BPFとは何か、どのように使うのか、本当に役立つ情報がたくさんある。
So, first of all, BPF stands for Berkeley Packet Filters.
まず、BPFとはバークレー・パケット・フィルターの略だ。
I'm going to use the word eBPF and BBF pretty interchangeably.
私はeBPFとBBFを同じ意味で使うつもりだ。
Historically, it was about filtering network packets, running custom code when a network packet arrived.
歴史的には、ネットワーク・パケットをフィルタリングし、ネットワーク・パケットが到着したらカスタム・コードを実行するというものだった。
That's been extended.
それは延長された。
You can now run your BPF programs in response to lots and lots of different types of events, not just the arrival of a network packet.
ネットワーク・パケットの到着だけでなく、さまざまなタイプのイベントに反応してBPFプログラムを実行できるようになった。
So, whether it's classic BPF or eBPF doesn't really matter these days.
だから、クラシックなBPFであろうとeBPFであろうと、最近はあまり関係ない。
But in both cases, you're running code in the kernel, and you really, really don't want the kernel to crash or hang.
しかしどちらの場合も、あなたはカーネル内でコードを実行しているわけで、カーネルがクラッシュしたりハングアップしたりするのは避けたい。
So, when we want to run some eBPF code, it goes through a verification step to make sure that it's safe to run.
そのため、eBPFのコードを実行したい場合、実行しても安全であることを確認するための検証ステップを経る。
So, that's something we'll talk a little bit more about later.
それについては、後でもう少し詳しく話すことにしよう。
So, we have some eBPF code that's going to run in the kernel.
つまり、カーネル内で実行されるeBPFコードがある。
We have user space application code that, as developers, is normally where we're used to writing code.
ユーザースペースのアプリケーションコードは、開発者として通常コードを書くのに慣れている場所だ。
And there's a system call interface between the two.
そして、この2つの間にはシステムコールのインターフェイスがある。
And I think if we look at the system calls, that can actually be quite helpful for understanding really what's happening when we're talking about inserting code into the kernel.
システムコールを見れば、カーネルにコードを挿入するときに何が起きているのかを理解するのに役立つと思う。
So, I'm going to start by using an example.
では、まず例を挙げて説明しよう。
I'm going to use BPF trace.
BPFトレースを使うつもりだ。
This is quite a widely used tool for running BPF scripts on a system.
これは、システム上でBPFスクリプトを実行するために広く使われているツールだ。
And so, I'm going to show this partly as an example to show the kind of things you can do with BPF, and partly so that we can examine the system calls that happen when we run it.
それで、BPFでどんなことができるかを示すための一例として、またBPFを実行したときに起こるシステムコールを調べるための一例として、これをお見せします。
So, let's explore some BPF trace and the system calls it uses.
それでは、BPFのトレースとそれが使用するシステムコールを調べてみよう。
So, I have an example that I'm going to use.
だから、これから使う例があるんだ。
It doesn't really matter that much what my example is.
私の例がどうであるかは、それほど重要ではない。
But in this case, I'm setting up a script that's going to run on a trace point.
しかし今回は、トレースポイントで実行するスクリプトをセットアップしている。
It's running when we call the sysenter.
シセンターを呼び出すと動いている。
The sysenter function actually gets triggered for every single system call.
sysenter関数は、実際にはシステムコールのたびにトリガーされる。
So, I'm going to run a script every time any process on my virtual machine calls a system call.
そこで、仮想マシン上のプロセスがシステムコールを呼び出すたびに、スクリプトを実行することにする。
And it's going to run this script.
そしてこのスクリプトを実行する。
What that script actually does is it takes, it sets up a counter for the number of times each different command makes a system call.
このスクリプトが実際に行うのは、各コマンドがシステムコールを行う回数のカウンターを設定することだ。
So, if I try to run that, well, you have to be a privileged user to do it.
だから、それを実行しようとすると、特権ユーザーでないとできない。
That kind of makes sense.
それは理にかなっている。
You don't want every unprivileged user running code in your kernel.
あなたは、すべての非特権ユーザーにカーネル内のコードを実行させたくない。
So, I can use sudo.
だからsudoが使える。
I just run that for a few seconds, and then I'll interrupt it.
それを数秒間実行して、中断するんだ。
And it's going to show us for each different kind of command that's running on my machine, the number of system calls.
私のマシンで実行されているコマンドの種類ごとに、システムコールの回数が表示される。
I mean, that's quite interestingly powerful that we can get to such fine granularity as counting every single system call that's happening on my machine.
つまり、私のマシンで起こっているシステムコールをひとつひとつカウントするような細かい粒度までできるのは、非常に興味深い強力なことなのだ。
So, I'm going to run the same thing again.
だから、もう一度同じことをやってみる。
But this time, let's have a look at the BPF system calls that are being called.
しかし今回は、呼び出されているBPFシステムコールを見てみよう。
So, I do that with strace.
だから、ストレースを使うんだ。
I'm just going to look for BPF system calls and quit it after a few seconds.
BPFのシステムコールを探して、数秒後に終了するんだ。
And the thing that I want to show you is these BPF system calls that are being called.
ここでお見せしたいのは、呼び出されるBPFシステムコールである。
So, we see a few map create, we see map update element, and we see program load.
マップの作成、マップの更新、そしてプログラムのロード。
So, that tells us something or indicates something about a couple of concepts we need to know about.
このことは、私たちが知るべきいくつかの概念について、何かを教えてくれる、あるいは示してくれる。
Those are BPF programs and BPF maps.
それがBPFプログラムとBPFマップだ。
So, we use the same call, but with a different parameter to manipulate programs and maps.
そこで、同じ呼び出しを、別のパラメータを使ってプログラムとマップを操作する。
So, starting with the programs, what are those programs?
では、まずプログラムとは何か?
Well, I mean, they're programs.
まあ、プログラムだからね。
They run on the CPU.
CPU上で動作する。
They're essentially machine code instructions.
基本的にはマシンコードの命令だ。
But for BPF, we're restricted in what we can run because of that requirement for BPF code to be safe.
しかし、BPFの場合は、BPFのコードが安全でなければならないため、実行できるものに制限がある。
It mustn't crash, it mustn't loop.
クラッシュしてはいけないし、ループしてはいけない。
So, we typically write our BPF programs in C, a restricted set of C, which we don't use any loops.
そのため、BPFのプログラムは通常、ループを使わないC言語で書いている。
We always have to check that a pointer is not null before we dereference it.
ポインターを再参照する前に、そのポインターがNULLでないことを常にチェックしなければならない。
And then we use the Clang compiler to convert it into an eBPF object, a set of bytecode instructions that are going to get run inside a BPF virtual machine inside the kernel.
そしてClangコンパイラーを使ってeBPFオブジェクトに変換し、カーネル内のBPF仮想マシン内で実行されるバイトコード命令のセットにする。
So, we're going to write the kernel code in C.
そこで、カーネルのコードをCで書くことにする。
We get some helper functions that give us some useful contextual information.
便利なコンテクスト情報を与えてくれるヘルパー関数がいくつかある。
So, for example, we can print debugging messages with a helper function.
例えば、ヘルパー関数を使ってデバッグ・メッセージを表示することができる。
We can get information about the current running command.
現在実行中のコマンドに関する情報を得ることができる。
That's how BPF trace knows which command is running, as we saw in the previous example.
前の例で見たように、これがBPFトレースがどのコマンドが実行されているかを知る方法だ。
Lots of, I guess, a few dozen of these helper functions that can help us with contextual information.
このようなヘルパー関数は、文脈に沿った情報を提供してくれる。
The other thing I talked about was maps, or the other thing we saw in our system course was maps.
もうひとつ話したのは地図で、システムコースで見たのも地図だった。
And maps are really how we get information between our eBPF program running in the kernel and user space.
そしてマップは、カーネルで動作するeBPFプログラムとユーザー空間の間で情報を得る方法なのだ。
We'll come back to a bit more detail about maps shortly.
地図については、もう少し詳しく説明することにしよう。
And then the last kind of conceptual thing we really need to know about is the fact that these programs are triggered by an event happening.
そして、最後に概念的なこととして知っておかなければならないのは、これらのプログラムは、ある出来事によってトリガーされるという事実だ。
We saw an event, an example where we run a program in response to system calls, in response to triggering a hook at the entry to the function called sysenter.
システムコールに応答してプログラムを実行する例として、sysenterと呼ばれる関数のエントリーでフックをトリガーするイベントを見た。
There are tons of these hooks already predefined, essentially every function entry and exit, every system call, every trace point in the kernel, every time a network packet arrives.
このようなフックはすでに大量に定義されており、本質的には、すべての関数の入口と出口、すべてのシステムコール、カーネル内のすべてのトレースポイント、ネットワークパケットが到着するたびにある。
All of these are possible points where you can trigger an eBPF program.
これらはすべて、eBPFプログラムを発動できる可能性のあるポイントである。
And we also have the term k-probe and u-probe.
また、kプローブやuプローブという言葉もある。
The k-probe is the entry to a kernel function.
kプローブはカーネル関数のエントリである。
A k-rep probe is the exit from a kernel function, and correspondingly the same for user space.
k-repプローブとは、カーネル関数からの出口であり、ユーザー空間についても同様である。
Combination of all these different types of events means we can really run eBPF code in response to pretty much anything that's happening in your Linux machine.
これらすべての異なるタイプのイベントを組み合わせることで、Linuxマシンで起こっているほとんどすべてのことに反応してeBPFコードを実行することができる。
So how do we attach the program to an event?
では、どうやってプログラムをイベントに組み込めばいいのか?
And again, I think it's a little bit helpful to look at the system calls that are happening.
そしてまた、起こっているシステムコールを見ることは少し役に立つと思う。
We're going to not just use the BPF call, but also a couple more system calls per event open, which sets up a trace point.
BPFコールだけでなく、イベントオープンごとにさらに2、3のシステムコールを使い、トレースポイントを設定する。
So program load gives us a file descriptor that I've called x here.
プログラム・ロードは、ここでxと呼んでいるファイル・ディスクリプタを与えてくれる。
The trace point comes back as y, and then there's an IO control event that associates the trace point with the program that should be triggered.
トレース・ポイントはyとして戻ってくる。そして、トレース・ポイントとトリガーされるべきプログラムを関連付けるIOコントロール・イベントがある。
So again we can take a look at that in our BPF trace example.
BPFトレースの例を見てみよう。
Again, I just trace out those additional system calls to event, oops, event open, and let's see.
もう一度、event、ops、event openへの追加のシステムコールをトレースしてみよう。
Again, it doesn't really matter too much exactly what's happening.
繰り返すが、何が起こっているかはあまり重要ではない。
I just really want to show you this program load BPF call that comes back with the file descriptor of nine.
このプログラム・ロードBPFコールが、9のファイル・ディスクリプタとともに戻ってくるところをお見せしたい。
There should be perf event open.
イベントが開かれているはずだ。
Yeah, this perf event open here, which comes back with a file descriptor of eight.
そう、このperfイベント・オープンは、ファイル・ディスクリプタが8で戻ってくる。
And then here is the IO control that associates eight and nine and says, this is the BPF program that I want you to run when we hit that trace point.
そしてこれはIOコントロールで、8と9を関連付け、トレースポイントに到達したときに実行させたいBPFプログラムであることを示す。
So loading the program and associating the program with the trace point is something we're going to have to do from user space.
だから、プログラムをロードし、トレース・ポイントにプログラムを関連づけるのは、ユーザー・スペースからやらなければならないことなのだ。
Okay, so if we want to write hello world in eBPF, what do we need to do?
では、eBPFでhello worldを書きたい場合、どうすればいいのだろう?
What do we need to have in place?
何が必要なのか?
We know we're going to have to write some C code that's going to run in the kernel and that's going to get compiled by Clang.
カーネルで実行されるCコードを書かなければならないことは分かっているし、それはClangによってコンパイルされる。
And we're going to have to write some user space code that gets the tracing, the hello world message from the kernel and displays it.
そして、カーネルからトレースやhello worldメッセージを取得して表示するユーザー・スペース・コードを書かなければならない。
And we can write that, at least in theory, we can write it in any language of our choice.
そして、少なくとも理論上は、好きな言語で書くことができる。
For most of us, we don't typically interact with system calls very often when we're writing user space applications.
ユーザー・スペース・アプリケーションを書いているとき、システム・コールを使うことはあまりない。
There's usually some level of abstraction.
通常、ある程度の抽象度はある。
And in fact, many of us don't know that system calls exist.
そして実際、私たちの多くはシステムコールの存在を知らない。
We don't have to deal with them on a day-to-day basis.
日常的に彼らと付き合う必要はない。
For BPF, there is, we would want a library, a BPF library that gives us a higher level of abstraction over those BPF system calls and things like the perf event open that we just saw.
BPFについては、BPFのシステムコールや、先ほど見たperfのイベントオープンのようなものをより抽象化してくれるような、BPFライブラリが欲しいところです。
And the library that I'm going to use today is called libbpf-go.
今日使うライブラリーはlibbpf-goだ。
And we actually wrote this as part of a tool called Tracy that's an eBPF security event detection tool we're working on.
これは、私たちが取り組んでいるeBPFセキュリティ・イベント検出ツールのTracyというツールの一部として書いたものです。
And we've isolated the libbpf wrapper.
そしてlibbpfラッパーを分離した。
So there's a C library called libbpf, which is a wrapper for the system calls.
libbpfというCライブラリがあり、これはシステムコールのラッパーだ。
And libbpf-go is a pretty thin go wrapper, giving us go bindings around those libbpf interface.
そしてlibbpf-goはかなり薄いgoラッパーで、libbpfインターフェースの周りにgoバインディングを与えてくれる。
So we're going to write some go code that uses libbpf-go.
では、libbpf-goを使ったgoのコードを書いてみよう。
And we're also going to write some C code, which we're going to compile into eBPF objects using the Clang compiler.
Clangコンパイラーを使ってeBPFオブジェクトにコンパイルする。
And then the go code is going to read that object file, get the contents out, insert it into the kernel.
そしてゴー・コードがそのオブジェクト・ファイルを読み込み、中身を取り出し、カーネルに挿入する。
So we have an object file that has the eBPF code and the definition of any maps.
つまり、eBPFコードとあらゆるマップの定義を持つオブジェクト・ファイルがある。
Talk about maps a bit more later.
地図についてはもう少し後で話そう。
We have our user space code that's driving our system calls and has the kind of logic around what programs we want to run, what we want to attach them to.
ユーザースペースコードがあり、それがシステムコールを駆動し、どのプログラムを実行し、何にアタッチするかというロジックを持っている。
When the user space code calls that BPF program load, it sends the program to the kernel.
ユーザー空間のコードがBPFプログラムのロードを呼び出すと、そのプログラムがカーネルに送られる。
The kernel will verify it, make sure that it's safe to run.
カーネルはそれを検証し、実行しても安全であることを確認する。
And if it is, it will start running it inside this BPF virtual machine.
もしそうなら、このBPF仮想マシン内で実行を開始する。
So we're going to build two objects.
そこで、2つのオブジェクトを作ることにする。
We've got two different compilation steps.
我々は2つの異なるコンパイルのステップを持っている。
We've got to use the go compiler, go build, to create a go executable.
goコンパイラー、go buildを使ってgoの実行ファイルを作らなければならない。
And we're going to use Clang to build the BPF object file.
そしてClangを使ってBPFオブジェクト・ファイルをビルドする。
All right.
分かった。
I think we have enough to actually start writing some code.
実際にコードを書き始めるには十分だと思う。
So let's go to my editor.
では、編集長に話を聞こう。
And this is my make file.
そしてこれが私のメイクファイルだ。
It's pretty much exactly what I just showed you on the slide.
先ほどスライドでお見せしたこととほぼ同じです。
So we have a go build step and a Clang step for building the eBPF object.
そこで、eBPFオブジェクトをビルドするためのgoビルドステップとClangステップを用意した。
And let's start with the C code.
そしてCのコードから始めよう。
So I'm going to write a function called hello.
そこで、helloという関数を書こうと思う。
Exit context pointer, they all do.
コンテクストポインタを終了する。
I'm going to just do some hello world tracing.
ちょっとハローワールドをトレースしてみるよ。
So let's say hello, go topia.
だから、挨拶しよう。
And we'll return zero exit code.
そして、ゼロの終了コードを返す。
The other thing I have to do is define an object code section.
もうひとつやらなければならないのは、オブジェクトコードのセクションを定義することだ。
This tells the, essentially, the object loader what kind of BPF program this is going to be.
これは、本質的には、オブジェクト・ローダーに、これがどのようなBPFプログラムになるのかを伝えるものだ。
This is kind of a level of detail we don't need to worry about too much today.
これは今日、あまり気にする必要のないレベルの話だ。
But you can write, you can use different helper functions and do different things depending on the type of program you're running and the type of event you're attaching it to.
しかし、プログラムの種類やイベントの種類によって、さまざまなヘルパー関数を使い、さまざまなことができる。
So I'm going to attach to a K probe the entry point to a function in a kernel.
そこで、Kプローブにカーネル内の関数へのエントリー・ポイントをアタッチすることにする。
And I'm actually going to run this whenever the system call exec V gets triggered.
そして、システムコールのexec Vがトリガーされるたびに、これを実行するつもりだ。
So that's my C code.
これが私のCコードだ。
And let's compile that.
そして、それをまとめよう。
So I'm just going to run the make on my BPF object file target to start with.
そこで、まずはBPFオブジェクト・ファイルをターゲットにしてmakeを実行することにする。
And that should give me an object file that I can look at.
そうすれば、私が見ることのできるオブジェクトファイルができるはずだ。
And there are a couple of interesting things to look at in this object file.
そして、このオブジェクト・ファイルには興味深いことがいくつかある。
So, first of all, it's a little engine machine.
だから、まず第一に、これは小さなエンジンマシンなんだ。
I will need that in a moment.
すぐに必要なんだ。
And this object file is designed, it's compiled to run in a Linux BPF virtual machine.
このオブジェクト・ファイルは、Linux BPF仮想マシンで実行できるようにコンパイルされている。
We can see here's the section declaration for the fact that it's running as a K probe on sysex exec V.
このセクションの宣言は、SYSEX EXEC VのKプローブとして実行されていることを示している。
And here is the function name.
そしてこれが関数名である。
So I might say eBPF program.
だから、私はeBPFプログラムと言うかもしれない。
A program is really a function.
プログラムとはまさに関数である。
So that information from the L file is what is going to help the Go code know how to insert it into the kernel.
つまり、Lファイルからの情報は、Goコードがカーネルに挿入する方法を知るのに役立つものなのだ。
So let's write some Go code.
では、Goのコードを書いてみよう。
Right.
そうだね。
I already have a reference to libBPF Go here.
libBPFへのリファレンスはすでにここにある。
And I have a convenience function called must that I'm going to use to trap any errors and panic crash if we see any errors.
mustという便利な関数があるので、それを使ってエラーをトラップし、エラーが出たらパニック・クラッシュする。
Hopefully we won't hit that.
そうならないことを祈るよ。
Don't do that in production.
本番でそれをやってはいけない。
Really bad idea.
本当に悪い考えだ。
But it will be fine for demo purposes.
しかし、デモ用には問題ないだろう。
So the first thing I'm going to do is I'm going to open this file.
まず最初に、このファイルを開きます。
I'm going to do new module from file.
ファイルから新しいモジュールを作るつもりだ。
And reading from that object file that we just built.
そして、先ほど構築したオブジェクト・ファイルから読み込む。
We want to catch any errors.
どんなエラーもキャッチしたい。
And I'm going to use a defer.
そして、私はディファーを使うつもりだ。
I don't know if we have any Go programmers here.
ここに囲碁のプログラマーがいるかどうかわからない。
If you're not familiar with Go, this defer keyword may be new to you.
Goに慣れていない人にとって、このdeferキーワードは新鮮かもしれない。
Basically make sure that on the exit from whatever function we're in, run this code.
基本的には、今いる関数から抜けるときに、このコードを実行するようにする。
In this case, I want to make sure that we tidy up and we close our file at the exit from this function.
この場合、この関数の終了時に、ファイルを整理して閉じるようにしたい。
And just for fun, I'm going to write cleaning up here so that we know when we're exiting from the statement that's been printed.
ちょっと面白いので、プリントされたステートメントから抜けるタイミングがわかるように、ここにクリーニングを書きます。
Okay.
オーケー。
So I've opened my object file.
オブジェクトファイルを開いた。
And I now need to load that into the kernel.
それをカーネルにロードする必要がある。
And that has to succeed.
そしてそれは成功しなければならない。
Okay.
オーケー。
Now I can I want to get the hello function program.
これで、ハローファンクションのプログラムを取得できるようになった。
And I want to attach it to a kprobe.
それをkprobeに取り付けたい。
So first of all, I need to get the program.
だからまず、プログラムを手に入れる必要がある。
Got a nice function to get that program.
そのプログラムを手に入れるための素晴らしい機能がある。
And we know it's called hello.
それが "ハロー "なんだ。
And we need to attach that to kprobe.
そして、それをkprobeにアタッチする必要がある。
So I'm attaching it to, yes, the P is my program.
そう、Pは僕のプログラムなんだ。
And I'm attaching it to the function call that relates to exec VE.
そして、exec VEに関連するファンクション・コールに添付している。
Now, on this particular kernel, the function name is this.
さて、この特定のカーネルでは、関数名はこうなっている。
And this could return me an error.
そして、これはエラーを返す可能性がある。
So I need to catch that error.
だから、そのエラーをキャッチする必要がある。
Okay.
オーケー。
So I've got my object opened.
だから、私はオブジェクトを開いた。
I've got the program from inside that object.
私はそのオブジェクトの中からプログラムを持ってきた。
And I have associated it with the exec VE system call.
そして、私はそれをexec VEシステムコールと関連付けた。
The C code is writing some tracing information whenever it sees that system call.
Cのコードは、そのシステムコールを見るたびにトレース情報を書いている。
And I need to do something in user space to print it out.
そして、それをプリントアウトするには、ユーザースペースで何かをする必要がある。
And there is a convenient function.
そして便利な機能がある。
Trace.
トレース。
There we go.
これでよし。
Trace print.
トレースプリント。
Now, this will basically block and print out whatever it receives from the debug tracing.
さて、これは基本的にブロックし、デバッグ・トレースから受け取ったものをプリントアウトする。
Okay.
オーケー。
So I think we should be able to make this and run it.
だから、これを作って走らせることができるはずだと思う。
I have to run it as a privileged user.
特権ユーザーとして実行しなければならない。
And hooray!
そして万歳!
We have the equivalent of hello world.
ハロー・ワールドに相当するものがある。
Every time exec VE is running on this machine, we're getting the trace written out.
exec VEがこのマシンで実行されるたびに、トレースが書き出される。
Now, something didn't happen.
今、何かが起こらなかった。
And that something is, we never saw the cleaning up line that I put.
そして、その何かとは、私たちが見たことがない、私が置いたクリーンアップラインだ。
So remember, I've got this here.
覚えておいてほしい。
And that's because when I interrupted the program, well, just interrupted the program and it stopped.
というのも、私がプログラムを中断したとき、そう、プログラムを中断しただけで止まってしまったからだ。
In fact, it was blocked somewhere in here in trace print and got interrupted.
実際、このトレースプリントのどこかでブロックされ、中断された。
If I want to clean up properly, I'm gonna have to catch that interrupt.
ちゃんと片付けたいなら、あの中断をキャッチしないとね。
Which I can do quite conveniently in Go.
それはGoで簡単にできる。
And this is also gonna illustrate Go channels, which we're gonna use a bit in a moment.
これは囲碁チャンネルを説明するものでもある。
So I'm gonna make a channel.
だからチャンネルを作ろうと思う。
This is a really nice feature of Go channels.
これは囲碁チャンネルの本当に素晴らしい機能だ。
And this channel receives one item at a time and that item is signals from the operating system.
そして、このチャンネルは一度に1つのアイテムを受信し、そのアイテムはオペレーティング・システムからの信号である。
And I want to be notified whenever there is an interrupt signal.
そして、割り込み信号があるたびに通知を受けたい。
That's gonna say if someone triggers interrupt, send a message on this signal channel.
誰かが割り込みをトリガーしたら、このシグナル・チャンネルにメッセージを送れ、ということだ。
Actually send the interrupt into the signal channel.
実際に割り込みをシグナル・チャンネルに送る。
And I'm gonna block on that here.
私はここでブロックするつもりだ。
This is essentially wait until you get an event on that signal and then throw it away on that channel.
これは要するに、そのシグナルでイベントが発生するまで待ち、そのチャンネルでそれを捨てるということだ。
And the last thing I need to do is send this blocking function off into its own Go routine.
そして最後に必要なのは、このブロック関数を独自のGoルーチンに送り出すことだ。
This is how Go handles concurrency.
これがGoの並行処理方法だ。
This basically means it's doing its own thing in another thread.
これは基本的に、別のスレッドで独自のことをやっているということだ。
So this won't block anymore.
これでもうブロックされることはない。
So I haven't done anything very different in terms program.
だから、プログラムに関しては特に変わったことはしていない。
But it should now run a bit more cleanly in that if I hit control C, we now see our cleaning up message.
コントロールCを押すと、クリーンアップのメッセージが表示される。
And we know that things like my defer function will be executed.
そして、私のdefer関数のようなものが実行されることもわかっている。
Because it will there's nothing interrupting it before it gets to complete the function.
なぜなら、その機能を完成させる前に邪魔するものは何もないからだ。
All right.
分かった。
So that's hello world.
それがハロー・ワールドだ。
But it's not terribly useful.
でも、あまり役に立たない。
In particular, this print K function is writing data to one well known pipe location on the machine.
特に、このプリントK関数は、マシンのよく知られたパイプのある場所にデータを書き込んでいる。
If I ran any number of EPF programs and they all call print K, they'd all be writing to the same pipe.
EPFプログラムをいくつも走らせて、それらがすべてプリントKを呼び出したとしたら、それらはすべて同じパイプに書き込まれることになる。
Which is not very useful for real world.
これは実社会ではあまり役に立たない。
So we're gonna have to go back and think a bit more about maps.
だから、地図についてはもう少し戻って考える必要がある。
So I mentioned before, maps are the way we can share data between the kernel code and whatever's happening in user space.
マップは、カーネル・コードとユーザー空間で起きていることとの間でデータを共有する方法だと前述した。
There are lots of different types of map.
地図にはたくさんの種類がある。
I'm gonna use a thing called the perf event array.
パーフ・イベント・アレイというものを使おう。
And this is nice partly because we can write an arbitrary blob of data.
これは、任意のブロブデータを書き込むことができるという点でも優れている。
So any kind of data we want to write, we can write it into this perf event buffer.
つまり、どんなデータでもこのperfイベント・バッファに書き込むことができる。
And on the user space side, there's a perf buffer implementation that can receive these data blobs on a go channel.
そしてユーザースペース側には、これらのデータブロブをゴーチャンネルで受け取ることができるパーフバッファの実装がある。
So it's very sort of idiomatic way of receiving data from the EPF code.
だから、EPFコードからデータを受け取るのは、とても慣用的な方法なんだ。
Right.
そうだね。
So let's use BPF perf event output.
そこで、BPFのperfイベント出力を使ってみよう。
So we're gonna do that here.
だから、ここでそうするつもりだ。
BPF perf event output.
BPF パーフイベント出力。
Get rid of my tracing call.
私のトレースコールをなくしてくれ。
And what does this require?
そのためには何が必要なのか?
This requires context.
これには文脈が必要だ。
We need a map.
地図が必要だ。
I'm gonna just call it, I'll define it in a second, but we'll call it GoTopia.
ゴートピアと呼ぶことにしよう。
I have to pass a flag that indicates it's the current CPU.
現在のCPUであることを示すフラグを渡さなければならない。
And I'm gonna pass some data.
そして、いくつかのデータを渡すつもりだ。
I have to say how big the data is.
データがいかに大きいかを言わなければならない。
So let's make things easy.
だから、物事を簡単にしよう。
Let's pass some data.
データを渡そう。
We'll just pass a value, make up a value and pass it.
値を渡して、値を作って渡すだけだ。
So whenever execve gets called, we're gonna pass this value into the perf buffer.
execveが呼ばれるたびに、この値をperfバッファに渡すことになる。
It just remains for me to define the perf buffer here.
ただ、ここでパーフバッファーを定義することが残っている。
Perf output.
パフ出力。
And that is called GoTopia.
それが『ゴートピア』だ。
Okay.
オーケー。
And if I were to make that object and have a quick look at it again.
そして、もしそのオブジェクトを作って、もう一度ざっと見てみるとしよう。
Oh, read elf.
ああ、エルフを読んで。
Hello.
こんにちは。
And this time we can see in addition to the function name, we've also got an object defined called GoTopia.
今回は、関数名に加えて、GoTopiaというオブジェクトも定義されていることがわかる。
That's the definition of the map that has to exist in the object file.
これが、オブジェクト・ファイルに存在しなければならないマップの定義だ。
Okay.
オーケー。
So the kernel side is writing this data into my perf buffer.
つまり、カーネル側はこのデータを私のPerfバッファに書き込んでいるのだ。
And I need to read it from the Go side.
そして、囲碁側から読む必要がある。
So I'm not gonna be using trace print anymore.
だから、もうトレースプリントは使わない。
But I am going to be using a perf buffer.
しかし、私はパーフバッファーを使うつもりだ。
So we'll call it PB.
だからPBと呼ぶことにする。
And we init a perf buffer.
そして、パーフバッファーを開始する。
And it's called GoTopia.
その名も『ゴートピア』。
And I need to pass in a channel for the events that we're gonna receive.
そして、受信するイベントのチャンネルを渡す必要がある。
I'll define that in a second.
その定義は後で説明する。
I'm going to ignore any lost events.
失われた出来事は無視するつもりだ。
Page size that I know works.
私が知っているページサイズ
Okay.
オーケー。
That has to succeed.
それは成功しなければならない。
And we have to start, oops, PB, not PS.
そして、我々はPSではなくPBを始めなければならない。
PB.
PBだ。
The perf buffer.
パーフバッファ。
And when we get to cleaning up, we're going to stop it.
そして後片付けが終わったら、それを止めるんだ。
I need to define this events channel.
このイベント・チャンネルを定義する必要がある。
So we'll make a channel.
だからチャンネルを作ろう。
And the type of data that we get from here is a slice of bytes.
そして、ここから得られるデータのタイプは、バイトのスライスである。
So that's what we need to see.
だから、それを見る必要がある。
And we'll just say some arbitrary length.
そして、任意の長さを言うだけだ。
Okay.
オーケー。
So that's set up the perf buffer.
これでパーフバッファーの設定は完了だ。
We now need to receive these events.
これらのイベントを受信する必要がある。
I'm gonna do it in a Go routine again.
また囲碁のルーティンでやるつもりだ。
Which I'll call inline.
これをインラインと呼ぶことにする。
And we're going to loop reading information out of this channel.
そして、このチャンネルから情報をループして読み出す。
So every time data arrives on that channel, it will get assigned to data.
そのため、そのチャンネルにデータが到着するたびに、データに割り当てられる。
And let's print it out.
そして印刷しよう。
Got something.
何かある。
Now, we know that it's an unsigned 64-bit integer.
これで、符号なし64ビット整数であることがわかった。
So I can convert my slice of bytes.
だから、私はバイトのスライスを変換することができる。
Our little endian.
リトル・エンディアンだ。
We saw that before.
以前にも見たことがある。
US64 data.
US64のデータ。
Okay.
オーケー。
So let's see if that builds.
では、それが構築されるかどうか見てみよう。
It does.
そうだ。
And hopefully this time, every time exec VE gets called by any process on the machine, it's sending us 64-bit integer.
そしてうまくいけば、exec VEがマシン上のプロセスから呼び出されるたびに、64ビット整数が送られてくることになる。
So we're using this perf buffer, but we're not doing anything very useful with that.
つまり、このパーフ・バッファーを使っているわけだが、そのバッファーを使って何か有用なことをしているわけではない。
It's just some number that I've decided to pass.
パスすると決めた数字なんだ。
How about we get some information about the current context, like what's the name of the command?
コマンドの名前など、現在のコンテキストに関する情報を得るのはどうだろう?
The current command being called that triggered this, the syscall, the exec VE syscall.
そのトリガーとなった現在呼び出されているコマンド、シスコール、exec VEシスコール。
So we, instead of passing numeric data, let's make this into some characters.
そこで、数値データを渡す代わりに、これを文字にしてみよう。
Again, a bit of an arbitrary length.
またしても、ちょっと恣意的な長さだ。
Size of that data.
そのデータのサイズ。
Okay.
オーケー。
And we write it into the perf buffer in exactly the same way.
そして、まったく同じ方法でパーフバッファに書き込む。
And I just need to change this so that converts my string of bytes, my series of bytes into a string.
そして、バイト列を文字列に変換するように、これを変更する必要がある。
So now I should be able to say that.
だから今、私はそう言えるはずだ。
So this time, every time exec VE gets called, we should see the name of the command.
だから今回は、exec VEが呼ばれるたびに、コマンドの名前が表示されるはずだ。
And yeah, we can see, I happen to have my Kubernetes and Docker running on this virtual machine.
この仮想マシン上でKubernetesとDockerが動いている。
So they're spawning quite a few new processes.
だから、かなりの数の新しいプロセスを生み出している。
So this is starting to get pretty close to the example we saw with BPF traffic.
つまり、BPFトラフィックで見た例にかなり近くなってきたということだ。
So let's do a little bit of trace.
では、少しトレースしてみよう。
If you remember that example, well, it was summing up a counter for each different command.
この例を覚えているなら、異なるコマンドごとにカウンターを合計していたのだ。
So I think that would be pretty easy to implement in Go.
だから、Goで実装するのはかなり簡単だと思う。
Let's make, so we're going to have a map of the command name, which is a string and the counter.
コマンド名(文字列)とカウンターのマップを作ろう。
Make some arbitrary size of that.
それを任意の大きさにする。
And instead of printing out the name of the command, we can just increment the counter for that name.
そして、コマンド名を表示する代わりに、その名前のカウンターをインクリメントすればいい。
Okay.
オーケー。
And when we finish, we will loop over that counter and print out the results.
そして終了したら、そのカウンターをループして結果をプリントアウトする。
So the key and the value from my counter will print out the name and the value.
だから、私のカウンターのキーと値は、名前と値を出力する。
Name and the value.
名前と値。
Excuse me.
すみません。
Right.
そうだね。
So have I missed anything there?
何か見落としていることはある?
I think that's okay.
それでいいと思う。
So this is still associated with the exec vehicle.
だから、これはまだ実行車両に関連している。
Let's see if it works.
うまくいくかどうか見てみよう。
So we'll just run that for a couple of seconds.
では、それを数秒間実行してみましょう。
When we interrupt it, it should print out the counters.
中断させると、カウンターをプリントアウトするはずだ。
Okay.
オーケー。
The last thing we need to do is change the attachment point.
最後に必要なのは、アタッチメントポイントの変更だ。
So currently we're attached to a K-probe at the entry of execve.
というわけで、現在はexecveのエントリーでKプローブにアタッチしている。
I'm going to change it so that it's associated with a trace point for sysenter.
sysenterのトレースポイントに関連するように変更するつもりだ。
And as I mentioned before, sysenter gets called every time any system call is invoked.
そして前にも述べたように、システムコールが呼び出されるたびにsysenterが呼び出される。
So I need to change that in two places.
だから、2箇所を変更する必要がある。
I need to change the section declaration here.
ここでセクション宣言を変更する必要がある。
So this becomes raw trace point sysenter.
つまり、これが生のトレースポイント・シセンターとなる。
And I need to change where we attached it here.
そして、ここに取り付ける場所を変える必要がある。
A raw trace point called sysenter.
sysenterと呼ばれる生のトレースポイント。
And with a bit of luck, this is going to recreate that BPF script.
そして運が良ければ、これであのBPFスクリプトを再現できる。
So we'll just run it for a few seconds.
では、数秒間だけ実行してみましょう。
And then when we interrupt it, we should see a counter.
そしてそれを中断すると、カウンターが表示されるはずだ。
And those counters should tell us how many system calls have been invoked by each of those different commands.
そしてこれらのカウンターは、それぞれのコマンドによってどれだけのシステムコールが呼び出されたかを教えてくれるはずだ。
So we've recreated that BPF trace command.
そこで、BPFトレースコマンドを再現してみた。
We've done it in just under 60 lines of Go code and a handful of lines of C.
わずか60行弱のGoコードと数行のCコードで実現した。
I hope that's given an illustration of the kind of things you could do.
どんなことができるのか、イメージしてもらえたと思う。
Now, obviously, you can attach to many different trace points.
もちろん、さまざまなトレースポイントに取り付けることができる。
I've only slightly scratched the surface of the things that you can do with BPF helper functions and all the different range of contextual information you could then observe and manipulate and pass up to user space.
BPFヘルパー関数でできること、そしてそれを観察し、操作し、ユーザースペースに渡すことができる様々なコンテキスト情報については、まだほんの少ししか触れていません。
I've had to gloss over lots and lots of details in the interest of time.
時間の都合上、細部についてはたくさん、たくさん説明しなければならなかった。
But the code that I've written is available on GitHub.
しかし、私が書いたコードはGitHubで公開されている。
And I'm really hoping that you have some questions for me.
そして、ぜひ私に質問をしてほしい。
Thank you.
ありがとう。