<kernel v6.0>
준비
- 다음 두 개의 스크립트는 raspeberry-pi 4 보드에서 ~/workspace/linux/v6.0 디렉토리에서 빌드된 커널 이미지를 대상으로 디버깅해본다.
- x86 호스트로 arm64를 emulation 하는 경우도 가능하며 -cpu host 대신 -cpu cortex-a57으로 변경하고, -enable-kvm을 제거해야 한다.
emu-kvm.sh
KER_VER=6.0 WORK_DIR=${HOME}"/workspace/linux" KERNEL_DIR=${WORK_DIR}"/${KER_VER}" KERNEL_IMG=${KERNEL_DIR}"/arch/arm64/boot/Image" ROOTFS="rootfs.ext4" DTB_FILE="virt.dtb" DTB="-dtb ${DTB_FILE}" echo "target remote localhost:1234" qemu-system-aarch64 -s -S -smp 1 -m 1024 -cpu host -nographic -enable-kvm \ -machine virt \ -kernel ${KERNEL_IMG} ${DTB} \ -nographic \ -device virtio-net-device,netdev=mynet1 \ -netdev tap,id=mynet1,ifname=tap0,script=no,downscript=no \ -device virtio-blk-device,drive=disk \ -drive if=sd,id=disk,format=raw,file=${ROOTFS} \ -append 'root=/dev/vda rw rootwait'
debug.sh
KER_VER=6.0 VMLINUX=~/workspace/linux/${KER_VER}/vmlinux echo "KER_VER=${KER_VER}" gdb -ex "target remote localhost:1234" \ -ex "set directories ${HOME}/workspace/linux/${KER_VER}" \ ${VMLINUX}
gdb 시작
(gdb) info target Symbols from "~/workspace/linux/6.0/vmlinux". Remote serial target in gdb-specific protocol: Debugging a target over a serial line. While running this, GDB does not access memory from... Local exec file: `/root/workspace/linux/6.0/vmlinux', file type elf64-littleaarch64. Entry point: 0xffff800008000000 0xffff800008000000 - 0xffff800008010000 is .head.text 0xffff800008010000 - 0xffff80000917c8d8 is .text 0xffff80000917c8d8 - 0xffff80000917c8f0 is .got.plt 0xffff800009180000 - 0xffff800009aeb5ae is .rodata 0xffff800009aeb5b0 - 0xffff800009aedfe0 is .pci_fixup 0xffff800009aedfe0 - 0xffff800009afde68 is __ksymtab 0xffff800009afde68 - 0xffff800009b14e18 is __ksymtab_gpl 0xffff800009b14e18 - 0xffff800009b55504 is __ksymtab_strings 0xffff800009b55508 - 0xffff800009b59cc0 is __param 0xffff800009b59cc0 - 0xffff800009b5a530 is __modver 0xffff800009b5a530 - 0xffff800009b5d3e0 is __ex_table 0xffff800009b5d3e0 - 0xffff800009b5d434 is .notes 0xffff800009b5e000 - 0xffff800009b62000 is .hyp.rodata 0xffff800009b62000 - 0xffff800009b66000 is .rodata.text 0xffff800009b70000 - 0xffff800009bee668 is .init.text 0xffff800009bee668 - 0xffff800009bf57ac is .exit.text 0xffff800009bf57ac - 0xffff800009c31e84 is .altinstructions 0xffff800009c43000 - 0xffff800009d6b6dd is .init.data 0xffff800009d6c000 - 0xffff800009d7ffe8 is .data..percpu 0xffff800009d80000 - 0xffff800009d81ee0 is .hyp.data..percpu 0xffff800009d81ee0 - 0xffff800009d82138 is .hyp.reloc 0xffff800009d82138 - 0xffff80000a5699b0 is .rela.dyn 0xffff80000a570000 - 0xffff80000a9c2400 is .data 0xffff80000a9c2400 - 0xffff80000a9dac4c is __bug_table 0xffff80000a9db000 - 0xffff80000a9db008 is .mmuoff.data.write 0xffff80000a9db800 - 0xffff80000a9db808 is .mmuoff.data.read 0xffff80000a9db808 - 0xffff80000a9dba00 is .pecoff_edata_padding 0xffff80000a9dc000 - 0xffff80000aa78fb8 is .bss
__HEAD(.head.text 섹션) 물리 주소
#define __HEAD .section ".head.text","ax"
시작 부분의 소스
/* * Kernel startup entry point. * --------------------------- * * The requirements are: * MMU = off, D-cache = off, I-cache = on or off, * x0 = physical address to the FDT blob. * * Note that the callee-saved registers are used for storing variables * that are useful before the MMU is enabled. The allocations are described * in the entry routines. */ __HEAD /* * DO NOT MODIFY. Image header expected by Linux boot-loaders. */ efi_signature_nop // special NOP to identity as PE/COFF executable b primary_entry // branch to kernel start, magic .quad 0 // Image load offset from start of RAM, little-endian le64sym _kernel_size_le // Effective size of kernel image, little-endian le64sym _kernel_flags_le // Informative flags, little-endian .quad 0 // reserved .quad 0 // reserved .quad 0 // reserved .ascii ARM64_IMAGE_MAGIC // Magic number .long .Lpe_header_offset // Offset to the PE header. __EFI_PE_HEADER __INIT /* * The following callee saved general purpose registers are used on the * primary lowlevel boot path: * * Register Scope Purpose * x20 primary_entry() .. __primary_switch() CPU boot mode * x21 primary_entry() .. start_kernel() FDT pointer passed at boot in x0 * x22 create_idmap() .. start_kernel() ID map VA of the DT blob * x23 primary_entry() .. start_kernel() physical misalignment/KASLR offset * x24 __primary_switch() linear map KASLR seed * x25 primary_entry() .. start_kernel() supported VA size * x28 create_idmap() callee preserved temp register */ SYM_CODE_START(primary_entry) bl preserve_boot_args bl init_kernel_el // w0=cpu_boot_mode mov x20, x0 bl create_idmap
TUI 기동
먼저 layout prev 명령을 수행하면 TUI 화면이 나타나고(만일 TUI 기능이 포함된 gdb) 첫 시작 부분은 qemu 코드이므로 0x40200000까지 이동해야 하므로 ni 명령을 7번 수행한다.
(gdb) layout prev
┌──Register group: general──────────────────────────────────────────────────────────────────────────────────────┐
│x0 0x0 0 x1 0x0 0 │
│x2 0x0 0 x3 0x0 0 │
│x4 0x0 0 x5 0x0 0 │
│x6 0x0 0 x7 0x0 0 │
│x8 0x0 0 x9 0x0 0 │
│x10 0x0 0 x11 0x0 0 │
│x12 0x0 0 x13 0x0 0 │
│x14 0x0 0 x15 0x0 0 │
│x16 0x0 0 x17 0x0 0 │
│x18 0x0 0 x19 0x0 0 │
│x20 0x0 0 x21 0x0 0 │
│x22 0x0 0 x23 0x0 0 │
│x24 0x0 0 x25 0x0 0 │
│x26 0x0 0 x27 0x0 0 │
│x28 0x0 0 x29 0x0 0 │
│x30 0x0 0 sp 0x0 0x0 │
│pc 0x40000000 0x40000000 cpsr 0x400003c5 1073742789 │
│fpsr 0x0 0 fpcr 0x0 0 │
│MVFR6_EL1_RESERVED0x0 0 MVFR7_EL1_RESERVED0x0 0 │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
>│0x40000000 ldr x0, 0x40000018 │
│0x40000004 mov x1, xzr │
│0x40000008 mov x2, xzr │
│0x4000000c mov x3, xzr │
│0x40000010 ldr x4, 0x40000020 │
│0x40000014 br x4 │
│0x40000018 stxrh w0, w0, [x0] │
│0x4000001c .inst 0x00000000 ; undefined │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
remote Thread 1.1 In: L?? PC: 0x40000000
(gdb)
__HEAD 까지 이동
아래 화면이 나타나는데 0x40200000 주소에 “ccmp x18, #0x0, #0xd, pl” 명령과 “b 0x41d70000″이 보이는데 이들은 __HEAD로 시작하는 어셈블리 코드이다.
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ >│0x40200000 ccmp x18, #0x0, #0xd, pl // pl = nfrst │ │0x40200004 b 0x41d70000 │ │0x40200008 .inst 0x00000000 ; undefined │ │0x4020000c .inst 0x00000000 ; undefined │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ remote Thread 1.1 In: L?? PC: 0x40200000 (gdb) ni 0x0000000040000000 in ?? () (gdb) ni 0x0000000040000004 in ?? () (gdb) ni 0x0000000040000008 in ?? () (gdb) ni 0x000000004000000c in ?? () (gdb) ni 0x0000000040000010 in ?? () (gdb) ni 0x0000000040000014 in ?? () (gdb) ni 0x0000000040200000 in ?? () (gdb)
__HEAD 진입하자 마자 efi_signature_nop 매크로가 보이는데 이를 살펴보면 ccmp로 시작하는 명령이 진행됨을 알 수 있다.
arch/arm64/kernel/efi-header.S
.macro efi_signature_nop
#ifdef CONFIG_EFI
.L_head:
/*
* This ccmp instruction has no meaningful effect except that
* its opcode forms the magic "MZ" signature required by UEFI.
*/
ccmp x18, #0, #0xd, pl
#else
/*
* Bootloaders may inspect the opcode at the start of the kernel
* image to decide if the kernel is capable of booting via UEFI.
* So put an ordinary NOP here, not the "MZ.." pseudo-nop above.
*/
nop
#endif
.endm
diff(가상주소 – 물리주소) 값 산출 및 offset 적용
그럼 __HEAD의 가상 주소에서 물리 주소인 0x40200000의 차이(diff)를 산출해둔다.
diff = 가상주소(0xffff800008000000) – 물리주소(0x40200000) = 0xffff7fffc7e00000
gdb로 살펴볼 코드 및 데이터 부분이 포함된 4 가지 섹션들의 물리 주소를 위에서 산출한 diff 값을 사용해서 산출해둔다. (필요한 만큼 추가한다)
- .text
- 0x40210000
- .head.text
- 0x40200000
- .init.text
- 0x41d70000
- .bss
- 0x42bdc000
위의 물리 주소를 사용한 심볼을 추가해본다. 추가하자마자 다음 화면과 같이 물리 주소 대신 심볼 주소가 보이기 시작한다.
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ >│0x40200000 <_text> ccmp x18, #0x0, #0xd, pl // pl = nfrst │ │0x40200004 <_text+4> b 0x41d70000 <primary_entry> │ │0x40200008 <_text+8> .inst 0x00000000 ; undefined │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ remote Thread 1.1 In: _text L60 PC: 0x40200000 (gdb) add-symbol-file ~/workspace/linux/6.0/vmlinux 0x40210000 -s .head.text 0x40200000 -s .init.text 0x41d70000 -s .bss 0x42bdc000 add symbol table from file "~/workspace/linux/6.0/vmlinux" at .text_addr = 0x40210000 .head.text_addr = 0x40200000 .init.text_addr = 0x41d70000 .bss_addr = 0x42bdc000 (y or n) y Reading symbols from ~/workspace/linux/6.0/vmlinux...done. (gdb)
추가된 심볼 주소에 대해 다시 한 번 “info target” 명령을 사용해보면 다음과 같이 4개의 섹션에 대해 물리주소가 추가되어 있음을 확인할 수 있다.
(gdb) info target Symbols from "~/workspace/linux/6.0/vmlinux". Remote serial target in gdb-specific protocol: Debugging a target over a serial line. While running this, GDB does not access memory from... Local exec file: `~/workspace/linux/6.0/vmlinux', file type elf64-littleaarch64. Entry point: 0xffff800008000000 0xffff800008000000 - 0xffff800008010000 is .head.text 0xffff800008010000 - 0xffff80000917c8d8 is .text 0xffff80000917c8d8 - 0xffff80000917c8f0 is .got.plt 0xffff800009180000 - 0xffff800009aeb5ae is .rodata 0xffff800009aeb5b0 - 0xffff800009aedfe0 is .pci_fixup 0xffff800009aedfe0 - 0xffff800009afde68 is __ksymtab 0xffff800009afde68 - 0xffff800009b14e18 is __ksymtab_gpl 0xffff800009b14e18 - 0xffff800009b55504 is __ksymtab_strings 0xffff800009b55508 - 0xffff800009b59cc0 is __param 0xffff800009b59cc0 - 0xffff800009b5a530 is __modver 0xffff800009b5a530 - 0xffff800009b5d3e0 is __ex_table 0xffff800009b5d3e0 - 0xffff800009b5d434 is .notes 0xffff800009b5e000 - 0xffff800009b62000 is .hyp.rodata 0xffff800009b62000 - 0xffff800009b66000 is .rodata.text 0xffff800009b70000 - 0xffff800009bee668 is .init.text 0xffff800009bee668 - 0xffff800009bf57ac is .exit.text 0xffff800009bf57ac - 0xffff800009c31e84 is .altinstructions 0xffff800009c43000 - 0xffff800009d6b6dd is .init.data 0xffff800009d6c000 - 0xffff800009d7ffe8 is .data..percpu 0xffff800009d80000 - 0xffff800009d81ee0 is .hyp.data..percpu 0xffff800009d81ee0 - 0xffff800009d82138 is .hyp.reloc 0xffff800009d82138 - 0xffff80000a5699b0 is .rela.dyn 0xffff80000a570000 - 0xffff80000a9c2400 is .data 0xffff80000a9c2400 - 0xffff80000a9dac4c is __bug_table 0xffff80000a9db000 - 0xffff80000a9db008 is .mmuoff.data.write 0xffff80000a9db800 - 0xffff80000a9db808 is .mmuoff.data.read 0xffff80000a9db808 - 0xffff80000a9dba00 is .pecoff_edata_padding 0xffff80000a9dc000 - 0xffff80000aa78fb8 is .bss 0x0000000040200000 - 0x0000000040210000 is .head.text 0x0000000040210000 - 0x000000004137c8d8 is .text 0xffff80000917c8d8 - 0xffff80000917c8f0 is .got.plt 0xffff800009180000 - 0xffff800009aeb5ae is .rodata 0xffff800009aeb5b0 - 0xffff800009aedfe0 is .pci_fixup 0xffff800009aedfe0 - 0xffff800009afde68 is __ksymtab 0xffff800009afde68 - 0xffff800009b14e18 is __ksymtab_gpl 0xffff800009b14e18 - 0xffff800009b55504 is __ksymtab_strings 0xffff800009b55508 - 0xffff800009b59cc0 is __param 0xffff800009b59cc0 - 0xffff800009b5a530 is __modver 0xffff800009b5a530 - 0xffff800009b5d3e0 is __ex_table 0xffff800009b5d3e0 - 0xffff800009b5d434 is .notes 0xffff800009b5e000 - 0xffff800009b62000 is .hyp.rodata 0xffff800009b62000 - 0xffff800009b66000 is .rodata.text 0x0000000041d70000 - 0x0000000041dee668 is .init.text 0xffff800009bee668 - 0xffff800009bf57ac is .exit.text 0xffff800009bf57ac - 0xffff800009c31e84 is .altinstructions 0xffff800009c43000 - 0xffff800009d6b6dd is .init.data 0xffff800009d6c000 - 0xffff800009d7ffe8 is .data..percpu 0xffff800009d80000 - 0xffff800009d81ee0 is .hyp.data..percpu 0xffff800009d81ee0 - 0xffff800009d82138 is .hyp.reloc 0xffff800009d82138 - 0xffff80000a5699b0 is .rela.dyn 0xffff80000a570000 - 0xffff80000a9c2400 is .data 0xffff80000a9c2400 - 0xffff80000a9dac4c is __bug_table 0xffff80000a9db000 - 0xffff80000a9db008 is .mmuoff.data.write 0xffff80000a9db800 - 0xffff80000a9db808 is .mmuoff.data.read 0xffff80000a9db808 - 0xffff80000a9dba00 is .pecoff_edata_padding 0x0000000042bdc000 - 0x0000000042c78fb8 is .bss
break point 설정
- b *primary_entry-0xffff7fffc7e00000
- b *0x41d70000
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ B+>│0x41d70000 <primary_entry> bl 0x41d70018 <preserve_boot_args> │ │0x41d70004 <primary_entry+4> bl 0x4137c000 <init_kernel_el> │ │0x41d70008 <primary_entry+8> mov x20, x0 │ │0x41d7000c <primary_entry+12> bl 0x41d70098 <create_idmap> │ │0x41d70010 <primary_entry+16> bl 0x4137c7d8 <__cpu_setup> │ └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ remote Thread 1.1 In: primary_entry L89 PC: 0x41d70000 (gdb) b *primary_entry-0xffff7fffc7e00000 Breakpoint 1 at 0x41d70000: file arch/arm64/kernel/head.S, line 89. (gdb) c Continuing. Breakpoint 1, primary_entry () at arch/arm64/kernel/head.S:89 (gdb)
(끝)
Enjoy your self ^^
참고
- QEMU 에뮬레이션(arm64) | 문c
- 어셈블리(head.S) – gdb 디버깅 방법 | 문c – 현재글
감사합니다.
이글을 참고하여 성공하였습니다.
도움이 되어 기쁩니다.
즐거운 하루 되세요. ^^
안녕하세요! 이파란입니다.
CONFIG_RANDOMIZE_BASE 컨피그를 끄고 빌드한 커널을 사용할 수 있고, 또는 해당 옵션을 y 로 빌드한 커널도
qemu 옵션인 -append 에 “nokaslr” 를 추가해도 가능하겠네요!
좋은 내용 항상 감사드립니다.
그렇죠. nokaslr 옵션 좋군요.
감사합니다. ^^