Linux/Linux Kernel
리눅스 커널 소스의 구조 및 objdump 바이너리 유틸리티
Study with Me!
2025. 4. 21. 17:15
리눅스 커널 소스의 구조
- arch : 아키텍처별로 동작하는 커널 코드
- arm : 32bit 계열 ARM 아키텍처 코드가 있으며, 라즈비안도 이 하부 디렉터리 코드를 실행함
- arm64 : 64bit 계열 ARM 아키텍처 코드가 있음
- x86 : 폴더 이름과 같이 인텔 x86 아키텍처 코드가 있음
- include : 커널 코드 빌드에 필요한 헤더파일이 있음
- Documentation : 커널 기술 문서가 있는 디렉터리로, 커널 시스템에 대한 기본 동작을 설명하는 문서를 찾을 수 있음
- kernel : 커널의 핵심 코드가 있는 디렉터리, 아키텍처와 무관한 커널 공통 코드가 있음
아키텍처별로 동작하는 메모리 관리 코드는 arch/*/mm/ 아래에 있음- irq : 인터럽트 관련 코드
- sched : 스케줄링 코드
- power : 커널 파워 매니지먼트 코드
- locking : 커널 동기화 관련 코드
- printk : 커널 콘솔 관련 코드
- trace : ftrace 관련 코드
- drivers : 모든 시스템의 디바이스 드라이버 코드가 있음, 하부 디렉터리에 드라이버 종류별 소스가 있음
- fs : 모든 파일 시스템 코드가 있음
fs 디렉터리의 파일에는 파일 시스템 공통 함수가 있고, 파일 시스템별로 하나씩 세분화된 디렉터리를 볼 수 있음 - lib : 커널에서 제공하는 라이브러리 코드가 있음
아키텍처에 종속적인 라이브러리 코드는 arch/*/lib/ 에 있음
objdump 바이너리 유틸리티
아래는 대표적인 바이너리 유틸리티 정리 표이다.
유틸리티 | 역할 |
objdump | 라이브러리나 ELF 형식의 파일을 어셈블리어로 출력 |
as | 어셈블러 |
ld | 링커 |
addr2line | 주소를 파일과 라인으로 출력 |
nm | 오브젝트 파일의 심벌을 출력 |
readelf | ELF 파일의 내용을 출력 |
이제 objdump 유틸리티를 사용해보자.
오브젝트 파일로는 리눅스 커널을 빌드하면 생성되는 vmlinux를 활용한다.
objdump -x 옵션(파일 헤더 및 섹션 정보 출력)을 사용해보자.
objdump -x vmlinux | more
명령어를 수행하면 아래와 같은 내용을 확인할 수 있다.
vmlinux: file format elf64-x86-64
vmlinux
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0000000001000080
Program Header:
LOAD off 0x0000000000200000 vaddr 0xffffffff81000000 paddr 0x0000000001000000 align 2**21
filesz 0x0000000001b786bc memsz 0x0000000001b786bc flags r-x
LOAD off 0x0000000001e00000 vaddr 0xffffffff82c00000 paddr 0x0000000002c00000 align 2**21
filesz 0x0000000000477000 memsz 0x0000000000477000 flags rw-
...
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 01400000 ffffffff81000000 0000000001000000 00200000 2**12
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 006f5c06 ffffffff82400000 0000000002400000 01600000 2**12
CONTENTS, ALLOC, LOAD, DATA
2 .pci_fixup 00003bb0 ffffffff82af5c10 0000000002af5c10 01cf5c10 2**4
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .tracedata 00000078 ffffffff82af97c0 0000000002af97c0 01cf97c0 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
...
내용을 살펴보면 아키텍처 이름(i386:x86-64)과 스타트업 코드의 위치(0x0000000001000080)를 확인할 수 있다.
(이 값들은 시스템에 따라 다를 수 있다.)
스타트업 코드는 이미지가 처음 실행될 때 동작하며, 어셈블리 코드로 구성되어 있다고 한다.
이번에는 objdump -d 옵션을 사용해서 vmlinux에서 어셈블리 코드를 출력해보자.
objdump -d vmlinux | more
명령어를 수행하면 아래와 같은 내용을 확인할 수 있다.
vmlinux: file format elf64-x86-64
Disassembly of section .text:
ffffffff81000000 <_stext>:
ffffffff81000000: fc cld
ffffffff81000001: 0f 01 15 80 24 19 03 lgdt 0x3192480(%rip) # ffffffff84192488 <__init_scratch_end+0x192488>
ffffffff81000008: b8 10 00 00 00 mov $0x10,%eax
ffffffff8100000d: 8e d8 mov %eax,%ds
그런데 이렇게 하면 너무 많은 어셈블리 코드가 출력되어 보기가 어렵다.
System.map 파일을 사용해 옵션을 지정해서 특정 함수의 어셈블리 코드를 보는 방법을 알아보자.
System.map에서는 심벌 별로 주소를 확인할 수 있다.
리눅스 커널의 schedule() 함수의 주소가 0xffffffff821d0ee0~0xffffffff821d0ff0 임을 확인했다.
이제, 이 값을 사용해 시작 주소와 끝 주소를 지정한 objdump -d 명령을 수행해보자.
objdump --start-address=0xffffffff821d0ee0 --stop-address=0xffffffff821d0ff0 -d vmlinux
vmlinux: file format elf64-x86-64
Disassembly of section .text:
ffffffff821d0ee0 <schedule>:
ffffffff821d0ee0: e8 3b af ed fe call ffffffff810abe20 <__fentry__>
ffffffff821d0ee5: 55 push %rbp
ffffffff821d0ee6: 48 89 e5 mov %rsp,%rbp
ffffffff821d0ee9: 41 54 push %r12
ffffffff821d0eeb: 53 push %rbx
ffffffff821d0eec: 65 48 8b 1c 25 40 43 mov %gs:0x34340,%rbx
ffffffff821d0ef3: 03 00
ffffffff821d0ef5: 8b 43 18 mov 0x18(%rbx),%eax
...
ffffffff821d0fdb: e8 70 dc 70 ff call ffffffff818dec50 <__ubsan_handle_load_invalid_value>
ffffffff821d0fe0: eb d1 jmp ffffffff821d0fb3 <schedule+0xd3>
ffffffff821d0fe2: 66 66 2e 0f 1f 84 00 data16 cs nopw 0x0(%rax,%rax,1)
ffffffff821d0fe9: 00 00 00 00
ffffffff821d0fed: 0f 1f 00 nopl (%rax)