오랫동안 프로그래밍을 해본 사용자라 할지라도 어셈블리어가 필요하거나 리버스엔지니어링(역공학)이 필요한 경우는 별로 많지 않다. 본인도 90년대에 처음 GW 베이직을 시작으로 터보파스칼/터보C를 사용하며 프로그래밍의 기초를 배웠던 것이 그 시작이었지만 어셈블리어를 사용할 기회는 그다지 많지 않았다. 터보 파스칼의 그래픽 라이브러리를 사용하다가 마이크로소프트웨어 잡지책에 나와있던 원그리기/직선 그리기 알고리즘을 MASM 어셈블리어로 구현하고 이를 개선하려고 하면서 VGA 메모리 접근을 위해 주소를 어셈블리어로 직접 다루던 부분을 디버깅하던 기억이 아직도 남아있다. 당시 어셈블리어는 이해하기 어려웠고 고치기도 힘들었으나 최적화되고 나서 기대했던 속도가 나왔을 당시의 느꼈던 감격은 어셈블러의 단점을 보상하고도 남음이 있었다.

아무튼 지금 이 글을 쓰고있는 시점에서 조차도 본인은 어셈블리어에 대한 경험이 별로 없지만, 어셈블리어 혹은 리버스엔지니어링에 대한 기초적인 입문서도 많지 않다는 것에 조금 의아스럽게 생각하며, 어셈블리어에 완전히 생초짜라도 누구나 쉽게 따라해보면서 어셈블리어의 기초와 리버스엔지니어링을 같이 배워가며 문서를 쓸 예정이다.

C언어 기초 및 리눅스/유닉스에 어느정도 익숙한 분들을 대상으로 하지만 내용은 훨씬 쉽게 쓰려고 한다.

gcc와 objdump를 사용한 간단한 역공학

가장 먼저 초간단 C 프로그램을 만들어보자.

int main() {
return 0;
}

이를 a.c로 저장하고 gcc -c a.c 라고 하면 a.o가 얻어지며 a.o 오브젝트 파일을 objdump를 사용하여 간단히 역어셈블해보면 다음과 같다. (여기서는 -O1 컴파일 옵션을 주었으며, objdump -d 명령으로 디스어셈블을 하되 -Mintel 옵션으로 인텔 방식의 명령을 사용했다. gcc 버전은 5.4.0 / OS는 우분투 16.04.4)

$ gcc -c -O1 a.c
$ objdump -d -Mintel a.o

a.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   b8 00 00 00 00          mov    eax,0x0
   5:   c3                      ret

