carloscn / blog

My blog
Apache License 2.0
134 stars 38 forks source link

[Linux] 简析Linux镜像生成过程 #209

Open carloscn opened 9 months ago

carloscn commented 9 months ago

[Linux] 简析Linux镜像生成过程

Linux镜像原始输出为vmlinux,后续可能已Image、Image.gz、uImage形式输出。具体过程如下:

  1. 通过link-vmlinux.sh生成vmlinux和System.map文件。
  2. 通过objcopy移除vmlinux中不必要段,输出binary格式Image。
  3. 对Image进行压缩,输出不同格式的压缩文件,比如gzip对应的Image.gz。
  4. 对Image.gz加上设备树及ramdisk,生成FIT格式的uImage文件。

image

1. 生成vmlinux和System.map

根目录Makefile生成.vmlinux.cmd:

cmd_vmlinux := /bin/bash scripts/link-vmlinux.sh aarch64-linux-gnu-ld -EL --no-undefined -X -pie -shared -Bsymbolic --no-apply-dynamic-relocs --fix-cortex-a53-843419 --build-id ; true

link-vmlinux.sh链接head.o和built-in.o,并最终输出vmlinux和System.map文件。

#!/bin/sh
set -e

# Nice output in kbuild format
# Will be supressed by "make -s"
info()
{
    if [ "${quiet}" != "silent_" ]; then
        printf "  %-7s %s\n" ${1} ${2}
    fi
}

# Thin archive build here makes a final archive with
# symbol table and indexes from vmlinux objects, which can be
# used as input to linker.
#
# Traditional incremental style of link does not require this step
#
# built-in.o output file
#
archive_builtin()
{
    if [ -n "${CONFIG_THIN_ARCHIVES}" ]; then
        info AR built-in.o
        rm -f built-in.o;
        ${AR} rcsT${KBUILD_ARFLAGS} built-in.o            \
                    ${KBUILD_VMLINUX_INIT}        \
                    ${KBUILD_VMLINUX_MAIN}

        if [ -n "${CONFIG_LTO_CLANG}" ]; then
            mv -f built-in.o built-in.o.tmp
            ${LLVM_AR} rcsT${KBUILD_ARFLAGS} built-in.o $(${AR} t built-in.o.tmp)
            rm -f built-in.o.tmp
        fi
    fi
}

# If CONFIG_LTO_CLANG is selected, collect generated symbol versions into
# .tmp_symversions
modversions()
{
    if [ -z "${CONFIG_LTO_CLANG}" ]; then
        return
    fi

    if [ -z "${CONFIG_MODVERSIONS}" ]; then
        return
    fi

    rm -f .tmp_symversions

    for a in built-in.o ${KBUILD_VMLINUX_LIBS}; do
        for o in $(${AR} t $a); do
            if [ -f ${o}.symversions ]; then
                cat ${o}.symversions >> .tmp_symversions
            fi
        done
    done

    echo "-T .tmp_symversions"
}

# Link of vmlinux.o used for section mismatch analysis
# ${1} output file
modpost_link()
{
    local objects

    if [ -n "${CONFIG_THIN_ARCHIVES}" ]; then
        objects="--whole-archive built-in.o"
    else
        objects="${KBUILD_VMLINUX_INIT}                \
            --start-group                    \
            ${KBUILD_VMLINUX_MAIN}                \
            --end-group"
    fi

    if [ -n "${CONFIG_LTO_CLANG}" ]; then
        # This might take a while, so indicate that we're doing
        # an LTO link
        info LTO vmlinux.o
    else
        info LD vmlinux.o
    fi

    ${LD} ${LDFLAGS} -r -o ${1} $(modversions) ${objects}--------------------------------------链接head.o和built-in.o生成vmlinux.o。
}

# If CONFIG_LTO_CLANG is selected, we postpone running recordmcount until
# we have compiled LLVM IR to an object file.
recordmcount()
{
    if [ -z "${CONFIG_LTO_CLANG}" ]; then
        return
    fi

    if [ -n "${CONFIG_FTRACE_MCOUNT_RECORD}" ]; then
        scripts/recordmcount ${RECORDMCOUNT_FLAGS} $*
    fi
}

# Link of vmlinux
# ${1} - optional extra .o files
# ${2} - output file
vmlinux_link()
{
    local lds="${objtree}/${KBUILD_LDS}"
    local objects

    if [ "${SRCARCH}" != "um" ]; then
        local ld=${LD}
        local ldflags="${LDFLAGS} ${LDFLAGS_vmlinux}"

        if [ -n "${LDFINAL_vmlinux}" ]; then
            ld=${LDFINAL_vmlinux}
            ldflags="${LDFLAGS_FINAL_vmlinux} ${LDFLAGS_vmlinux}"
        fi

        if [[ -n "${CONFIG_THIN_ARCHIVES}" && -z "${CONFIG_LTO_CLANG}" ]]; then
            objects="--whole-archive built-in.o ${1}"
        else
            objects="${KBUILD_VMLINUX_INIT}            \
                --start-group                \
                ${KBUILD_VMLINUX_MAIN}            \
                --end-group                \
                ${1}"
        fi

        ${ld} ${ldflags} -o ${2} -T ${lds} ${objects}
    else
...
    fi
}

