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创建流程执行完成。