2024년 7월 15일 월요일

STM32MP157C Discovery Kit로 알아보는 Embedded Linux Programming

이번 시간에는 (아주 오래 간만에) STM32MP157C-DK2 보드에 관하여 못다한 이야기를 좀 더 해 보고자 한다. 😎

목차

보드 개발 시 가장 먼저 하게 되는 작업은 compile된 code(binary or firmware)를 target board(flash memory)에 올려 보는 일(flash writing or programming) 일일 것이다. 여기에는 ROM writer를 이용하는 고전적인 방법 부터, ROM code를 이용하는 방법, 그리고 JTAG을 활용하는 방법까지 다양한 방법이 존재한다.


9. ROM Code 기반으로 Flash Writing 하기
이번 장에서는 ROM code를 기반으로 DK2 보드에 firmware를 올리는 방법에 관하여 소개하고자 한다. 사실, 이 내용은 이미 이전 blog post를 통해 한 차례 소개한 바 있다. 따라서 이번 글에서는 Trusted Firmwaresnagboot를 중심으로 보충 설명을 해 보고자 한다.

1) Trusted Firmware 기반의 STM32MP1 boot flow
ROM code를 통해 firmware image를 flash에 writing하려면, 관련 firmware image가 준비되어 있어야 한다. 이번 글에서는 편의상 buildroot를 통해 필요한 image를 만들어 사용하도록 하겠다.

<buildroot build 절차>
$ git clone git://git.buildroot.net/buildroot
$ cd buildroot/
$ make stm32mp157c_dk2_defconfig
$ make menuconfig

[그림 9.1] buildroot menuconfig - Bootloaders

$ make
...
build 결과물은 output/images 디렉토리에 만들어진다.

chyi@earth:/mnt/hdd/workspace/bootlin/buildroot/output/images$ ls -la
합계 54632
drwxr-xr-x  3 chyi chyi      4096  7월  9 17:18 .
drwxrwxr-x  6 chyi chyi      4096  7월  9 15:57 ..
-rw-r--r--  1 chyi chyi   1154223  7월  9 15:55 fip.bin
-rw-r--r--  1 chyi chyi 125829120  7월  9 17:18 rootfs.ext2
lrwxrwxrwx  1 chyi chyi        11  7월  9 17:18 rootfs.ext4 -> rootfs.ext2
-rw-r--r--  1 chyi chyi   5951488  7월  9 17:18 rootfs.squashfs
-rw-r--r--  1 chyi chyi   8222720  7월  9 17:18 rootfs.tar
-rw-r--r--  1 chyi chyi   8386560  7월  9 17:18 rootfs.ubifs
-rw-r--r--  1 chyi chyi 128380928  7월  9 17:18 sdcard.img
-rwxr-xr-x  1 chyi chyi     60008  7월  9 15:56 stm32mp157c-dk2.dtb
-rw-r--r--  1 chyi chyi    209261  7월  9 15:55 tf-a-stm32mp157c-dk2.stm32
-rwxr-xr-x  1 chyi chyi   1007380  7월  9 15:55 u-boot-nodtb.bin
-rw-r--r--  1 chyi chyi     94472  7월  9 15:55 u-boot.dtb
-rw-r--r--  1 chyi chyi   4411208  7월  9 15:56 zImage

내용을 들여다 보니, 평상시에는 보이지 않던, fip.bin, tf-a-stm32mp157c-dk2.stm32 등의 파일이 보인다. STM32MP1은 ARM Trusted Firmware를 기반으로 부팅하도록 되어 있어, 이러한 추가 파일이 생성된 듯 싶은데, 부팅 flow를 통해 관련 파일의 용도를 확인해 보자.

먼저, 우리가 익히 알고 있는 embedded linux booting flow와 최신의 ARM booting flow를 먼저 비교해 보자.

<기존 부팅 방식>
ROM code => u-boot(1st bootloader) => Linux Kernel

<최신 ARM 부팅 방식>
ROM code(BL1) => TF-A(BL2, 1st bootloader) => BL32 | BL33(2nd bootloader) =>  Linux Kernel

