1、解压zImage --- head.s 分析笔记

本文详细解析了嵌入式系统中内核解压启动的过程,包括解压前的准备、解压过程、缓存管理及MMU配置等内容。介绍了如何通过设置页表实现虚拟地址到物理地址的映射,确保内核正确解压并运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. start:

  1. .type start,#function
  2. .rept 8
  3. mov r0, r0 @ 空语句
  4. .endr
  5. b 1f
  6. /*魔数,和uboot的魔数相对应*/
  7. .word 0x016f2818 @ Magic numbers to help the loader
  8. .word start @ absolute load/run zImage address
  9. .word _edata @ zImage end address
  10. 1: mov r7, r1 @ save architecture ID
  11. mov r8, r2 @ save atags pointer
  12. #ifndef __ARM_ARCH_2__
  13. ......
  14. #else
  15. /*关中断*/
  16. teqp pc, #0x0c000003 @ turn off interrupts
  17. #endif
  18. /*
  19. * Note that some cache flushing and other stuff may
  20. * be needed here - is there an Angel SWI call for this?
  21. */
  22. /*
  23. * some architecture specific code can be inserted
  24. * by the linker here, but it should preserve r7, r8, and r9.
  25. */
  26. .text
  27. /*
  28. * LC0标签在下面定义,这里把LC0的地址装载到r0,和ldr不同,adr不需要考虑偏移
  29. * 下面ldmia命令把r0(LC0)的内容依次赋给r1,r2等寄存器,对应关系看下面LC0标签
  30. */
  31. adr r0, LC0
  32. ARM( ldmia r0, {r1, r2, r3, r4, r5, r6, r11, ip, sp})
  33. THUMB( ldmia r0, {r1, r2, r3, r4, r5, r6, r11, ip} )
  34. THUMB( ldr sp, [r0, #32] )
  35. /*这里 r0 = r0 - r1 并更新 N、Z、C 和 V 标记*/
  36. subs r0, r0, r1 @ calculate the delta offset
  37. @ if delta is zero, we are
  38. @ 如果运行当前运行地址和链接地址相等,则不需进行重定位。直接清除bss段
  39. beq not_relocated @ running at the address we
  40. ......

  1. not_relocated:

  1. mov r0, #0
  2. /* r2:bbs开始位置
  3. * r3: bbs结束位置
  4. * str命令: STR    Rd, [Rbase], Rindex  存储 Rd 到 Rbase 所包含的有效地址。把 Rbase
  5. * + Rindex 所合成的有效地址写回 Rbase。  
  6. * 下面4句str命令将清空 bss 32个字节 ,r2 累加 32
  7. * */
  8. 1: str r0, [r2], #4 @ clear bss r2+=4
  9. str r0, [r2], #4 @ clear bss r2+=4
  10. str r0, [r2], #4 @ clear bss r2+=4
  11. str r0, [r2], #4 @ clear bss r2+=4
  12. cmp r2, r3 @ 判断是否到bss的结束位置,
  13. blo 1b @ 若不是,继续清空
  14. /*
  15. * The C runtime environment should now be setup
  16. * sufficiently. Turn the cache on, set up some
  17. * pointers, and start decompressing.
  18. */
  19. bl cache_on
  20. /*r1、r2作为参数传给decompress_kernel */
  21. mov r1, sp @ malloc space above stack
  22. add r2, sp, #0x10000 @ 64k max 分配一段解压函数需要的内存缓冲 sp 栈指针,
  23. /*
  24. * Check to see if we will overwrite ourselves.
  25. * r4 = final kernel address 最终的内核开始地址,就是解压后的地址
  26. * r5 = start of this image 解压前的映像开始地址
  27. * r6 = size of decompressed image 解压前的映像大小
  28. * r2 = end of malloc space (and therefore this image)
  29. * We basically want:
  30. * r4 >= r2 -> OK
  31. * r4 + image length <= r5 -> OK
  32. * 内核映像解压后不会超过解压前的4倍大小
  33. * 调用decompress_kernel前,要准备4个参数:r0 (解压后的内核开始位置),r1(动态内存开始位置)
  34. * r2(动态内存结束位置),r3(结构ID))
  35. */
  36. cmp r4, r2 @ r4为内核执行地址,此时为0X50008000,r2此时为用户栈顶,
  37. @ 即解压函数所需内存缓冲的开始处
  38. bhs wont_overwrite
  39. add r0, r4, r6
  40. cmp r0, r5
  41. bls wont_overwrite
  42. mov r5, r2 @ decompress after malloc space
  43. mov r0, r5
  44. mov r3, r7
  45. bl decompress_kernel
  46. add r0, r0, #127 + 128 @ alignment + stack
  47. bic r0, r0, #127 @ align the kernel length
  48. /*
  49. * r0 = decompressed kernel length
  50. * r1-r3 = unused
  51. * r4 = kernel execution address
  52. * r5 = decompressed kernel start
  53. * r7 = architecture ID
  54. * r8 = atags pointer
  55. * r9-r12,r14 = corrupted
  56. */
  57. add r1, r5, r0 @ end of decompressed kernel
  58. adr r2, reloc_start
  59. ldr r3, LC1
  60. add r3, r2, r3
  61. 1: ldmia r2!, {r9 - r12, r14} @ copy relocation code
  62. stmia r1!, {r9 - r12, r14}
  63. ldmia r2!, {r9 - r12, r14}
  64. stmia r1!, {r9 - r12, r14}
  65. cmp r2, r3
  66. blo 1b
  67. mov sp, r1
  68. add sp, sp, #128 @ relocate the stack
  69. bl cache_clean_flush
  70. ARM( add pc, r5, r0 ) @ call relocation code
  71. THUMB( add r12, r5, r0 )
  72. THUMB( mov pc, r12 ) @ call relocation code
  73. /*
  74. * We're not in danger of overwriting ourselves. Do this the simple way.
  75. *
  76. * r4 = kernel execution address
  77. * r7 = architecture ID
  78. */

  1. wont_overwrite:

  1. mov r0, r4 @ 解压后的内核开始位置
  2. mov r3, r7 @ 结构ID
  3. bl decompress_kernel @ 这个是跳转到arch/arm/boot/compressed/misc.c的
  4. @ decompress_kernel 函数
  5. b call_kernel @ 解压完毕调用这个
  6. .align 2
  7. .type LC0, #object

  1. LC0:

  1. .word LC0 @ r1
  2. .word __bss_start @ r2
  3. .word _end @ r3
  4. .word zreladdr @ r4
  5. .word _start @ r5
  6. .word _image_size @ r6
  7. .word _got_start @ r11
  8. .word _got_end @ ip
  9. .word user_stack+4096 @ sp

  1. LC1:

  1. .word reloc_end - reloc_start
  2. .size LC0, . - LC0
  3. ......
  4. /*
  5. * Turn on the cache. We need to setup some page tables so that we
  6. * can have both the I and D caches on.
  7. *
  8. * We place the page tables 16k down from the kernel execution address,
  9. * and we hope that nothing else is using it. If we're using it, we
  10. * will go pop!
  11. *
  12. * On entry,
  13. * r4 = kernel execution address
  14. * r7 = architecture number
  15. * r8 = atags pointer
  16. * r9 = run-time address of "start" (???)
  17. * On exit,
  18. * r1, r2, r3, r9, r10, r12 corrupted
  19. * This routine must preserve:
  20. * r4, r5, r6, r7, r8
  21. */
  22. .align 5

  1. cache_on:

  1. mov r3, #8 @ cache_on function
  2. b call_cache_fn
  3. ......
  4. /*
  5. * Here follow the relocatable cache support functions for the
  6. * various processors. This is a generic hook for locating an
  7. * entry and jumping to an instruction at the specified offset
  8. * from the start of the block. Please note this is all position
  9. * independent code.
  10. *
  11. * r1 = corrupted
  12. * r2 = corrupted
  13. * r3 = block offset value和mask各占了4byte,
  14. * 所以在cache_on标签处被赋值为8,
  15. * 用于偏移到函数跳转的位置,具体看代码
  16. * r9 = corrupted
  17. * r12 = corrupted
  18. */

  1. call_cache_fn:

  1. adr r12, proc_types
  2. #ifdef CONFIG_CPU_CP15
  3. mrc p15, 0, r9, c0, c0 @ get processor ID
  4. #else
  5. ldr r9, =CONFIG_PROCESSOR_ID
  6. #endif
  7. /*上面找到processor ID放入 r9 , 下面标号1的代码循环调用,在proc_types中
  8. 查找和processor ID匹配的cache函数,找到后即调用
  9. */
  10. 1: ldr r1, [r12, #0] @ get value
  11. ldr r2, [r12, #4] @ get mask
  12. eor r1, r1, r9 @ (real ^ match)
  13. tst r1, r2 @ & mask
  14. /*若eq(匹配成功),跳到cache函数*/
  15. ARM( addeq pc, r12, r3 ) @ call cache function
  16. THUMB( addeq r12, r3 )
  17. THUMB( moveq pc, r12 ) @ call cache function
  18. add r12, r12, #4*5
  19. b 1b @ proc_types 是一个列表,要一个一个匹配,跳回标号1,匹配下一个
  20. /*
  21. * Table for cache operations. This is basically:
  22. * - CPU ID match
  23. * - CPU ID mask
  24. * - 'cache on' method instruction
  25. * - 'cache off' method instruction
  26. * - 'cache flush' method instruction
  27. *
  28. * We match an entry using: ((real_id ^ match) & mask) == 0
  29. *
  30. * Writethrough caches generally only need 'on' and 'off'
  31. * methods. Writeback caches _must_ have the flush method
  32. * defined.
  33. */
  34. .align 2
  35. .type proc_types,#object

  1. proc_types:

  1. .word 0x41560600 @ ARM6/610
  2. .word 0xffffffe0
  3. W(b) __arm6_mmu_cache_off @ works, but slow
  4. W(b) __arm6_mmu_cache_off
  5. mov pc, lr
  6. ......
  7. ... @ ARM7/710
  8. mov pc, lr
  9. ... @ ARM720T (writethrough)
  10. mov pc, lr
  11. ......这里忽略一些项
  12. @ These match on the architecture ID
  13. @ 这个是匹配ARMv4T的,如ARM920Ts3c2440等等
  14. .word 0x00020000 @ ARMv4T
  15. .word 0x000f0000
  16. W(b) __armv4_mmu_cache_on
  17. W(b) __armv4_mmu_cache_off
  18. W(b) __armv4_mmu_cache_flush
  19. ......这里忽略一些项
  20. /*目前我使用S5PV210是cortexA8的cpu,v7架构,匹配这个
  21. 上面的语句addeq pc, r12, r3将跳转到__armv7_mmu_cache_on,
  22. 因为r3事先被赋值为8。
  23. */
  24. .word 0x000f0000 @ new CPU Id 4 字节
  25. .word 0x000f0000 @ 4 字节
  26. W(b) __armv7_mmu_cache_on @ r12 偏移 r3 后,是这里
  27. W(b) __armv7_mmu_cache_off @ 在内核解压完毕,r3会被赋值为12,即调用这个
  28. W(b) __armv7_mmu_cache_flush @ 在内核解压完毕,r3会被赋值为16,即调用这个
  29. .word 0 @ unrecognised type
  30. .word 0
  31. mov pc, lr
  32. ......

  1. __setup_mmu:

  1. sub r3, r4, #16384 @ Page directory size 16384 是16K
  2. /*上面r4是zreladdr 内核执行地址*/
  3. bic r3, r3, #0xff @ Align the pointer 16K对齐
  4. bic r3, r3, #0x3f00 
  5.         /*为了容易理解注释,这里假设对齐后 r3 = 0x50004000*/
  6. /*
  7. * Initialise the page tables, turning on the cacheable and bufferable
  8. * bits for the RAM area only.
  9. */
  10. mov r0, r3 @ r0 = 0x50004000
  11. mov r9, r0, lsr #18 @ r0 先 lsr(逻辑右移)18位,再mov给r9
  12. mov r9, r9, lsl #18 @ r9 先 lsl(逻辑左移)18位,再mov给r9 = 0x50000000
  13. @ start of RAM
  14. add r10, r9, #0x10000000 @ a reasonable RAM size r10 = 0x60000000
  15. /*上面得到r9为256M对齐的地址,作为RAM的开始地址,r10作为RAM的结束地址*/
  16. /* r9 = 0x50004000 r10 = 0x50008000*/

  17. mov r1, #0x12 @ r1 = 0b 0000 0000 0001 0010
  18. orr r1, r1, #3 << 10 @ r1 = 0b 0000 1100 0001 0010 = 0xC12
  19. add r2, r3, #16384 @ r2 = 0x 5000 8000
  20.        /*下面一个循环,把虚拟空间的256MB映射的页表项 设置 缓存和写缓存*/
  21. 1: cmp r1, r9 @ if virt > start of RAM
  22. orrhs r1, r1, #0x0c @ set cacheable, bufferable
  23. cmp r1, r10 @ if virt > end of RAM
  24. bichs r1, r1, #0x0c @ clear cacheable, bufferable
  25. str r1, [r0], #4 @ 1:1 mapping
  26. add r1, r1, #1048576 @ 判断下一个 1M 的节区
  27. teq r0, r2
  28. bne 1b
for(r0=0x50004000,r1=0xc12; r0!=0x50008000; r0+=4,r1+=0x100000)
{
if(r1 > 0x50000000)
r1 = r1 or 0x0c; // 0xC12 -> 0xC1E 第2、3个bit为1,即set cacheable, bufferable
if(r1 > 0x60000000)
r1 = r1 xor 0x0c; // 0xC1E -> 0xC12 这样,从0x50000000到0x60000000的虚拟地址为cacheable, bufferable
*r0 = r1; // 把虚拟地址值直接写入到对应的页表物理空间
}
  1. /*
  2. * If ever we are running from Flash, then we surely want the cache
  3. * to be enabled also for our execution instance... We map 2MB of it
  4. * so there is no map overlap problem for up to 1 MB compressed kernel.
  5. * If the execution is in RAM then we would only be duplicating the above.
  6. */
  7. mov r1, #0x1e
  8. orr r1, r1, #3 << 10
  9. mov r2, pc, lsr #20
  10. orr r1, r1, r2, lsl #20
  11. add r0, r3, r2, lsl #2
  12. str r1, [r0], #4
  13. add r1, r1, #1048576
  14. str r1, [r0]
  15. mov pc, lr
  16. ENDPROC(__setup_mmu)

  1. __armv4_mmu_cache_on:

  1. mov r12, lr @ 保存返回地址到r12,因为下面调用bl __setup_mm
  2. @ 时会将返回地址lr更新掉
  3. #ifdef CONFIG_MMU
  4. bl __setup_mmu
  5. mov r0, #0
  6. /* 利用cp15将所有写缓冲的内容更新到内存,并清除指令缓存I-Cache
  7. 和数据缓存D-Cache、TLB等
  8. 读取CP15的控制寄存器内容,设置指令缓存激活位、RoundRobin缓存交替
  9. 策略激活位。这部分参考ARM Linux内核源码剖析
  10. mcr p15, 0, r0, c7, c10, 4 @ drain write buffer
  11. mcr p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
  12. mrc p15, 0, r0, c1, c0, 0 @ read control reg
  13. orr r0, r0, #0x5000 @ I-cache enable, RR cache replacement
  14. orr r0, r0, #0x0030
  15. #ifdef CONFIG_CPU_ENDIAN_BE8
  16. orr r0, r0, #1 << 25 @ big-endian page tables
  17. #endif
  18. /*__common_mmu_cache_on子程序使用了上面变更的域设置和“指令缓存”激活
  19. 、缓存循环交替策略。同时将页目录的起始地址值存入CP15专用寄存器
  20. */
  21. bl __common_mmu_cache_on
  22. @ 清除指令缓存、数据缓存、TLB
  23. mov r0, #0
  24. mcr p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
  25. #endif
  26. mov pc, r12 @返回

  1. __armv7_mmu_cache_on:

  1. mov r12, lr
  2. #ifdef CONFIG_MMU
  3. mrc p15, 0, r11, c0, c1, 4 @ read ID_MMFR0
  4. tst r11, #0xf @ VMSA
  5. blne __setup_mmu
  6. mov r0, #0
  7. mcr p15, 0, r0, c7, c10, 4 @ drain write buffer
  8. tst r11, #0xf @ VMSA
  9. mcrne p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
  10. #endif
  11. mrc p15, 0, r0, c1, c0, 0 @ read control reg
  12. orr r0, r0, #0x5000 @ I-cache enable, RR cache replacement
  13. orr r0, r0, #0x003c @ write buffer
  14. #ifdef CONFIG_MMU
  15. #ifdef CONFIG_CPU_ENDIAN_BE8
  16. orr r0, r0, #1 << 25 @ big-endian page tables
  17. #endif
  18. orrne r0, r0, #1 @ MMU enabled
  19. movne r1, #-1
  20. mcrne p15, 0, r3, c2, c0, 0 @ load page table pointer
  21. mcrne p15, 0, r1, c3, c0, 0 @ load domain access control
  22. #endif
  23. mcr p15, 0, r0, c1, c0, 0 @ load control register
  24. mrc p15, 0, r0, c1, c0, 0 @ and read it back
  25. mov r0, #0
  26. mcr p15, 0, r0, c7, c5, 4 @ ISB
  27. mov pc, r12
  28. ......

  1. __common_mmu_cache_on:

  1. #ifndef CONFIG_THUMB2_KERNEL
  2. #ifndef DEBUG
  3. orr r0, r0, #0x000d @ Write buffer, mmu
  4. #endif
  5. mov r1, #-1
  6. mcr p15, 0, r3, c2, c0, 0 @ load page table pointer
  7. mcr p15, 0, r1, c3, c0, 0 @ load domain access control
  8. b 1f
  9. .align 5 @ cache line aligned
  10. 1: mcr p15, 0, r0, c1, c0, 0 @ load control register
  11. mrc p15, 0, r0, c1, c0, 0 @ and read it back to
  12. sub pc, lr, r0, lsr #32 @ properly flush pipeline
  13. #endif

  1. call_kernel:

  1. bl cache_clean_flush @清理缓存
  2. bl cache_off @关闭缓存
  3. mov r0, #0 @ must be zero
  4. mov r1, r7 @ restore architecture number
  5. mov r2, r8 @ restore atags pointer
  6. mov pc, r4 @ call kernel r4是解压后的内核开始地址
  1. /*
  2. * Clean and flush the cache to maintain consistency.
  3. *
  4. * On exit,
  5. * r1, r2, r3, r9, r11, r12 corrupted
  6. * This routine must preserve:
  7. * r0, r4, r5, r6, r7
  8. */
  9. .align 5

  1. cache_clean_flush:

  1. mov r3, #16 @ 在调用cache函数时,通过r3=16,偏移16字节,即调用
  2. b call_cache_fn
  1. /*
  2. * Turn off the Cache and MMU. ARMv3 does not support
  3. * reading the control register, but ARMv4 does.
  4. *
  5. * On exit, r0, r1, r2, r3, r9, r12 corrupted
  6. * This routine must preserve: r4, r6, r7
  7. */
  8. .align 5

  1. cache_off:

  1. mov r3, #12 @ cache_off function 在调用cache函数时,通过r3=12,偏移12字节,即调用
  2. b call_cache_fn
<think>我们讨论的是关于Linux内核的zImage和initramfs的镜像解压与重新打包问题。虽然这个问题涉及到底层操作,但我们可以用C语言编写工具来处理这些镜像。需要注意的是,内核镜像的格式是特定的,通常包含一个压缩的内核和一个可选的initramfs(初始内存文件系统)。步骤:1.解压zImagezImage是一个压缩的内核镜像,它通常使用gzip压缩,但前面有一个头部(自解压头)。我们可以编写程序跳过头部,然后解压出内核。2.处理initramfs:initramfs通常是一个cpio归档文件,可能经过压缩(如gzip)。它被嵌入到内核镜像中。重新打包的步骤:1.将新的initramfs(可能是修改后的)嵌入到解压后的内核中。2.重新压缩内核,并加上自解压头,生成新的zImage。注意:内核镜像的结构比较复杂,不同版本的内核可能有所不同。这里我们以较新的内核(2.6及以后)为例,它们通常使用一个叫做“piggy”的部分来存放压缩的内核。由于整个过程比较复杂,我们通常使用已有的工具(如extract-vmlinux)来解压,但这里我们尝试用C语言编写一个简单的工具来解压和重新打包。但是,请注意:这通常是由内核的构建脚本(如scripts/extract-vmlinux等)完成的。我们这里为了教学目的,编写一个简化版本。步骤1解压zImage-找到zImage中的压缩内核部分(跳过自解压头)。自解压头是一个可执行的代码,用于解压内核。我们可以通过查找压缩流的魔数(例如gzip的魔数0x1f8b)来定位。-然后从这个位置开始解压,得到vmlinux(未压缩的内核)。步骤2:提取和替换initramfs-在vmlinux中,initramfs是以一个cpio归档(可能压缩)的形式存在,并且在内核二进制文件中有特定的标记(如"TRAILER!!!")或者通过链接脚本放置在特定位置(如__initramfs_start和__initramfs_end符号之间)。-我们可以通过查找这些标记或符号来定位initramfs的位置,然后替换它。步骤3:重新打包-将新的initramfs放入vmlinux的相应位置(注意大小不能超过原来的空间,否则需要调整内核)。-然后压缩vmlinux(使用gzip),并加上自解压头(可以从原始的zImage中提取,或者使用内核源码中的自解压头)。由于整个过程非常复杂,且需要深入了解内核镜像格式,这里我们只编写一个简单的工具,用于解压zImage(假设我们能够找到gzip流的起始位置)和重新打包。注意:在实际操作中,我们通常使用现有的脚本和工具,比如:extract-vmlinux解压出vmlinuxbuild-initramfs:构建initramfs然后使用内核的链接器脚本重新链接并压缩。但是,为了满足要求,我们编写一个C语言程序,它能够:1.从一个zImage文件中提取出压缩的内核部分(即跳过自解压头,找到gzip流)。2.解压这个gzip流,得到vmlinux。3.然后,我们假设不修改vmlinux,而是重新压缩它,并生成一个新的zImage(使用相同的自解压头)。重新打包时,我们将原来的自解压头(从原始zImage中提取)和重新压缩的内核拼接起来。注意:自解压头的大小是不固定的,我们通过查找gzip魔数来确定压缩内核的起始位置。程序功能:-解压:输入zImage,输出vmlinux-打包:输入vmlinux(已经修改过),输出zImage(需要原始zImage的自解压头部分)步骤:解压1.读取zImage文件,查找gzip魔数(0x1f8b)。2.从找到的位置开始,将剩余部分写入一个临时文件(作为gzip流)。3.使用zlib解压这个临时文件,得到vmlinux。打包:1.压缩vmlinux(使用gzip)。2.读取原始zImage文件,从开头到gzip魔数之前的部分(即自解压头)保存下来。3.将自解压头和压缩后的内核拼接起来,形成新的zImage。注意:自解压头可能会包含一些校验信息,直接拼接可能在某些情况下不工作。但这种方法对于许多内核是有效的。由于我们使用C语言,我们将使用zlib库进行解压和压缩。因此,编译时需要链接zlib(-lz)。代码结构:1.解压函数:unzImage2.打包函数:rezImage但是,请注意:initramfs的替换不在这个工具中处理,因为initramfs是包含在vmlinux中的。我们需要在vmlinux层面替换initramfs,这通常需要解析内核的ELF格式并找到initramfs的位置,这非常复杂。因此,我们这里只处理整个vmlinux的压缩和解压。下面给出代码示例:注意:由于时间限制,我们只实现解压和重新压缩,不处理initramfs的单独替换。替换initramfs需要更复杂的操作,通常在内核构建时完成。代码示例:</think>### zImage-initramfs 镜像解压与重新打包 在 Linux 内核开发中,`zImage` 是压缩的内核镜像,`initramfs` 是初始内存文件系统。以下是完整的解压和重新打包流程: --- #### **1. 解压 zImage 提取内核** ```c #include <stdio.h> #include <stdlib.h> #include <string.h> // 查找 gzip 魔数位置 (0x1F 0x8B) long find_gzip_offset(FILE *f) { unsigned char buf[4]; long offset = 0; while (!feof(f)) { buf[0] = buf[1]; buf[1] = buf[2]; buf[2] = buf[3]; fread(&buf[3], 1, 1, f); if (buf[0]==0x1F && buf[1]==0x8B && buf[2]==0x08) { return offset - 2; // 返回 gzip 起始位置 } offset++; } return -1; } int main(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Usage: %s <zImage> <output_vmlinux>\n", argv[0]); return 1; } FILE *f_in = fopen(argv[1], "rb"); long gzip_start = find_gzip_offset(f_in); if (gzip_start < 0) { fprintf(stderr, "GZIP header not found!\n"); fclose(f_in); return 1; } // 提取压缩内核数据 fseek(f_in, gzip_start, SEEK_SET); FILE *f_out = fopen(argv[2], "wb"); int ch; while ((ch = fgetc(f_in)) != EOF) { fputc(ch, f_out); } fclose(f_in); fclose(f_out); printf("Extracted kernel to %s\n", argv[2]); return 0; } ``` **原理**: - zImage = 引导头 + 压缩内核(gzip 格式) - 通过扫描 `0x1F 0x8B 0x08`(gzip 魔数)定位压缩内核起始位置 - 提取 gzip 数据后可用 `gunzip` 解压得到原始内核 --- #### **2. 解压 initramfs** ```c #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> // 从内核镜像中提取 initramfs int extract_initramfs(const char *kernel, const char *output) { char cmd[256]; // 使用 initramfs 边界符号定位 snprintf(cmd, sizeof(cmd), "objcopy -O binary -j .init.ramfs %s %s 2>/dev/null", kernel, output); return system(cmd); } int main(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Usage: %s <vmlinux> <output.cpio>\n", argv[0]); return 1; } if (extract_initramfs(argv[1], argv[2]) != 0) { fprintf(stderr, "Extraction failed!\n"); return 1; } printf("Extracted initramfs to %s\n", argv[2]); return 0; } ``` **原理**: - initramfs 存储在内核的 `.init.ramfs` 段 - 通过 `objcopy` 提取二进制段数据 - 提取的 `.cpio` 文件可用 `cpio -idmv` 解包 --- #### **3. 重新打包流程** ```bash # 1. 解压原始 initramfs mkdir initramfs && cd initramfs cpio -idmv < ../initramfs.cpio # 2. 修改文件系统(示例:添加文件) echo "Test" > newfile.txt # 3. 重新打包 initramfs find . | cpio -H newc -o > ../new_initramfs.cpio gzip ../new_initramfs.cpio # 4. 替换内核中的 initramfs objcopy --update-section .init.ramfs=new_initramfs.cpio.gz vmlinux new_vmlinux # 5. 重新压缩内核生成 zImage gzip -c new_vmlinux > piggy_data # 需链接引导头(需内核源码中的压缩工具) ./scripts/mkimage -A arm -O linux -T kernel -C gzip -d piggy_data new_zImage ``` --- #### **关键问题与解决方案** 1. **initramfs 定位失败** - **原因**:内核版本差异导致段名变化(如 `.initrd`) - **解决**:使用 `readelf -S vmlinux` 检查实际段名 2. **zImage 引导头兼容性** - **原因**:不同架构的引导头格式不同(ARM/x86) - **解决**:使用内核源码中的 `arch/*/boot/mkimage` 工具 3. **initramfs 大小超限** - **现象**:内核启动时卡在 `unpacking initramfs...` - **解决**:增大 `CONFIG_INITRAMFS_SIZE` 配置值后重新编译内核 4. **重新打包后无法启动** - **调试**:添加 `rdinit=/bin/sh` 内核参数进入急救 Shell - **检查**:使用 `dmesg | grep initramfs` 查看解压错误 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值