Uboot 命令是如何被使用的?
有什么问题请 发邮件至syyxy@outlook.com, 欢迎交流~
在uboot代码中命令的模式是这个样子:
这样是如何和命令行交互的呢?
在command.h 中, 我们可以看到如下宏定义
将其拆分出来:
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
U_BOOT_CMD_COMPLETE(name,maxargs, rep,cmd,usage,help,NULL)
我们可以看到
U_BOOT_CMD(
md, 3, 1, do_mem_md,
"memory display",
"[.b, .w, .l] address [# of objects]"
);
被define成了:
U_BOOT_CMD_COMPLETE(
md, 3, 1, do_mem_md,
"memory display",
"[.b, .w, .l] address [# of objects]",NULL
);
继续:
#define U_BOOT_CMD_COMPLETE(name,maxargs,rep,cmd,usage,help,comp) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = \
U_BOOT_CMD_MKENT_COMPLETE(name,maxargs,rep,cmd,usage,help,comp)
其中,##name 意思是 使用 name 替换 ##name。
由此可见,上述被define成了:
cmd_tbl_t __u_boot_cmd_md Struct_Section = \
U_BOOT_CMD_MKENT_COMPLETE(md, 3, 1, do_mem_md, "memory display","[.b, .w, .l] address [# of objects]",NULL);
继续:
#define U_BOOT_CMD_MKENT_COMPLETE(name,maxargs,rep,cmd,usage,help,comp) \
{#name, maxargs, rep, cmd, usage, _CMD_HELP(help) _CMD_COMPLETE(comp)}
其中,#的作用是字符串化,意思是将 name 转化成一个字符串。
具体## 和 # 请参阅如下文档。
https://wenku.baidu.com/view/56ed000216fc700abb68fcdd.html
可以看到,U_BOOT_CMD_MKENT_COMPLETE(md, 3, 1, do_mem_md, "memory display","[.b, .w, .l] address [# of objects]"); 被define成了
{“md”, 3, 1, do_mem_md, "memory display", _CMD_HELP("[.b, .w, .l] address [# of objects]") _CMD_COMPLETE(NULL)}
因此 U_BOOT_CMD_COMPLETE被定义成了:
cmd_tbl_t __u_boot_cmd_md Struct_Section = \
{
“md”,
3,
1,
do_mem_md,
"memory display",
_CMD_HELP("[.b, .w, .l] address [# of objects]") _CMD_COMPLETE(NULL)
}
继续:
由于 #define Struct_Section __attribute__((unused, section(".u_boot_cmd"), aligned(4))),而
# define _CMD_COMPLETE(x) x,
# define _CMD_HELP(x) x,
(注意最后都有一个逗号) , 因此 上述被定义成了
cmd_tbl_t __u_boot_cmd_md __attribute__((unused, section(".u_boot_cmd"), aligned(4))) =
{
“md”,
3,
1,
do_mem_md,
"memory display",
"[.b, .w, .l] address [# of objects]",
NULL,
}
上述是我们的分析,接下来 通过将cmd_mem.c 预编译得到
cmd_tbl_t __u_boot_cmd_md __attribute__((unused, section(".u_boot_cmd"), aligned(4))) =
{"md",
3,
1,
do_mem_md,
"memory display",
"[.b, .w, .l] address [# of objects]",
((void *)0),};
完全一致,因此上述分析正确。
=================================我是分割线===================================
接下来第二步,这些命令是如何被使用的?
首先分析我们在第一步得到的结构体:
cmd_tbl_t __u_boot_cmd_md __attribute__((unused, section(".u_boot_cmd"), aligned(4))) =
{"md",
3,
1,
do_mem_md,
"memory display",
"[.b, .w, .l] address [# of objects]",
((void *)0),};
__attribute__((unused, section(".u_boot_cmd"), aligned(4))) 说明,将这个结构体放到 .u_boot_cmd 段中,该段4字节对其,unused 代表 告诉编译器 该函数或者变量可能不使用,GGC不要对其报警告。
请注意, 这里是将该结构体放到编译出来的.o 文件的 .u_boot_cmd段, 还没有放到输出文件的.u_boot_cmd段。当链接的时候才能放到 输出文件的 .u_boot_cmd段。
我们知道 GCC 默认的段中没有 .u_boot_cmd 这个段,那么就要手动创建这个段。Uboot通过链接脚本创建的这个段, 图片截取自u-boot.lds
既然说在会将指令放到 .u_boot_cmd 这个段,那么能不能证明呢?
以澜起平台为例,找到生成的中间文件 u-boot 通过objdump –t 找到symbol table 然后 grep 出u_boot_cmd,使用如下指令,
/opt/Montage-tech/mips-4.3/bin/mips-linux-gnu-objdump -t u-boot |grep u_boot_cmd >~/u-boot.txt 可以得到 如下cmd , 其中加红 的是我们上述 md 命令 : 8017964c l d .u_boot_cmd 00000000 .u_boot_cmd 80179b70 g O .u_boot_cmd 0000001c __u_boot_cmd_nf 80179b1c g O .u_boot_cmd 0000001c __u_boot_cmd_macmode 801799e8 g O .u_boot_cmd 0000001c __u_boot_cmd_cpu 80179dbc g O .u_boot_cmd 0000001c __u_boot_cmd_freeze_with_dioff 80179c6c g O .u_boot_cmd 0000001c __u_boot_cmd_sspi 80179a90 g O .u_boot_cmd 0000001c __u_boot_cmd_macinit 8017964c g O .u_boot_cmd 0000001c __u_boot_cmd_runapp 801798d0 g O .u_boot_cmd 0000001c __u_boot_cmd_mm 80179b8c g O .u_boot_cmd 0000001c __u_boot_cmd_env 80179da0 g O .u_boot_cmd 0000001c __u_boot_cmd_av_launch 801797b8 g O .u_boot_cmd 0000001c __u_boot_cmd_fatls 8017987c g O .u_boot_cmd 0000001c __u_boot_cmd_loady 80179d14 g O .u_boot_cmd 0000001c __u_boot_cmd_version 80179b54 g O .u_boot_cmd 0000001c __u_boot_cmd_ntt 80179cf8 g O .u_boot_cmd 0000001c __u_boot_cmd_usbboot 80179748 g O .u_boot_cmd 0000001c __u_boot_cmd_coninfo 80179be0 g O .u_boot_cmd 0000001c __u_boot_cmd_setenv 80179d68 g O .u_boot_cmd 0000001c __u_boot_cmd_tsi 80179940 g O .u_boot_cmd 0000001c __u_boot_cmd_cp 801796bc g O .u_boot_cmd 0000001c __u_boot_cmd_reset 80179ca4 g O .u_boot_cmd 0000001c __u_boot_cmd_false 80179994 g O .u_boot_cmd 0000001c __u_boot_cmd_loop 80179a04 g O .u_boot_cmd 0000001c __u_boot_cmd_nand 801796d8 g O .u_boot_cmd 0000001c __u_boot_cmd_bootm 80179a58 g O .u_boot_cmd 0000001c __u_boot_cmd_mpw 80179978 g O .u_boot_cmd 0000001c __u_boot_cmd_base 80179bc4 g O .u_boot_cmd 0000001c __u_boot_cmd_printenv 80179c34 g O .u_boot_cmd 0000001c __u_boot_cmd_snf 801799cc g O .u_boot_cmd 0000001c __u_boot_cmd_sleep 80179ac8 g O .u_boot_cmd 0000001c __u_boot_cmd_macrx 80179c18 g O .u_boot_cmd 0000001c __u_boot_cmd_sf 80179d84 g O .u_boot_cmd 0000001c __u_boot_cmd_show_logo 80179764 g O .u_boot_cmd 0000001c __u_boot_cmd_echo 8017980c g O .u_boot_cmd 0000001c __u_boot_cmd_help 80179844 g O .u_boot_cmd 0000001c __u_boot_cmd_itest 80179df4 g .u_boot_cmd 00000000 __u_boot_cmd_end 80179d4c g O .u_boot_cmd 0000001c __u_boot_cmd_secure 80179780 g O .u_boot_cmd 0000001c __u_boot_cmd_exit 801796a0 g O .u_boot_cmd 0000001c __u_boot_cmd_goo 80179bfc g O .u_boot_cmd 0000001c __u_boot_cmd_run 80179c88 g O .u_boot_cmd 0000001c __u_boot_cmd_test 80179828 g O .u_boot_cmd 0000001c __u_boot_cmd_question_mark 80179908 g O .u_boot_cmd 0000001c __u_boot_cmd_mw 80179dd8 g O .u_boot_cmd 0000001c __u_boot_cmd_jpeg_logo 80179d30 g O .u_boot_cmd 0000001c __u_boot_cmd_vid 80179a3c g O .u_boot_cmd 0000001c __u_boot_cmd_mpr 80179ae4 g O .u_boot_cmd 0000001c __u_boot_cmd_bootp 80179ba8 g O .u_boot_cmd 0000001c __u_boot_cmd_editenv 80179898 g O .u_boot_cmd 0000001c __u_boot_cmd_loadimg 80179924 g O .u_boot_cmd 0000001c __u_boot_cmd_cmp 8017972c g O .u_boot_cmd 0000001c __u_boot_cmd_iminfo 80179860 g O .u_boot_cmd 0000001c __u_boot_cmd_loadb 801798ec g O .u_boot_cmd 0000001c __u_boot_cmd_nm 801797f0 g O .u_boot_cmd 0000001c __u_boot_cmd_fatwrite 801799b0 g O .u_boot_cmd 0000001c __u_boot_cmd_mtest 8017995c g O .u_boot_cmd 0000001c __u_boot_cmd_crc32 80179b38 g O .u_boot_cmd 0000001c __u_boot_cmd_dhcp 8017979c g O .u_boot_cmd 0000001c __u_boot_cmd_fatload 801797d4 g O .u_boot_cmd 0000001c __u_boot_cmd_fatinfo 80179cc0 g O .u_boot_cmd 0000001c __u_boot_cmd_true 801798b4 g O .u_boot_cmd 0000001c __u_boot_cmd_md 80179c50 g O .u_boot_cmd 0000001c __u_boot_cmd_source 80179aac g O .u_boot_cmd 0000001c __u_boot_cmd_mactx 80179cdc g O .u_boot_cmd 0000001c __u_boot_cmd_usb 80179b00 g O .u_boot_cmd 0000001c __u_boot_cmd_tftpboot 80179668 g O .u_boot_cmd 0000001c __u_boot_cmd_bdinfo 80179710 g O .u_boot_cmd 0000001c __u_boot_cmd_bootd 80179a20 g O .u_boot_cmd 0000001c __u_boot_cmd_nboot 8017964c g .u_boot_cmd 00000000 __u_boot_cmd_start 801796f4 g O .u_boot_cmd 0000001c __u_boot_cmd_boot 80179684 g O .u_boot_cmd 0000001c __u_boot_cmd_go 80179a74 g O .u_boot_cmd 0000001c __u_boot_cmd_macfilter
如上,就是定义的结构如何被使用, 并且得到了证明。
=================================我是分割线===================================
接下来是最后一步,我们从命令行输入指令是是如何和我们的 .u_boot_cmd 段连接起来的?
在 command.c 中的 num command_ret_t cmd_process(int flag, int argc, char * const argv[],
int *repeatable) 函数中 调用了 find_cmd, 以下为其实现。
cmd_tbl_t *find_cmd (const char *cmd)
{
int len = &__u_boot_cmd_end - &__u_boot_cmd_start;
return find_cmd_tbl(cmd, &__u_boot_cmd_start, len);
}
其中 __u_boot_cmd_end 和 __u_boot_cmd_start 为在u-boot.lds 中定义的字段
.u_boot_cmd : {
__u_boot_cmd_start = .;
*(.u_boot_cmd)
__u_boot_cmd_end = .;
}
等号后面的 ' . ' 代表链接后的当前位置。在上一步中我们查到了 __u_boot_cmd_start 位置为 0x8017964c , __u_boot_cmd_end 位置为 0x80179df4。Len = 0x7A8
当我们输入一个存在的指令后, find_cmd_tbl(cmd, &__u_boot_cmd_start, len); 会得到该指令的地址, 然后调用 cmd_call 执行, 具体请看command.c 下的cmd_process 函数。
=================================我是分割线===================================
至此,Uboot 命令的流程已经说明白了。
=================================我是分割线===================================
此外, 对Uboot 有所研究的朋友在makefile 中发现了这么一句话:
GEN_UBOOT = \
UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
sed -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
cd $(LNDIR) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) $$UNDEF_SYM $(__OBJS) \
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
-Map u-boot.map -o u-boot
这句话解析如下 :
首先 在Makefile中, ` ` 代表 执行shell代码, 让我们把该代码拿出来 :
$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
sed -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq
$(OBJDUMP) 代表 objdump 工具, $(LIBBOARD) $(LIBS) 明显代表一些库文件, $(SYM_PREFIX) 代表前缀,这些对我们分析问题无关紧要 ,这里我们将其简化。于是上述代码简化成了:
objdump -x libtest.a | sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq
objudmp -x libtest.a 可以得到 libtest.a 库中所有的 symbol, 包括函数,数组,文件等,这里我们简称为符号 .
然后将得到的符号通过"管道" 传递给sed 进行编辑。接下来对sed 进行分析:
sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'
sed -n -e 代表使用 silent 模式,以及直接在命令行进行编辑动作。
' ' 两个单引号之间表示将要进行的动作 。
标记成黑色的 s 代表会进行替换操作('s/要被取代的字串/新的字串/').
p 代表打印出来。
因此上面的表达式的最浅显的意思是:
将 .*\(__u_boot_cmd_.*\) 替换为 -u\1
并且由于sed 支持正则表达式, 在正则表达式中括号()代表分组, 使用 \1 \2 \3 获取第一个分组,第二个分组, 第三个分组。因此上述表达式进一步被理解为:
将 .*__u_boot_cmd_.* 替换为 -u.*__u_boot_cmd_.*
请注意, 上面的字符串和上上面字符串的不同。
在正则表达式中 . 代表任意字符(不包括空格),* 代表任意个, 因此我们将上述语句按照汉语翻译一遍:
使用 objdump -x 解析 libtest.a 得到一些符号,将这些符号使用sed 进行处理, 处理方式是: 找到符号中有__u_boot_cmd_字段的那一部分, 然后在这些字段前面加上-u。
接下来 |sort|uniq 就比较好理解了,就是进行排序和消除重复。
然后将其赋值给 UNDEF_SYM . 至此
UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
sed -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`; 这一部分分析完毕, 总结来说就是 找到一些字段,在字段前面加上-u ,最后赋值给 UNDEF_SYM 这个变量。
接下来分析:
cd $(LNDIR) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) $$UNDEF_SYM $(__OBJS) \
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
-Map u-boot.map -o u-boot
其实这一点没什么好说的, 无非就是执行ld 链接,然后生成u-boot这个文件, 这里需要注意的是 在其中使用了 UNDEF_SYM 变量,UNDEF_SYM 代表-uxxx__u_boot_cmd_xxx ,这是什么意思呢?
通过 man ld 可以找到 :
-u symbol --undefined=symbol Force symbol to be entered in the output file as an undefined symbol. Doing this may, for example, trigger linking of additional modules from standard libraries. -u may be repeated with different option arguments to enter additional undefined symbols. This option is equivalent to the "EXTERN" linker script command.
也就是说在可执行文件中通过-u可以插入未定义变量。
这样一来, 也就明白了 这段代码的目的是u_boot_cmd 变量插入到可执行文件中。
接下来继续最开始的问题: 既然u-boot 使用 __attribute__((unused, section(".u_boot_cmd"), aligned(4))) 的方式将uboot 的指令插入到 输出文件的 .u_boot_cmd 段, 那 GEN_UBOOT 这个 ‘函数' 的意义是什么呢? 将重复的工作在做一遍吗 ?
这个问题也困扰了我很久,直到本篇文档写完也没有头绪, 我的分析过程简单的说一下,
使用ld -u 插入符号, 一般都会在 .strtab 这个段中, 可以通过 readelf -S test.out,可以找到 .strtab 这个段的index ,比如说是 38, 然后通过 readelf -x38 test.out ,查看源文件。可以清楚的看到这些字段。
但是使用 __attribute__((unused, section(".u_boot_cmd"), aligned(4))) 的方式链接器会自动的将 我们定义的变量的字段系写入到 .strtab 这个section 中(大家可以写一个简单的小程序验证下,我之前验证过,但是代码没有保存下来)。 这样看来 就重复了。 因此从目前来看, GEN_UBOOT 是没有什么用的。当然, 如果果有哪位朋友知道原因,请发邮件给我 syyxy@outlook.com 或者评论一下,感激不尽。