아래 그림은 최신의 ARM 부팅 방식을 따르는 STMP32MP1의 booting flow를 표현한 것이다. 이중 TF-A(BL2)는 (위의 build 결과물 중) tf-a-stm32mp157c-dk2.stm32 파일에 해당하며, TF-A Secure Monitor(BL32) + U-Boot(BL33)은 fip.bin 파일에 해당한다.

[그림 9.2] STM32MP1 booting flow [출처 - 참고문헌 15]

한편, 아래 그림은 stm32mp157a-dk1 보드 sdcard image(sdcard.img) 파일의 layout을 표현한 것으로, DK-2 보드와는 살짝 다르지만, tf-a 부분과 fip.bin 부분의 내용이 동일하게 담겨 있어 참고할 만한 가치가 크다고 하겠다.

[그림 9.3] STM32MP1 SD card layout [출처 - 참고문헌 15]
📌 TF-A code 앞에는 STM32 ROM code용 header 파일이 위치하게 되는데, 이 안에는 ECDSA 서명 알고리즘과 public key 정보가 담겨있다.

그렇다면, ROM code에서 TF-A(BL2)로 넘어가는 단계와 TF-A(BL2)에서 FIP(BL31, BL32, BL33)로 넘어가는 단계에서는 과연 무슨 일이 벌어지는 것일까 ? 정답은 아래 그림과 같이 ECDSA 알고리즘 기반의 검증(인증) 절차를 통해, 정상적인 과정을 통해 생성된 bootloader인지를 가려내는 절차가 진행된다. 즉, 제 3자가 bootloader image를 불법적으로 개조하여 올릴 경우, 이를 차단하는 과정을 거치게 된다(이런 기법을 secure boot라고 함).

[그림 9.4] STM32MP1 Secureboot - ECDSA 서명/검증 과정
📌 위의 그림은 대략적인 개념을 정리한 것으로, 사실과 정확하게 일치하지 않을 수도 있다.

<ECDSA 서명/검증 절차>
  • bootloader image 생성(build) 과정에서 ECDSA(P-256 NIST or Brainpool 256) key pair(private, public key)를 생성하고, 
  • Private key를 이용해 서명(signature) 하는 작업이 진행한다.
  • 한편 부팅 시에는 ROM code에서 해당 image(TF-A, FIP)에 대한 검증 작업(verification)을 진행하게 되는데,
  • 이때 사용하는 ECDSA 검증을 위한 알고리즘과 Public key 정보는 TF-A image header 부분에서 가져오게 된다.
📌 사실 secure boot 기법은 새로운 내용이 아니다. 여러분이 사용하는 Android Phone은 모두 이미 이러한 방식으로 부팅하고 있다.

끝으로, 최신의 부팅 방식을 이해하기 위해서는 ARM Trusted Firmware에 대한 개념을 이해할 필요가 있다. 하지만, 한꺼번에 정리하기에는 내용이 아주 방대한 관계로, 어쩔 수 없이 용어만 간단하게 정리하고 넘어가도록 하자(늘 이런게 힘들다). 😓

<최신의 ARMv7, ARMv8 Trusted Firmware 개요>
  • Trusted firmware는 OS(linux)에게로 제어권이 넘어가더라도 RAM에 상주함.
  • RAM(DDR)의 특정 영역에 위치하고 있다가, OS가 할 수 없는 특정 서비스를 제공하게 됨.
  • 4개의 Exception Level 존재
    • EL3, the most priviledged, runs secure firmware
    • EL2, typically used by hypervisors, for virtualization
    • EL1, used to run the Linux kernel
    • EL0, used to run Linux user-space applications
  • 2개의 world(Normal world, Secure world) 존재
    • Normal world는 linux 같은 OS가 동작하는 영역
    • Secure world는 안전한 기능을 수행하는 별도로 분리된 영역임. ARM의 TrustZone이 대표적인 예에 해당함.
  • TF-A(Trusted Firmware-A) - Secure world s/w의 reference 구현 내용
  • TEE(Trusted Execution Environment)
    • OP-TEE는 open source TEE를 일컬음.
  • FIP - Firmware Image Package(2개 이상의 bootloader와 device tree, config 등을 묶어줌), BL2 다음에 실행됨. Trusted Firmware-A 규격을 따름.

