Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

493
557
422263


[라즈베리파이] 라즈비안: objdump 바이너리 유틸리티 2. 라즈베리 파이 설정

바이너리 유틸리티는 오브젝트 포맷의 파일을 조작할 수 있는 프로그램입니다. 다음은 대표적인 바이너리 유틸리티를 정리한 표입니다.

objdump: 라이브러리나 ELF(Executable and Linkable Format) 형식의 파일을 어셈블리어로 출력
as: 어셈블러
ld: 링커
addr2line: 주소를 파일과 라인으로 출력
nm: 오브젝트 파일의 심벌을 출력
readelf ELF 파일의 내용을 출력

이 중에서 리눅스 커널 어셈블리 코드와 섹션 정보를 볼 수 있는 objdump라는 바이너리 유틸리티 사용법을 소개합니다. 오브젝트 파일로는 리눅스 커널을 빌드하면 생성되는 vmlinux를 활용합니다.

다음 명령어를 입력하면 objdump를 실행할 때 지정 가능한 옵션 정보를 확인할 수 있습니다.

root@raspberrypi:/home/pi/kernel_obj# objdump
Usage: objdump <option(s)> <file(s)>
 Display information from object <file(s)>.
 At least one of the following switches must be given:
  -a, --archive-headers    Display archive header information
  -f, --file-headers       Display the contents of the overall file header
  -p, --private-headers    Display object format specific file header contents
  -P, --private=OPT,OPT... Display object format specific contents
  -h, --[section-]headers  Display the contents of the section headers
  -x, --all-headers        Display the contents of all headers
  -d, --disassemble        Display assembler contents of executable sections
  -D, --disassemble-all    Display assembler contents of all sections
  -S, --source             Intermix source code with disassembly
  -s, --full-contents      Display the full contents of all sections requested


라즈비안에서는 기본적으로 바이너리 유틸리티를 사용할 수 있어서 바이너리 유틸리티를 따로 설치할 필요가 없습니다.


먼저 /home/pi/kernel_obj라는 디렉터리를 만들고 리눅스 커널 이미지 생성 폴더에 있는 vmlinux 파일을 복사합니다.
root@raspberrypi:/home/pi# mkdir kernel_obj
root@raspberrypi:/home/pi# cd kernel_obj/
root@raspberrypi:/home/pi/kernel_obj# cp ../rpi_kernel_src/out/vmlinux  .

objdump -x vmlinux 명령어를 입력해 vmlinux의 헤더 정보를 확인합니다.

01 root@raspberrypi:/home/pi/kernel_obj# objdump -x vmlinux | more
02 vmlinux:     file format elf32-littlearm
03 vmlinux
04 architecture: arm, flags 0x00000112:
05 EXEC_P, HAS_SYMS, D_PAGED
06 start address 0x80008000
07 
08 Program Header:
09    LOAD off    0x00000000 vaddr 0x80000000 paddr 0x80000000 align 2**16
10         filesz 0x0000826c memsz 0x0000826c flags r-x
11    LOAD off    0x00010000 vaddr 0x80100000 paddr 0x80100000 align 2**16
12         filesz 0x006a873c memsz 0x006a873c flags r-x
...
13 Sections:
14 Idx Name          Size      VMA       LMA       File off  Algn
15  0 .head.text    0000026c  80008000  80008000  00008000  2**2
16                  CONTENTS, ALLOC, LOAD, READONLY, CODE
17  1 .text         006a8720  80100000  80100000  00010000  2**6
18                  CONTENTS, ALLOC, LOAD, READONLY, CODE
19  2 .fixup        0000001c  807a8720  807a8720  006b8720  2**2
20                  CONTENTS, ALLOC, LOAD, READONLY, CODE
21  3 .rodata       001d0890  80800000  80800000  006c0000  2**12
22                  CONTENTS, ALLOC, LOAD, DATA
23  4 __ksymtab     00007f58  809d0890  809d0890  00890890  2**2
24                  CONTENTS, ALLOC, LOAD, READONLY, DATA  
 
vmlinux 헤더 출력 내용을 좀 더 자세히 살펴보겠습니다. 

04~06번째 줄에서는 아키텍처 이름과 스타트업 코드의 위치를 표시합니다.

04 architecture: arm, flags 0x00000112:
05 EXEC_P, HAS_SYMS, D_PAGED
06 start address 0x80008000

보다시피 스타트업 코드의 주소는 0x80008000입니다.


스타트업 코드는 이미지가 처음 실행될 때 동작하며, 어셈블리 코드로 구성돼 있습니다. 보통 시스템 초기 설정을 수행하고 arm 모드별로 스택 주소를 설정합니다.


13~24번째 줄은 섹션 정보입니다.
13 Sections:
14 Idx Name          Size      VMA       LMA       File off  Algn
15  0 .head.text    0000026c  80008000  80008000  00008000  2**2
16                  CONTENTS, ALLOC, LOAD, READONLY, CODE
17  1 .text         006a8720  80100000  80100000  00010000  2**6
18                  CONTENTS, ALLOC, LOAD, READONLY, CODE

