어셈블리(head.S) – gdb 디버깅 방법

<kernel v6.0>

준비

먼저 gdb를 사용하여 커널 소스 중 start_kernel()로 시작하는 C 부분의 디버깅이 가능한 환경이 이미 구축되어 있고 경험이 있다는 전제로 시작한다.
커널이 매핑되는 주소를 랜덤 위치로 변경하지 않도록 커널 빌드 전에 커널 옵션 중 “Kernel Features -> Randomize the address of the kernel image” 항목이 꺼져있어야 한다.(CONFIG_RANDOMIZE_BASE)
아래 debug 환경은 각자 상황에 따라 스크립트는 고쳐사용하여야 한다.
  • 다음 두 개의 스크립트는 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 시작

 

위의 두 커멘드를 각자의 창에서 순서대로 실행하여 vmlinux 파일을 로드하여 (gbd) 프롬프트가 출력되면,
다음과 같이 로드한 이미지내에 정의된 섹션들에 대한 offset 주소를 확인해본다.
(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 섹션)  물리 주소

다음 시작 부분에서 “b primary_entry” 부분에 대해 gdb의 TUI를 켜 실제 아래 위치까지 이동할 계획이다.
__HEAD 위치는 컴파일 당시에 .head.text 섹션에 위치한 0xffff800008000000 주소이고, 실제 실행될 물리 주소는 다음 코드 진행을 통해서 0x40200000임을 알 수 있다.
#define __HEAD          .section        ".head.text","ax"

 

시작 부분의 소스

arch/arm64/kernel/head.S
/*
* 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 설정

아래 화면과 같이 primary_entry에 브레이크 포인터를 위치하고 해당 위치까지 진행해본다. 그러기 위해선 primary_entry 에 위에서 산출한 diff 값을 뺀 주소에 브레이크 포인터를 위치하기 위해 다음 2가지 명령 중 하나의 방법을 선택한다.
  • 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 ^^

 

참고

4 thoughts to “어셈블리(head.S) – gdb 디버깅 방법”

  1. 안녕하세요! 이파란입니다.

    CONFIG_RANDOMIZE_BASE 컨피그를 끄고 빌드한 커널을 사용할 수 있고, 또는 해당 옵션을 y 로 빌드한 커널도
    qemu 옵션인 -append 에 “nokaslr” 를 추가해도 가능하겠네요!

    좋은 내용 항상 감사드립니다.

댓글 남기기