여기 처음으로 두개의 어셈블리어 명령이 보이는데, mov eax,0x0 명령은 eax 레지스터에 0x0을 옮기고(mov), ret 명령은 리턴하라는 것이다. (여기서는 eax레지스터를 처음으로 보게 되는데, 인텔의 16비트 레지스터는 ax,bx,cx,dx였던 것이 32비트로 넘어오면서 eax,ebx,ecx,edx로 확장되었고 ax,bx,cx,dx는 각각 8비트 레지스터 ah/al,bh/bl,ch/cl,dh/dl 등으로 구성된다. x86에서 지원하고 있는 레지스터에 대한 좀 더 자세한 정보는 위키피디아 https://en.wikipedia.org/wiki/X86#x86_registers 문서를 참고)

gcc에 최적화 옵션을 -O2로 주어서 컴파일한 후에 디스어셈블해보면 다음과 같다.

$ objdump -d -Mintel a.o

a.o:     file format elf64-x86-64


Disassembly of section .text.startup:

0000000000000000 <main>:
   0:   31 c0                   xor    eax,eax
   2:   c3                      ret

O2로 최적화를 하는 경우에는 오브젝트의 크기가 6바이트에서 3바이트로 줄어든 것을 볼 수 있는데, xor 명령을 사용하면 mov eax,0x0 명령이 5바이트인 것에 비해서 xor 명령은 2바이트밖에 안되면서 eax 레지스터의 내용을 지워서 0으로 만드는 동일한 작업을 수행하고 있다.

그렇다면 다음을 역어셈블해보면 어떻게 될까?

int main() {
return -1;
}

gcc -O2로 컴파일 한 후에 objdump -d -Mintel로 역어셈블 해보면 다음과 같다.

0000000000000000 <main>:
   0:   b8 ff ff ff ff          mov    eax,0xffffffff
   5:   c3                      ret

mov eax,0xffffffff 명령은 -1을 eax로 옮기는(mov)명령인데, -1는 32비트(4바이트)로 0xffffffff에 해당하며 mov eax, -1 명령과 똑같다.

NASM/YASM 소스 생성하기

이렇게 objdump를 이용해서 어셈블러 명령을 볼 수 있는 방법을 알게 되었지만 이와 같은 원리를 이용해서 컴파일 가능한 어셈블리어 소스를 만드는 것도 가능하다. (물론 몇가지 제한이 있다.) 다음 링크에서 찾아볼 수 있는 objconv를 사용하면 된다.

※ 다음 링크를 보면 gcc+objconv를 사용하여 nasm/yasm 소스를 만드는 방법을 볼 수 있는데, 이 내용을 참고하였다. http://stackoverflow.com/questions/35102193/how-to-generate-assembly-code-with-gcc-that-can-be-compiled-with-nasm

objconv 실행 파일은 다음 사이트를 통해 받을 수 있는데 리눅스에서 사용하려면 직접 컴파일 해야 한다. 소스 제공 및 다운로드: http://www.agner.org/optimize/ (x86/x86_64를 위한 최적화 자료모음이 함께 있다.)

이제 위에서 만든 초간단 소스 a.c을 gcc로 컴파일 하여 오브젝트 파일 a.o을 만든 후에 이것을 objconv를 통해서 NASM/YASM으로 컴파일 할 수 있는 asm 소스파일을 만들어보자.

이 경우 -fno-asynchronous-unwind-tables 옵션을 통해서 몇가지 디버깅 정보를 생성하지 않는 것이 편리하다. (gcc man 페이지를 살펴보면 -fasynchronous-unwind-tables 옵션은 찾을 수 있는데 이 옵션에 대한 해설은없다.  이 옵션은 디버거 혹은 가비지 콜렉터에서 사용할 수 있는 DWARF 2 형식의 해석 테이블(unwind table)을 만들지 않도록 한다.)

$ gcc -fno-asynchronous-unwind-tables -O2 -c a.c

다음 명령을 통해 a.o 오브젝트 파일을 objconv를 통해서 asm 소스 파일로 변환한다. (-fnasm 혹은 -fyasm 옵션을 써서 NASM/YASM 형식으로 출력한다.)

$ objconv -fnasm a.o

(이렇게 하면 a.asm 소스 파일을 얻는다. 출력 파일 이름을 별도로 지정하려면 objconv -fnasm a.o my.asm 라고 하면 my.asm 소스파일을 얻게 된다)

얻은 asm 소스 파일의 내용은 다음과 같다.

; Disassembly of file: a.o
; Wed Mar  1 7:00:34 2017
; Mode: 64 bits
; Syntax: YASM/NASM
; Instruction set: 8086, x64

default rel

global main: function


SECTION .text   align=1 execute                         ; section number 1, code


SECTION .data   align=1 noexecute                       ; section number 2, data


SECTION .bss    align=1 noexecute                       ; section number 3, bss


SECTION .text.unlikely align=1 execute                  ; section number 4, code


SECTION .text.startup align=16 execute                  ; section number 5, code

main:   ; Function begin
        xor     eax, eax                                ; 0000 _ 31. C0
        ret                                             ; 0002 _ C3
; main End of function

이렇게 얻어진 위 소스는 몇가지 오류를 바로잡아야 NASM/YASM 명령을 통해 컴파일 할 수 있는데, sed 명령을 통해 다음과 같이 필터링해야 한다. (위의 stackoverflow에 나와있는 것을 참고로 해서 조금 수정한 sed 명령은 다음과 같다)

$ sed -i "s/align=1 //g;s/[a-z]*execute//g;s/: *function//g;/default *rel/d;" orig.asm

혹은 다음과 같이 불필요한 부분을 지우는 대신에 주석처리해서 남길 수도 있다. (";" 기호 뒤의 내용은 주석으로 인식됨)

$ sed -i "s/\(align=1\) /;\1 /g;s/\([a-z]*execute\)/;\1/g;s/: \(function\)/ ; \1/g;/default *rel/d;" a.asm

(-i 옵션을 사용하지 않으면 콘솔 화면으로 출력된다. 이를 쉘스크립트로 만들거나 해서 좀 더 쉽게 써먹을 수 있을 것이다.)

이렇게 필터링 처리한 소스는 다음과 같이 된다.

; Disassembly of file: a.o
; Wed Mar  1 1 7:00:34 2017
; Mode: 64 bits
; Syntax: YASM/NASM
; Instruction set: 8086, x64


global main


SECTION .text                            ; section number 1, code


SECTION .data                          ; section number 2, data


SECTION .bss                           ; section number 3, bss


SECTION .text.unlikely                   ; section number 4, code


SECTION .text.startup align=16                   ; section number 5, code

main:   ; Function begin
        xor     eax, eax                                ; 0000 _ 31. C0
        ret                                             ; 0002 _ C3
; main End of function

이 소스는 비어있는 섹션을 지워서 다음과 같이 더 간단히 만들 수도 있다.

; Disassembly of file: a.o
; Wed Mar  1 7:00:34 2017
; Mode: 64 bits
; Syntax: YASM/NASM
; Instruction set: 8086, x64

global main ; function
SECTION .text.startup                                   ; section number 5, code
main:   ; Function begin
        xor     eax, eax                                ; 0000 _ 31. C0
        ret                                             ; 0002 _ C3
; main End of function

이것을 YASM/NASM을 통해 컴파일해서 오브젝트 파일을 얻고 실행파일을 얻으려면 다음과 같이 한다.

$ yasm -f elf64 a.asm gcc a.o -o a.out

(이 경우 얻어지는 a.o을 objdump -d 명령으로 보려고 하면 내용이 출력이 안되는데 이 경우에는 objdump -D 옵션으로 출력하면 된다. objdump --help에 의하면 -D, --disassemble-all 이라는 설명을 볼 수 있다.) 이를 실행해보면 다음과 같다. (리턴값이 0임을 확인할 수 있다)

$ ./a.out ; echo $? 0

Hello World!

그러면 이제 조금 더 그럴듯한 C프로그램을 nasm으로 변환시켜보자.

#include <stdio.h>

int main() {
    printf("Hello World!\n");
    return 0;
}

이를 gcc로 컴파일하고 objconv로 nasm 소스로 변환시키고 수동으로 정리하면 다음과 같다.

; Disassembly of file: c.o
; Mode: 64 bits
; Syntax: YASM/NASM
; Instruction set: 8086, x64
;
; manually simplified

default rel
global main
extern puts                                             ; near

SECTION .rodata.str1.1
?_001:                                                  ; byte
        db 48H, 65H, 6CH, 6CH, 6FH, 20H, 57H, 6FH       ; 0000 _ Hello Wo
        db 72H, 6CH, 64H, 21H, 00H                      ; 0008 _ rld!.

SECTION .text.unlikely
SECTION .text.startup align=16
main:   ; Function begin
        sub     rsp, 8                                  ; 0000 _ 48: 83. EC, 08
        mov     edi, ?_001                              ; 0004 _ BF, 00000000(d)
        call    puts                                    ; 0009 _ E8, 00000000(rel)
        xor     eax, eax                                ; 000E _ 31. C0
        add     rsp, 8                                  ; 0010 _ 48: 83. C4, 08
        ret                                             ; 0014 _ C3
; main End of function

이를 컴파일하고 실행시키면 정상적으로 실행됨을 확인할 수 있다.

위 소스에서 문자열이 저장된 부분을 다음과 같이 좀 더 정리할 수도 있다.

; Disassembly of file: c.o
; Mode: 64 bits
; Syntax: YASM/NASM
; Instruction set: 8086, x64
;
; manually simplified

default rel
global main
extern puts                                             ; near

SECTION .rodata.str1.1
msg:
        db "Hello World!", 00h

SECTION .text.unlikely
SECTION .text.startup align=16
main:   ; Function begin
        sub     rsp, 8                                  ; 0000 _ 48: 83. EC, 08
        mov     edi, msg                              ; 0004 _ BF, 00000000(d)
        call    puts                                    ; 0009 _ E8, 00000000(rel)
        xor     eax, eax                                ; 000E _ 31. C0
        add     rsp, 8                                  ; 0010 _ 48: 83. C4, 08
        ret                                             ; 0014 _ C3
; main End of function

clang의 출력과 비교

이번에는 clang을 이용하여 동일한 소스를 컴파일하고 역어셈블 해보자. 순서의 차이 및 그밖의 차이가 약간 있으나 다음과 같이 거의 동일한 결과를 얻을 수 있다. (소스를 약간 정리함)

$ clang -fno-asynchronous-unwind-tables -O2 -c c.c

; Disassembly of file: c.o
; Wed Mar  1 11:13:03 2017
; Mode: 64 bits
; Syntax: YASM/NASM
; Instruction set: 8086, x64

global main
extern puts                                             ; near

SECTION .text   align=16                         ; section number 1, code

main:   ; Function begin
        push    rax                                     ; 0000 _ 50
        mov     edi, ?_001                              ; 0001 _ BF, 00000000(d)
        call    puts                                    ; 0006 _ E8, 00000000(rel)
        xor     eax, eax                                ; 000B _ 31. C0
        pop     rcx                                     ; 000D _ 59
        ret                                             ; 000E _ C3
; main End of function

SECTION .rodata.str1.1                 ; section number 2, const

?_001:                                                  ; byte
        db 48H, 65H, 6CH, 6CH, 6FH, 20H, 57H, 6FH       ; 0000 _ Hello Wo
        db 72H, 6CH, 64H, 21H, 00H                      ; 0008 _ rld!.ob

objdump와 gdb 비교

objdump -d 명령을 사용하여 간단히 디스어셈블 할 수 있지만 gdb도 역시 가능하다. objdump를 사용하면 명령행으로 간단히 디스어셈블을 할 수 있는 반면 gdb를 사용하면 옵션을 조절해가며 볼 수 있는 등의 장점이 있다.

gdb를 사용하기 위해서 위의 가장 간단한 프로그램 a.c를 다음과 같이 컴파일하고 gdb로 로드해보자.

$ gcc -O2 -g -c a.c
$ gdb a.o
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.o...done.
(gdb) _

gdb 파일명 명령을 내리면 파일이 실행파일/오브젝트 파일 등을 로드하고 (gdb) 프롬프트가 뜨고 명령을 기다린다.

이 상태에서 help 명령을 내리면 gdb에서 사용할 수 있는 몇가지 명령 목록이 나오는데, help all 등을 하면 모든 gdb 명령 목록을 보여주며, gdb 명령이 잘 생각이 나지 않는다면 탭키를 두어번 누르면 명령 목록이 주르륵 나온다.

우선 x86 사용자라면 NASM/YASM 스타일이 익숙할 것이므로 set disassembly-flavor intel로 인텔 스타일로 바꿔준다. 오브젝트 파일을 디스어셈블하려면 disassemble을 입력하고 탭을 몇번 치면 disassemble 명령을 통해 디스어셈블 가능한 함수 목록이 나온다. disassemble main라고 입력하면 objdump -d -Mintel 명령으로 디스어셈블 하듯 콘솔에 내용을 출력한다.

(gdb) disassemble (탭을 몇번 치면 다음과 같이 함수 목록이 나온다)
a.c   int   main (a.c 소스의 int main 함수)
(gdb) disassemble main
Dump of assembler code for function main:
   0x0000000000000000 <+0>:     xor    %eax,%eax
   0x0000000000000002 <+2>:     retq
End of assembler dump.
(gdb) set disassembly-flavor intel (인텔 스타일로 출력)
(gdb) disassemble main
Dump of assembler code for function main:
   0x0000000000000000 <+0>:     xor    eax,eax
   0x0000000000000002 <+2>:     ret
End of assembler dump.
(gdb)

a.c 소스를 -g 옵션을 넣어 디버깅 정보를 넣어주었으므로, gdb의 list 명령으로 소스를 같이 볼 수 있다.

(gdb) list
1       int main() {
2       return 0;
3       }
(gdb)

위에서 사용했던 set disassembly-flavor intel같은 옵션은 ~/.gdbinit 설정 파일에 저장해 두면 편리할 것이다. 즉 다음과 같은 내용을 .gdbinit 파일에 넣어둘 수 있다.

set disassembly-flavor intel
set history save on
set history size 1000
set history filename ~/.gdb_history

여기서는 gdb를 통해 간단히 디스어셈블을 하는 방법만 알아보았지만, 더 궁금한 사용자라면 gdb를 사용하려면 필수적인 몇가지 명령은 https://blogs.oracle.com/ksplice/entry/8_gdb_tricks_you_should 문서를 통해 볼 수 있다.

gdb의 사용법은 조금 복잡해보이지만 기본적인 사용법을 익히고 나면 어렵지만은 않다. 검색해보면 한글로 된 좋은 자료도 많으니 이를 참고할 수 있을 것이다.

※몇몇 참고문서

- Relocatable Global Data on x86 - http://nullprogram.com/blog/2016/12/23/

by dumpcookie 2017. 3. 1. 11:24

ndk-build는 의존성이 여러개인 외부 라이브러리를 손쉽게(?) 빌드하기 어렵다. 이 경우 ndk-build를 사용하는 대신에 AOSP를 소스 트리를 그대로 가져와서 arm_aosp-eng를 빌드한 후에 external/ 하위에 외부 라이브러리를 빌드하면 여러모로 편리하다. 또한 이렇게 하는 경우에 ndk-build와는 다르게 라이브러리 뿐만 아니라 apk 패키지도 같이 빌드할 수 있는 장점도 있다.

기존에는 AOSP 트리 대신에 CyanogenMod 킷캣 소스트리를 사용하고 있었는데 킷캣의 경우 arm64 및 x86_64를 지원하지 않아서 롤리팝 소스트리를 가져다가 빌드 환경을 재구성 하였다.

(AOSP 마쉬멜로우 트리의 경우 전체 빌드는 문제 없이 잘 되었으나, 이를 사용하려 하니 하위 호환성을 무시하고 과감하게 없애버린 몇몇 라이브러리 등등이 문제가 되어 롤리팝 소스트리를 선택하였다)

아무튼 이러한 이유로 AOSP 트리를 받아서 롤리팝을 다음과 같이 컴파일 하였다.

$ repo init -u https://android.googlesource.com/platform/manifest -b android-5.1.1_r38
$ repo sync
$ . build/envsetup.sh
$ lunch aosp_arm-eng
$ make -j4
...

보통은 이렇게 하면 아무런 문제 없이 전체 빌드가 되는데, 우분투 16.04 환경에서 빌드하다고 못보던 오류가 떴다.

해당 오류를 구글로 검색해보니 대략 다음과 같은 내용을 찾을 수 있었다.

http://stackoverflow.com/questions/36048358/building-android-from-sources-unsupported-reloc-43

https://android-review.googlesource.com/#/c/223100/

롤리팝부터는 clang을 host 바이너리 빌드하는 경우에 사용하게 되는데, 이 경우 clang은 자체적으로 내장된 as를 사용하는 대신에 별도의 gcc 툴체인의 as도 같이 사용하고 있다. 그런데 clang에 -B 옵션으로 툴체인의 경로를 지정하지 않는 경우, 툴체인의 as를 사용하는 대신에 host에 이미 설치된 as를 이용해 컴파일 된다. host의 as가 버전이 낮은 경우에는 별 문제가 되지 않는데 최신의 배포판을 사용하는 경우에는 host의 as 버전이 높고,  gcc 툴체인의 ld는 높은 버전으로 컴파일된 오브젝트의 relocation 타입을 인식하지 못하는 문제가 발생하는 것이다.

우분투 16.04를 사용하는 경우 host의 as 버전 (binutils 2.26.1)

$ as -v
GNU assembler version 2.26.1 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.26.1

툴체인의 as 버전 (gcc 4.6 + binutils 2.23.2)

$ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6/bin/x86_64-linux-as -v
GNU assembler version 2.23.2 (x86_64-linux) using BFD version (GNU Binutils) 2.23.2

위와 같이 툴체인의 as 버전이 host의 as 버전보다 낮다.

일단 위 링크에 써있듯이 -B 옵션을 build/core/clang/HOST_clang_common.mk에 추가하고 다시 컴파일 하였다. (stackoverflow 링크에 나와있듯이 롤리팝의 경우에는 다른 옵션을 건드리지 말고 패치를 수동으로 추가)

  -B$($(clang_2nd_arch_prefix)HOST_TOOLCHAIN_FOR_CLANG)/x86_64-linux/bin

주의해야 할 것은 이 경우 이미 컴파일 된 오브젝트 파일을 모두 지워줘야 한다는 점이다. 따라서 out/host/* 파일을 지워주고 make -j4 로 재빌드 한다.

by dumpcookie 2017. 2. 26. 21:25

PC를 한대 업그레이드 하고, 기존에 잘 쓰던 PC에 우분투 리눅스를 설치해서 개발용 빌드머신으로 활용하게 되었습니다. AOSP 마쉬멜로우를 테스트삼아 빌드하는데 시스템이 중간에 알 수 없는 이유로 죽는겁니다.

  1. 한참 컴파일 잘 하다가 시스템의 로드가 높아지면 갑자기 죽음.
  2. make -j2 등으로 시스템의 로드를 낮춰보면 시스템은 거의 죽지 않음.

지금은 문제 없이 잘 사용하고 있어서 위 두가지 항목으로 간단히 정리했지만, 당시에는 당황해서 상당한 삽질했던 것을 생각하면.... 아무튼 기억나는대로 다시 적어보면 다음과 같습니다.

  1. 메모리 문제는 아닌가? - 메모리 테스트 하고, 메모리를 하나씩 빼고 다시 컴파일을 해봄 - 여전히 시스템의 로드가 올라가면 죽음.
  2. 시스템 파워의 문제는 아닌가? - 시스템 파워를 좀 더 새것으로 잠시 교체해봄 - 여전히 죽음
  3. 커널이 맞지 않는가? - 커널을 좀 더 최신으로 바꿔봐도 시스템의 로드가 상당히 올라가면 여전히 죽음.

여기까지 테스트를 하니 벌써 반나절 이상을 소요... 일단 컴파일은 make -j2 등으로 로드를 낮춰서 컴파일을 하였고, 마지막으로 의심이 부분은 CPU 쿨링이었습니다. 쿨러는 사제 쿨러가 아닌 기본 쿨러를 쓰고 있었고, 먼지 청소를 하기 위해서 쿨러를 떼어내다가 서멀 구리스가 일부 제거되어 쿨러와 CPU의 밀착이 잘 되지 않고 있던 상태였던 것을 기억해 내었습니다. 메인보드가 구형이기는 하였지만 자동으로 오버클럭 기능을 제공하는 그러한 기종이었는데 펌웨어도 좀 더 안정성 있다고 써있는 최신으로 업그레이드 하고, 메인보드 모델명으로 검색을 해보니 바이오스에서 자동 오버클럭 기능(?)을 모두 꺼버리라는 내용이 보이더군요. 시스템의 로드가 올라가다가 죽는 현상과도 연관이 있는 것 같아서 이 기능을 끄고, 비교적 저렴한 서멀 구리스를 하나 구입하였습니다. 거의 하루를 이 삽질로 낭비...

몇일 뒤에도 시스템의 로드가 올라가면 여전히 죽는 현상이 있었으나, 배송되어 날아온 서멀구리스를 꼼꼼히 바르고 CPU 재 장착... make -j4 등등의 작업을 하니 그 뒤로는 시스템이 단 한번도 알 수 없는 이유로 죽지 않고 있습니다.

시스템이 알 수 없는 원인으로 죽거나 알 수 없는 오류가 자주 날 때에 살펴보아야 할 것을 나름 정리해보면

  1. 랜덤하게 시스템이 죽는 경우 - 경험상 거의 100% 메모리 문제. 메모리 문제의 경우에는 파일이 알 수 없는 원인으로 랜덤하게 망가지기도 한다.
  2. 랜덤하게 파일이 망가지는 경우 - 시스템이 죽지 않는 경우에도 컴파일 할 때마다 다른 곳에서 랜덤하게 컴파일이 멈추고 오류가 나는 현상도 보이며, 파일이 이상하게 깨지는 경우가 목격된다. - 이 경우도 메모리 문제일 확률이 높은데, 메모리 문제로 추측되면 memory 테스트를 해보라.
  3. 하드디스크가 이상이 있는 경우 - 컴파일시 이상한 지연 현상이 있으며 시스템이 이상하게 한참을 기다린다면 dmesg로 커널 로그를 살펴보자. I/O 오류가 간간히 보이고 하드디스크에서 이상한 끼릭끼릭 소리가 난다면 100% 하드디스크 교체 타이밍. 새 하드 디스크를 사서 신속하게 교체해야 하고, 데이터를 옮겨야 한다. 잘 하면 데이터를 그대로 살릴 수 있다.
  4. 컴퓨터가 어느날 갑자기 부팅이 안될 때 - 이 경우에는 여러가지 원인 일 수 있으나, 일단 HDD등의 기기는 무조건 분리하고 컴퓨터 청소등을 실시해보자. 파워에서 이상한 냄새가 난다거나 하면 파워의 문제일 수 있다. 메인보드의 콘덴서가 모두 정상적인지도 살펴보자. 콘덴서가 부풀어 오르거나 했을 경우는 메인보드 문제. (몇년간 잘 쓰던 컴퓨터가 갑자기 부팅이 안되어 컴퓨터 청소하고 재 연결하고 다시 켜는 과정에서 파워가 갑자기 번쩍 하면서..  메인보드가 타고, HDD도 칩이 타서 모두 날려버린 경험을 했던...ㅠ)


by dumpcookie 2017. 2. 25. 00:21

2009년 구입한 스탠드형 김치냉장고가 갑자기 제대로 작동을 하지 않았다.

김치가 시어져서 이상하다 했더니만 김치냉장고의 문제였던 것.

인터넷에 검색을 해보니 A/S를 인터넷을 통해서 신청할 수 있었고, 곧바로 A/S를 다음날 예약해두었다.

http://www.winiasvc.co.kr (방문 서비스 신청하기)


다음날 김치냉장고를 확인해보니 고장의 원인은 냉매가 순환이 안되고 있었다.

(참고로, 딤채 김치냉장고의 전원을 뺐다가 켜면 6분 정도 뒤에 모터가 가동되기 시작한다)

1. 모터 이상 무 -> 온도가 내려가지 않고 있음

2. 냉매가 없는가?

3. 냉매는 있으나 제대로 순환이 안됨


12만원 정도 든다고 알려주었고, 다음과 같은 조치를 취하고 수리를 마쳤다.

1. 냉매를 모두 뺀다.

2. 배관 진공작업(?? 배관 뚫기)

3. 냉매를 다시 채움

4. 온도가 정상적으로 내려가는지 확인


이와 관련된 내용을 검색해보니 별다른 내용이 검색되지 않길래 간략하게나마 정리하였고, 혹시 김치냉장고가 고장나신 분은 참고하시기 바란다.


※추가: 1개월도 안되어 이사를 한 후에 다시 이상에 생겨서 서비스를 받았고, 냉매를 채워넣고서야 다시 정상 작동하게 되었다. 서비스 받은지 얼마 되지 않아 A/S를 다시 받은 것이라서 비용은 들지 않았다.

by dumpcookie 2016. 11. 8. 10:45

웹버그라고도 불리는 1x1 GIF 이미지파일은 그 크기가 35 바이트 혹은 투명인 경우 43 바이트라고 알고있었습니다. http://www.perlmonks.org/?node_id=7974 (by turnstep이 링크가 원래의 출처이며 http://stackoverflow.com/questions/2933251/code-golf-1x1-black-pixel 링크를 통해 알게된 내용입니다.

그런데 최근 이보다 더 작은 크기의 GIF도 있다는 것을 알게 되었습니다. (다음은 텀블러에서 사용하는 1x1 GIF)


http://www.bignosebird.com/docs/h3.shtml ftp://ftp.bignosebird.com/spacer.gif 이 이미지는 텀블러에서 사용하는 이미지와 약간의 차이가 있으며 42바이트의 최소 크기로 텀블러 GIF 이미지 크기와 동일합니다.


그러면 43바이트 GIF 파일과의 차이점은 무엇일까 궁금해서 살펴보았는데, 두 파일은 다음과 같이 별로 다를 것이 없었고 이미지 마지막 부분의 바이트에 약간의 실수가 있었던 모양이었습니다.


이것이 42바이트의 GIF이미지이고,

0000000: 4749 4638 3961 0100 0100 8000 0000 0000  GIF89a..........
0000010: ffff ff21 f904 0100 0000 002c 0000 0000  ...!.......,....
0000020: 0100 0100 0002 0144 003b                 .......D.;

이것이 43바이트의 GIF이미지입니다.

0000000: 4749 4638 3961 0100 0100 9000 0000 0000  GIF89a..........
0000010: 0000 0021 f904 0510 0000 002c 0000 0000  ...!.......,....
0000020: 0100 0100 0002 0104 0100 3b              ..........;

얼핏 봐서는 거의 차이가 없어보이는데, 마지막쪽의 바이트가 차이가 있음을 볼 수 있습니다.


두 이미지가 정상적인 이미지인지 테스트를 하기위해 테스트를 해보면 두번째 이미지는 giftopnm 프로그램을 사용하였을때에 다음과 같은 경고 메시지가 나오는 것을 알 수 있습니다.

$ giftopnm --verbose 43b.gif giftopnm: GIF format version is '89a' giftopnm: GIF Width = 1 GIF Height = 1 Pixel aspect ratio = 0 (1.000000:1) giftopnm: Colors = 2 Color Resolution = 3 giftopnm: Color map doesn't contain grays, doesn't contain colors giftopnm: got a 'Graphic Control' extension giftopnm: transparent background color: rgb:00/00/00 Index 0 giftopnm: reading 1 by 1 GIF image giftopnm: too much input data, ignoring extra... giftopnm: writing a PBM file P4 1 1

간단히 0x10을 제거하니 위의 경고 오류는 사라지더군요.


호기심이 생겨서 검색을 더 해보니 스택오버플로우에 다음과 같은 링크를 찾을 수 있었다. http://stackoverflow.com/questions/2570633/smallest-filesize-for-transparent-single-pixel-image 질문에 대한 http://stackoverflow.com/a/15960901/1696120 답변 내용에 의하면 가장 작은 크기의 투명 GIF 이미지는 32바이트라고 되어있습니다. 답변을 살펴보면, The Global Color Table can be removed safely by disabling it in the Logical Screen Descriptor라고 되어있는데 즉 Logical Screen Descriptor를 조정해서 Global Color Table을 제거할 수 있다는 내용이 있습니다.


xxd를 이용해서 바이트열로 변환해 살펴보면 다음과 같습니다.


0000000: 4749 4638 3961 0100 0100 0000 0021 f904  GIF89a.......!..
0000010: 0100 0000 002c 0000 0000 0100 0100 0002  .....,..........

이것을 43바이트 GIF 이미지와 쉽게 비교하기 위해 다시 써보면

0000000: 4749 4638 3961 0100 0100 8000 0000 0000  GIF89a..........
0000010: ffff ff21 f904 0100 0000 002c 0000 0000  ...!.......,....
0000020: 0100 0100 0002 0144 003b                 .......D.;

즉 Logical Screen Descriptor 값이 0x00으로 되어있고, Global 컬러 테이블을 없애서 6바이트가 줄어들었습니다. 또한 마지막의 바이트도 삭제되어있는데, 위의 답변 내용에 보면 Only 3 bytes of the LZW data are required and the bytes can be almost anything. Though only the first byte of 0x02 is strictly required. 라고 되어있으며 LZW 데이터인 0x02 이후의 0x10, x44,0x00 3 바이트가 제거되어 있으며, 마지막으로 파일의 끝을 알려주는 0x3b 1바이트 총 4바이트가 제거되어있습니다.


단, 32바이트의 경우에 원문에는 대부분의 브라우저에서 작동한다고 되어있으나, giftopnm 혹은 gifinfo(libgif) 등등의 프로그램으로 테스트할 경우에 오류를 뿜었습니다.

32바이트의 GIF는 http://stackoverflow.com/a/15960901/1696120 에 다음과 같이 나오며, 이는 대부분의 브라우저에서 잘 작동합니다.

GIF 스펙(http://www.w3.org/Graphics/GIF/spec-gif89a.txt)을 보니 GIF89a버전의 경우 옵션으로 처리 가능하므로, GIF스펙상 문제 없는 웹버그는 34바이트까지가 가능합니다.)


http://u229.no/stuff/GIFFormat/ 이 문서에 의하면 몇가지 조정이 가능한 값들이 있는데 예를 들어 delay time 값이 2바이트인데, 이 값을 조정하면 65,536가지의 조합을 만들 수 있습니다. 또한 Background color index가 안쓰이는데, 이 값을 조정하게되면 2^24(16,777,216)개의 조합도 가능합니다. (더 많은 개수의 웹버그 조합이 필요하다면 컬러테이블 6바이트를 추가한 42바이트짜리 GIF를 만들어서 이미지 색상 팔레트값을 임의로 조정하면 될 것입니다.)

웹버그를 만들어주는 간단한 PHP 스크립트를 같이 참고하시기 바랍니다.

<?php
$transparency = 1;
printf("GIF89a\1\0\1\0%c%c\0%s,\0\0\0\0\1\0\1\0\0%s",
0x00 /* packed field */,
0xff /* background color index */,
($transparency ? pack("c8",0x21,0xf9,4,1,0,0,0,0) : ''),
"\2\1\x44\0\x3b"); # LZW data 4 bytes + ";"