# Create ${2} .o file with all symbols from the ${1} object file
kallsyms()
{
    info KSYM ${2}
    local kallsymopt;

    if [ -n "${CONFIG_HAVE_UNDERSCORE_SYMBOL_PREFIX}" ]; then
        kallsymopt="${kallsymopt} --symbol-prefix=_"
    fi

    if [ -n "${CONFIG_KALLSYMS_ALL}" ]; then
        kallsymopt="${kallsymopt} --all-symbols"
    fi

    if [ -n "${CONFIG_KALLSYMS_ABSOLUTE_PERCPU}" ]; then
        kallsymopt="${kallsymopt} --absolute-percpu"
    fi

    if [ -n "${CONFIG_KALLSYMS_BASE_RELATIVE}" ]; then
        kallsymopt="${kallsymopt} --base-relative"
    fi

    local aflags="${KBUILD_AFLAGS} ${KBUILD_AFLAGS_KERNEL}               \
              ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS}"

    local afile="`basename ${2} .o`.S"

    ${NM} -n ${1} | scripts/kallsyms ${kallsymopt} > ${afile}
    ${CC} ${aflags} -c -o ${2} ${afile}
}

# Create map file with all symbols from ${1}
# See mksymap for additional details
mksysmap()
{
    ${CONFIG_SHELL} "${srctree}/scripts/mksysmap" ${1} ${2}
}

sortextable()
{
    ${objtree}/scripts/sortextable ${1}
}

# Delete output files in case of error
cleanup()
{
    rm -f .old_version
    rm -f .tmp_System.map
    rm -f .tmp_kallsyms*
    rm -f .tmp_version
    rm -f .tmp_symversions
    rm -f .tmp_vmlinux*
    rm -f built-in.o
    rm -f System.map
    rm -f vmlinux
    rm -f vmlinux.o
}

on_exit()
{
    if [ $? -ne 0 ]; then
        cleanup
    fi
}
trap on_exit EXIT

on_signals()
{
    exit 1
}
trap on_signals HUP INT QUIT TERM

#
#
# Use "make V=1" to debug this script
case "${KBUILD_VERBOSE}" in
*1*)
    set -x
    ;;
esac

if [ "$1" = "clean" ]; then
    cleanup
    exit 0
fi

