読者です 読者をやめる 読者になる 読者になる

linux: livepatchめも klp_find_object_symbol()でpatch対象関数のアドレスを探すところ

kernel linux

前のlinux: livepatchコードリーディングめも1 〜 patch側のデータ設定とpatchの登録まで - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモではさらっと流したklp_find_object_symbol()でpatch対象関数のアドレスを探すところを見てみる。

呼んでいる場所としてklp_find_verify_func_addr()を見てみると、

231 static int klp_find_verify_func_addr(struct klp_object *obj,
232                                      struct klp_func *func)
233 {
234         int ret;
235 
236 #if defined(CONFIG_RANDOMIZE_BASE)
237         /* KASLR is enabled, disregard old_addr from user */
238         func->old_addr = 0;
239 #endif
240 
241         if (!func->old_addr || klp_is_module(obj))
242                 ret = klp_find_object_symbol(obj->name, func->old_name,
243                                              &func->old_addr);
244         else
245                 ret = klp_verify_vmlinux_symbol(func->old_name,
246                                                 func->old_addr);
247 
248         return ret;
249 }

patchのカーネルモジュールでstruct klp_funcのold_addrを設定しなかった場合はここは0で初期化されているはずなので、klp_find_object_symbol()が使われます。

klp_find_object_symbol()はこのような関数で実際にシンボルを探すのはkallsyms_on_each_symbol()です。

172 static int klp_find_object_symbol(const char *objname, const char *name,
173                                   unsigned long *addr)
174 {
175         struct klp_find_arg args = {
176                 .objname = objname,
177                 .name = name,
178                 .addr = 0,
179                 .count = 0
180         };
181 
182         kallsyms_on_each_symbol(klp_find_callback, &args);
183 
184         if (args.count == 0)
185                 pr_err("symbol '%s' not found in symbol table\n", name);
186         else if (args.count > 1)
187                 pr_err("unresolvable ambiguity (%lu matches) on symbol '%s' in object '%s'\n",
188                        args.count, name, objname);
189         else {
190                 *addr = args.addr;
191                 return 0;
192         }
193 
194         *addr = 0;
195         return -EINVAL;
196 }

kallsyms_on_each_symbol()はkernel/kallsyms.cにあってコールバック関数と、コールバック関数に渡す引数を引数として取る関数です。

196 int kallsyms_on_each_symbol(int (*fn)(void *, const char *, struct module *,
197                                       unsigned long),
198                             void *data)
199 {
200         char namebuf[KSYM_NAME_LEN];
201         unsigned long i;
202         unsigned int off;
203         int ret;
204 
205         for (i = 0, off = 0; i < kallsyms_num_syms; i++) {
206                 off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));
207                 ret = fn(data, namebuf, NULL, kallsyms_addresses[i]);
208                 if (ret != 0)
209                         return ret;
210         }
211         return module_kallsyms_on_each_symbol(fn, data);
212 }

コールバック関数を使うと言っても処理はasyncでは無いですね。基本的にシンボル毎にコールバック関数を呼んでいく感じです。最後のmodule_kallsyms_on_each_symbol()カーネルモジュール毎にシンボルを見ていって、kallsyms_on_each_symbol()と同じようにコールバック関数を呼んでいきます。

ここで使われているコールバック関数は当然livepatchが用意しているものでklp_find_callback()です。

147 static int klp_find_callback(void *data, const char *name,
148                              struct module *mod, unsigned long addr)
149 {
150         struct klp_find_arg *args = data;
151 
152         if ((mod && !args->objname) || (!mod && args->objname))
153                 return 0;
154 
155         if (strcmp(args->name, name))
156                 return 0;
157 
158         if (args->objname && strcmp(args->objname, mod->name))
159                 return 0;
160 
161         /*
162          * args->addr might be overwritten if another match is found
163          * but klp_find_object_symbol() handles this and only returns the
164          * addr if count == 1.
165          */
166         args->addr = addr;
167         args->count++;
168 
169         return 0;
170 }

どんな場合もreturnするのは0ですね。これは欲しいシンボルじゃないけどエラーでもないので0を返すというところですかね。 もしくは全シンボルをチェックしたいからかな? 最初のほうのチェックで、モジュール名が入っている・いない(シンボルがカーネルモジュールにあるのかvmlinuxにあるのかというところで)、関数名が違うとかのチェックをして、これらをくぐり抜けるとそのシンボルのアドレスやカウンタがargs(1番目の引数のdata)に設定されます。

さて、klp_find_object_symbol()に戻ってkallsyms_on_each_symbol()が終わると以下の処理になります。

184         if (args.count == 0)
185                 pr_err("symbol '%s' not found in symbol table\n", name);
186         else if (args.count > 1)
187                 pr_err("unresolvable ambiguity (%lu matches) on symbol '%s' in object '%s'\n",
188                        args.count, name, objname);
189         else {
190                 *addr = args.addr;
191                 return 0;
192         }
193 
194         *addr = 0;

まあ見たとおりですね。countが0ならシンボルが見つからなかった、逆にシンボルが1つ以上あった場合はどちらが正しいかlivepatchからだと解決できない(この場合、patch側でstruct klp_funcのold_addrを指定する必要がある)という場合はエラー。そうじゃなければ関数の引数の*addrにシンボルのアドレスを設定。

klp_find_object_symbol()の呼び出し側は下記のようにしているのでfunc->old_addrにシンボルのアドレスが設定されます。

242                 ret = klp_find_object_symbol(obj->name, func->old_name,
243                                              &func->old_addr

ということで、シンボルのアドレスを探すのはkallsyms_on_each_symbol()module_kallsyms_on_each_symbol()で各シンボルをコールバック関数で受け取って、そのアドレスが期待しているものかを調べるという方法で行われていました( ´ー`)フゥー...