by dumpcookie 2015. 10. 16. 10:10

https://github.com/xpressengine/xe-core/pull/1598


XE는 느리다는 악평이 많은 편입니다. XE는 여러가지 현대적인 프레임워크 형태이면서 트리거, 애드온 위젯 등등의 여러 인터페이스를 통해서 확장이 가능한 형태이고, 이러한 확장성때문에 느리다고 하더라도 많은 불만이 있지는 않은 것 같습니다.

그러나 XE는 몇가지만 고치더라도 클라우드플레어나 varnish 캐서서버 혹은 nginx 리버스 프록시 캐시서버를 통해서 그 성능을 획기적으로 향상시킬 수 있습니다. 그것도 아주 간단한 패치를 하는 것 만으로 이러한 성능향상이 가능합니다.

그것은 다름아닌 캐시서버의 적중률을 방해하는 대표적인 요소인 Set-Cookie: 헤더를 최소화 하는 것입니다.

Set-Cookie:헤더는 세션이 시작되는 경우 및 쿠키를 설정하는 경우에 설정이 됩니다. 웹 서비스를 하게되면 대부분의 사용자는 로그인사용자가 아닌 익명의 사용자이고, 로봇등에 의해서 상당량의 접속이 있게됩니다. 이러한 상당수의 익명의 클라이언트의 경우 세션을 세팅하지 않거나 쿠키 헤더를 쓰지 않는것 만으로도 캐시서버의 적중률을 높일 수 있습니다.

