몇년간 방치했던 HDR-HC3 캠코더를 고쳐보기 위해 분해 및 조립했다가 E:91:01 오류(flash error)를 만났다.

원래는 LCD가 제대로 나오지 않아서, 연결 케이블 단선을 가장 의심하고, 이것저것 알아보던 차에, 같은 모델이 중고로 싸게 나온 매물이 있어서 몇년전 구입해서 썼는데, 고질적인 뚜껑이 닫히지 않는 문제때문에 임시 방편으로 간단히 수리해 썼었다가, 귀찮아서 방치했던 것이었다.

불과 몇년이 지났지만 이제는 유튜브등에 검색해보면 상당히 자세한 분해 영상이 있어서 분해를 따라해보고, LCD단선 케이블을 확인하고 알리에 주문을 넣었고, 또 다른 한대는 LCD가 멀쩡했으나 분해를 비교 확인하기 위해서 같이 분해했다가 재조립했는데 E:91:01 오류가 난 것이다. 이 오류를 검색해보면 flash error라고 하는데, 이를 검색해보면 캠 메뉴얼이 나오고, 여기에 설명이 나오길 "abnomal situation"을 예방하기 위해서 펌웨어 특정 영역에 표식을 하고 (이를테면 특정 펌웨어 주소에 "00"이 저장됨), 서비스 센터를 찾아가지 않는 이상 고치기 어렵다는 것이었다... 황당

그런데 좀 더 찾아보면 RM95 컨트롤러를 사용하면 펌웨어 특정 영역을 강제로 "01" 등으로 고치고 리셋을 하면 멀쩡해진다는 것.

lea.hamradio.si/~s51kq/DV-IN.HTM 사이트에 여러가지 유용한 내용이 나오는데, flash error를 고치는 방법을 종합해서 정리해보면

  • 복구를 위해 RM95 LANC 컨트롤러가 필요하나 꽤 비쌈 (중고나라에 가끔 싼 매물이 나오기도 하나 기다리기 어려움
  • RM95 LANC 컨트롤러가 없다면 위 사이트에 설명된 PC인터페이스 용 케이블을 자작한다. (재료값을 다 합해도 1만원 수준임)
    • 위 사이트에 보면 자작 케이블을 만들기 위한 회로도 등이 나와있다. 디x이스마트에서 구매함.
  • LANC 자작 케이블을 사용하려면 윈도우95/98에서 dvin lite를 사용해야 하는데, 윈도우10에서는 작동이 안됨
  • 다행히도 도스용 RM95EMUL이 작동한다. (다만, 자잘한 버그때문에 약간의 오작동을 보여주나, flash error 오류를 수정하는 것은 가능함)
  • 도스로 부팅해서 RM95EMUL을 실행 후 소니 HDR-HC3 을 LANC케이블로 PC와 연결하고 play 모드로 변경, 메뉴얼에 설명한 것처럼 펌웨어 2바이트를 수정 확인함 (아래에 설명)->리셋 버튼을 눌러줌.
 

Process After Fixing Flash Error - Sony HDR-HC3 Service Manual [Page 11] | ManualsLib

 

www.manualslib.com

  1. 페이지 7 / 주소 01에 75라고 들어있는지 확인함. 이 값이 75가 아니면 75로 수정해주고 write.
  2. 페이지 7 / 주소 00의 값이 00이라고 되어있는 것을 01로 고친 후에 write.
  3. 페이지 7 / 주소 02의 값이 01이라고 되어있는지 확인함. 위의 1) 2)단계가 성공하면 01이라고 바뀌어 있음
  4. 캠을 끄고 리셋 버튼을 눌러준다.

이 과정에서 온갖 뻘짓을 다 해봤는데, RM95EMUL 도스용 프로그램은 RM95 링크를 통해 받을 수 있다. (소스코드가 같이 들어있으며 어셈블러)

