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 或者评论一下,感激不尽。

 

posted @ 2018-05-04 20:47  癫色深浅入时无  阅读(2277)  评论(0编辑  收藏  举报