XE의 경우에도  session_start()가 무조건적으로 호출되고 있습니다. 이 경우 Set-Cookie:PHPSESSID=... 쿠키가 설정이 되며 varnish 캐시서버가 효율적으로 작동하지 않게 됩니다. 아래는 varnish 캐서서버 + XE + nginix (php5-fpm) 설정을 한 서버에 XE를 설치한 후에 살펴본 헤더입니다.

$ curl -I  http://localhost/
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
X-Powered-By: PHP/5.5.9-1ubuntu4.9
Cache-Control: public, must-revalidate, post-check=0, pre-check=0
Set-Cookie: PHPSESSID=1g12hp22q8r0ojbjog7seaft26; path=/
Accept-Ranges: bytes
Date: Thu, 09 Jul 2015 00:54:35 GMT
X-Varnish: 1471733958
Age: 0
Via: 1.1 varnish
Connection: keep-alive
X-Backend-Server: nginx/1.4.x (Ubuntu)
Server: nginx/1.4.x (Ubuntu)
X-Cache-Hit: MISS

위에서 보는바와 같이 varnish 캐시서버에서 캐시가 미스가 되고 있습니다.

ab 벤치마크입니다. 1000회를, 동시에 3개 요청하는 경우입니다. 상당히 느리고 이것이 현재 XE를 nginx + varnish 캐시서버로 운용할때의 대략적 성능입니다. (혹은 단순히 nginx 혹은 아파치 웹서버만 운용하는 경우)

