0
点赞
收藏
分享

微信扫一扫

linux内核压缩制作bzImage

setup.bin

OBJCOPYFLAGS_setup.bin  := -O binary
$(obj)/setup.bin: $(obj)/setup.elf FORCE
        $(call if_changed,objcopy)

  call if_changed,objcopy这里找到arch/x86/boot/.setup.elf.cmd文件:

cmd_arch/x86/boot/setup.elf := ld -m elf_x86_64   -m elf_i386 -T arch/x86/boot/setup.ld arch/x86/boot/a20.o arch/x86/boot/bioscall.o arch/x86/boot/cmdline.o arch/x86/boot/copy.o arch/x86/boot/cpu.o arch/x86/boot/cpuflags.o arch/x86/boot/cpucheck.o arch/x86/boot/early_serial_console.o arch/x86/boot/edd.o arch/x86/boot/header.o arch/x86/boot/main.o arch/x86/boot/memory.o arch/x86/boot/pm.o arch/x86/boot/pmjump.o arch/x86/boot/printf.o arch/x86/boot/regs.o arch/x86/boot/string.o arch/x86/boot/tty.o arch/x86/boot/video.o arch/x86/boot/video-mode.o arch/x86/boot/version.o arch/x86/boot/video-vga.o arch/x86/boot/video-vesa.o arch/x86/boot/video-bios.o -o arch/x86/boot/setup.elf

  这里通过ld链接生成arch/x86/boot/setup.elf文件,其中.o文件是main函数中用到函数及依赖函数、变量等生成的二进制文件。


vmlinux.bin

OBJCOPYFLAGS_vmlinux.bin := -O binary -R .note -R .comment -S
$(obj)/vmlinux.bin: $(obj)/compressed/vmlinux FORCE
        $(call if_changed,objcopy)

  vmlinux.bin依赖于vmlinux,查看vmlinux分支:

$(obj)/compressed/vmlinux: FORCE
        $(Q)$(MAKE) $(build)=$(obj)/compressed $@

  查看arch/x86/boot/compressed/vmlinux/Makefile:

# create a compressed vmlinux image from the original vmlinux
#
# vmlinuz is:
#       decompression code (*.o)
#       asm globals (piggy.S), including:
#               vmlinux.bin.(gz|bz2|lzma|...)
#
# vmlinux.bin is:
#       vmlinux stripped of debugging and comments
# vmlinux.bin.all is:
#       vmlinux.bin + vmlinux.relocs
# vmlinux.bin.(gz|bz2|lzma|...) is:
#       (see scripts/Makefile.lib size_append)
#       compressed vmlinux.bin.all + u32 size of vmlinux.bin.all

# Sanitizer runtimes are unavailable and cannot be linked for early boot code.
KASAN_SANITIZE                  := n
KCSAN_SANITIZE                  := n
OBJECT_FILES_NON_STANDARD       := y

...

KBUILD_LDFLAGS := -m elf_$(UTS_MACHINE)

...

hostprogs       := mkpiggy
HOST_EXTRACFLAGS += -I$(srctree)/tools/include

...

$(obj)/../voffset.h: vmlinux FORCE
        $(call if_changed,voffset)

$(obj)/misc.o: $(obj)/../voffset.h

vmlinux-objs-$(CONFIG_EFI_MIXED) += $(obj)/efi_thunk_$(BITS).o
efi-obj-$(CONFIG_EFI_STUB) = $(objtree)/drivers/firmware/efi/libstub/lib.a

# The compressed kernel is built with -fPIC/-fPIE so that a boot loader
# can place it anywhere in memory and it will still run. However, since
# it is executed as-is without any ELF relocation processing performed
# (and has already had all relocation sections stripped from the binary),
# none of the code can use data relocations (e.g. static assignments of
# pointer values), since they will be meaningless at runtime. This check
# will refuse to link the vmlinux if any of these relocations are found.
quiet_cmd_check_data_rel = DATAREL $@
define cmd_check_data_rel
        for obj in $(filter %.o,$^); do \
                $(READELF) -S $$obj | grep -qF .rel.local && { \
                        echo "error: $$obj has data relocations!" >&2; \
                        exit 1; \
                } || true; \
        done
endef

$(obj)/vmlinux: $(vmlinux-objs-y) $(efi-obj-y) FORCE
        $(call if_changed,check-and-link-vmlinux)