위와 같은 기초적인 내용을 토대로, ARM Trusted Firmware를 하나의 그림으로 표현해 보면 다음과 같다.

[그림 9.5] ARM Trusted Firmware 개요 [출처 - 참고문헌 15]
📌 요즘은 bootin 분들이 아주 훌륭한 일들을 많이 하시는 것 같다. 경의를 표한다. 😍 

Trusted Firemware 관련하여, 보다 자세한 사항은 아래 link를 참고하기로 하자. 갈수록 숙제가 많아진다. 😂

2) ROM Code 기반으로 program 올리기
요즘 나오는 대부분의 board(SBC - single board computer)는 아래 그림과 같이 ROM code와 USB interface를 이용하여 flash memory(NOR, NAND, eMMC)에 write하는 방법을 제공한다. ROM code를 이용하는 대표적인 예로는 Arduino, ESP32, BBB, SAMA5DX 등을 생각해 볼 수 있을 것 같다(사실 이 밖에도 셀 수 없을 정도로 많다).

[그림 9.6] STM32MP1 ROM code recovery 개요도[출처 - 참고문헌 15]
📌 위의 그림은 eMMC가 있는 STM32MP1 series 중의 하나를 표현한 것인데, 유감스럽게도 STM32MP157C DK2 보드에는 eMMC가 존재하지 않는다.

ROM code는 chip 제조사에서 제공하는 것으로 Read Only Memory에 올라가 있어, 수정이 불가하며, system을 복구하는 용도로 사용된다. ROM code를 통해 flash writing을 하기 위해서는, 부팅 시 강제로 recovery mode로 진입하는 작업을 해 주어야 한다. 

<Recovery mode 진입 방법>
  • Recovery button을 누른 상태에서 전원을 다시 넣고 부팅하기
  • DIP switch를 recovery mode로 조정 후 부팅하기
  • 특정 명령을 실행하여, 재부팅되면서 진입하기

3) Snagboot 소개
Snagboot는 bootlin에서 진행 중인 project(python으로 만듦)로 칩 제조사(ST STM32MP1, Microchip SAMA5, NXP i.MX6/7/8/93, TI AM335x, Allwinner SUNXI and TI AM62x 등)에서 제공하는 다양한 flash writing tool을 대체하는 목적으로 만들어졌다.


snagboot는 아래와 같이 2가지 실행 파일로 구성되어 있다.
  • snagrecover - 칩 제조사에서 제공하는 ROM code 방식을 이용하여 RAM을 초기화하고, u-boot을 실행하는 역할 수행.
  • snagflash - u-boot과 통신하여 실제로 flash memory에 write 작업하는 tool. 이때 DFU, UMS or Fastboot protocol이 활용됨.
__________________________________________

백견이 불여일타~ 설치하여 실행해 보도록 하자.

<Ubuntu 22.04 LTS>
$ git clone https://github.com/bootlin/snagboot
$ cd snagboot
./install.sh
snagrecover --udev > 50-snagboot.rules
sudo cp 50-snagboot.rules /etc/udev/rules.d/
$ sudo udevadm control --reload-rules
$ sudo udevadm trigger

$ snagrecover --list-socs
  => 먼저 snagboot에서 지원하는 soc list를 확인하도록 한다.
SoCs that are supported and tested:

a20
a33
a64
am3358
am625
am62a7
am62p
h3
imx28
imx53
imx6q
imx6ull
imx7d
imx8mm
imx8qm
imx8qxp
imx93
r8
sama5d2
sama5d3
sama5d4
stm32mp13
stm32mp15

SoCs that are supported but untested:

...

다음으로, 아래(왼쪽 첫번째)와 같이 DK2 보드의 DIP switch를 조정하여 recovery mode로 진입하도록 한다.


[그림 9.7] DK2 보드의 부팅 모드 조정 DIP switch [출처 - 참고문헌 14]

$ lsusb -d 0483:df11
  => recovery mode로 진입했는지를 확인한다.