# We need access to CONFIG_ symbols
case "${KCONFIG_CONFIG}" in
*/*)
    . "${KCONFIG_CONFIG}"
    ;;
*)
    # Force using a file from the current directory
    . "./${KCONFIG_CONFIG}"
esac

# Update version
info GEN .version
if [ ! -r .version ]; then
    rm -f .version;
    echo 1 >.version;
else
    mv .version .old_version;
    expr 0$(cat .old_version) + 1 >.version;
fi;

archive_builtin

#link vmlinux.o
modpost_link vmlinux.o-----------------------------------------------生成vmlinux.o文件。

# modpost vmlinux.o to check for section mismatches
${MAKE} -f "${srctree}/scripts/Makefile.modpost" vmlinux.o

# final build of init/
${MAKE} -f "${srctree}/scripts/Makefile.build" obj=init GCC_PLUGINS_CFLAGS="${GCC_PLUGINS_CFLAGS}"

if [ -n "${CONFIG_LTO_CLANG}" ]; then
    # Re-use vmlinux.o, so we can avoid the slow LTO link step in
    # vmlinux_link
    KBUILD_VMLINUX_INIT=
    KBUILD_VMLINUX_MAIN=vmlinux.o

    # Call recordmcount if needed
    recordmcount vmlinux.o
fi

kallsymso=""
kallsyms_vmlinux=""
if [ -n "${CONFIG_KALLSYMS}" ]; then

    # kallsyms support
    # Generate section listing all symbols and add it into vmlinux
    # It's a three step process:
    # 1)  Link .tmp_vmlinux1 so it has all symbols and sections,
    #     but __kallsyms is empty.
    #     Running kallsyms on that gives us .tmp_kallsyms1.o with
    #     the right size
    # 2)  Link .tmp_vmlinux2 so it now has a __kallsyms section of
    #     the right size, but due to the added section, some
    #     addresses have shifted.
    #     From here, we generate a correct .tmp_kallsyms2.o
    # 2a) We may use an extra pass as this has been necessary to
    #     woraround some alignment related bugs.
    #     KALLSYMS_EXTRA_PASS=1 is used to trigger this.
    # 3)  The correct ${kallsymso} is linked into the final vmlinux.
    #
    # a)  Verify that the System.map from vmlinux matches the map from
    #     ${kallsymso}.

    kallsymso=.tmp_kallsyms2.o
    kallsyms_vmlinux=.tmp_vmlinux2

    # step 1
    vmlinux_link "" .tmp_vmlinux1---------------------------------------------生成.tmp_vmlinux1文件。
    kallsyms .tmp_vmlinux1 .tmp_kallsyms1.o-----------------------------------生成.tmp_kallsyms1.o文件。

    # step 2
    vmlinux_link .tmp_kallsyms1.o .tmp_vmlinux2-------------------------------生成.tmp_vmlinux2文件。
    kallsyms .tmp_vmlinux2 .tmp_kallsyms2.o-----------------------------------生成.tmp_kallsyms2.o文件。

    # step 2a
    if [ -n "${KALLSYMS_EXTRA_PASS}" ]; then
        kallsymso=.tmp_kallsyms3.o
        kallsyms_vmlinux=.tmp_vmlinux3

        vmlinux_link .tmp_kallsyms2.o .tmp_vmlinux3

        kallsyms .tmp_vmlinux3 .tmp_kallsyms3.o
    fi
fi

info LD vmlinux
vmlinux_link "${kallsymso}" vmlinux-------------------------------------------生成vmlinux文件。

if [ -n "${CONFIG_BUILDTIME_EXTABLE_SORT}" ]; then
    info SORTEX vmlinux
    sortextable vmlinux
fi

info SYSMAP System.map
mksysmap vmlinux System.map---------------------------------------------------从vmlinux生成System.map文件。

# step a (see comment above)
if [ -n "${CONFIG_KALLSYMS}" ]; then
    mksysmap ${kallsyms_vmlinux} .tmp_System.map

    if ! cmp -s System.map .tmp_System.map; then
        echo >&2 Inconsistent kallsyms data
        echo >&2 Try "make KALLSYMS_EXTRA_PASS=1" as a workaround
        exit 1
    fi
fi

# We made a new kernel - delete old version file
rm -f .old_version

arch/arm64/boot/.Image.cmd:

cmd_arch/arm64/boot/Image := aarch64-linux-gnu-objcopy -O binary -R .note -R .note.gnu.build-id -R .comment -S vmlinux arch/arm64/boot/Image

移除vmlinux中.note、.note.build-id、.comment段,并且移除所有符号和重定位信息,输出binary格式到Image中。

arch/arm64/boot/.Image.gz.cmd:

cmd_arch/arm64/boot/Image.gz := (cat arch/arm64/boot/Image | gzip -n -f -9 > arch/arm64/boot/Image.gz) || (rm -f arch/arm64/boot/Image.gz ; false)

Image.gz文件就是将Image文件通过gzip进行打包。

3. 生成uImage文件

uImage是对而进行文件加上Uboot头信息,Uboot读取后进行解析,校验并加载到特定位置运行。

在scripts/Makefile.lib中定义了生成uImage命令:

# U-Boot mkimage
# ---------------------------------------------------------------------------

MKIMAGE := $(srctree)/scripts/mkuboot.sh

# SRCARCH just happens to match slightly more than ARCH (on sparc), so reduces
# the number of overrides in arch makefiles
UIMAGE_ARCH ?= $(SRCARCH)
UIMAGE_COMPRESSION ?= $(if $(2),$(2),none)
UIMAGE_OPTS-y ?=
UIMAGE_TYPE ?= kernel
UIMAGE_LOADADDR ?= arch_must_set_this
UIMAGE_ENTRYADDR ?= $(UIMAGE_LOADADDR)
UIMAGE_NAME ?= 'Linux-$(KERNELRELEASE)'
UIMAGE_IN ?= $<
UIMAGE_OUT ?= $@

quiet_cmd_uimage = UIMAGE  $(UIMAGE_OUT)
      cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A $(UIMAGE_ARCH) -O linux \
            -C $(UIMAGE_COMPRESSION) $(UIMAGE_OPTS-y) \
            -T $(UIMAGE_TYPE) \
            -a $(UIMAGE_LOADADDR) -e $(UIMAGE_ENTRYADDR) \
            -n $(UIMAGE_NAME) -d $(UIMAGE_IN) $(UIMAGE_OUT)

关于uImage的生成可以参考:[LS104x] 使用FIT的kernel格式和initramfs

Reference

konosubakonoakua commented 8 months ago

大佬,问个无关的问题,用issue记笔记持久么,比如会不会有img失效这种情况,还有用issue的话不太方便做本地备份 🏄

carloscn commented 8 months ago

大佬,问个无关的问题,用issue记笔记持久么,比如会不会有img失效这种情况,还有用issue的话不太方便做本地备份 🏄

是持久的。image不会失效的,github用的引用策略,只要有引用这个图片的地方就不会被清空。issue可以备份,有相关的api可以自动化备份。