$ ab -n 1000 -c 3  http://localhost/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
....
Requests per second:    74.85 [#/sec] (mean)
Time per request:       40.079 [ms] (mean)
Time per request:       13.360 [ms] (mean, across all concurrent requests)
Transfer rate:          2533.88 [Kbytes/sec] received
...

그러면 이번에는 XE 성능을 100배 향상시키는 속도 패치를 적용한 후에 테스트를 해보겠습니다. 즉 session_start()가 기본적으로 호출이 안되고 Set-Cookie 헤더가 붙지 않게 되며, 캐시 적중률이 올라가게 됩니다.

$ curl -I  http://localhost/
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
X-Powered-By: PHP/5.5.9-1ubuntu4.9
Cache-Control: public, must-revalidate, post-check=0, pre-check=0
X-Backend-Status: normal
Date: Thu, 09 Jul 2015 00:59:14 GMT
X-Varnish: 1471735403 1471735392
Age: 6
Via: 1.1 varnish
Connection: keep-alive
X-Backend-Server: nginx/1.4.x (Ubuntu)
Server: nginx/1.4.x (Ubuntu)
X-Cache-Hit: HIT

(몇차례 curl -I를 실행하면 Cache-Hit가 HIT 상태임을 나타내고 있습니다.)

ab 벤치마크 측정

$ ab -n 1000 -c 3  http://localhost/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
....
Requests per second:    8018.35 [#/sec] (mean)
Time per request:       0.374 [ms] (mean)
Time per request:       0.125 [ms] (mean, across all concurrent requests)
Transfer rate:          269428.95 [Kbytes/sec] received
...

8000 RPS로 아파치 벤치값으로 비교하여 무려 ~100배가 향상되었습니다.

동시에 200개 요청을 하고 20000회 요청을 해면 다음과 같습니다.

$ ab -n 20000 -c 200  http://localhost/
...
Requests per second:    14025.98 [#/sec] (mean)
Time per request:       14.259 [ms] (mean)
Time per request:       0.071 [ms] (mean, across all concurrent requests)
Transfer rate:          473883.72 [Kbytes/sec] received

무려 14000 RPS나 나옵니다. 초당 470MB/s
동시에 500개 요청을 해도 결과는 비슷합니다. (ab -n 20000 -c 500)

더 놀라운 것은 시스템의 부하 상태입니다. (ab -n 20000 -c 500을 처리하고 난 이후)

$ w
 10:04:25 up xx days, 17:28,  x user,  load average: 0.25, 0.19, 0.18

그러면 nginx만의 성능은 어떨까요 ? ab -n 10000 -c 50 http://localhost:8000/ 1만회 50개 요청을 동시에 해보았습니다.

$ ab -n 10000 -c 50  http://localhost:8000/ (ngix가 8000 포트로 설정되어 있음)
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
....
Requests per second:    106.64 [#/sec] (mean)
Time per request:       468.850 [ms] (mean)
Time per request:       9.377 [ms] (mean, across all concurrent requests)
Transfer rate:          3570.91 [Kbytes/sec] received
....

~100RPS 수준이고 3MB/s 밖에 안나옵니다.

그리고 시스템 부하상태입니다.

$ w
 10:09:01 up xx days, 17:33,  x user,  load average: 3.64, 1.45, 0.63

현재 이 패치는 https://github.com/xpressengine/xe-core/pull/1598 PR로 등록되어 있으며 반영되기를 기다리고 있습니다. 또한 XE 포럼에도 글을 등록하였습니다. https://www.xpressengine.com/forum/23040664

XE3 개발때문인지 그다지 반응이 시근둥한 편입니다만 많은 관심가져주시기 바랍니다~

참고로 현재 이 패치는 리그베다위키 BBS에 적용되어 운용중입니다.

by dumpcookie 2015. 7. 19. 04:00

세션($_SESSION쿠키)의 사용은 캐시 서버의 캐시 적중률에 방해를 일으켜서 캐시서버 성능을 저하시키는 원인이 됩니다.

https://github.com/gnuboard/gnuboard5/pull/15

이 패치는 그누보드에서 무조건적으로 session_start()가 호출되는 부분을 변경하여. session_start()가 선택적으로 호출되게 하여 캐시서버의 캐시 적중률을 높여줍니다.

이 패치를 적용하고 난 후에 다음과 같은 줄을 커맨트아웃시킨 후에 아파치벤치의 성능을 살펴보겠습니다.

// common.php의 다음 줄을 찾아서 커맨트아웃
// 방문자수의 접속을 남김
#include_once(G5_BBS_PATH.'/visit_insert.inc.php');

위 visit_insert.inc.php는 중복 접속이 카운팅되는 것을 막기위해 쿠키를 사용하고 있는데, 쿠키를 사용하는 부분때문에 캐시 적중률을 떨어뜨리고 있습니다. 이 부분은 다른 방식으로 고쳐야만 그누보드가 좀 더 캐시서버 친화적이 될 수 있습니다.

패치 적용하지 않은 경우

$ ab -n 1000 -c 30 "http://g4.xxxxx.xxx"

Requests per second:    1854.10 [#/sec] (mean)
Time per request:       16.180 [ms] (mean)
Time per request:       0.539 [ms] (mean, across all concurrent requests)
Transfer rate:          21510.47 [Kbytes/sec] received
...

~1800RPS 및 21MB/s가 나오고 있습니다.

패치 적용 후

이 패치를 적용한 후에 중복 접속 카운팅 부분을 커맨트아웃 시키고
varnish 캐시서버 + 그누보드5 + nginx (php4-fpm 세팅) 성능을 아파치벤치마크 성능이 다음과 같이 나옵니다.

$ ab -n 1000 -c 30 "http://g4.xxxxxx.xxxx/"
...
Requests per second:    12976.40 [#/sec] (mean)
Time per request:       2.312 [ms] (mean)
Time per request:       0.077 [ms] (mean, across all concurrent requests)
Transfer rate:          148354.17 [Kbytes/sec] received
...

~12000RPS 및 148MB/s 속도가 나오고 있습니다.

접속회수 1만회 동시접속을 300까지 늘려보면

$ ab -n 10000 -c 300 "http://g4.xxxxxx.xxxx/"
...
Requests per second:    17307.54 [#/sec] (mean)
Time per request:       17.333 [ms] (mean)
Time per request:       0.058 [ms] (mean, across all concurrent requests)
Transfer rate:          197871.10 [Kbytes/sec] received
...

서버 로드상태

$ w
 01:30:28 up xx days,  8:54,  x users,  load average: 0.13, 0.08, 0.11

단순히 아파치벤치 성능상으로 최대 ~8배의 성능 향상이 있음을 볼 수 있습니다.


이 패치는 현재 https://github.com/gnuboard/gnuboard5/pull/15 에 등록시켜 반영을 기다리고 있는 중입니다. (http://sir.co.kr/g5_tip/2964 글도 같이 올렸습니다.)

by dumpcookie 2015. 7. 19. 03:38

예전에 엔하위키 시절 모니위키가 감당못할정도로 느려졌을 무렵, 모니위키는 텍스트기반이라서 DB기반과는 다르기때문에 느리다는 주장을 인터넷에서 종종 보아왔다. 그 당시 엔하위키가 느렸던 것은 버그로 밝혀졌고, 그것을 고친 이후에도 그와 관련된 버그가 여전히 말끔하게 고쳐지지 않아서, 20만 페이지가 되었을 무렵에도 특정 병목 현상때문에 모니위키가 느려지는 현상이 있었고, 이러한 문제가 발생하면 어김없이 이런 얘기가 나왔다. 모니위키는 텍스트 기반이라서 느리다.


이미 이와 관련된 이야기를 여기서 한차례 언급했지만, 이번에는 좀 더 구체적으로 모니위키가 왜 당시에 엔하위키에서 느렸었는지 그 이유를 좀 더 자세히 밝혀보고, 현재 30만 페이지의 규모인 리그베다위키로 그 당시 불가능해 보였던 모니위키가 현재 어떻게 사용되고 있는지 등등을 써보려고 한다.


이 이상은 보여주지 않는다구 엔하위키 시절에 서버가 터졌을때에 올라오곤 했던 짤방



페이지 개수 매크로의 문제

그 당시 구버전의 모니위키의 경우 페이지 개수를 세는 매크로가 호출되면 디렉토리의 파일 개수를 모두 세었다. 사실 요즈음 파일 시스템 성능이 워낙 좋아져서, 30만 페이지가 가까이 되는 경우 ls 명령으로 시간을 재면 성능이 별로인 서버에서는 10초 미만으로 든다. 성능이 괜찮은 서버라면 훨씬 더 빠르다. (time ls text |wc) ls 명령을 쓰지 않고 간단히 PHP로 짜면 몇초가 들까? 놀랍게도 대폭 줄어들어 1초 수준이 된다. 게다가 이게 캐싱도 된다. 한번 명령을 내리면 1초라면 다음 한번 더 명령을 내리면 0.5초 미만으로 대폭 줄어든다. 정확한 지표는 아니겠지만 이정도로 빠르다.


그런데 모니위키의 속도 문제를 겪었던 구 버전의 경우에 페이지 개수만 세는 방식이 아니였다. 모든 페이지 목록을 가져오고 난 다음에 그 개수를 세었다. 5천 페이지 미만에서는 이 방식도 아주 빠르고 문제 없었지만 1만 페이지 이상 넘어가니 이게 문제가 되었던 것이다. 그래서 페이지 목록을 가져오지 않고 단순히 파일 개수를 세는 방식으로 바꾸었던 것이다. (변경 내역은 https://github.com/wkpark/moniwiki/commit/0ee14c6362ae945e8363ea975fb5721364f2d3eb#diff-10732b52856680bd57ef6fcab3abbbcf 참조)

구 엔하위키의 경우 예전 방식이 문제가 될 수 밖에 없었던 것은, 페이지 오른쪽 상단에 페이지 개수를 항상 보여주고 있었기 때문인데, 페이지 개수 세는 매크로가 테마에서 호출되어졌고, 테마는 페이지를 보여줄때마다 매번 페이지 세는 매크로를 호출했던 것이고, 더군다나 해당 매크로는 한번 갯수를 센 결과를 저장하지 않았기때문에 지속적인 CPU 부하의 원인이 되었던 것이다.


랜덤 페이지의 문제

아무튼 이렇게 페이지 개수를 세는 방법을 고치고나니 속도가 대폭 개선되었다. 그런데 또 다른 문제가 있었는데 그것은 랜덤페이지의 문제였다. 엔하위키시절부터 랜덤페이지는 꽤 인기있는 기능이어서 모니위키의 핫키 기능을 사용하여, 키보드의 "a"를 눌러서 랜덤페이지를 보며 여행하고는 했다. 그런데 이 랜덤페이지도 페이지 목록을 전부 가져와서 그중에 랜덤 페이지 하나를 고르는 방식이였다. 랜덤페이지 기능 자체가 이렇게 리소스를 차지하고 무거운 기능인데, 이 기능을 많이 이용하니 전체적으로 시스템의 로드가 올라갈 수밖에 없었다. 눈치챈 분이라면 페이지 개수를 세던 방식과 동일하게 페이지 전체 목록을 가져오는 문제가 있었다. 즉, 두 문제는 사실상 같은 문제였던 것이다.


그래서 랜덤페이지 기능을 개선하기 위해서 페이지 이름만을 따로 목록을 만드는 방식을 쓰게끔 고쳤다. 매번 페이지 목록을 만들어 읽어들이는것이 아니라, 페이지 목록을 미리 만들어서 텍스트로 저장하고, 랜덤으로 페이지를 고를 때는 전체 페이지 개수에 대해 랜덤으로 페이지 몇개를 고르고 정해진 개수의 페이지만을 리턴한다. 그런데 이런 단순 무식한 방법을 써도 꽤 빨랐고 문제점이 비로소 해결된 듯 보였다. 전페 페이지 목록을 미리 만들어두니, 20만 페이지가 되어도 전체 페이지 목록은 이미 만들어둔 파일을 읽기만 하면 된다. 30여만 페이지의 페이지 목록 파일은 5메가 수준이지만, 별 걱정할 정도로 큰게 아니였고, 랜덤 페이지 기능은 매우 빨랐다. 그 이후로는 이 무식한 방식이 리소스를 많이 쓰는 것을 보완하기 위해서 페이지 목록에 대한 작은 인덱스를 미리 만들어서, 전체 페이지 목록을 모두 읽어들일 필요가 없도록 고쳤다.


리소스를 최소로 사용하기

그리고 한동안 속도문제는 크게 문제가 되지 않다가 20여만 페이지 규모가 되었을 2013년 당시에도 상당히 사이트가 느려져 있었다. 그래서 원인을 점검해보니 역시나 페이지 개수를 세는 문제와 비슷한 문제로 인해 리소스를 많이 쓰고 있던 것이 문제였다. 그래서 고친 것은 다음과 같았다.

  • 페이지가 업데이트되면 매번 페이지 개수를 다시 세는 방식 => 페이지가 추가되거나 삭제되면 페이지 카운팅만 가감.
  • 페이지가 업데이트되면 매번 페이지 목록을 갱신하던 방식 => 페이지가 추가/삭제되면 목록에서 해당 페이지만 추가/삭제.

별것도 아닌 고침이였지만 리소스 사용률이 줄어서 훨씬 나아졌다. (https://github.com/wkpark/moniwiki/commit/91bb73078e7e2ac7f6092d315b93942855062167 참고) 그리고 뒤를 이은 일련의 고침들은 리소스 사용률과 CPU 부담을 최소로 하면서 최소한의 검색 기능이 작동하도록 하는 고침이였다.



검색의 문제

우선 그동안 제대로 지원하지 않던 검색을 보완했다. 제목 검색은 의외로 매우 단순한 방식으로 해결하였다. 모니위키의 경우에는 페이지 개수가 작은 경우에는 별 문제 없이 제목 검색에 regex를 쓸 수 있었다. 그런데 20여만 페이지 규모가 되니 MySQL같은 DBMS에 20만여개의 페이지에 대한 제목을 넣고 index를 생성한다고 할지라도 regex 검색을 하기는 어렵다. regex 검색을 하면 row를 모두 타야 검색이 되는 것이다. 그래서 간단한 Like 검색밖에 쓸 수 없다. 그러나 그 대신에 20여만 페이지의 파일 이름을 텍스트 파일로 만든 후에, 이 텍스트 파일을 적당히 잘라내어 읽어서 regex로 검색하면 놀라울 정도로 빠르게 20만 페이지의 제목을 모두 검색해낸다. 자모 검색을 할때에도 굳이 별도로 한글 자모 인덱스를 만들 필요조차 없었다. (리그베다위키는 현재 이 방식을 사용중이다)


하지만 본문 검색은 이 방식을 쓸 수 없었고 DBMS 없이 쉽게 지원하기는 어려웠다. 일단 개인 사용자라면 MySQL이나 DBMS에 익숙하지 않을 것을 가정하고 PHP 자체적으로 지원하고 있는 SQLite를 쓰거나 버클리DB를 이용하기로 하였다. 버클리DB를 이용한 간단한 n-gram PHP 검색 엔진이 있었기때문에 이를 고쳐서 모니위키에 적용했는데, 5만여 페이지일 경우에는 꽤 빠르게 검색이 되었는데, 20만 페이지 이상이 되니 이것도 그리 빠르지가 않았다. 그래서 일단은 아예 작동이 안되던 본문검색을 제한적으로나마 작동이 되도록 먼저 고쳤다. 검색을 하는 경우에는 5천여개의 페이지 목록만 가져와서 5천여개의 페이지의 본문만 검색하도록 하는 것이였다. 이러한 제한이 있는 대신에 regex를 사용할 수 있는 유연함을 가지고 있다. 물론 5천개의 검색 결과에서 못 찾으면 그 다음의 5천 페이지에 대한 검색을 하기 위해서, 다음 그 다음 버튼을 눌러야 하는 불편한 방식이다. 사용자가 검색어를 너무 간단한 것을 넣으면 검색 결과가 너무 많이 나오기때문에 사용자는 알아서 검색어를 수정할 것이며, 운 좋게 사용자가 원하는 페이지가 나올 수도 있다. (하지만 이런 방식으로는 부족했기때문에 Elastic Search 엔진을 달아서 테스트해보았다. Elastic Search엔진은 매우 유연하고 모니위키에 적용하기도 쉬웠고 속도도 적당히 빠른 수준이였지만, 결국엔 리그베다위키에 적용되지는 않았고, Elastic Search 엔진은 아직 실험중인 상태에 머물고 있다.)


이렇게 페이지를 몇천 페이지 단위로 잘라서 검색하는 간단한 방식을 쓰니 리소스도 적게 들고 원하는 반응 속도를 얻기 위한 적절한 타협점을 찾을 수 있었다. 속도를 얻기 위해 검색의 완전한 기능은 포기한 셈이다. dba를 사용한 tri-gram 검색엔진도 같이 만들었고 꽤 쓸만했지만 이는 모니위키에 아직 적용하지 못했으며, 그 대신에 이번에 모니위키 1.2.5 게발버전이 되어서야 MySQL을 통한 fulltext 서치를 본문 검색을 옵션을 제공하게 되었으며 30여만 페이지에 대한 검색을 비로서 대응할 수 있게 되었다. (미디어위키 역시 MySQL의 MYISAM 엔진의 FULLTEXT 인덱스를 사용함. 보다 최신의 MySQL은 InnoDB엔진에서도 FULLTEXT 지원)


개인위키를 위한 위키엔진과 RCS 버전 관리

모니위키는 사용자가 별로 좋지 않은 서버나 열악한 호스팅 환경을 가정으로 한다. 최신 PHP도 설치되어 있지 않고, MySQL 지원도 없는 값싼 호스팅 환경, 혹은 PC에서도 문제 없이 사용할 수 있는 개인 사용자에게 초점이 맞춰져 있다.

이러한 상황에서 MySQL지원은 당연히 첫번째 단계에서부터 제외된다. 모니위키가 텍스트기반이 될 수 밖에 없었던 첫번째 이유가 바로 모니위키는 개인위키를 목표로 하고있다는 것이다.

그런데 이러한 부분은 적은 리소스밖에 없는 상황에서도 굴러갈 수 있게 만드는 장점이 있다. 엔하위키 초창기에 모니위키를 선택했던 이유는 다름아닌 소규모 위키를 운영하려고 시작했던 것이 그 원인일 수 있다. 과거 트래픽이 부족해서 엔하위키를 오후에는 쓸 수 없는 경우가 많았다고 하니 어느정도 소규모로 처음에 운영했는지 알만하기는 하다.


두번째로 고려할 수 밖에 없는 부분은 위키엔진의 핵심적인 부분이기도 한 버전 컨트롤 시스템의 선택이다. 그 당시에 많은 위키엔진들은 텍스트 기반이 상당수가 있었고, 그중에 중대규모 위키엔진으로는 TWiki라는 둘째 가라하면 서러워할 유명한 위키엔진이 있다. 트위키는 RCS를 버전 컨트롤 시스템으로 채택하였다. 비단 TWiki뿐만 아니다. 상당수의 위키엔진은 RCS를 쓰고 있었는데 이것이 RCS를 자연스럽게 선택하게된 이유였다.

게다가 파이썬(Python)으로 만든 모니위키의 모체가 된 모인모인(MoinMoin)의 가장 큰 단점을 익히 알고있기도 했다. 모인모인은 자체적인 버전 컨트롤을 하고 있었는데, 모인모인은 과거 버전의 기록을 모두 완전한 텍스트로 저장하고 있었다. 과거 변경 이력을 보려면 과거 히스토리 파일을 모두 뒤져서 보여준다. 이게 소규모일 경우에는 별 문제 없지만, 몇천 페이지만 되어도 문제가 발생하게 된다. 히스토리 보기는 파일의 히스토리가 쌓일 수록 점점 느려졌고, 계속 거의 비슷한 복사본이 쌓이게 되는 구조였다. (노스모크는 이러한 문제점을 해결하기 위해서 주기적으로 과거의 히스토리를 지워버리고는 했다;;; 심지어 사용자의 편집 기록이 별도로 저장되는 editlog마져도 주기적으로 날려버렸다...) 최종버전 텍스트가 1GB라고 하고, 과거 변경 이력이 평균잡아 10개라고만 해도 벌써 10GB에 이르는 용량이 된다. 느리고 리소스도 많이 잡아먹는 이러한 자체적 버전관리 방식의 단점을 알고 있었기때문에 모니위키는 자체적인 버전관리 시스템을 사용하지 않고, RCS 버전관리 시스템을 당연한 듯 사용하게 되었다.


이번에 모니위키 1.2.5를 개발하면서 리그베다위키 서버를 잠시 살펴볼 수 있는 좋은 기회를 가지게 되었는데, 리그베다위키의 경우에는 28만 페이지의 규모에 최종 텍스트 버전이 2.8GB 수준이였으며 (tar.gz으로 압축하니 880MB), RCS 히스토리 파일의 개수는 32만개 정도 되고 그 용량은 12GB가 되었고, tar.gz로 압축하니 2.5GB가 되었다. 단순히 산술적으로 평균 버전 개수가 10개일 경우에 28GB 용량이 될 것이라는 예상을 깨고 그 절반도 안되는 12GB라는 것은 RCS 히스토리 파일의 효율성을 잘 보여준다. RCS는 최종 버전을 고스란히 가지고 있으며, 편집 로그도 자체적으로 보관하고, 각 버전간의 변경분인 delta만을 보존하고 있다. 뿐만아니라 리그베다위키는 editlog도 잘 보존하고 있었다. 2012년 이전의 기록은 안타깝게도 시스템 이전을 하다가 날려버리는 불상사를 겪었으나, 그 이후의 editlog는 모두 보존하고 있었으며 그 기록에는 2012년부터 2015년 5월까지의 편집 기록이 무려 7백4십만건이나 되었다. (editlog 파일은 아파치 로그처럼 일반 텍스트로 저장되므로 유닉스의 wc -l 명령으로 간단히 그 편집 항목수를 알 수 있다. 리그베다의 이러한 기록들을 분석한 결과는 다른 글로 보고할 기회가 있으면 한다)


RCS를 쓰지 않고 자체적으로 버전관리 방식을 쓰는 도쿠위키의 경우에는 과거의 버전을 온전한 텍스트 형태로 보존하되 압축해서 저장하고 있고, 미디어위키 역시 자체적인 버전관리방식을 쓰는데, 과거 버전이 모두 온전한 텍스트로 저장된다. @ditto님의 wikistat (http://sapzil.org/wikistat/)을 살펴보면 한국어 위키백과의 경우 사용자/토론 등등의 페이지를 모두 합쳐서 리그베다위키에 비해 3배 이상의 100백만 페이지를 가지고 있고, 이를 단순히 산술적으로 계산해도 10GB가 되며, 온전한 텍스트로 과거 이력을 보관하고 있기때문에 평균잡아 페이지당 버전이 10개만 되어도 전체 페이지 용량이 100기가 바이트를 넘기고 있다.



리소스 부족리소스가 부족합니다



문제는 리소스

위키엔진은 사실 매우 단순한 형태라서 DB schema가 복잡할 이유가 전혀 없다. 검색도 제목검색, 본문검색 수준의 기본적인 작업만 된다면 문제될 것이 별로 없다. 제목은 UNIQ하고 index타기에 좋아 검색에 매우 유리하고, 가장 리소스를 잡아먹을 부분은 본문 검색 뿐이다. 본문검색의 경우 기능도 별로 없는 MySQL을 쓸것이 아니라 전문적인 검색엔진인 ElasticSearch을 쓰면 루씬과 같은 고급 형태소분석 엔진을 같이 사용하는게 가능하다. 위키엔진 그 자체는 사실 DBMS보다는 오히려 key-value 형태의 NoSQL이 유리하다 할 수 있다.

미디어위키처럼 DBMS를 써야하는 이유는 다른 것이 원인일 수는 있다. 수많은 사용자들에 대한 데이터베이스가 필요하고, 반달러의 어뷰징에 대비하고 IP를 블럭시키고 사용자에 대한 적절한 대응을 하려면 사용자의 패턴 및 과거 페이지 변경 이력에 대한 추적이 필요할 것이고, 이를 위해서라면 DBMS를 당연히 써야할 수밖에 없어 보인다.

그러나 모니위키는 개인위키를 목표로 하기때문에 이러한 사용자 추적이나 관리 기능이 사실상 전무하고 지원하고 있지도 않았다.


문제는 리소스와의 싸움이다. 모니위키가 DBMS 기반이 아니기때문에 모니위키가 느리다는 말은 전혀 잘못된 말이다. 리소스를 잘 활용하면 DBMS가 전혀 없는 상황에서도 대규모 위키를 지원하는 것이 가능하고, DBMS가 필요한 경우가 있고 그렇지 않은 경우가 있으며, DBMS를 쓰더라도 리소스 관리를 잘못하면 적은 자원으로는 서비스 하기가 곤란한 상황이 된다. 위키백과 같은 경우에는 리소스 부족하면 서버 더 사고, 하드디스크 싸니까 부족하면 붙이고, 메모리 늘리면 된다는 식으로 쉽게 말할 수 있지만 그게 그리 쉬운 말인가? 최소의 자원으로 최대의 효과를 내려는 노력은 당연한 것이고, 모니위키가 지금까지 DBMS를 쓸 필요를 느끼지 않았던 것은 이것만으로도 제 효과를 충분히 내었다고 판단했기 때문이다. 앞으로 MySQL과 같은 검색엔진의 지원이 추가되었으니, 규모에 맞춰서 선택할 수 있는 폭도 넓어졌고, 모니위키는 앞으로도 개인위키를 목표로 하고, 더 사용하기 쉬운 간단한 위키엔진을 추구할 것이다.


여전히 파일시스템 기반의 기조를 지킬생각이라고 누군가가 질문한다면 그렇다고 대답하겠다. 사실 지금의 파일시스템 성능은 과거 10년과 비교가 안될 정도로 좋아진 성능을 자랑한다. 리눅스의 파일 시스템 자체가 개속 개선되고 있는 상황이라는 것이다. 과거에는 DBMS만의 장점으로 꼽히던 일부 기능을 파일시스템 자체기능으로 포함시켜버리려는 시도도 있다. DBMS만 개선되는게 아니라 그만큼 파일시스템 그 자체도 진화하고 있다는 것이다. 그러한 와중에도 역시 리소스를 적게 차지하는 쪽은 파일시스템쪽이며, 특히나 버전관리 시스템의 대부분은 파일시스템을 사용한다. git / SVN / CVS / mecurial 등등의 버전관리 시스템은 모두 파일시스템을 사용한다. SVN은 약간 특이한 케이스인데, 구 버전의 SVN은 버클리DB기반의 버전 컨트롤을 사용했고, 이것이 자주 깨지는 등의 문제가 발생하자 FSFS라는 파일시스템 기반의 버전관리를 지원하게 되었다. (자세한 내용은 http://web.mit.edu/ghudson/info/fsfs를 참고) 그밖에 파일시스템이 더 유리한 경우는 https://www.eldos.com/solfs/articles/7853.php?page=all 문서를 참고하라. RCS보다 더 훌륭한 파일기반이 아닌 버전관리 시스템이 있다면 상황이 달라질 수 있겠지만, 위키위키에서만큼은 아직은 RCS가 버전관리 시스템으로 가장 훌륭해 보인다. (사실 git을 가지고 실험해본 적이 있다. 하나의 파일에 대해서 1000개의 버전을 생성시켜서 저장했더니 RCS이 경우에는 최종 텍스트 크기는 46KB, RCS 히스토리 파일은 820KB정도밖에 안되는 반면, git의 경우에는 46KB 최종 본문 크기 동일하지만 .git 크기가 19MB나 되었다...)


리그베다 위키와 모니위키의 개발 방향은..

모니위키처럼 개인위키를 표방하는 위키엔진이 30여만 페이지를 운영하고 있는 예는 사실 거의 없다. 도쿠위키는 본인의 실험에 의하면 5천 페이지에서 조차도 본문 검색이 너무 느렸다. (못하다는 뜻으로 말하는게 아니다. 그러한 대응이 필요했다면 이미 도쿠위키는 고쳤을 것이다. 다만 아직 필요하지 않아서 고칠 필요성을 못느끼는 것이다.) 유명한 중대규모 위키인 트위키조차도 이런 큰 규모의 위키를 운영하고 있지 않고, 트위키 개발 사이트조차도 사실 너무 느리다. 트위키 역시 대규모 위키에 대한 적절한 대응책을 마련하지 않은 것이다. 이것은 DBMS를 쓰느냐 쓰지 않느냐의 문제가 전혀 아니다.


모니위키는 이번 리그베다위키 사건으로 말미암아 기로에 서게되었지만 다른 한편으로 모니위키 및 모니위키 사용자에게 소식도 있다. 리그베다측에서는 본인의 개발에 필요할 수 있는 리그베다 운영시에 겪었던 여러 문제점을 알려주고, 서버를 개방하여서 본인이 직접 문제를 테스트할 수 있는 테스트 베드를 제공함과 동시에, 모니위키의 안정화를 위한 테스트과정에 참가한 테스터들의 협력을 앞으로도 이어나가며 안정화된 기능을 점진적으로 개방해 나아갈 것이라는 것이다.

뿐만 아니라 그간의 운영을 위해 자체적으로 개발하던 소스와 운영 노하우를 모니위키의 적용할 수 있도록 제공하고 적극 협조해 준다고 하였다. 이는 수많은 모니위키 개인 사용자에게 환영받을 소식임과 동시에 모니위키를 사용하고 있는 커뮤니티위키에도 좋은 소식이 된다.


이러한 것이 모니위키의 코드를 더 복잡하게 만들것이라 생각하는 분들도 계실것이다. 물론 단기적으로 봤을때에는 그렇게 보일 수도 있겠다. 그러나 모니위키는 그 코어 코드를 점점 줄이고 특정 기능을 확장하기 쉬운 구조로 개선하게 될 것이며, 작은 규모의 위키위키에서 고려하지 않았던 부분이 커다란 위키가 되었을 때에 리소스 문제가 발생할 수 있는 부분을 해결해 나아가며, 보안에 관련된 부분은 점점 보강하게 될 것이다.


그간 모니위키는 리그베다와 지극히 한정적인 형태의 협업을 하고 일년에 한두번 할까 말까했던 소통을 하였으나, 이번에 예정된 모니위키 1.2.5 개발판의 경우 리그베다에 100% 적용되었으며, 이제 리그베다위키의 소스는 99.9%가 모니위키와 일치하고, 그 나머지 부분도 모니위키에 반영되거나 더 간단한 방식이 적용될 것이다. 리그베다는 더이상 모니위키 소스를 별도로 관리할 필요가 없어지게 된 것이고, 보안에 대한 문제가 발생하였을 경우에 신속한 대처를 할 수 있게 되었다. 모니위키 1.2.5의 베타테스팅은 리그베다위키와 함께 진행되며, 테스터들과 직접 소통을 하며 의견을 조율하여 모니위키 소스에도 직접 반영할 수 있다는 것을 의미한다.


마치며

리그베다위키 사건으로 시작되었던 일련의 일들이 사실 상당히 혼란스럽고 보는 사람 입장에서도 당혹스럽지만, 이것을 일반 사용자 측면에서 본다면, 리그베다측이 그간 사용자의 요구를 제대로 반영하지 못했다는 점이 분명한 것 같다. 저작권 부분이 가장 이슈화 되었지만, 어디 저작권 뿐이겠는가. 위키백과의 경우에도 아무런 문제 없는 듯이 보이지만, 각 개별 기여자들이 당연히 누려야할 부분은 누리지 못하고 소외된 느낌이다. 위키백과는 누가 기여했는지 그 기여목록을 보려면 특수 기능을 눌러야 한다. 레퍼런스를 달려고 하면 기여자의 이름은 보이지 않고 Wikipedia contributors 라고만 나온다. 히스토리를 봐야 기여자의 이름을 볼 수 있을 뿐이다. 기여자의 링크나 연락처는 쉽게 알 방법이 없다. 하다못해 홈페이지나 이메일 주소도 일일히 찾아봐야 한다. 기여자에게 모든 저작권이 있다고 말하지만 정작 기여자에게 돌아가는 것은 없어보인다.

모니위키는 앞으로 리그베다와 기술적인 협력을 통해서 리그베다위키의 기여자들에게 돌아가야 할 마땅한 권리를 보장하려고 노력하고, 기술적인 것 뿐만 아니라 오픈소스/자유소프트웨어의 가치실현 및 위키위키의 공유정신을 실현해 나아가는 모습을 보여줄 수 있게 되기를 바란다.

by dumpcookie 2015. 6. 12. 11:48

PHP 경험이 많은 분들이라면 한번정도는 PHP 확장 모듈을 만들어 보았을 것이다. 본인의 경우는 비로서 최근에 PHP 확장 모듈을 만들어 보았는데 다음의 내용이 게중에 가장 쉬웠다. 뚝딱 테스트 해보는데에 20분이면 충분했다.

http://devzone.zend.com/303/extension-writing-part-i-introduction-to-php-and-zend/

위 사이트에서 복사해서 붙이기만 하고 최초 작동 테스트 완료 하는데에 20분 걸렸으니, 더 간단하고 쉬운 문서를 만들면 좋겠다 싶어서 문서를 더 찾아보았다. 그래서 찾은 문서는 exman으로 유명한 박준철님이 작성하신 다음 문서이다. (박준철님의 사이트는 닫혀있었으나 문서는 다른 곳에서 찾을 수 있었다)

http://srctalk.imfree.co.kr/view.ife?seq=268


이 문서의 특징은 PHP로 확장모듈을 만드는 프로그래머가 실제로 확장 모듈을 만들게 될 상황에 맞춰서 구성되었다는 점이다.

그리고 또 찾은 문서는 다음과 같은데, 문서가 길지 않고 매우 짧고 쉬워서 그대로 따라하기 쉽다.

http://eddmann.com/posts/introduction-to-creating-a-basic-php-extension/


이정도 문서만 참고하더라도 10분안에 나만의 PHP 확장을 만들고 최소한의 테스트를 완료할 수 있을 것 같은데, 열거한 문서를 읽어본 사람이라면 다음의 내용을 따라해서 좀 더 빨리 확장 모듈을 만들어보고 테스트 할 수도 있다.


스크립트 실행 준비

먼저 원하는 디렉토리로 위치로 가서 새로 디렉토리를 만든다음 그곳에서 스크립트를 실행시킬 준비를 한다. 이때에 적절한 프로젝트 이름을 생각해둔다. 여기서는 그 이름을 foobar로 하겠다.

cd ~/home
mkdir php-foobar
cd php-foobar

스크립트 받기

디렉토리에 들어가서 (여기서는 php-foobar) 다음과 같이, 복사하고 붙이기를 최소화 하기 위하여 미리 만들어둔 쉘 스크립트를 다운로드 받아서 압축을 푼다.

php_ext.sh.gz


이 스크립트는 PHP 소스에 포함되어있는 ext_skel 스크립트와 하는 기능은 거의 같지만, 더 단순한 기능만 하고, 실행 즉시 곧바로 아무것도 건드리지 않고 phpize를 통해 컴파일 할 수 있다는 점이 다르며, skeleton 디렉토리에 의존하는 스크립트가 아니기때문에 아무데서나 사용할 수 있다.

스크립트 압축 풀기

스크립트의 압축을 푼다.

gunzip php_ext.sh.gz

스크립트 실행

디렉토리에 들어가서 (여기서는 php-foobar) 다음과 같이 스크립트를 실행시킨다.

sh php_ext.sh foobar

그러면 config.m4, foobar.c, php_foobar.h 세개의 파일이 생성된다.


컴파일하기

콘솔에서 phpize를 실행하면 각종 파일이 자동으로 생성되며, configure를 실행하고 난 후에 make 한다.

phpize
sh configure
make
...

테스트하기

콘솔에서 다음의 명령을 실행시켜보자

php -dextension=.libs/foobar.so -r "foobar_world('hello');"

화면에 "Hello hello"라고 뜨면 정상 작동한 것이다. 테스트 종료.


아마 이대로만 정확히 따라했다면 5분안에도 만들고 테스트 완료 했을 것이다. 그러나 원하는 기능을 만들기 위해서는 이제 몇십배 더 많은 시간을 투자해야 할지도 모른다!!

by dumpcookie 2015. 5. 2. 10:36

그간 몇차례 그누보드4를 설치하고 사용자 모듈을 만들어보려고 시도를 했었습니다. 그런데 그누보드4가 short open 태그여서 수정하기 귀찮음으로 그만두고는 하다가, 이번에 간단한 스크립트를 돌려서 short open 태그를 모두 고친 후에 설치를 하고 사용자 모듈을 만들어 봤습니다.


사용자 모듈은 다음과 같은 형태입니다.

<?php

class User_g4 extends WikiUser {

    function g4_init() {
        global $g4, $member, $g4_root_dir;

        $g4_root_dir = !empty($DBInfo->g4_root_dir) ?
                $DBInfo->g4_root_dir : __DIR__.'/../../../gb4';

        require_once('g4.common.php');
        $member = g4_get_member();
    }

    function User_g4($id = '') {

여기서 g4.common.php는 그누보드의 루트 디렉토리에 있는 common.php를 간소화시킨 버전으로 다음과 같은 형태입니다.


<?php
/**
 * simplified common.php by wkpark @ gmail.com
 * 2015/04/29
 */

function g4_get_member() {
    global $g4, $g4_root_dir;
...
include_once("$g4_root_dir/lib/constant.php");  // 상수 정의
include_once("$g4_root_dir/config.php");  // 설정 파일
include_once("$g4_root_dir/lib/common.lib.php"); // 공통 라이브러리
include_once("$g4_root_dir/dbconfig.php");

$connect_db = sql_connect($mysql_host, $mysql_user, $mysql_password);
$select_db = sql_select_db($mysql_db, $connect_db);
...

이런 식으로 연결을 한 후에 그누보드 함수 get_menber()로 사용자를 가져오게 됩니다. 자세한 내용은 소스를 참고하시기 바라며, 소스 다운로드 및 사용 설명은 http://moniwiki.kldp.net/wiki.php/G4UserPlugin.를 참조하시기 바랍니다~

by dumpcookie 2015. 4. 29. 18:36
| 1 2 3 4 5 ··· 7 |