Bus 001 Device 015: ID 0483:df11 STMicroelectronics STM Device in DFU Mode

아래 위치로 이동하여 snagrecover를 위해 필요한 yaml 파일을 하나 만든 후, snagrecover 명령을 실행한다.
$ cd src/snagrecover/templates
vi stm32mp1-stm32mp157c-dk2.yaml
tf-a:
 path: /mnt/hdd/workspace/bootlin/buildroot/output/images/tf-a-stm32mp157c-dk2.stm32
fip:
 path: /mnt/hdd/workspace/bootlin/buildroot/output/images/fip.bin
~
📌 파일 내용을 보니, tf-a(BL2)와 fip(BL32, BL33, device tree)에 대한 binary path를 지정하는 것이 전부이다.

자, snagrecover tool을 돌려 보자.

$ snagrecover -s stm32mp15 -f ./stm32mp1-stm32mp157c-dk2.yaml
 => 어라 errror가 난다. ???
Searching for USB device paths matching 0483:df11...
Starting recovery of stm32mp15 board
Installing firmware tf-a
Searching for partition id...
Downloading file...
Traceback (most recent call last):
 File "/home/chyi/.local/bin/snagrecover", line 8, in <module>
   sys.exit(cli())
 File "/home/chyi/.local/lib/python3.10/site-packages/snagrecover/cli.py", line 131, in cli
   recovery()
 File "/home/chyi/.local/lib/python3.10/site-packages/snagrecover/recoveries/stm32mp1.py", line 46, in main
   run_firmware(dev, "tf-a")
 File "/home/chyi/.local/lib/python3.10/site-packages/snagrecover/firmware/firmware.py", line 102, in run_firmware
   stm32mp1_run(port, fw_name, fw_blob)
 File "/home/chyi/.local/lib/python3.10/site-packages/snagrecover/firmware/firmware.py", line 51, in stm32mp1_run
   dfu_cmd.download_and_run(fw_blob, partid, offset=0, size=len(fw_blob))
 File "/home/chyi/.local/lib/python3.10/site-packages/snagrecover/protocols/dfu.py", line 140, in download_and_run
   state = self.get_status()
 File "/home/chyi/.local/lib/python3.10/site-packages/snagrecover/protocols/dfu.py", line 105, in get_status
   status = self.dev.ctrl_transfer(0xa1, 3, wValue=0, wIndex=0, data_or_wLength=6)# DFU_GETSTATUS
 File "/home/chyi/.local/lib/python3.10/site-packages/usb/core.py", line 1082, in ctrl_transfer
   ret = self._ctx.backend.ctrl_transfer(
 File "/home/chyi/.local/lib/python3.10/site-packages/usb/backend/libusb1.py", line 893, in ctrl_transfer
   ret = _check(self.lib.libusb_control_transfer(
 File "/home/chyi/.local/lib/python3.10/site-packages/usb/backend/libusb1.py", line 602, in _check
   raise USBTimeoutError(_strerror(ret), ret, _libusb_errno[ret])
usb.core.USBTimeoutError: [Errno 110] Operation timed out

<Target board>
STM32MP> NOTICE:  CPU: STM32MP157CAC Rev.B
NOTICE:  Model: STMicroelectronics STM32MP157C-DK2 Discovery Board
NOTICE:  Board: MB1272 Var2.0 Rev.C-01
ERROR:   Boot interface 6 not supported
PANIC at PC : 0x2ffeb577
  => Target board(minicom console)에서는 위와 같은 에러가 보인다.

인터넷을 뒤져 보니, 아무래도 tf-a 파일에 문제가 있는 듯 하다. (뭐가 문제인지 정확히 알 수가 없으니) 이번에는 buildroot 대신 yocto build 결과물을 활용해 보도록 하자. 편의상 DK2 board에 대한 Yocto project build 절차는 생략하였으나, 필요 시 아래 link 3장을 참조하기 바란다.

$ vi stm32mp1-stm32mp157c-dk2.yaml
tf-a:
 path: /mnt/hdd/workspace/bootlin/stm32mp1/DP/build-openstlinuxweston-stm32mp1/tmp-glibc/deploy/images/stm32mp1/arm-trusted-firmware/tf-a-stm32mp157c-dk2-usb.stm32
fip:
 path: /mnt/hdd/workspace/bootlin/stm32mp1/DP/build-openstlinuxweston-stm32mp1/tmp-glibc/deploy/images/stm32mp1/fip/fip-stm32mp157c-dk2-optee-sdcard.bin
~

위와 같이 수정 후, 다시 실행해 보니, OK 이번에는 정상 동작한다.

$ snagrecover -s stm32mp15 -f ./stm32mp1-stm32mp157c-dk2.yaml

[그림 9.8] snagrecover 명령 실행 모습

아래 내용은 target board(minicom)에서 동시에 capture한 내용이다. 얘도 정상 message가 보인다.

[그림 9.9] snagrecover 명령 실행시, target board 모습(console)

이 상태에서 snagflash 명령을 돌려 보자.

$ snagflash -P dfu -p 0483:df11 -D 0:/mnt/hdd/w
orkspace/bootlin/snagboot/src/snagrecover/templates/binaries2/fip-stm32mp157c-dk2-optee-sdcard.bin


[그림 9.10] snagflash 명령 실행 모습

OK, 역시 정상 동작한다. target board(minicom)의 내용도 정상처럼 보인다. 

[그림 9.11] snagflash 명령 실행시, target board 모습(console)

이후 DIP switch를 원복 후 부팅하면 정상적으로 부팅이 된다. 😎
📌 아직까지는 bootloader(tf-a, fip)만 flashing하는 수준인 듯 하다. kernel까지 교체할 수 있다면 좋으련만 ...
 
뭔가 좀 아쉬움이 남지만, (추가 분석은) 후일을 기약하며, 다음장으로 넘어가도록 하자(아무래도 코드를 좀 들여다 볼 필요가 있어 보인다). 😓



10. GDB/OpenOCD/ST-LINK로 Flash Writing & Debugging 하기
이번 장에서는 GDB와 OpenOCD 및 ST-Link를 이용하여 target board에 image를 올리는 방법과 debugging하는 방법을 소개해 보고자 한다.

1) OpenOCD와 JTAG 이야기
JTAG 기반의 debugging 환경을 위해서는 target board에 JTAG 혹은 SWD pin이 나와 있어야 하고, 동시에 JTAG adapter(예: ST-Link, J-Link)가 준비되어 있어야 한다. 그리고 마지막으로 adapter와 통신하는 s/w(예: OpenOCD)가 갖추어져 있어야 가능하다.

[그림 10.1] GDB/OpenOCD/ST-Link로 DK2 보드 debugging하기

JTAG(Joint Test Action Group) vs SWD(Serial Wire Debug)
  • JTAG은 5개의 pin이 필요함.
  • TDI (Test Data In): Serial input pin
  • TDO (Test Data Out): Serial output pin
  • TCK (Test Clock): Clock pin, usually with a 100k pull-down resistor
  • TMS (Test Mode Select): Mode select (control signal) pin
  • TRST (Test Reset): Reset pin
  • SWD는 2개의 pin이 필요함.
  • SWDIO (Serial Wire Data Input Output): Serial data input/output pin
  • SWCLK (Serial Wire Clock): Serial wire clock pin
📌 실제 사용 시에는 (당연히) 전원 pin, Ground pin이 추가로 필요하다.

SWD는 ARM 전용으로 개발된 반면, JTAG은 multiple microcontroller/processor architecture용으로 보면 된다. SWD와 JTAG 모두 flashing(programming, f/w downloading)과 debugging이 가능하다.


2) Kernel Debugging 하기
DK2 보드를 대상으로 flash writing 테스트를 해보고 싶지만, (이것 저것 시도해 보았으나) 생각처럼 잘 안된다. 😓 따라서 우선 openocd & gdb 기반으로 debugging 하는 방법을 먼저 정리해 본다.

buildroot 결과물에는 openocd와 gdb 등이 포함되어 있으니, 이를 이용하도록 하자.
$ cd buildroot/output/host/bin
$ ./openocd -s /mnt/hdd/workspace/bootlin/buildroot/output/host/sh
are/openocd/scripts -f board/
stm32mp15x_dk2.cfg

[그림 10.2] openocd 구동 모습

그림 10.1을 보면 알 수 있듯이, openocd는 telnet server 역할도 한다. 따라서 4444번 port로 접속을 시도한 후, board reset, flash writing 등의 다양한 명령을 사용할 수가 있다.

$ telnet localhost 4444

[그림 10.3] telnet으로 targetboard 제어 모습
📌 target board의 flash writing을 위해서는 telnet 연결 후, 적절한 명령을 주어야 하는데, DK2 board에 맞는 명령을 못 찾겠다.

이어서 linux kernel elf 파일인 vmlinux을 이용하여 gdb debugging을 시도해 보자.

$ gdb-multiarch ./vmlinux
(gdb) target remote :3333     #3333 port에 연결해 주어야 한다.
(gdb) bt
(gdb) hbreak <func_name>
(gdb) c
(gdb) ...
[그림 10.4] gdb로 kernel debugging 하기
📌 gdb-multiarch 대신 buildroot 결과물인 output/host/bin/arm-linux-gdb 파일을 사용해도 된다.

3) STM32 board 기반 GDB/OpenOCD debugging 하기
(이대로 넘어갈 수가 없어) DK2 보드를 제어하는데 문제가 있어, 그 보다는 훨씬 수월한 STM32 board를 이용하여 GDB/OpenOCD issue를 좀 더 파헤쳐 보도록 하자. 오랫동안 잠자고 있던, STM32 board(STM32F767ZI)를 서랍에서 다시 꺼냈다. 💤