LANCManager라는 프로그램도 있는데, RM95EMUL과 매우 유사하게 작동하고, FreeDos의 문제인지, 약간의 오작동이 있었다. (위의 세번째 단계가 제대로 작동하지 않음) xoomer.virgilio.it/carlopra/lanc/

 

KKLANC

My activity is currently freeware if you want to contribute to this project, you can do it very simply using standard paypal: PayPal     Email me to discuss any technical  issue kksmafgp@tin.it

xoomer.virgilio.it

LPT포트가 있는 컴이 없어서 좀 낡은 방치된 컴을 정비하느라, dvin이 윈도우10에서 제대로 작동이 안되어 한참을 헤메다가 겨우 고친 것이라, 혹시 비슷한 경험을 하실 분도 있을 것 같아 기록으로 남겨둔다.

 

'자가수리' 카테고리의 다른 글

HDR-HC3 flash error 복구하기  (0) 2021.02.21
소니 HDR-HC3 뚜껑이 닫히지 않는 경우 조치법  (10) 2014.10.25
by dumpcookie 2021. 2. 21. 04:49


최근의 일련의 x86 어셈블리어 관련 글은 클론 리플레이어상에서 x86을 지원하기 위해 할 수 있는게 무엇일까 하고 살펴보던 차에 썼던 것이다. 이런 저런 관련 정보를 찾아보고 테스트를 해보니, x86을 위해 빌드한 공유 라이브러리의 TEXTRELs 문제를 고치기 위해서는 MMX/SSE 등등의 지원을 위해 들어가있는 꽤 오래된 어셈블리어 소스를 고쳐야만 하는 문제였다.

https://sourceforge.net/p/mpg123/bugs/168/

소스를 살펴보다가 알게된 사실중 하나는, x264/ffmpeg/mplayer/libvpx 등등의 프로젝트에서 꽤 오래전에 누군가가 만들었던 소스를 서로 의도하지 않게(?) 공유하고 있었다는 사실이었다. 최초 누가 만들었는지 조차도 명확하지 않지만 그에 대한 언급이 소스에 있으며,  또 다른 것으로는, 어셈블리어로 있던 소스를 인라인 어셈블리로 바꾸면서 c 소스로 바뀐 경우, 혹은 그 반대로 c 소스를 어셈블러 소스로 변환시킨 후에 손으로 최적화 한 흔적들도 보였다.

아무튼 TEXTREL 문제를 고치기 위해 PIC 코드로 고쳐야 하는 부분은 많지 않았으나, 암호같던 어셈블리어가 조금씩 눈에 들어오니 고치는 것이 가능하겠다 싶어서 어셈블리어 공부(?)/테스트 및 삽질을 같이하여 TEXTREL 문제를 제거한 패치를 만들게 되었다.

패치는 https://github.com/wkpark/mpg123/tree/textrels 링크를 통해 받고 테스트해볼 수 있다.

일단 간단히 테스트를 해볼 수 있는 것이 mpg123 -t 명령을 통해서 입력 mp3를 받아 디코딩하여 -w foo.wav 옵션으로 WAV 파일로 디코딩된 파일을 들어보는 것이었다. 패치를 거듭 살펴보고 최종적으로 빌드후 실행해서 core 덤프가 나면 어디가 문제인지 gdb로 몇차례 살펴보고, 더이상 core dump 안되고 실행이 된 후 디코딩된 wav파일이 정상적으로 소리가 들렸으며, readelf -d 명령을 통해서 TEXTREL이 더이상 보이지 않는 것을 확인하였다.

$ readelf -d src/libmpg123/.libs/libmpg123.so