objdump -d vmlinux 명령어를 입력하면 vmlinux에서 어셈블리 코드를 출력할 수 있습니다.

root@raspberrypi:/home/pi/kernel_obj# objdump -d vmlinux
vmlinux:     file format elf32-littlearm
80008000 <stext>:
80008000: eb0430de  bl 80114380 <__hyp_stub_install>
80008004: e10f9000  mrs r9, CPSR
80008008: e229901a  eor r9, r9, #26
8000800c: e319001f  tst r9, #31
80008010: e3c9901f  bic r9, r9, #31
80008014: e38990d3  orr r9, r9, #211 ; 0xd3

그런데 너무 많은 어셈블리 코드가 출력돼 어셈블리 코드를 보기 어렵습니다. 그래서 이번에는 옵션을 지정해서 특정 함수 어셈블리 코드를 보는 방법을 소개하겠습니다.

먼저 커널 이미지를 빌드하면 함께 생성되는 System.map 파일을 열어 보겠습니다.

root@raspberrypi:/home/pi# mkdir kernel_obj
root@raspberrypi:/home/pi# cd kernel_obj/
root@raspberrypi:/home/pi/kernel_obj# cp ../rpi_kernel_src/out/System.map  .

System.map 파일을 열어보면 다음과 같이 심벌별 주소를 확인할 수 있습니다.

01 80004000 A swapper_pg_dir
02 80008000 T _text
03 80008000 T stext
04 8000808c t __create_page_tables
05 80008138 t __turn_mmu_on_loc
...
06 807a0208 t __schedule
07 807a0b6c T schedule
08 807a0c14 T yield_to

함수 목록 중에서 리눅스 커널에서 가장 유명한 schedule() 함수의 주소 범위가 0x807a0b6c ~ 0x807a0c14임을 유추할 수 있습니다. 참고로 주소 출력 결과는 16진수 형식입니다.

이번에는 다음과 같은 형식으로 시작 주소와 끝 주소를 지정하면 해당 주소에 대한 어셈블리 코드를 출력합니다.

objdump --start-address=[시작주소] --stop-address=[끝주소] -d vmlinux

라즈베리 파이에서 다음 명령어를 입력하면 schedule() 함수의 어셈블리 코드만 볼 수 있습니다. 

root@raspberrypi:/home/pi/kernel_obj# objdump --start-address=0x807a0b6c --stop-address=0x807a0c14 -d vmlinux 
vmlinux:     file format elf32-littlearm


Disassembly of section .text:

807a0b6c <schedule>:
807a0b6c: e1a0c00d  mov ip, sp
807a0b70: e92dd830  push {r4, r5, fp, ip, lr, pc}
807a0b74: e24cb004  sub fp, ip, #4
807a0b78: e52de004  push {lr} ; (str lr, [sp, #-4]!)
807a0b7c: ebe5b8e2  bl 8010ef0c <__gnu_mcount_nc>  
 
이번 절에서 소개한 방법을 활용하면 커널을 빌드한 후 생성되는 vmlinux 파일로 커널 어셈블리 코드를 분석할 수 있습니다. 참고로 이 책의 다음 장에서는 이번 장에서 소개한 방식으로 어셈블리 코드를 확인하고 다음과 같은 요소를 분석합니다. 

인터럽트
커널 스케줄링
시스템 콜

* 유튜브 강의 동영상도 있으니 같이 들으시면 더 많은 걸 배울 수 있습니다. 




라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)



# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2


 


핑백

덧글

  • 박진혁 2021/02/22 15:40 # 삭제 답글

    좋은 글 감사합니다! objdump 사용법을 찾고 있었는데 정리가 잘 되어 있네요
  • AustinKim 2021/02/25 00:18 #

    감사합니다. 자주 오셔서 유용한 정보를 많이 얻으셨으면 좋겠습니다.
  • 따뜻한 순록 2021/06/06 02:29 # 답글

    When used objdump from host system(x86_64), I could notice architecture as UNKNOWN
    out$ objdump -x vmlinux | more

    vmlinux: file format elf32-little
    vmlinux
    architecture: UNKNOWN!, flags 0x00000112:
    EXEC_P, HAS_SYMS, D_PAGED
    start address 0xc0008000

    So used objdump provided by toolchain of the target system (ARM)
    out$ ./arm-linux-androideabi-objdump -x vmlinux | more
    vmlinux: file format elf32-littlearm
    vmlinux
    architecture: arm, flags 0x00000112:
    EXEC_P, HAS_SYMS, D_PAGED
    start address 0xc0008000

    Thanks
  • AustinKim 2021/06/06 11:08 #

    Thanks for information.

    If vmlinux is generated with arm-gcc compiler, the *arm*-binary utility should be used rather than "x86_64"-binary utility.

    When you use objdump with raspberry-pi, you can use arm-objdump utility.
    Since objdump is provided with armv7(32bit).
댓글 입력 영역