[그림 10.5] STM32F767ZI board

STM32 board에는 대부분 ST-Link가 장착되어 있어, programming & debugging 하기에 편리하다.

[그림 10.6] GDB/OpenOCD/ST-Link로 STM32F767ZI board debugging 하기

이번 posting의 주제하고는 다소 거리가 있지만, Zephyr RTOS 환경을 준비하고, 간단한 example 코드를 target board에 올려 보도록 한다.


Zephyr project에 관해서는 일전에 두차례 소개한 적이 있으니, 이전 post의 내용도 함께 참조해 주기 바란다.

왜 갑자기 Zephyr(제퍼) 냐고 ? Zephyr에는 이미 openocd 기반으로 flash writing & debugging 하는 환경이 잘 갖추어져 있기 때문이다. 따라서 이를 참조하면 앞서 문제가 되었던 issue를 해결할 수가 있을 것으로 기대된다.

먼저, west(zephyr의 swiss army knife)로 example code(식사하는 철학자들) 하나를 build 한다.

$ west build -p always -b nucleo_f767zi samples/philosophers
chyi@earth:~/zephyrproject/zephyr/build/zephyr$ ls -la
-rwxrwxr-x  1 chyi chyi   18860  7월 12 16:23 zephyr.bin
-rwxrwxr-x  1 chyi chyi  514092  7월 12 16:23 zephyr.elf
-rw-rw-r--  1 chyi chyi   53150  7월 12 16:23 zephyr.hex
...