Dynamic section at offset 0x56e20 contains 27 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libm.so.6]
 0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000e (SONAME)                     Library soname: [libmpg123.so.0]
 0x0000000c (INIT)                       0x2f08
 0x0000000d (FINI)                       0x441b4
 0x00000019 (INIT_ARRAY)                 0x57a74
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x57a78
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x138
 0x00000005 (STRTAB)                     0x1160
 0x00000006 (SYMTAB)                     0x5f0
 0x0000000a (STRSZ)                      2954 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000003 (PLTGOT)                     0x58000
 0x00000002 (PLTRELSZ)                   848 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x2bb8
 0x00000011 (REL)                        0x1f48
 0x00000012 (RELSZ)                      3184 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x1e58
 0x6fffffff (VERNEEDNUM)                 3
 0x6ffffff0 (VERSYM)                     0x1cea
 0x6ffffffa (RELCOUNT)                   392
 0x00000000 (NULL)                       0x0

$ readelf -d src/libmpg123/.libs/libmpg123.so |grep TEXT
$

이렇게 빌드된 mpg123 라이브러리를 클론 리플레이어에 x86 jni 라이브러리로 넣고 x86/x86_64 AOSP+vmware에서 테스트해보니, 전에는 TEXTRELs 문제로 정지되던 것이 이제는 실행이 잘 되었다.

이 패치는 mpg123 sourceforge 사이트에 보고하였으며, 몇번의 피드백과 테스트를 통해 MMX/3DNow/3dnowext/SSE 디코더를 모두 지원하게 되었으며, 고친 소스는 위에서 언급한 github 사이트를 통해 확인할 수 있다.

by dumpcookie 2017. 3. 14. 04:06

X86 32비트 어셈블리어 소스코드 예제를 통해서 TEXTREL 문제를 확인해보고, 이를 TEXTREL 문제 없는 어셈블리어 소스를 만들어보자. 먼저 다음과 같은 간단한 소스코드 예제를 보자.

#include <stdio.h>
int main() {
   puts("Hello World\n");
   return 0;
}

이 소스를 clang으로 컴파일하고, objconv를 통해 NASM/YASM 소스코드로 변환시켜 조금 정리하면 다음과 같다. (clang -m32 -O2 -c 옵션으로 컴파일)

global main: function
extern puts

SECTION .text
main:
        sub     esp, 12
        mov     dword [esp], .str
        call    puts
        xor     eax, eax
        add     esp, 12
        ret
; main End of function

SECTION .data

.str:
        db "Hello World", 0xa, 0x0

스택 조작하는 부분을 더 간단히 고쳐보면 다음과 같다.

global main: function
extern puts

SECTION .text
main:
        mov     eax, .str
        push    eax
        call    puts
        add     esp, 4
        xor     eax, eax
        ret

SECTION .data

.str:
        db "Hello World", 0xa, 0x0

이를 my.asm로 저장하고 yasm -fnasm my.asm으로 컴파일 후에 gcc -m32 my.o 명령으로 링크를 하면 실행이 잘 됨을 확인할 수 있다

인라인 어셈블리에 익숙해지려면 AT&T 스타일의 어셈블리어도 알아할 것이므로 -S 옵션으로 어셈블리어 소스를 얻고, 이를 정리해보면 다음과 같다.

        .text
        .globl  main
        .align  16
main:
        movl    $.str, %eax
        push    %eax
        calll   puts
        addl    $4, %esp
        xorl    %eax, %eax
        retl

        .section        .data
        .align  16
.str:
        .asciz  "Hello World"

일대일 대응이므로 이해하는데 어렵지 않을 것이다. 이 소스는 my2.s로 저장한 후에 gcc -m32 my2.s로 컴파일하면 실행이 잘 됨을 확인할 수 있다.

이렇게 얻어진 어셈블리어 소스를 오브젝트 파일로 만든 후에 공유 라이브러리를 만들면 어떻게 될까?
다음과 같은 명령으로 공유라이브러리를 얻을 수 있으며, TEXREL 문제 역시 확인할 수 있다.