OBJCOPYFLAGS_vmlinux.bin :=  -R .comment -S
$(obj)/vmlinux.bin: vmlinux FORCE
        $(call if_changed,objcopy)

$(obj)/vmlinux.relocs: vmlinux FORCE
        $(call if_changed,relocs)

vmlinux.bin.all-y := $(obj)/vmlinux.bin
vmlinux.bin.all-$(CONFIG_X86_NEED_RELOCS) += $(obj)/vmlinux.relocs

$(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE //默认配置只开启gzip
        $(call if_changed,gzip)
$(obj)/vmlinux.bin.bz2: $(vmlinux.bin.all-y) FORCE
        $(call if_changed,bzip2)
$(obj)/vmlinux.bin.lzma: $(vmlinux.bin.all-y) FORCE
        $(call if_changed,lzma)
$(obj)/vmlinux.bin.xz: $(vmlinux.bin.all-y) FORCE
        $(call if_changed,xzkern)
$(obj)/vmlinux.bin.lzo: $(vmlinux.bin.all-y) FORCE
        $(call if_changed,lzo)
$(obj)/vmlinux.bin.lz4: $(vmlinux.bin.all-y) FORCE
        $(call if_changed,lz4)
$(obj)/vmlinux.bin.zst: $(vmlinux.bin.all-y) FORCE
        $(call if_changed,zstd22)

...

quiet_cmd_mkpiggy = MKPIGGY $@
      cmd_mkpiggy = $(obj)/mkpiggy $< > $@

targets += piggy.S
$(obj)/piggy.S: $(obj)/vmlinux.bin.$(suffix-y) $(obj)/mkpiggy FORCE
        $(call if_changed,mkpiggy)

  首先执行$(obj)/…/voffset.h:

nm vmlinux | sed -n -e 's/^\([0-9a-fA-F]*\) [ABCDGRSTVW] \(_text\|__bss_start\|_end\)$$/$(pound)define VO_\2 _AC(0x\1,UL)/p' > arch/x86/boot/compressed/../voffset.h

#define VO___bss_start _AC(0xffffffff81c0c000,UL)
#define VO__end _AC(0xffffffff81e2c000,UL)
#define VO__text _AC(0xffffffff81000000,UL)

  从vmlinux中拿到VO___bss_start _AC、VO__end _AC和VO__text _AC地址

objcopy  -O binary -R .note -R .comment -S arch/x86/boot/compressed/vmlinux arch/x86/boot/vmlinux.bin

  vmlinux.bin:由vmlinux生成二进制文件vmlinux.bin(去除.note和.comment段内容)

$(obj)/vmlinux.relocs: vmlinux FORCE
        $(call if_changed,relocs)

  生成arch/x86/boot/compressed/vmlinux.relocs

$(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE
        $(call if_changed,gzip)

  .config默认开启gzip:

cat arch/x86/boot/compressed/vmlinux.bin arch/x86/boot/compressed/vmlinux.relocs | gzip -n -f -9 > arch/x86/boot/compressed/vmlinux.bin.gz

  使用gzip(最高压缩比)把vmlinux.bin和vmlinux.relocs压缩到vmlinux.bin.gz

$(obj)/piggy.S: $(obj)/vmlinux.bin.$(suffix-y) $(obj)/mkpiggy FORCE
        $(call if_changed,mkpiggy)
gcc -Wp,-MMD,arch/x86/boot/compressed/.mkpiggy.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89     -I./tools/include    -o arch/x86/boot/compressed/mkpiggy arch/x86/boot/compressed/mkpiggy.c

  生成mkpiggy工具

quiet_cmd_check-and-link-vmlinux = LD      $@
      cmd_check-and-link-vmlinux = $(cmd_check_data_rel); $(cmd_ld)

$(obj)/vmlinux: $(vmlinux-objs-y) $(efi-obj-y) FORCE
        $(error check-and-link-vmlinux,$(vmlinux-objs-y),$(efi-obj-y)) 
        $(call if_changed,check-and-link-vmlinux)
for obj in ; do readelf -S $obj | grep -qF .rel.local && { echo "error: $obj has data relocations!" >&2; exit 1; } || true; done

  检查是否有数据重载地址(通过.rel.local),如果有提示"error: $obj has data relocations!"


build

$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE
        $(call if_changed,image)
        @$(kecho) 'Kernel: $@ is ready' ' (#'`cat .version`')'

  现在开始分析build,查看arch/x86/boot/.bzImage.cmd:

arch/x86/boot/tools/build arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin arch/x86/boot/zoffset.h arch/x86/boot/bzImage

  这里执行build setup.bin vmlinux.bin zoffset.h bzImage,进入arch/x86/boot/tools/build.c中的main函数:

int main(int argc, char ** argv)
{
        unsigned int i, sz, setup_sectors, init_sz;
        int c;
        u32 sys_size;
        struct stat sb;
        FILE *file, *dest;
        int fd;
        void *kernel;
        u32 crc = 0xffffffffUL;

        efi_stub_defaults();

  efi_stub_defaults中32位定义efi_pe_entry = 0x10;,64位定义efi_pe_entry = 0x210;及startup_64 = 0x200;(起始地址512字节)

if (argc != 5)        //参数不是5将显示帮助信息,build setup.bin vmlinux.bin zoffset.h bzImage
                usage();
        parse_zoffset(argv[3]);

#define ZO__ehead 0x0000000000000305
#define ZO__end 0x000000000021a000
#define ZO__text 0x00000000001e5ea0
#define ZO_input_data 0x0000000000000305
#define ZO_kernel_info 0x00000000001ecce3
#define ZO_startup_32 0x0000000000000000
#define ZO_startup_64 0x0000000000000200
#define ZO_z_input_len 0x00000000001e5b90
#define ZO_z_output_len 0x0000000000e27bfc

  解析zoffset.h中的定义地址,上述列表进行对应项赋值

dest = fopen(argv[4], "w");
        if (!dest)
                die("Unable to write `%s': %m", argv[4]);

        /* Copy the setup code */
        file = fopen(argv[1], "r"); /* 读方式打开 setup.bin */
        if (!file)
                die("Unable to open `%s': %m", argv[1]);

/* This must be large enough to hold the entire setup */
/* u8 buf[SETUP_SECT_MAX*512];  64 * 512  32K*/

        c = fread(buf, 1, sizeof(buf), file);
        if (ferror(file))
                die("read-error on `setup'");
        if (c < 1024)
                die("The setup must be at least 1024 bytes");
        if (get_unaligned_le16(&buf[510]) != 0xAA55)
                die("Boot block hasn't got boot flag (0xAA55)");
        fclose(file);

  读取setup.bin内容,必须大于1024字节(最大32K),如果第511-512字节不是0xAA55表示没有启动标志

/*
#define PECOFF_RELOC_RESERVE 0x20

#ifdef CONFIG_EFI_MIXED
#define PECOFF_COMPAT_RESERVE 0x20
#else
#define PECOFF_COMPAT_RESERVE 0x0
#endif
*/

 c += reserve_pecoff_compat_section(c); //为.compat段预留0x20个字节,setup.bin之后的空间
        c += reserve_pecoff_reloc_section(c); //为.reloc段预留0x20个字节

        /* Pad unused space with zeros */
        setup_sectors = (c + 511) / 512;   //计算占用了多少个扇区空间大小

/*
Minimal number of setup sectors
#define SETUP_SECT_MIN 5
#define SETUP_SECT_MAX 64
*/

        if (setup_sectors < SETUP_SECT_MIN) //如果占用扇区空间小于5,设置为5
                setup_sectors = SETUP_SECT_MIN;
        i = setup_sectors*512;
        memset(buf+c, 0, i-c);  //未使用的内存空间补零(一般最后一个扇区空间未完全使用)

        update_pecoff_setup_and_reloc(i);
        //填充.setup和.reloc段,如果是IA-32架构需要额外填充.compat段

  1. 读取setup.bin内容,必须大于1024字节(最大32K),如果第511-512字节不是0xAA55表示没有启动标志
  2. 为.compat段预留0x20个字节(setup.bin之后的空间),为.reloc段预留0x20个字节
  3. 计算占用了多少个扇区空间大小,预设扇区范围内未使用的内存补零
  4. 填充.setup和.reloc段,如果是IA-32架构需要额外填充.compat段


/*
#define DEFAULT_MAJOR_ROOT 0
#define DEFAULT_MINOR_ROOT 0
#define DEFAULT_ROOT_DEV (DEFAULT_MAJOR_ROOT << 8 | DEFAULT_MINOR_ROOT)
 */
 
 /* Set the default root device */
        put_unaligned_le16(DEFAULT_ROOT_DEV, &buf[508]);
        //509-510字节设置root路径

        printf("Setup is %d bytes (padded to %d bytes).\n", c, i);

        /* Open and stat the kernel file */
        fd = open(argv[2], O_RDONLY); //只读方式打开vmlinux.bin
        if (fd < 0)
                die("Unable to open `%s': %m", argv[2]);
        if (fstat(fd, &sb))
                die("Unable to stat `%s': %m", argv[2]);
        sz = sb.st_size;
        printf("System is %d kB\n", (sz+1023)/1024); //输出vmlinux.bin大小
        kernel = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0); //映射只读内存
        if (kernel == MAP_FAILED)
                die("Unable to mmap '%s': %m", argv[2]);

  1. 509-510字节设置root路径
  2. 只读方式打开vmlinux.bin
  3. 输出vmlinux.bin大小
  4. 映射只读内存

 /* Number of 16-byte paragraphs, including space for a 4-byte CRC */
        sys_size = (sz + 15 + 4) / 16; //计算有多少个16字节,包括4字节CRC
#ifdef CONFIG_EFI_STUB
        /*
         * COFF requires minimum 32-byte alignment of sections, and
         * adding a signature is problematic without that alignment.
         */
        sys_size = (sys_size + 1) & ~1;
#endif

        /* Patch the setup code with the appropriate size parameters */
        buf[0x1f1] = setup_sectors-1; //设置setup(扇区)长度
        put_unaligned_le32(sys_size, &buf[0x1f4]); 

        init_sz = get_unaligned_le32(&buf[0x260]);
#ifdef CONFIG_EFI_STUB
        /*
         * The decompression buffer will start at ImageBase. When relocating
         * the compressed kernel to its end, we must ensure that the head
         * section does not get overwritten.  The head section occupies
         * [i, i + _ehead), and the destination is [init_sz - _end, init_sz).
         *
         * At present these should never overlap, because 'i' is at most 32k
         * because of SETUP_SECT_MAX, '_ehead' is less than 1k, and the
         * calculation of INIT_SIZE in boot/header.S ensures that
         * 'init_sz - _end' is at least 64k.
         *
         * For future-proofing, increase init_sz if necessary.
         */

        if (init_sz - _end < i + _ehead) {
                init_sz = (i + _ehead + _end + 4095) & ~4095;
                put_unaligned_le32(init_sz, &buf[0x260]); //向buf内写入数据长度
        }
#endif 
        update_pecoff_text(setup_sectors * 512, i + (sys_size * 16), init_sz);
       
        efi_stub_entry_update();
		//写入efi入口地址
/* Update kernel_info offset. */
        put_unaligned_le32(kernel_info, &buf[0x268]);

        crc = partial_crc32(buf, i, crc);
        if (fwrite(buf, 1, i, dest) != i)
                die("Writing setup failed");

        /* Copy the kernel code */
        crc = partial_crc32(kernel, sz, crc);
        if (fwrite(kernel, 1, sz, dest) != sz)
                die("Writing kernel failed");

        /* Add padding leaving 4 bytes for the checksum */
        while (sz++ < (sys_size*16) - 4) {
                crc = partial_crc32_one('\0', crc);
                if (fwrite("\0", 1, 1, dest) != 1)
                        die("Writing padding failed");
        }

  写入kernel_info地址(按顺序写入),写入内核编码,为校验和留下4个字节,剩下未使用的内存空间填充零

 /* Write the CRC */
        printf("CRC %x\n", crc);
        put_unaligned_le32(crc, buf);
        if (fwrite(buf, 1, 4, dest) != 4)
                die("Writing CRC failed");

        /* Catch any delayed write failures */
        if (fclose(dest))
                die("Writing image failed");

        close(fd);

        /* Everything is OK */
        return 0;

  写入CRC校验,关闭fd,退出main函数


  build执行过程:
    1. 创建bzImage文件;
    2. 写入数据头部;
    3. 写入setup.bin和vmlinux.bin数据;
    4. 写入efi入口;
    5. 写入kernel_info地址等数据和CRC4字节校验;
    6. 关闭文件描述符,退出main函数。
    部分细节(如各种段赋值,固定字节写入特定信息)参考代码中添加的注释信息,到这里bzImage创建流程执行完成。

举报

相关推荐

0 条评论