다음으로 flash option을 주어, target board에 firmware 이미지를 올린다.
west flash

[그림 10.7] STM32F767ZI board에서 example code 실행 모습

이어서, attach option을 주어, 현재 동작 중인 program에 gdb로 attach한다.

$ west attach

[그림 10.8] STM32F767ZI board에서 running 중인 program debugging 하기

자, 준비작업은 모두 끝났다. 이제 부터는 west -v flash 및 west -v attach 명령 결과를 참조하여, 아래와 같이 openocd, telnet, gdb 명령을 (west를 사용하지 않고) 직접 실행해 보도록 하자.

<flash writing 하기>
$ /home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/bin/openocd -s /home/chyi/zephyrproject/zephyr/boards/st/nucleo_f767zi/support -s /home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts -f /home/chyi/zephyrproject/zephyr/boards/st/nucleo_f767zi/support/openocd.cfg '-c init' '-c targets' -c 'reset init'

[그림 10.9] OpenOCD 명령 실행 모습

이 상태에서 telnet으로 4444번 port에 접속을 하면, openocd를 통해 ST-Link로 flash write 명령을 날릴 수가 있다. 당연히 flash writing이 잘된다.

telnet localhost 4444
> reset init
> flash write_image erase /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.hex
> reset run

[그림 10.10] telnet 접속 후, flash write하는 모습

