contributed by < Risheng1128
>
參考 Passing Command Line Arguments to a Module ,發現 kernel 是使用巨集 module_param
傳遞參數,接著可以在檔案 main.c
發現該巨集的使用,可以得知 khpptd
可以讓使用者自己設定 port
及 backlog
static ushort port = DEFAULT_PORT;
module_param(port, ushort, S_IRUGO);
static ushort backlog = DEFAULT_BACKLOG;
module_param(backlog, ushort, S_IRUGO);
接著研究 module_param
的實作,可以在 linux/include/linux/moduleparam.h 找到數個定義,將相關定義表示在下方
#define module_param(name, type, perm) \
module_param_named(name, name, type, perm)
#define module_param_named(name, value, type, perm) \
param_check_##type(name, &(value)); \
module_param_cb(name, ¶m_ops_##type, &value, perm); \
__MODULE_PARM_TYPE(name, #type)
可以分成 param_check_##type
, module_param_cb
及 __MODULE_PARM_TYPE
做討論
param_check_##type
由於 khttpd
的變數是使用 ushort
的型態,因此巨集會被展開成 param_check_ushort
,以下為相關巨集
#define param_check_ushort(name, p) __param_check(name, p, unsigned short)
/* All the helper functions */
/* The macros to do compile-time type checking stolen from Jakub
Jelinek, who IIRC came up with this idea for the 2.4 module init code. */
#define __param_check(name, p, type) \
static inline type __always_unused *__check_##name(void) { return(p); }
從註解很明顯可以知道 param_check_##type
的目的是要在編譯時期就判斷變數 p
是否真的是 type
型態,方法是藉由回傳 p
判斷函式是否回傳相同型態
module_param_cb
/**
* module_param_cb - general callback for a module/cmdline parameter
* @name: a valid C identifier which is the parameter name.
* @ops: the set & get operations for this parameter.
* @arg: args for @ops
* @perm: visibility in sysfs.
*
* The ops can have NULL set or get functions.
*/
#define module_param_cb(name, ops, arg, perm) \
__module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1, 0)
/* This is the fundamental function for registering boot/module
parameters. */
#define __module_param_call(prefix, name, ops, arg, perm, level, flags) \
/* Default value instead of permissions? */ \
static const char __param_str_##name[] = prefix #name; \
static struct kernel_param __moduleparam_const __param_##name \
__used __section("__param") \
__aligned(__alignof__(struct kernel_param)) \
= { __param_str_##name, THIS_MODULE, ops, \
VERIFY_OCTAL_PERMISSIONS(perm), level, flags, { arg } }
__module_param_call
建立一個型態為 kernel_param
且名稱為 __param_##name
的結構,並告訴編譯器以下資訊
__param
區__alignof__(struct kernel_param)
的大小接著查看結構 kernel_param
的宣告
struct kernel_param {
const char *name;
struct module *mod;
const struct kernel_param_ops *ops;
const u16 perm;
s8 level;
u8 flags;
union {
void *arg;
const struct kparam_string *str;
const struct kparam_array *arr;
};
};
由以上資訊我們可以得到最後 __module_param_call
建立的結構,以變數 port
作為範例,如以下所示
struct kernel_param _param_name {
const char *name = "port";
struct module *mod = THIS_MODULE;
const struct kernel_param_ops *ops = ¶m_ops_ushort;
const u16 perm = VERIFY_OCTAL_PERMISSIONS(S_IRUGG);
s8 level = -1;
u8 flags = 0;
void *arg = &port;
};
最後使用命令 readelf -r khttpd.ko
查看 __param
的區域,的確有 port
和 backlog
的資料
Relocation section '.rela__param' at offset 0xc1a48 contains 8 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000000 000700000001 R_X86_64_64 0000000000000000 .rodata + 1248
000000000008 005600000001 R_X86_64_64 0000000000000000 __this_module + 0
000000000010 005400000001 R_X86_64_64 0000000000000000 param_ops_ushort + 0
000000000020 000e00000001 R_X86_64_64 0000000000000000 .data + 4
000000000028 000700000001 R_X86_64_64 0000000000000000 .rodata + 1250
000000000030 005600000001 R_X86_64_64 0000000000000000 __this_module + 0
000000000038 005400000001 R_X86_64_64 0000000000000000 param_ops_ushort + 0
000000000048 000e00000001 R_X86_64_64 0000000000000000 .data + 6
__MODULE_PARM_TYPE
#define __MODULE_PARM_TYPE(name, _type) \
__MODULE_INFO(parmtype, name##type, #name ":" _type)
#define __MODULE_INFO(tag, name, info) \
static const char __UNIQUE_ID(name)[] \
__used __section(".modinfo") __aligned(1) \
= __MODULE_INFO_PREFIX __stringify(tag) "=" info
#define MODULE_PARAM_PREFIX KBUILD_MODNAME "."
#define KBUILD_MODNAME /* empty */
參考 Linux 核心模組掛載機制 可以很清楚的知道 __UNIQUE_ID
的功能
__UNIQUE_ID
會根據參數產生一個不重複的名字,其中使用到的技術是利用巨集中的 ##
來將兩個參數合併成一個新的字串__attribute__
關鍵字告訴編譯器,這段訊息
.modinfo
區 (__section(".modinfo")
)__used
)__aligned(1)
)__stringify
的目的是為了把參數轉換成字串形式MODULE_PARAM_PREFIX
由巨集 KBUILD_MODNAME
和 "."
組合而成,簡單來說就只是個字串最後以變數 port
為例,會產生以下巨集
#define __MODULE_INFO(tag, name, info) \
static const char __UNIQUE_ID(name)[] \
__used __section(".modinfo") __aligned(1) \
= ".parmtype=port:ushort."
接著使用命令 objdump -s khttpd.ko
查看 .modinfo
的區域
...
Contents of section .modinfo:
...
0070 00706172 6d747970 653d6261 636b6c6f .parmtype=backlo
+ 0080 673a7573 686f7274 00706172 6d747970 g:ushort.parmtyp
+ 0090 653d706f 72743a75 73686f72 74007372 e=port:ushort.sr
...
繼續根據 Linux 核心模組掛載機制 使用 strace 追蹤 insmod fibdrv.ko
$ sudo strace insmod khttpd.ko port=1999
execve("/usr/sbin/insmod", ["insmod", "khttpd.ko", "port=1999"], 0x7fff08f9ff70 /* 25 vars */) = 0
brk(NULL) = 0x5607976a6000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
...
mmap(NULL, 1366608, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa7c761b000
finit_module(3, "port=1999", 0) = -1 EEXIST (File exists)
write(2, "insmod: ERROR: could not insert "..., 62insmod: ERROR: could not insert module khttpd.ko: File exists
) = 62
munmap(0x7fa7c761b000, 1366608) = 0
close(3) = 0
exit_group(1) = ?
+++ exited with 1 +++
查看位於第 8 行 finit_module
的實作,參考 kernel/module.c 及 finit_module(2) - Linux man page
int finit_module(int fd, const char *param_values, int flags);
對應 strace 的結果
fd = 3
param_values = "port=1999"
flag = 0
SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)
{
...
return load_module(&info, uargs, flags);
}
函式 finit_module
呼叫函式 load_module
,接著繼續分析
static int load_module(struct load_info *info, const char __user *uargs,
int flags)
{
/* Now copy in args */
mod->args = strndup_user(uargs, ~0UL >> 1);
if (IS_ERR(mod->args)) {
err = PTR_ERR(mod->args);
goto free_arch_cleanup;
}
}
從上述程式碼可以看到從命令列的輸入參數已經被複製到 mod->arg
,且 mod
的型態為 struct mod
,參考 include/linux/module.h
struct module {
...
/* The command line arguments (may be mangled). People like
keeping pointers to this stuff */
char *args;
...
}
找到了 args
的宣告,從註解可以知道 args
的目的就是儲存 command line 的設定參數
回到 load_module
,發現了函式 parse_args
,從註解可以知道是要將 command line 的字串拆解
/* Module is ready to execute: parsing args may do that. */
after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
-32768, 32767, mod,
unknown_module_param_cb);
進到函式 parse_args
,參考 kernel/params.c
/* Args looks like "foo=bar,bar2 baz=fuz wiz". */
char *parse_args(const char *doing,
char *args,
const struct kernel_param *params,
unsigned num,
s16 min_level,
s16 max_level,
void *arg,
int (*unknown)(char *param, char *val,
const char *doing, void *arg))
{
char *param, *val, *err = NULL;
/* Chew leading spaces */
args = skip_spaces(args);
if (*args)
pr_debug("doing %s, parsing ARGS: '%s'\n", doing, args);
while (*args) {
int ret;
int irq_was_disabled;
args = next_arg(args, ¶m, &val);
/* Stop at -- */
if (!val && strcmp(param, "--") == 0)
return err ?: args;
irq_was_disabled = irqs_disabled();
ret = parse_one(param, val, doing, params, num,
min_level, max_level, arg, unknown);
if (irq_was_disabled && !irqs_disabled())
pr_warn("%s: option '%s' enabled irq's!\n",
doing, param);
switch (ret) {
case 0:
continue;
case -ENOENT:
pr_err("%s: Unknown parameter `%s'\n", doing, param);
break;
case -ENOSPC:
pr_err("%s: `%s' too large for parameter `%s'\n",
doing, val ?: "", param);
break;
default:
pr_err("%s: `%s' invalid for parameter `%s'\n",
doing, val ?: "", param);
break;
}
err = ERR_PTR(ret);
}
return err;
}
函式 parse_args
做了以下幾件事
skip_spaces
將字串的第一個字元如果為空白字元,將空白字元全部移除next_arg
找到下一個 argument ,參考 lib/cmdline.cparse_one
將試著將 argument 加進 module 裡,該函式位於 kernel/params.c最後討論函式 parse_one
static int parse_one(char *param,
char *val,
const char *doing,
const struct kernel_param *params,
unsigned num_params,
s16 min_level,
s16 max_level,
void *arg,
int (*handle_unknown)(char *param, char *val,
const char *doing, void *arg))
{
unsigned int i;
int err;
/* Find parameter */
for (i = 0; i < num_params; i++) {
if (parameq(param, params[i].name)) {
if (params[i].level < min_level
|| params[i].level > max_level)
return 0;
/* No one handled NULL, so do it here. */
if (!val &&
!(params[i].ops->flags & KERNEL_PARAM_OPS_FL_NOARG))
return -EINVAL;
pr_debug("handling %s with %p\n", param,
params[i].ops->set);
kernel_param_lock(params[i].mod);
if (param_check_unsafe(¶ms[i]))
err = params[i].ops->set(val, ¶ms[i]);
else
err = -EPERM;
kernel_param_unlock(params[i].mod);
return err;
}
}
if (handle_unknown) {
pr_debug("doing %s: %s='%s'\n", doing, param, val);
return handle_unknown(param, val, doing, arg);
}
pr_debug("Unknown argument '%s'\n", param);
return -ENOENT;
}
注意第 17 行的部份, linux 核心逐步尋找符合的參數,並在第 29 行呼叫函式指標 params[i].ops->set(val, ¶ms[i])
,將輸入的資料複製到模組的資料裡,以下為其結構宣告
struct kernel_param_ops {
/* How the ops should behave */
unsigned int flags;
/* Returns 0, or -errno. arg is in kp->arg. */
int (*set)(const char *val, const struct kernel_param *kp);
/* Returns length written or -errno. Buffer is 4k (ie. be short!) */
int (*get)(char *buffer, const struct kernel_param *kp);
/* Optional function to free kp->arg when module unloaded. */
void (*free)(void *arg);
};