$ gcc -Wl,--warn-shared-textrel -m32 -shared -Wl,-soname,my2.so -o /tmp/my2.so my.o
/usr/bin/ld: my.o: warning: relocation in readonly section `.text'
/usr/bin/ld: warning: creating a DT_TEXTREL in a shared object.

그렇다면 이와 같은 문제의 어셈블리어 소스의 TEXTREL 문제를 어떻게 해결할 수 있을까?

함수의 리로케이션

우선 objdump -r 명령을 통해 리로케이션 정보를 살펴보자. (readelf -r 명령과 거의 동일)

$ objdump -r my.o

my.o:     file format elf32-i386

RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE
00000001 R_386_32          .data
00000007 R_386_PC32        puts

puts 함수의 리로케이션 정보를 고치기 위해서는 NASM의 경우 메뉴얼에 나온것처럼 다음과 같이 고치면 함수의 리로케이션 문제가 해결된다. 즉 함수 호출을 call puts로 하는 대신에 call puts wrt ..plt라고 해준다.

global main: function
extern puts

SECTION .text
main:
        mov     eax, .str
        push    eax
        call    puts wrt ..plt
        add     esp, 4
        xor     eax, eax
        ret

SECTION .data

.str:
        db "Hello World", 0xa, 0x0

이렇게 고친 후에 yasm -f elf my.asm으로 컴파일하고, objdump -r 명령으로 확인해보면 뭔가 달라졌음을 확인할 수 있다.

$ objdump -r my.o

my.o:     file format elf32-i386

RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE
00000001 R_386_32          .data
00000007 R_386_PLT32       puts

즉, 리로케이션 테이블에 있는 puts 엔트리의 오프셋은 전혀 변화가 없는데, 리로케이션 타입이 R_386_PLT32로 바뀌었으며, 함수 puts에 대한 리로케이션 문제는 수정된 것이다.

AT&T 스타일의 소스의 경우는 다음과 같은 식으로 고쳐야 한다.

        .text
        .globl  main
        .align  16
main:
        movl    $.str, %eax
        push    %eax
        calll   puts@PLT
        addl    $4, %esp
        xorl    %eax, %eax
        retl

        .section        .data
        .align  16
.str:
        .asciz  "Hello World"

YASM/NASM에서는 조금 헷갈리게 만드는 wrt ..plt라는 지시자를 썼지만, AT&T 스타일에서는 왠지 한결 쉬운듯한 @PLT를 사용하고 있다. 이 경우 역시 gcc -m32 -c 명령으로 컴파일한 후에 gcc로 공유 라이브러리를 생성시켜보면 여전히 TEXTREL문제가 남아있음을 확인할 수 있는데, 이는 함수에 대한 리로케이션 뿐만 아니라, 데이터에 대한 리로케이션 정보도 바꿔줘야 하기 때문이다.

어셈블리어 소스는 @PLT 지시자를 통해서 고쳤으나, objdump -d를 통해서 바이너리 코드를 살펴보면 바뀌기 전 코드와 완전히 일치한다. @PLT같은 지시자는 리로케이션 테이블의 정보를 바꿔주는 역할을 할 뿐, 실제 실행 코드 (.text 섹션내 실행 코드) 자체는 변화가 없다.

리로케이션 테이블을 readelf -r 명령을 통해서 보면 다음과 같다.

$ readelf -r my.o

Relocation section '.rel.text' at offset 0x54 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000001  00000201 R_386_32          00000000   .data
00000007  00000604 R_386_PLT32       00000000   puts

오브젝트 파일을 xxd로 덤프해서 바이너리 파일을 비교해보면 다음과 같은 차이가 있는 것을 볼 수 있다. (아래쪽 변경점은 소스파일의 파일 이름이 fa.asm 에서 fb.asm으로 변한 정보)

--- a   2017-03-10 18:54:40.016601488 +0900
+++ b   2017-03-10 18:54:43.364574036 +0900
@@ -4,11 +4,11 @@
 00000030: 0700 0100 0000 0000 0000 0000 0000 0000  ................
 00000040: b800 0000 0050 e8fc ffff ff83 c404 31c0  .....P........1.
 00000050: c300 0000 0100 0000 0102 0000 0700 0000  ................
-00000060: 0406 0000 4865 6c6c 6f20 576f 726c 6421  ....Hello World!
+00000060: 0206 0000 4865 6c6c 6f20 576f 726c 6421  ....Hello World!
 00000070: 0a00 0000 002e 7465 7874 002e 6461 7461  ......text..data
 00000080: 002e 7265 6c2e 7465 7874 002e 7374 7274  ..rel.text..strt
 00000090: 6162 002e 7379 6d74 6162 002e 7368 7374  ab..symtab..shst
-000000a0: 7274 6162 0000 0000 0066 612e 6173 6d00  rtab.....fa.asm.
+000000a0: 7274 6162 0000 0000 0066 622e 6173 6d00  rtab.....fb.asm.
 000000b0: 6d61 696e 0070 7574 7300 0000 0000 0000  main.puts.......
 000000c0: 0000 0000 0000 0000 0000 0000 0100 0000  ................
 000000d0: 0000 0000 0000 0000 0400 f1ff 0000 0000  ................

데이터 리로케이션

다음은 데이터 리로케이션 정보를 넣어줄 차례이다. 그런데 이 경우는 x86_32비트의 경우 문제가 약간 복잡한데, 이유는 차차 설명하기로 한다.

먼저 64비트의 경우를 살펴보자. hello world 프로그램을 clang -O2 -S 옵션으로 소스를 얻고 정리를 해보면 다음과 같다.

        .text
        .globl  main
main:
        movl    $.str, %edi
        callq   puts
        xorl    %eax, %eax
        retq

        .section        .rodata
.str:
        .asciz  "Hello World"

여기서 movl $.str, %edi 코드는 .str 문자열의 주소값($.str)을 %edi 레지스터에 넣으라는 것이다. 이 명령은 leal .str, %edi와 하는 일이 같다.

64비트의 경우에는 함수 호출 규약이 더 간단한데, 스택으로 호출 인자를 넘기는 것이 아니라 rdi, rsi 등의 레지스터를 통해 인자를 넘기고 있다. 따라서 push 혹은 pop을 통해 인자를 넘기지 않고 곧바로 edi, esi 레지스터를 수정하게 된다.

이 프로그램의 리로케이션 문제를 해결하려 하면 다음과 같이 바꿔주어야 한다.

        .text
        .globl  main
main:
        leal    .str(%rip), %edi
        callq   puts@PLT
        xorl    %eax, %eax
        retq

        .section        .rodata
.str:
        .asciz  "Hello World"

32비트에서처럼 함수의 경우에는 @PLT 지시자를 넣었으나, 데이터 섹션의 .str 주소를 넘기는 부분은 leal .str(%rip), %edi를 사용했다.
이것은 단순히 지시자를 사용한 것이 아니며, X86_64(AMD64)에서 새로 도입된 레지스터 %rip를 사용해서 .str 문자열 주소를 넘겨준 것이다.

AT&T의 문법이 헷갈릴 수 있으니 이에 대응하는 YASM/NASM 어셈블리어로 살펴보자.

default rel

global main

extern puts

SECTION .text

main:
        lea     edi, [rel .str]
        call    puts wrt ..plt
        xor     eax, eax
        ret

SECTION .rodata

.str:
        db "Hello World"

default rel등등이 포함된 것을 제외하고는 32비트/64비트 프로그램 소스가 거의 같고, .str 문자열 주소를 넘겨주는 부분이 다른 것을 볼 수 있다.

문자열의 주소는 .str이며, .str의 주소의 내용은 [.str]인데, lea eax, [.str]이라고 쓰면 .str의 주소값을 넘겨주라는 것이다. (NASM/YASM에서 사용할 수 있는 몇몇 주소 지정 방식은 다음 링크를 통해서 볼 수 있다. https://www.tortall.net/projects/yasm/manual/html/nasm-effaddr.html#nasm-effaddr-riprel)

이렇게 문자열 .str의 주소를 넘겨주는 코드에 rel이라는 지시자를 넣으면 리로케이션 문제가 없는 오브젝트 파일이 생성된다. (NASM/YASM에서는 rip 레지스터를 직접 사용하면 오류가난다.) 이렇게 해서 생성된 코드의 바이트수도 조금 다르다는 것을 다음과 같이 확인할 수 있다.

$ objdump -d nopic.o

ffa-nopic.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:
   0:   8d 3c 25 00 00 00 00    lea    0x0,%edi
   7:   e8 00 00 00 00          callq  c <main+0xc>
   c:   31 c0                   xor    %eax,%eax
   e:   c3                      retq

$ objdump -d pic.o # 수정된 PIC 코드의 경우

ffa.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:
   0:   8d 3d 00 00 00 00       lea    0x0(%rip),%edi        # 6 <main+0x6>
   6:   e8 00 00 00 00          callq  b <main+0xb>
   b:   31 c0                   xor    %eax,%eax
   d:   c3                      retq

함수와는 다르게, 데이터의 주소를 상대적 주소로 바꿔주기 위해서 명령 코드 자체가 조금 바뀐 것을 알 수 있는데, 64비트에서 데이터 주소를 상대적 주소로 바꿔주기 위해서 %rip 레지스터를 사용한다. %rip 레지스터는 X86_32에서는 %eip이며, 16비트 32비트의 경우 %eip 어드레스의 값을 손쉽게 불러오지 못하였으나, X86_64(AMD64)부터는 %rip를 불러서 상대적 주소를 쉽게 계산할 수 있게 된 것이다.

그렇다면 X86 32비트에서는 데이터의 상대주소를 어떻게 구할까?

Call Pop 트릭

콜팝은 콜라+팝콘의 뜻이 아니고, 32비트에서 %eip 레지스터의 값을 가져오는 트릭이다. call + pop을 통해 %eip 레지스터 값을 읽는 방식을 구현할 수 있다. 32비트에서도 명령 카운트 레지스터가 존재하지만, 이것은 직접 읽기가 불가능한 레지스터이다. 그런데 함수를 콜하게 되면 스택에 리턴할 주소를 저장한다는 사실을 알고 있다. 스택에 저장되는 리턴할 주소가 바로 명령 카운터 레지스터 %eip의 값인 셈이다. 또한, 리턴할 주소는 바로 다음 명령이 실행될 위치이며, 이 경우 local:주소의 값이 된다. 따라서 아래와 같이 하면 %ebx 레지스터에 local:의 실제 주소값이 저장될 것이다.

    call local
local:
    pop %ebx

pop 대신에 mov (esp), %ebx라고 해도 된다. 그렇다면 이렇게 얻어진 %ebx 값은 local:의 실제 메모리에 올라간 진짜 주소가 된다는 것인데, 그렇다면 데이터가 적재된 곳의 주소 위치는 어떻게 구하게 될까? 64비트에서 lea .str(%rip), %eax라고 하면 되었듯이, %rip 대신에 %eip가 저장된 위치인 %ebx를 사용하여 leal .str(%ebx), %eax 라고 하면 될 것 같은데, 실제로는 이런 메커니즘을 지원하지 않으며, 다음과 같은 조금 아리송한 코드가 실제 사용된다. 즉,

    call local
local:
    pop %ebx
    leal .str(%ebx), %eax

라고 해야 하는 것이 아니라 다음과 같이 한다

    call local
local:
    pop %ebx
local2:
    addl $_GLOBAL_OFFSET_TABLE_ + (local2 - local), %ebx
    leal .str@GOTOFF(%ebx), %eax

알쏭달쏭한 이 코드를 gdb로 파헤쳐보면 내막을 알 수 있지만, 실제로 _GLOBAL_OFFSET_TABLE_ 이라는 주소의 값은 프로그램이 적재되기 이전까지 알 수 없는 값이고, 소스코드상의 _GLOBAL_OFFSET_TABLE_은 조금 다르게 해석 되어진다. (이 부분은 아래에서 살펴보겠다.) 소스를 컴파일해서 얻어진 오브젝트 파일은 $_GLOBAL_OFFSET_TABLE_값을 아직 알지 못하고, 최종 실행파일은 링커에 의해 해석되어진 후에 GLOBAL OFFSET TABLE이 만들어지게 되어 비로소 GLOBAL_OFFSET_TABLE을 알게 된다.
본인도 이 부분이 제대로 이해되지 않아서 어려워했으나, gdb를 통해 내막을 살펴보고서야 이해하게 되었다.

_GLOBAL_OFFSET_TABLE은 변수? 상수?

우선 다음의 소스코드를 살펴보자. 이것은 hello world 프로그램의 PIC 버전이다. clang -O2 -fPIC -m32 -S 옵션으로 컴파일해서 소스를 얻고 이를 정리한 것이다.

        .text
        .globl  main
main:
        call    .tmp0
.tmp0:
        popl    %ebx
.tmp1:
        addl    $_GLOBAL_OFFSET_TABLE_+(.tmp1-.tmp0), %ebx
        leal    .str@GOTOFF(%ebx), %eax
        movl    $_GLOBAL_OFFSET_TABLE_, %ebx ; 디버그용 코드
        movl    $_GLOBAL_OFFSET_TABLE_, %ebx ; 디버그용 코드
        push    %eax
        call    puts@PLT
        
        add     $4, %esp
        xorl    %eax, %eax
        retl

        .section        .rodata
.str:
        .asciz  "Hello World"

$_GLOBAL_OFFSET_TABLE값을 알기 위해서 중간에 디버그 코드를 넣었다. 이를 컴파일해서 오브젝트 파일 및 실행파일을 얻었다. 이를 objdump -d 명령으로 살펴보면 main 함수가 다음과 같다.

objdump -d 목적파일

00000000 <main>:
   0:   e8 00 00 00 00          call   5 <.tmp0>

00000005 <.tmp0>:
   5:   5b                      pop    %ebx

00000006 <.tmp1>:
   6:   81 c3 03 00 00 00       add    $0x3,%ebx
   c:   8d 83 00 00 00 00       lea    0x0(%ebx),%eax
  12:   bb 01 00 00 00          mov    $0x1,%ebx
  17:   bb 01 00 00 00          mov    $0x1,%ebx
  1c:   50                      push   %eax
  1d:   e8 fc ff ff ff          call   1e <.tmp1+0x18>
  22:   83 c4 04                add    $0x4,%esp
  25:   31 c0                   xor    %eax,%eax
  27:   c3                      ret

위의 내용을 잘 살펴보면 $_GLOBAL_OFFSET_TABLE_ 값이 들어가야 할 자리가 $0x1 이라는 값이 들어있다. 또한, $_GLOBAL_OFFSET_TABLE_+(.tmp1-.tmp0)가 들어가야 할 자리는 두 주소의 차이 (.tmp1-.tmp0) 값이 1이므로 0x02이어야 할 것 같은데 이상하게 $0x03이 들어있다. 그러면 실행파일을 덤프해보면 어떤가 살펴보자.

objdump -d 실행파일

...(생략)
0804840b <main>:
 804840b:       e8 00 00 00 00          call   8048410 <.tmp0>

08048410 <.tmp0>:
 8048410:       5b                      pop    %ebx

08048411 <.tmp1>:
 8048411:       81 c3 f0 1b 00 00       add    $0x1bf0,%ebx
 8048417:       8d 83 c0 e4 ff ff       lea    -0x1b40(%ebx),%eax
 804841d:       bb e3 1b 00 00          mov    $0x1be3,%ebx
 8048422:       bb de 1b 00 00          mov    $0x1bde,%ebx
 8048427:       50                      push   %eax
 8048428:       e8 b3 fe ff ff          call   80482e0 <puts@plt>
 804842d:       83 c4 04                add    $0x4,%esp
 8048430:       31 c0                   xor    %eax,%eax
 8048432:       c3                      ret
...(생략)
Disassembly of section .got.plt:

0804a000 <_GLOBAL_OFFSET_TABLE_>:
 804a000:       14 9f                   adc    $0x9f,%al
 804a002:       04 08                   add    $0x8,%al

실행파일에서는 $_GLOBAL_OFFSET_TABLE_이 들어갈 위치의 값이 모두 다른 값으로 채워져 있으며, $_GLOBAL_OFFSET_TABLE_+(.tmp1-.tmp0) 값에는 $0x1bf0(7152)가 들어가고, $_GLOBAL_OFFSET_TABLE_값이 들어갈 줄 알았던 곳은 각각 $0x1be3(7139) , $0x1bde(7134) 값이 채워져 있다. 그리고 정작 _GLOBAL_OFFSET_TABLE_ 주소는 0x0804a000값이다.
실제로 상수인줄 알았던 소스코드상에 들어간 $_GLOBAL_OFFSET_TABLE_ 값은 링커에 의해 고정된 값으로 해석되는 것이 아니라 .data 섹션과의 오프셋값으로 해석되어지고 있는 것이다.

따라서 실제 GLOBAL_OFFSET_TABLE 즉 실행파일이 메모리에 올라갔을 때에 .data 세그먼트의 위치를 가리키는 값은 다음과 같이 계산되어진다.

        GLOBAL_OFFSET_TABLE의 실제 위치 = 현재 실행 코드의 %eip + 현재 실행 코드와 GLOBAL_OFFSET_TABLE 주소와 오프셋(여기서는 $_GLOBAL_OFFSET_TABLE_)

아무튼 이 과정은 GNU as + GND ld의 합작 과정인 것인데, YASM/NASM에서는 이마져도 약간 다르게 보이지만 원리는 같다. 다음 코드는 YASM/NASM에서 GLOBAL_OFFSET_TABLE를 구하는 코드이다.

global main: function
extern puts
extern _GLOBAL_OFFSET_TABLE_ ; YASM/NASM에서는 이를 선언해주어야 한다.

SECTION .text
main:
        call .get_got
.get_got:
        pop     ebx
.tmp:
        add     ebx, _GLOBAL_OFFSET_TABLE_ + .tmp - .get_got ; AT&T 스타일의 어셈블리어와 같은 방식
        ;add    ebx, _GLOBAL_OFFSET_TABLE_ + $$ - .get_got wrt ..gotpc ; NASM/YASM 메뉴얼에 나와있는 방식.
        lea     eax, [.str + ebx wrt ..gotoff]
        push    eax
        call    puts wrt ..plt
        add     esp, 4
        xor     eax, eax
        ret

SECTION .data

.str:
        db "Hello World", 0xa, 0x0

이렇게 하여 이 소스를 컴파일하여 오브젝트 파일의 리로케이션 정보를 보면 다음과 같게 되며 TEXTRELs 문제가 없게 된다.

$ objdump -r test.o

test.o:     file format elf32-i386

RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE
00000008 R_386_GOTPC       _GLOBAL_OFFSET_TABLE_
0000000e R_386_GOTOFF      .data
00000014 R_386_PLT32       puts

※참고:

http://nullprogram.com/blog/2016/12/23/ - GLOBAL_OFFSET_TABLE 및 x86에서 리로케이션에 대한 설명이 되어있다.

by dumpcookie 2017. 3. 11. 09:40