이번에는 gdb/openocd로 debugging을 할 차례이다. 먼저, (약간은 다른 option을 주어) openocd를 다시 실행하자.

<debugging 하기>
/home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/bin/openocd -s /home/chyi/zephyrproject/zephyr/boards/st/nucleo_f767zi/support -s /home/chyi/zephyr-sdk-0.16.8/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts -f /home/chyi/zephyrproject/zephyr/boards/st/nucleo_f767zi/support/openocd.cfg -c 'tcl_port 6333' -c 'telnet_port 4444' -c 'gdb_port 3333' '-c init' '-c targets' '-c halt'
📌 자세한 option은 west -v attach 명령을 시도해 보면 알 수가 있다.

이어서, (다른 창에서) gdb 명령을 실행해 보자. gdb를 실행할 때는 반드시 실행 파일(여기서는 zephyr.elf)을 argument로 지정해 주어야 한다.

/home/chyi/zephyr-sdk-0.16.8/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb -ex 'target extended-remote :3333' /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.elf

(gdb) bt
(gdb) hbreak <func_name>
(gdb) c
(gdb) ...
[그림 10.11] gdb로 debugging하는 모습

gdbgui를 이용하면 web browser 환경에서 source code도 trace할 수가 있다.

[그림 10.12] gdb gui banner [출처 - https://github.com/cs01/gdbgui]


chyi@earth:~/zephyrproject/zephyr$ ~/.local/bin/gdbgui -g "/home/chyi/zephyr-sdk-0.16.8/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb -ex 'target extended-remote :3333' /home/chyi/zephyrproject/zephyr/build/zephyr/zephyr.elf"

[그림 10.13] gdbgui로 debugging하는 모습 - source code trace
📌 Visual studio를 통해 debugging을 할 수도 있다.

____________________________________
이상으로 GDB/OpenOCD/ST-Link를 기반으로 programming(flash writing) 및 debugging하는 방법을 간략히 살펴 보았다. 내용을 보면서 느꼈겠지만, STM32에 올라가는 간단한 program은 GDB/OpenOCD/ST-Link 기반으로 debugging 하기에 무리가 없다. 문제는 Cortex-A series에 올라가는 Linux kernel 등을 debugging하는 것이 생각 처럼 간단하지 않다는데 있다. 특히 Trusted Firmware 개념이 포함된 환경에서는 더욱더 힘들 수 밖에 없다. 😅
아무튼 이번 장에서는, 고가의 debugging tool을 사용하지 않고, 최소한의 환경에서 최대한의 효과를 끌어내 보기 위한 방법을 (부족하지만) 정리해 보았다.



11. KGDB를 이용하여 Linux Kernel Debugging 하기
이번 장에서는 ST-Link나 JTAG adatper 없이 Linux kernel에서 제공하는 KGDB를 이용하여 kernel을 debugging하는 방법을 소개해 보고자 한다. 사실 이 내용은 아주 새로운 주제는 아니다. 하지만 직접 해 보는 것과 글로 읽어 보는 것과는 크게 차이가 있으니, 직접 확인해 보도록 하자.

<JTAG 기반의 debugging의 조건>
  • 보드에 JTAG pin이 나와 있어야 한다.
  • JTAG adapter가 갖추어져 있어야 한다.
  • JTAG adapter와 target board를 연결하기 위해 납땜이 필요할 수도 있다.
  • OpenOCD 같은 s/w가 필요하다.

JTAG 기반의 debugging은 절차가 복잡하고 어렵다. Linux kenel에는 이미 debugging을 위해 KGDB라는 GDB server(kernel)가 갖추어져 있다.

[그림 11.1] Linux kernel debugging 개요도 [출처 - 참고문헌 7]

<Kernel debugging 절차>
1) KGDB를 지원하도록 kernel config 수정 후 build
2) 부팅 후, KGDB 시작(trigger)
3) GDB로 KGDB에 연결하여 debugging 시작

