Linuxカーネル・ドライバにおいて、Instruction Pointer (=プログラムカウンタ)を取得したい場合、linux/kernel.hで定義された_THIS_IP_
マクロを使います
定義は下記のとおり。
#define _THIS_IP_ ({ __label__ __here; __here: (unsigned long)&&__here; })
LinuxカーネルにおけるInstruction Pointerの簡単な歴史
かつては各アーキテクチャで独自にcurrent_text_addr()
を定義していたのですが、v4.20あたりでそれらが全て撤廃され、linux/kernel.hにある_THIS_IP_
を使うように統一されたようです。
このときのpatchを見ると、各アーキテクチャのアセンブラによるプログラムカウンタの取得方法が見れておもしろいです。
アセンブラを書いたことがある人なら当たり前の話なのですが、プロセッサのプログラムカウンタを保持するレジスタは、アーキテクチャ毎に異なります。そのため、アーキテクチャ毎に個別の実装を用意するというのは、理にかなっているような気がします。ではなぜ、マクロ1本に統一されたのか?
なんだこのマクロは?
まずマクロがまったく理解できなかったのでググってみたところ、stackoverflowで優しいお兄様方が解説してくれていました。さすがです。
要約すると、
-
__label__ __here;
はローカルラベル名の宣言 -
__here:
はローカルラベル -
&&__here;
は上記ローカルラベルのアドレス (unsigned long型にキャストされている)
ということのようです。カッコ()の中が1行だとわかりにくいので改行を入れると、
{
__label__ __here;
__here:
(unsigned long)&&__here;
}
となり、1行目がローカルラベル名の宣言、2行目がラベルです。
ローカルラベルは、どうやらGCCで使えるものらしいです。
https://gcc.gnu.org/onlinedocs/gcc-5.2.0/gcc/Local-Labels.html
ブロック内でのみ参照できるラベルということですね。
3行目の'&&'はラベルのアドレスを示すための特別な演算子です(参考:https://docs.oracle.com/cd/E19205-01/821-0386/bjabt/index.html)
このマクロを埋め込んだ位置では、ブロック内のローカルラベルとして__here
が貼られ、そのラベルのアドレスを取得することが、詰まるところ現在のプログラムの位置(=プログラムカウンタ)となるわけです。
よって、プログラムの現在の位置を、アセンブラに頼らずC言語のみで表現できるため、カーネル内でも_THIS_IP_
マクロ1本に統一できたというわけです。(なお、gccを使う限りはLinuxに限らず様々な環境に移植可能なはずです。)
そしてC言語ようわからんという愚痴
問題はこの外側について。正直よくわからない。通常、ブロック文{}は式として使用できない。試しに、下記のようなコードを書くとコンパイルエラーになる。
int i = {1;};
ところが、ブロック文をカッコ()で括るとなぜかコンパイルが通る。
int i = ({1;});
このとき、カッコ()式の値はブロック{}文の最後に書いた式を評価した時の値が使用される。試しに下記のコードをgccでコンパイルして実行してみると、コンソールにはブロック文最後の式dの値(=5)が表示される。
#include <stdio.h>
int main(void)
{
int a = ({
int a = 2;
int b = 3;
int c = 4;
int d = 5;
a;
b;
c;
d;
});
printf("%d\n", a);
return 0;
}
これは文法的にどうなんだ。。。
以上、かれこれ10年以上はC言語を扱ってるのに未だに文法的に知らない・読めないものが出てくることに対する無力感から、備忘録としてまとめました。...精進します。
でもC言語は綺麗じゃないと思う。だって'&&'、お前ずっと二項演算子として俺と付き合ってたはずだよな...?
('&&'もGCCの拡張ですかね https://gcc.gnu.org/onlinedocs/gcc-5.2.0/gcc/Labels-as-Values.html#Labels-as-Values)