ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [개발 도구] objdump
    Linux/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
Designed by Tistory.