1) Kernel 설정 변경하기
먼저, KGDB를 사용하기 위한 설정을 진행하도록 한다.

$ make linux-menuconfig

CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
CONFIG_MAGIC_SYSRQ = y
CONFIG_DEBUG_INFO = y
CONFIG_FRAME_POINTER = y

[그림 11.2] kernel debug option 켜기(1)

[그림 11.3] kernel debug option 켜기(2)

[그림 11.4] kernel debug option 켜기(3)

$ make linux-update-defconfig
$ make
...

chyi@earth:/mnt/hdd/workspace/bootlin/buildroot/output/build/linux-5.13file vmlinux
vmlinux: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, BuildID[sha1]=cbab8842afe4fd1bc13f2aa3d0464b68
96c9ea27,
with debug_info, not stripped

sdcard image 파일을 microSD card에 복사한다.
chyi@earth:/mnt/hdd/workspace/bootlin/buildroot/output/images$ sudo dd if=./sdcard.img of=/dev/sdb
250744+0 레코드 들어옴
250744+0 레코드 나감
128380928 bytes (128 MB, 122 MiB) copied, 28.2438 s, 4.5 MB/s

kernel 재 부팅 후, 아래 명령을 통해 KGDB가 제대로 enable되어 있는지 확인한다.

# zcat /proc/config.gz | grep KGDB
# CONFIG_SERIAL_KGDB_NMI is not set
CONFIG_HAVE_ARCH_KGDB=y
CONFIG_KGDB=y
CONFIG_KGDB_HONOUR_BLOCKLIST=y
CONFIG_KGDB_SERIAL_CONSOLE=y
# CONFIG_KGDB_TESTS is not set
CONFIG_KGDB_KDB=y


2) 부팅 후, KGDB 시작하기
command line 설정 option을 추가 후, booting을 한다.

# cat /boot/extlinux/extlinux.conf  
label stm32mp157c-dk2-buildroot
 kernel /boot/zImage
 devicetree /boot/stm32mp157c-dk2.dtb
 append root=/dev/mmcblk0p4 rootwait
kgdboc=ttySTM0,115200 kgdbwait


echo g > /proc/sysrq-trigger
[   34.435465] sysrq: HELP : loglevel(0-9) reboot(b) crash(c) terminate-all-tasks(e) memory-full-oom-kill(f) kill-
all-tasks(i) thaw-filesystems(j) sak(k) show-backtrace-all-active-cpus(l) show-memory-usage(m) nice-all-RT-tasks(n
) poweroff(o) show-registers(p) show-all-timers(q) unraw(r) sync(s) show-task-states(t) unmount(u) force-fb(v) sho
w-blocked-tasks(w)


3) GDB로 Debugging 하기
gdb를 tui mode로 실행한다.

chyi@earth:/mnt/hdd/workspace/bootlin/buildroot/output/build/linux-5.13$ /mnt/hdd/workspace/bootlin/buildroot/output/host/bin/arm-
linux-gdb ./vmlinux -tui


(gdb) set serial baud 115200
(gdb) target remote /dev/ttyACM0


[그림 11.5] gdb로 kgdb에 연결하는 모습


To be continued...



12. References
    => GDB, OpenOCD debugging

[14] Discovery kits with STM32MP157 MPUs - User Manual
   => ST Micro sites for OpenSTLinux

[16] petazzoni=device-tree-101.pdf
[18] Linux_5.4_STM32MP1_practical_labs.pdf, Alberto Liberal de los Rios
[19] Mastering Embedded Linux Programming, 3rd edition, Frank Vasquez and Chris Simmonds
[20] Linux Device Driver Development, 2nd edition, John Madieu
[21] Linux Kernel Debugging, Kaiwan N Billimoria
   => bootlin documents and some good books

[24] And, Google~


Slowboot

댓글 없음:

댓글 쓰기