-
[개발 도구] objdumpLinux/development tool 2023. 6. 10. 02:21
글의 참고
글의 전제
- 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다. 나도 모르기 때문에 나중에 알아봐야 할 내용이라는 뜻이다.
- 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다. 그러므로, 밑 줄 처친 글이 이해가 안간다면 링크를 따라서 관련 내용을 공부하자.
- `글의 참조`에서 빨간색 볼드체로 체크된 링크는 이 글을 작성하면 가장 많이 참조한 링크다.
- `운영체제 만들기` 파트에서 퍼온 모든 참조 글들과 그림은 반드시 `이 글과 그림을 소스 코드로 어떻게 구현을 해야할까` 라는 생각으로 정말 심도있게 잠시 멈춰서 생각해봐야 실력이 발전한다.
글의 내용
- 개요
" 리눅스 환경에서 OS 개발시에 디버깅을 위해서 어셈블리 파일을 ELF로 빌드하기 위해 DWARF 정보를 삽입한다. 이 DWARF는 ELF 파일을 디버깅 정보의 표준 포맷과 같은 스펙이다. DWARF 정보를 집어넣기 위해서는 대개는 GNU 빈유틸리티들은 `-g` 옵션을 통해 이 기능을 제공한다. 뒤에 숫자가 붙는 경우가 있는데, 그건 DWARF의 버전을 의미한다. 예를 들어, `-g3` 및 `-g4` 등이 있다.
" 이 DWARF 정보를 기반으로 objdump, readelf, nm, size 등 많은 유틸리티들이 동작한다.
" 나는 objdump를 통해서 대개 많은 일을 하지만 주로 2가지가 아주 기본이다.
1" 파일의 시작 주소 파악
2" `objdump -d`를 통해 실행 코드 파악" 대개 GDB와 함께 주로 objdump를 함께 많이 이용한다.
- 옵션
: objdump -M
" 아무 옵션없이 objdump로 역어셈블리를 하면, AT&T 문법을 보여준다. 인텔을 보고싶다면 `-M intel`을 추가하자.
$ objdump -d pbl.o
pbl.o: file format elf32-i386
Disassembly of section .text:
00000000 <_pbl_start>:
0: 66 b8 20 00 mov $0x20,%ax
4: 66 b9 10 00 mov $0x10,%cx
8: eb fe jmp 8 <_pbl_start+0x8>$ objdump -M intel -d pbl.o
pbl.o: file format elf32-i386
Disassembly of section .text:
00000000 <_pbl_start>:
0: 66 b8 20 00 mov ax,0x20
4: 66 b9 10 00 mov cx,0x10
8: eb fe jmp 8 <_pbl_start+0x8>: objdump -f
" 헤더의 내용을 보여준다. `stat address`에 집중하자. `start address`는 말 그대로 해당 파일의 시작지점을 의미한다. 아래의 `objdump -d`에서 0x00000000번지에 `<_pbl_start>` 심볼이 있는 것을 알 수 있다. 즉, pbl.o 파일의 시작은 `<_pbl_start>:0x00000000`이다.
$ objdump -f pbl.o pbl.o: file format elf32-i386 architecture: i386, flags 0x00000011: HAS_RELOC, HAS_SYMS start address 0x00000000
: objdump -d
" 텍스트 섹션만 보여준다. 이 내용을 코드의 흐름을 알 수 있다. 굉장히 자주 쓰인다. 4바이트가 증가되고 있는 것을 알 수 있다.
$ objdump -d pbl.o pbl.o: file format elf32-i386 Disassembly of section .text: 00000000 <_pbl_start>: 0: 66 b8 20 00 mov $0x20,%ax 4: 66 b9 10 00 mov $0x10,%cx 8: eb fe jmp 8 <_pbl_start+0x8>
: 처음 명령어는 `mov $0x20, %ax`이다. 이 명령어의 주소는 0x00000000이다. 그리고 나서 `mov $0x10, %cx` 명령어의 주소는 0x00000004인 것을 알 수 있다.
: objdump -D
" 해당 파일의 모든 섹션들을 보여준다. 디버깅 섹션까지 모두 보여준다. 위에 `objdump -d pbl.o`과 비교해보자. 동일한 파일을 옵션만 달리한 것이다.
$ objdump -D pbl.o pbl.o: file format elf32-i386 Disassembly of section .text: 00000000 <_pbl_start>: 0: 66 b8 20 00 mov $0x20,%ax 4: 66 b9 10 00 mov $0x10,%cx 8: eb fe jmp 8 <_pbl_start+0x8> Disassembly of section .debug_aranges: 00000000 <.debug_aranges>: 0: 1c 00 sbb $0x0,%al 2: 00 00 add %al,(%eax) 4: 02 00 add (%eax),%al 6: 00 00 add %al,(%eax) 8: 00 00 add %al,(%eax) a: 04 00 add $0x0,%al ... 14: 0a 00 or (%eax),%al ... Disassembly of section .debug_info: 00000000 <.debug_info>: 0: 8c 00 mov %es,(%eax) 2: 00 00 add %al,(%eax) 4: 02 00 add (%eax),%al 6: 00 00 add %al,(%eax) 8: 00 00 add %al,(%eax) a: 04 01 add $0x1,%al c: 00 00 add %al,(%eax) e: 00 00 add %al,(%eax) 10: 0a 00 or (%eax),%al 12: 00 00 add %al,(%eax) 14: 00 00 add %al,(%eax) 16: 00 00 add %al,(%eax) 18: 70 62 jo 7c <.debug_info+0x7c> 1a: 6c insb (%dx),%es:(%edi) ... ... ...
: 각 섹션마다 시작 주소는 0부터 시작이기 때문에, 각 섹션의 시작 심볼들()이 0x00000000(<_pbl_start>, <.debug_aranges>, <.debug_info>)을 차지한다.
- 디버깅
: 예제 1
" 아래는 QEMU에서 #GP(0x0D)가 발생한 에러다. #GP는 대개 접근하면 안되는 메모리 영역에 접근할 때, 발생한다.
check_exception old: 0xffffffff new 0xd
1: v=0d e=0000 i=0 cpl=0 IP=0018:ffffffff80106843 pc=ffffffff80106843 SP=0020:000000000004ee80 env->regs[R_EAX]=6c5b5d73253a665b
RAX=6c5b5d73253a665b RBX=000000000000000d RCX=ffffffff80107090 RDX=0000000000000000
RSI=0000000000000000 RDI=6c5b5d73253a665b RBP=000000000004ee80 RSP=000000000004ee80
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=ffffffff80106843 RFL=00200002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0018 0000000000000000 ffffffff 00af9a00 DPL=0 CS64 [-R-]
SS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT= 0000000000007f4b 00000028
IDT= 000000000001b010 000007ff
CR0=80000011 CR2=0000000000000000 CR3=0000000000101000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000000 CCD=6c5b5d73253a665b CCO=EFLAGS
EFER=0000000000000500
check_exception old: 0xd new 0xd
2: v=08 e=0000 i=0 cpl=0 IP=0018:ffffffff80106843 pc=ffffffff80106843 SP=0020:000000000004ee80 env->regs[R_EAX]=6c5b5d73253a665b
RAX=6c5b5d73253a665b RBX=000000000000000d RCX=ffffffff80107090 RDX=0000000000000000
RSI=0000000000000000 RDI=6c5b5d73253a665b RBP=000000000004ee80 RSP=000000000004ee80
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=ffffffff80106843 RFL=00200002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0018 0000000000000000 ffffffff 00af9a00 DPL=0 CS64 [-R-]
SS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT= 0000000000007f4b 00000028
IDT= 000000000001b010 000007ff
CR0=80000011 CR2=0000000000000000 CR3=0000000000101000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000000 CCD=6c5b5d73253a665b CCO=EFLAGS
EFER=0000000000000500" 인터럽트 벡터는 0x0D로 `General Protecton Fault`다. 에러 코드는 e=0x0000이다. 에러 코드의 해석은 이 글을 참조하자. 중요한 건 IP 및 PC다(사실 에러 코드가 더 중요하다). 이제 `objdump`를 통해서 커널 이미지에서 `ffffffff80106843` 가 어디에 있는지 확인하면 된다.
ffffffff80106801 <strlen>:
ffffffff80106801: 55 push %rbp
ffffffff80106802: 48 89 e5 mov %rsp,%rbp
ffffffff80106805: 48 89 7d e8 mov %rdi,-0x18(%rbp)
ffffffff80106809: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
ffffffff80106810: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
ffffffff80106817: 48 83 7d e8 00 cmpq $0x0,-0x18(%rbp)
ffffffff8010681c: 75 07 jne ffffffff80106825 <strlen+0x24>
ffffffff8010681e: b8 00 00 00 00 mov $0x0,%eax
ffffffff80106823: eb 38 jmp ffffffff8010685d <strlen+0x5c>
ffffffff80106825: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
ffffffff8010682c: eb 08 jmp ffffffff80106836 <strlen+0x35>
ffffffff8010682e: 83 45 fc 01 addl $0x1,-0x4(%rbp)
ffffffff80106832: 83 45 f8 01 addl $0x1,-0x8(%rbp)
ffffffff80106836: 8b 45 fc mov -0x4(%rbp),%eax
ffffffff80106839: 48 63 d0 movslq %eax,%rdx
ffffffff8010683c: 48 8b 45 e8 mov -0x18(%rbp),%rax
ffffffff80106840: 48 01 d0 add %rdx,%rax
ffffffff80106843: 0f b6 00 movzbl (%rax),%eax
ffffffff80106846: 84 c0 test %al,%al
ffffffff80106848: 75 e4 jne ffffffff8010682e <strlen+0x2d>
ffffffff8010684a: 81 7d fc ff ff ff 7f cmpl $0x7fffffff,-0x4(%rbp)
ffffffff80106851: 74 05 je ffffffff80106858 <strlen+0x57>
ffffffff80106853: 8b 45 fc mov -0x4(%rbp),%eax
ffffffff80106856: eb 05 jmp ffffffff8010685d <strlen+0x5c>
ffffffff80106858: b8 00 00 00 00 mov $0x0,%eax
ffffffff8010685d: 5d pop %rbp
ffffffff8010685e: c3 retq"`strlen` 함수에서 발생한 에러다. 이제 저 함수에 브레이킹 포인트를 걸고 에러가 발생하기를 기다리면 된다.
: 예제 2
" 아래의 에러는 왜 발생했을까?
check_exception old: 0xffffffff new 0xe
1: v=0e e=0002 i=0 cpl=0 IP=0018:ffffffff80106019 pc=ffffffff80106019 SP=0020:0000000000600000 CR2=00000000005ffff8
RAX=0000000000000020 RBX=000000000000000d RCX=00000000c0000080 RDX=0000000000000000
RSI=000000000000000a RDI=0000000000000000 RBP=0000000000600000 RSP=0000000000600000
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=ffffffff80106019 RFL=00200086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0018 0000000000000000 ffffffff 00af9a00 DPL=0 CS64 [-R-]
SS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT= 0000000000007f4b 00000028
IDT= 000000000001b010 000007ff
CR0=80000011 CR2=00000000005ffff8 CR3=0000000000101000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000010 CCD=ffffffff80000011 CCO=LOGICL
EFER=0000000000000500
check_exception old: 0xe new 0xd
2: v=08 e=0000 i=0 cpl=0 IP=0018:ffffffff80106019 pc=ffffffff80106019 SP=0020:0000000000600000 env->regs[R_EAX]=0000000000000020
RAX=0000000000000020 RBX=000000000000000d RCX=00000000c0000080 RDX=0000000000000000
RSI=000000000000000a RDI=0000000000000000 RBP=0000000000600000 RSP=0000000000600000
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=ffffffff80106019 RFL=00200086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0018 0000000000000000 ffffffff 00af9a00 DPL=0 CS64 [-R-]
SS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0020 0000000000000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT= 0000000000007f4b 00000028
IDT= 000000000001b010 000007ff
CR0=80000011 CR2=00000000005ffff8 CR3=0000000000101000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000010 CCD=ffffffff80000011 CCO=LOGICL
EFER=0000000000000500
check_exception old: 0x8 new 0xd: 인터럽트 벡터 `0x0E`면, x86 기준 `페이지 폴트`에러다. #PF는 에러 코드보다 `CR2`의 값이 더 중요하다. #PF가 발생했다는 것은 페이징이 활성화됬다는 것임을 의미한다. 그리고, `CR2`는 어디에 접근하려다가 #PF가 발생했는지를 말해준다. `CR2`의 값은 가상 주소다. 추측이지만, `0xffffffff80106019`를 실행했는데, 이 가상 주소의 매핑이 `0x00000000005ffff8` 물리 주소로 매핑되었을 확률이 높다. 제일 먼저 봐야하는 부분은 `0xffffffff80106019` 가상 주소가 `0x00000000005ffff8` 물리 주소로 매핑되는게 맞는지 부터 확인해봐야 한다. 즉, 의도한 것인지 맞는지부터 확인해야 한다.
: 일단, `ffffffff80106019` 주소에 뭐가 있는지를 objdump로 확인해보자.
Disassembly of section .text:
ffffffff80106000 <_lenter>:
ffffffff80106000: b8 20 00 00 00 mov $0x20,%eax
ffffffff80106005: 8e d8 mov %eax,%ds
ffffffff80106007: 8e c0 mov %eax,%es
ffffffff80106009: 8e e0 mov %eax,%fs
ffffffff8010600b: 8e e8 mov %eax,%gs
ffffffff8010600d: 8e d0 mov %eax,%ss
ffffffff8010600f: bd 00 00 60 00 mov $0x600000,%ebp
ffffffff80106014: bc 00 00 60 00 mov $0x600000,%esp
ffffffff80106019: e8 49 0b 00 00 callq ffffffff80106b67 <main>
ffffffff8010601e <itoa>:
ffffffff8010601e: 55 push %rbp
ffffffff8010601f: 48 89 e5 mov %rsp,%rbp
ffffffff80106022: 48 83 ec 30 sub $0x30,%rsp
ffffffff80106026: 89 7d dc mov %edi,-0x24(%rbp)
ffffffff80106029: 48 89 75 d0 mov %rsi,-0x30(%rbp)
ffffffff8010602d: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
ffffffff80106034: 8b 45 dc mov -0x24(%rbp),%eax
ffffffff80106037: 89 45 f8 mov %eax,-0x8(%rbp)
ffffffff8010603a: 83 7d f8 00 cmpl $0x0,-0x8(%rbp)
ffffffff8010603e: 79 08 jns ffffffff80106048 <itoa+0x2a>
ffffffff80106040: 8b 45 dc mov -0x24(%rbp),%eax
ffffffff80106043: f7 d8 neg %eax
ffffffff80106045: 89 45 f8 mov %eax,-0x8(%rbp): `main` 함수를 호출하다가 발생한 에러다.
: 예제 3
" 아래 코드를 NASM으로 빌드하고 `objdump -d`를 통해 확인하면, eax 레지스터의 값은 몇일까? 참고로, 80286부터는 16비트 환경에서도 32비트 레지스터(접두사 `E` 레지스터)들을 사용할 수 있게됬다.
%ifndef _FBL_ASM_ %define _FBL_ASM_ [BITS 16] ; VGA VGA_TEST_BASE equ 0xB800 VGA_LINE_BYTES equ 160 ; Memory STACK_TOP equ 0x7C00 ; BIOS INT BIOS_READ_SECS equ 0x02 BIOS_READ_DISK_INFO equ 0x08 ; Disk DISK_SBL_SECS equ 4 extern _sbl_start global vga_rows SECTION .text jmp 0x0000:_fbl_start _fbl_start: cli ; Disable interrupt mov byte [drive_number], dl ; set drive number mov eax, 0x100000 ...
: 정답은 `0`이다. 왜? `[BITS 16]` 때문에 NASM이 모든 코드를 16비트로 빌드해버린다. 이런 문제는 사실 GDB로 디버깅을 해도 알 수가 없다. 왜? GDB는 16비트를 인지하지 못하기 때문이다. 참고로, `BOCHS`를 쓰면 바로 알 수 있다.
'Linux > development tool' 카테고리의 다른 글
[GIT] - git diff를 통한 patch 파일 생성 및 적용 (0) 2023.08.03 Shell script (0) 2023.07.10 dd (0) 2023.06.10 [개발도구] - inline assembly (0) 2023.06.07 [개발도구] - Linker (0) 2023.06.04