本文分析的u-boot的版本为2010.03
正常编译u-boot的流程如下
make xxx_config make all
在u-boot之mkconfig解析中,我们已经了解了make xxx_config做了哪些事情,今天我们就要利用它做的事情来继续分析make all这个命令。其实make all就等于make
其实分析这个命令,也就是等于分析顶层目录下面的Makefile。下面就开始我们的探索
# 和U-Boot版本相关的一些内容 VERSION = 2010 PATCHLEVEL = 03 SUBLEVEL = EXTRAVERSION = ifneq "$(SUBLEVEL)" "" U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) else U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION) endif TIMESTAMP_FILE = $(obj)include/timestamp_autogenerated.h VERSION_FILE = $(obj)include/version_autogenerated.h
上面是一些和u-boot版本相关的信息
# 确定主机CPU架构,将其名称赋值给HOSTARCH变量 HOSTARCH := $(shell uname -m | \ sed -e s/i.86/i386/ \ -e s/sun4u/sparc64/ \ -e s/arm.*/arm/ \ -e s/sa110/arm/ \ -e s/powerpc/ppc/ \ -e s/ppc64/ppc/ \ -e s/macppc/ppc/) # 确定主机操作系统,将其名称赋值给HOSTS变量 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \ sed -e 's/\(cygwin\).*/cygwin/') # 确定编译环境的shell位置,将其赋值给SHELL # Set shell to bash if possible, otherwise fall back to sh SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ else if [ -x /bin/bash ]; then echo /bin/bash; \ else echo sh; fi; fi) # 将上面三个变量设置为全局环境变量 export HOSTARCH HOSTOS SHELL # 供应商设置为空 # Deal with colliding definitions from tcsh etc. VENDOR=
确定主机CPU架构、确定主机操作系统,确定编译环境中shell,形成三个全局变量,里面有一个sed语句,关于它的解释,可以去sed用法看看
######################################################################### # Allow for silent builds # findstring 是一个用来从MAKEFLAGS字符串中查找s的命令, # 如果找到,则返回这个字符串,否则返回空 ifeq (,$(findstring s,$(MAKEFLAGS))) XECHO = echo else XECHO = : endif
用来设置静默编译,如果设置了静默编译(我们在命令行结束输入了make -slient
),XECHO就会被设置为空,后面什么编译信息也不会输出,这就是静默编译。
# 函数$( origin, variable) 输出的结果是一个字符串,输出结果由变量variable定义的方式决定, # 若variable在命令行中定义过,则origin函数返回值为"command line"。 ifdef O ifeq ("$(origin O)", "command line") BUILD_DIR := $(O) endif endif ifneq ($(BUILD_DIR),) saved-output := $(BUILD_DIR) # 如果目录中没有定义BUILD_DIR,则定义这个目录 # Attempt to create a output directory. $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}) # 验证目录是否创建成功,如果没有,则把当前路径赋值给它 # Verify if it was successful. BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd) $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist)) endif # ifneq ($(BUILD_DIR),)
确定输出目录,按照官方注解,我们可以用下面的指令来确定设置输出目录
make O='your dir' all
或者
export BUILD_DIR='your dir' make
由于我编译时不设置额外的输出目录,所以文件就输出在源码目录下面,O和BUILD_DIR都没有定义,所以上面的代码会直接跳过。
# CURDIR就是U-Boot的根目录 # 执行完下面的代码后, SRCTREE,src变量就是U-Boot代码顶层目录, # 而OBJTREE,obj变量就是输出目录,若没有定义BUILD_DIR环境变量, # 则SRCTREE,src变量与OBJTREE,obj变量都是U-Boot源代码目录。 # 而MKCONFIG则表示U-Boot根目录下的mkconfig脚本。 # 用export把新建立的几个变量设为全局变量 OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR)) SRCTREE := $(CURDIR) TOPDIR := $(SRCTREE) LNDIR := $(OBJTREE) export TOPDIR SRCTREE OBJTREE MKCONFIG := $(SRCTREE)/mkconfig export MKCONFIG ifneq ($(OBJTREE),$(SRCTREE)) REMOTE_BUILD := 1 export REMOTE_BUILD endif
这段代码执行后,由于没有额外的BUILD_DIR目录,所以OBJTREE、SRCTREE、TOPDIR都是源码目录,MKCONFIG被设置为上一篇文章中我们分析的mkconfig。
# $(obj) and (src) are defined in config.mk but here in main Makefile # we also need them before config.mk is included which is the case for # some targets like unconfig, clean, clobber, distclean, etc. ifneq ($(OBJTREE),$(SRCTREE)) obj := $(OBJTREE)/ src := $(SRCTREE)/ else obj := src := endif export obj src ifeq ($(ARCH),powerpc) ARCH = ppc endif # The "tools" are needed early, so put this first # Don't include stuff already done in $(LIBS) SUBDIRS = tools \ examples/standalone \ examples/api
由于我们没有定义额外的OBJTREE,所以OBJTREE和SRCTREE是相同的,所以obj和src都是空
# 若include/config.mk 文件存在,则$(wildcard $(obj)include/config.mk) # 命令执行的结果是“$(obj)include/config.mk”展开的字符串,否则结果为空。 # 由于include/config.mk是“make <board_name>_config”命令执行过程生成的, # 若从没有执行过“make <board_name>_config”命令则include/config.mk必然不存在。 # 因此Make就执行else分支的代码,在输出“System not configured - see README”的信息后就返回了。 ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk)) xxx xxx xxx else # !config.mk all $(obj)u-boot.hex $(obj)u-boot.srec $(obj)u-boot.bin \ $(obj)u-boot.img $(obj)u-boot.dis $(obj)u-boot \ $(filter-out tools,$(SUBDIRS)) $(TIMESTAMP_FILE) $(VERSION_FILE) gdbtools \ updater env depend dep tags ctags etags cscope $(obj)System.map: @echo "System not configured - see README" >&2 @ exit 1
上面代码的意思是,如果我们在include下面有config.mk(等价于执行了make xxx_config),那么就继续执行xxx,否则就执行else,wildcard的用法在这里wildcard用法。下面我们继续分析xxx里面的内容。
# Include autoconf.mk before config.mk so that the config options are available # to all top level build files. We need the dummy all: target to prevent the # dependency target in autoconf.mk.dep from being the default. # Include autoconf.mk before config.mk so that the config options are available # to all top level build files. We need the dummy all: target to prevent the # dependency target in autoconf.mk.dep from being the default. # 因此include/autoconf.mk实质上就是config_defaults.h,configs/mini2440.h, # asm/config.h三个文件中“CONFIG_”开头的有效的宏定义的集合。 # autoconf.mk.dep有什么用暂时还不清楚 all: sinclude $(obj)include/autoconf.mk.dep sinclude $(obj)include/autoconf.mk
这里有个autoconf.mk和autoconf.mk.dep文件,下面是它的生成规则
# # Auto-generate the autoconf.mk file (which is included by all makefiles) # # This target actually generates 2 files; autoconf.mk and autoconf.mk.dep. # the dep file is only include in this top level makefile to determine when # to regenerate the autoconf.mk file. $(obj)include/autoconf.mk.dep: $(obj)include/config.h include/common.h @$(XECHO) Generating $@ ; \ set -e ; \ : Generate the dependancies ; \ $(CC) -x c -DDO_DEPS_ONLY -M $(HOSTCFLAGS) $(CPPFLAGS) \ -MQ $(obj)include/autoconf.mk include/common.h > $@ # include/autoconf.mk依赖于make <board_name>_config 命令生成的include/config.h。 # 因此执行make <board_name>_config命令后再执行make all将更新include/autoconf.mk。 # 编译选项“-dM”的作用是输出include/common.h中定义的所有宏。 # include/common.h文件包含了include/config.h文件,而include/config.h文件又包含了config_defaults.h, # configs/mini2440.h,asm/config.h文件。因此include/autoconf.mk实质上就是config_defaults.h, # configs/mini2440.h,asm/config.h三个文件中“CONFIG_”开头的有效的宏定义的集合。 $(obj)include/autoconf.mk: $(obj)include/config.h @$(XECHO) Generating $@ ; \ set -e ; \ : Extract the config macros ; \ $(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | \ sed -n -f tools/scripts/define2mk.sed > $@.tmp && \ mv $@.tmp $@
具体的分析,看代码中的注释,我们继续往下走
# load ARCH, BOARD, and CPU configuration # 将make xxx_config命令生成的include/config.mk包含进来。 include $(obj)include/config.mk export ARCH CPU BOARD VENDOR SOC
包含config.mk文件,设置全局变量ARCH、 CPU 、BOARD、VENDOR、SOC
# 根据架构类型选择编译器。我的开发板是ARM架构 ifndef CROSS_COMPILE ifeq ($(HOSTARCH),$(ARCH)) CROSS_COMPILE = else ifeq ($(ARCH),ppc) CROSS_COMPILE = ppc_8xx- endif ifeq ($(ARCH),arm) #CROSS_COMPILE = arm-linux- #CROSS_COMPILE = /usr/local/arm/4.4.1-eabi-cortex-a8/usr/bin/arm-linux- #CROSS_COMPILE = /usr/local/arm/4.2.2-eabi/usr/bin/arm-linux- #CROSS_COMPILE = /opt/toolchains/arm-2009q3/bin/arm-none-linux-gnueabi- CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi- endif ifeq ($(ARCH),i386) CROSS_COMPILE = i386-linux- endif ifeq ($(ARCH),mips) CROSS_COMPILE = mips_4KC- endif ifeq ($(ARCH),nios) CROSS_COMPILE = nios-elf- endif ifeq ($(ARCH),nios2) CROSS_COMPILE = nios2-elf- endif ifeq ($(ARCH),m68k) CROSS_COMPILE = m68k-elf- endif ifeq ($(ARCH),microblaze) CROSS_COMPILE = mb- endif ifeq ($(ARCH),blackfin) CROSS_COMPILE = bfin-uclinux- endif ifeq ($(ARCH),avr32) CROSS_COMPILE = avr32-linux- endif ifeq ($(ARCH),sh) CROSS_COMPILE = sh4-linux- endif ifeq ($(ARCH),sparc) CROSS_COMPILE = sparc-elf- endif # sparc endif # HOSTARCH,ARCH endif # CROSS_COMPILE export CROSS_COMPILE
根据架构类型,确定编译器,也就是CROSS_COMPILE的值
# 加载其他的配置 # load other configuration include $(TOPDIR)/config.mk
加载其他的一些配置,刚刚我们在include下面有一个config.mk,在这里我们有一个顶层目录下面的config.mk,关于这个文件的分析,可以去看U-Boot编译过程完全分析,我这里就不作分析了。
######################################################################### # U-Boot objects....order is important (i.e. start must be first) # LIBS变量指明了U-Boot需要的库文件,包括平台/开发板相关的目录、 # 通用目录下相应的库,都通过相应的子目录编译得到的。 OBJS = cpu/$(CPU)/start.o ifeq ($(CPU),i386) OBJS += cpu/$(CPU)/start16.o OBJS += cpu/$(CPU)/resetvec.o endif ifeq ($(CPU),ppc4xx) OBJS += cpu/$(CPU)/resetvec.o endif ifeq ($(CPU),mpc85xx) OBJS += cpu/$(CPU)/resetvec.o endif # addprefix是用来给每一个字符串的前面加上前缀的 # 这里的意思是给OBJS加上obj前缀 OBJS := $(addprefix $(obj),$(OBJS)) LIBS = lib_generic/libgeneric.a LIBS += lib_generic/lzma/liblzma.a LIBS += lib_generic/lzo/liblzo.a LIBS += $(shell if [ -f board/$(VENDOR)/common/Makefile ]; then echo \ "board/$(VENDOR)/common/lib$(VENDOR).a"; fi) LIBS += cpu/$(CPU)/lib$(CPU).a ifdef SOC LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a endif ifeq ($(CPU),ixp) LIBS += cpu/ixp/npe/libnpe.a endif LIBS += lib_$(ARCH)/lib$(ARCH).a LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a \ fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a fs/yaffs2/libyaffs2.a \ fs/ubifs/libubifs.a LIBS += net/libnet.a LIBS += disk/libdisk.a LIBS += drivers/bios_emulator/libatibiosemu.a LIBS += drivers/block/libblock.a LIBS += drivers/dma/libdma.a LIBS += drivers/fpga/libfpga.a LIBS += drivers/gpio/libgpio.a LIBS += drivers/hwmon/libhwmon.a LIBS += drivers/i2c/libi2c.a LIBS += drivers/input/libinput.a LIBS += drivers/misc/libmisc.a LIBS += drivers/mmc/libmmc.a LIBS += drivers/mtd/libmtd.a LIBS += drivers/mtd/nand/libnand.a LIBS += drivers/mtd/onenand/libonenand.a LIBS += drivers/mtd/ubi/libubi.a LIBS += drivers/mtd/spi/libspi_flash.a LIBS += drivers/net/libnet.a LIBS += drivers/net/phy/libphy.a LIBS += drivers/pci/libpci.a LIBS += drivers/pcmcia/libpcmcia.a LIBS += drivers/power/libpower.a LIBS += drivers/spi/libspi.a ifeq ($(CPU),mpc83xx) LIBS += drivers/qe/qe.a endif ifeq ($(CPU),mpc85xx) LIBS += drivers/qe/qe.a LIBS += cpu/mpc8xxx/ddr/libddr.a LIBS += cpu/mpc8xxx/lib8xxx.a endif ifeq ($(CPU),mpc86xx) LIBS += cpu/mpc8xxx/ddr/libddr.a LIBS += cpu/mpc8xxx/lib8xxx.a endif LIBS += drivers/rtc/librtc.a LIBS += drivers/serial/libserial.a LIBS += drivers/twserial/libtws.a LIBS += drivers/usb/gadget/libusb_gadget.a LIBS += drivers/usb/host/libusb_host.a LIBS += drivers/usb/musb/libusb_musb.a LIBS += drivers/usb/phy/libusb_phy.a LIBS += drivers/video/libvideo.a LIBS += drivers/watchdog/libwatchdog.a LIBS += common/libcommon.a LIBS += libfdt/libfdt.a LIBS += api/libapi.a LIBS += post/libpost.a LIBS := $(addprefix $(obj),$(LIBS)) .PHONY : $(LIBS) $(TIMESTAMP_FILE) $(VERSION_FILE) LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a LIBBOARD := $(addprefix $(obj),$(LIBBOARD)) # Add GCC lib ifdef USE_PRIVATE_LIBGCC ifeq ("$(USE_PRIVATE_LIBGCC)", "yes") PLATFORM_LIBGCC = -L $(OBJTREE)/lib_$(ARCH) -lgcc else PLATFORM_LIBGCC = -L $(USE_PRIVATE_LIBGCC) -lgcc endif else PLATFORM_LIBGCC = -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc endif PLATFORM_LIBS += $(PLATFORM_LIBGCC) export PLATFORM_LIBS
添加相关的库文件,这些文件一开始是没有的,在后面才会被编译生成
ifeq ($(CONFIG_NAND_U_BOOT),y) NAND_SPL = nand_spl U_BOOT_NAND = $(obj)u-boot-nand.bin endif ifeq ($(CONFIG_ONENAND_U_BOOT),y) ONENAND_IPL = onenand_ipl U_BOOT_ONENAND = $(obj)u-boot-onenand.bin ONENAND_BIN ?= $(obj)onenand_ipl/onenand-ipl-2k.bin endif __OBJS := $(subst $(obj),,$(OBJS)) __LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD))
由于CONFIG_NAND_U_BOOT、和CONFIG_ONENAND_U_BOOT都没有定义,所以这里是不会执行的,我们直接跳过。
# 其中U_BOOT_NAND与U_BOOT_ONENAND 为空,而u-boot.srec,u-boot.bin,System.map都依赖与u-boot。 # 因此执行“make all”命令将生成u-boot,u-boot.srec,u-boot.bin,System.map 。 # 其中u-boot是ELF文件,u-boot.srec是Motorola S-Record format文件,System.map 是U-Boot的符号表, # u-boot.bin是最终烧写到开发板的二进制可执行的文件。 # Always append ALL so that arch config.mk's can add custom ones ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND) all: $(ALL) $(obj)u-boot.hex: $(obj)u-boot $(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@ $(obj)u-boot.srec: $(obj)u-boot $(OBJCOPY) -O srec $< $@ # 编译命令中的“-O binary”选项指定了输出的文件为二进制文件。 # 而“--gap-fill=0xff”选项指定使用“0xff”填充段与段间的空闲区域。 # 这条编译命令实现了ELF格式的U-Boot文件到BIN格式的转换。 $(obj)u-boot.bin: $(obj)u-boot $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@ @#./mkuboot @split -b 14336 u-boot.bin bl2 @+make -C sdfuse_q/ @#cp u-boot.bin u-boot-4212.bin @#cp u-boot.bin u-boot-4412.bin @#./sdfuse_q/add_sign @./sdfuse_q/chksum @./sdfuse_q/add_padding @rm bl2a* @echo $(obj)u-boot.ldr: $(obj)u-boot
这里生成的四个文件,都依赖u-boot,所以我们先看看u-boot是怎样生成的
$(obj)u-boot: depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds $(GEN_UBOOT)
u-boot依赖于八个对象:depend
、SUBDIRS
、OBJS
、LIBBOARD
、LIBS
、LDSCRIPT
、u-boot.lds
,GEN_UBOOT
,下面我们逐个分析这八个对象
# Explicitly make _depend in subdirs containing multiple targets to prevent # parallel sub-makes creating .depend files simultaneously. # 对于$(SUBDIRS),cpu/$(CPU),$(dir $(LDSCRIPT))中的每个元素都进入该目录执行“make _depend”, # 生成各个子目录的.depend文件,.depend列出每个目标文件的依赖文件。 depend dep: $(TIMESTAMP_FILE) $(VERSION_FILE) $(obj)include/autoconf.mk for dir in $(SUBDIRS) cpu/$(CPU) $(dir $(LDSCRIPT)) ; do \ $(MAKE) -C $$dir _depend ; done
生成各个子目录的.depend文件,这个文件列出了每一个目标文件的依赖文件
SUBDIRS# The "tools" are needed early, so put this first # Don't include stuff already done in $(LIBS) SUBDIRS = tools \ examples/standalone \ examples/api xxx xxx xxx xxx $(SUBDIRS): depend $(MAKE) -C $@ all
执行tools ,examples/standalone ,examples/api目录下的Makefile。
$(OBJS): depend $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
OBJS的值是“cpu/arm_cortexa9/start.o”。
$(LIBBOARD): depend $(LIBS) $(MAKE) -C $(dir $(subst $(obj),,$@))
这里LIBBOARD的值是 $(obj)board/samsung/smdkc210/libsmdkc210.a。make执行board/samsung/smdkc210/目录下的Makefile,生成libsmdkc210.a。
$(LIBS): depend $(SUBDIRS) $(MAKE) -C $(dir $(subst $(obj),,$@))
上面的规则表明,对于LIBS中的每个成员,都进入相应的子目录执行“make”命令编译它们。例如对于LIBS中的“common/libcommon.a”成员,程序将进入common目录执行Makefile,生成libcommon.a 。
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds $(LDSCRIPT): depend $(MAKE) -C $(dir $@) $(notdir $@)
“$(MAKE) -C $(dir $@) $(notdir $@)”命令经过变量替换后就是“make -C board/samsung/smdkc210/u-boot.lds”。也就是转到board/samsung/smdkc210/目录下,执行“make u-boot.lds”命令,但实际上在这个目录下,并没有这个链接脚本。
$(obj)u-boot.lds: $(LDSCRIPT) $(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@
以上执行结果,实质上是将cpu/arm_cortexa9/u-boot.lds经编译器简单预处理后输出到U-Boot顶层目录下的u-boot.lds文件。
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) $$UNDEF_SYM $(__OBJS) \ --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \ -Map u-boot.map -o u-boot
GEN_UBOOT是一段生成命令,用来生成u-boot
# 编译命令中的“-O binary”选项指定了输出的文件为二进制文件。 # 而“--gap-fill=0xff”选项指定使用“0xff”填充段与段间的空闲区域。 # 这条编译命令实现了ELF格式的U-Boot文件到BIN格式的转换。 $(obj)u-boot.bin: $(obj)u-boot $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
这是最终的u-boot.bin文件的生成过程
补充
在U-Boot的源码里有一个README文件,里面有这样一段信息
If the system board that you have is not listed, then you will need to port U-Boot to your hardware platform. To do this, follow these steps: 1. Add a new configuration option for your board to the toplevel "Makefile" and to the "MAKEALL" script, using the existing entries as examples. Note that here and at many other places boards and other names are listed in alphabetical sort order. Please keep this order. 2. Create a new directory to hold your board specific code. Add any files you need. In your board directory, you will need at least the "Makefile", a "<board>.c", "flash.c" and "u-boot.lds". 3. Create a new configuration file "include/configs/<board>.h" for your board 4. If you're porting U-Boot to a new CPU, then also create a new directory to hold your CPU specific code. Add any files you need. 5. Run "make <board>_config" with your new name. 6. Type "make", and you should get a working "u-boot.srec" file to be installed on your target system. 7. Debug and solve any problems that might arise. [Of course, this last step is much harder than it sounds.]
这段信息是新板子的配置教程,核心信息一共四段
1. Add a new configuration option for your board to the toplevel "Makefile" and to the "MAKEALL" script, using the existing entries as examples. Note that here and at many other places boards and other names are listed in alphabetical sort order. Please keep this order.
如果我们想配置新板子的U-Boot,我们首先要改动Makefile和MAKEALL,也就是在里面添加配置脚本信息
2. Create a new directory to hold your board specific code. Add any files you need. In your board directory, you will need at least the "Makefile", a "<board>.c", "flash.c" and "u-boot.lds".
我们还需要再板子目录下创建一个文件夹来存放我们的板子代码,这个文件夹下至少要包含Makefile、<board>.c和u-boot.lds。
3. Create a new configuration file "include/configs/<board>.h" for your board
在include/configs文件夹下为新板子创建一个的配置文件
4. If you're porting U-Boot to a new CPU, then also create a new directory to hold your CPU specific code. Add any files you need.
创建一个用来保存cpu信息的文件夹,添加我们想要的文件
来源:https://www.cnblogs.com/YinShijia/p/12010622.html