晚上有回过头来看MAC的问题,虽然试验证明是可以,但我还是不清楚:
/* need to get the ether addr from u-boot */
static int __init el_addr_setup(char *line)
{
return 0;
}
__setup("mac=", el_addr_setup);
这里的el_addr_setup函数是如何被调用到的。
类似的问题其实很早就有人问过:
> I'm trying to track down why ide_setup() doesn't seem to be called on
> my system. I'm using KGDB to put breakpoints in ide_setup(), but they
> never get hit. I see that ide_setup is defined as:
>
> int __init ide_setup (char *s)
> {
> ...
>
> and there is also a line:
>
> __setup("", ide_setup);
>
> What do these macros do? How can I get KGDB to break in ide_setup()?
take a look at the source (include/linux/init.h). it places code in a
special section, then is used during init/main.c::checksetup() to find
matching setup strings from the kernel boot commandline. it's not going
to be called if you don't have an "idex=xxx" or "hdx=" string on the
commandline. it's also called if you compile ide modular and give it
options during insmod.
我们过一遍:
/*
* Used for kernel command line parameter setup
*/
struct kernel_param {
const char *str;
int (*setup_func)(char *);
};
extern struct kernel_param __setup_start, __setup_end;
#define __setup(str, fn) /
static char __setup_str_##fn[] __initdata = str; /
static struct kernel_param __setup_##fn __attribute__((unused)) __initsetup = { __setup_str_##fn, fn }
__setup("mac=", el_addr_setup);就是说:
static char __setup_str_el_addr_setup[] __initdata = “mac=”;
static struct kernel_param __setup_el_addr_setup __attribute__((unused)) __initsetup = { __setup_str_el_addr_setup, el_addr_setup }
也就是定义了一个字符串,定义了一个结构体;
在看/2.4.20/init/main.c
int __init checksetup(char *line)
{
struct kernel_param *p;
if (line == NULL)
return 0;
p = &__setup_start;
do {
int n = strlen(p->str);
if (!strncmp(line,p->str,n)) {
if (p->setup_func(line+n))
return 1;
}
p++;
} while (p < &__setup_end);
return 0;
}
其输入的参数就是cmdline
当我们拿到cmdline中的一个子命令段的时候,就回去和__setup_start到__setup_end之间的这些已经注册了的,处理结构体进行比对的工作。
找到匹配项就掉其对应的处理函数予以执行。
从System.map文件中找到:
802874d0 A __setup_start
802875b8 A __setup_end
这样你还可能不是很清楚,那你就要参考arch/i386/vmlinuz.lds这个关于ld 链接器的脚本文件有这样的一段
__setup_start = .;
.setup.init : { *(.setup.init) }
__setup_end = .;
这里的意思就是__setup_start是一个节的开始,而__setup_end是一个节的结束,这个节的名称是.setup.init
通过readelf命令:
readelf -S vmlinux
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[10] .setup.init PROGBITS 802874d0 1874d0 0000e8 00 WA 0 0 4
通过下面这个命令,我们就知道现在已经注册了的结构体列表。
[root@localhost 2.4.20_mtd]# hexdump -n 232 -s 1602768 ./vmlinux
01874d0 2880 9859 2780 8060 2880 a459 2780 0063
01874e0 2880 ac59 2780 2463 2880 b859 2780 c467
01874f0 2880 c459 2780 d467 2880 d459 2780 0468
0187500 2880 d859 2780 3068 2880 cc5c 2780 8469
0187510 2880 d45c 2780 5c6a 2880 e05c 2780 6c6a
0187520 2880 f05c 2780 c86f 2880 005d 2780 f86f
0187530 2880 205d 2780 cc94 2880 2c5d 2780 2cb3
0187540 2880 345d 2780 58b3 2880 405d 2780 94b6
0187550 2880 7c5d 2780 8ccf 2880 b85d 2780 a0e1
0187560 2880 9c65 2780 ace9 2880 a865 2780 70ed
0187570 2880 b865 2780 9ced 2880 e065 2880 180e
0187580 2880 ec65 2880 440e 2880 fc65 2880 600e
0187590 2880 5066 2880 c417 2880 5866 2880 c417
01875a0 2880 a067 2880 0c38 2880 a467 2880 d03a
01875b0 2880 c474 2880 e450
上面黑体的部分就和System.map里的802850e4 t el_addr_setup向对应(这个是一个MIPS的kernel,用的是大端)。
那么我们可以得到他的str 的指针:802874c4
继续readelf
[Nr] Name Type Addr Off Size
[ 9] .data.init PROGBITS 80285998 185998 001b34
0x802874c4 – 0x80285998 = 6956
185998 = 1595800
1595800+6956 = 1602756
我们假设字符串的长度不到10个字节。
[root@localhost 2.4.20_mtd]# hexdump -c -n 10 -s 1602756 ./vmlinux
01874c4 m a c = /0 /0 /0 /0 /0 /0
[root@localhost 2.4.20_mtd]# hexdump -n 10 -s 1602756 ./vmlinux
01874c4 616d 3d63 0000 0000 0000
这里就验证了我们看到的情况。
那为什么是.setup.init而不是别的段呢?
#define __initsetup __attribute__ ((unused,__section__ (".setup.init")))
这里有个技术点是如何在文件中找到我们用的数据,而不是在loader将ELF加载到了内存之后。