http://jhrogue.blogspot.com/2009/05/20.html
소개받은책.
살까나
http://sig9.com/articles/att-syntax
http://sourceware.org/binutils/docs-2.16/as/index.html
Updated: May/10 '06
This article is a 'quick-n-dirty' introduction to the AT&T assembly language syntax, as implemented in the GNU Assembler as(1). For the first timer the AT&T syntax may seem a bit confusing, but if you have any kind of assembly language programming background, it's easy to catch up once you have a few rules in mind. I assume you have some familiarity to what is commonly referred to as the INTEL-syntax for assembly language instructions, as described in the x86 manuals. Due to its simplicity, I use the NASM (Netwide Assembler) variant of the INTEL-syntax to cite differences between the formats.
The GNU assembler is a part of the GNU Binary Utilities (binutils), and a back-end to the GNU Compiler Collection. Although as is not the preferred assembler for writing reasonably big assembler programs, its a vital part of contemporary Unix-like systems, especially for kernel-level hacking. Often criticised for its cryptic AT&T-style syntax, it is argued that as was written with an emphasis on being used as a back-end to GCC, with little concern for "developer-friendliness". If you are an assembler programmer hailing from an INTEL-Syntax background, you'll experience a degree of stifling with regard to code-readability and code-generation. Nevertheless, it must be stated that, many operating systems' code-base depend on as as the assembler for generating low-level code.
The structure of a program in AT&T-syntax is similar to any other assembler-syntax, consisting of a series of directives, labels, instructions - composed of a mnemonic followed by a maximum of three operands. The most prominent difference in the AT&T-syntax stems from the ordering of the operands.
For example, the general format of a basic data movement instruction in INTEL-syntax is,
mnemonic destination, source
whereas, in the case of AT&T, the general format is
mnemonic source, destination
To some (including myself), this format is more intuitive. The following sections describe the types of operands to AT&T assembler instructions for the x86 architecture.
All register names of the IA-32 architecture must be prefixed by a '%' sign, eg. %al,%bx, %ds, %cr0 etc.
mov %ax, %bx
The above example is the mov instruction that moves the value from the 16-bit register AX to 16-bit register BX.
All literal values must be prefixed by a '$' sign. For example,
mov $100, %bx mov $A, %al
The first instruction moves the the value 100 into the register AX and the second one moves the numerical value of the ascii A into the AL register. To make things clearer, note that the below example is not a valid instruction,
mov %bx, $100
as it just tries to move the value in register bx to a literal value. It just doesn't make any sense.
In the AT&T Syntax, memory is referenced in the following way,
segment-override:signed-offset(base,index,scale)
parts of which can be omitted depending on the address you want.
%es:100(%eax,%ebx,2)
Please note that the offsets and the scale should not be prefixed by '$'. A few more examples with their equivalent NASM-syntax, should make things clearer,
GAS memory operand NASM memory operand ------------------ ------------------- 100 [100] %es:100 [es:100] (%eax) [eax] (%eax,%ebx) [eax+ebx] (%ecx,%ebx,2) [ecx+ebx*2] (,%ebx,2) [ebx*2] -10(%eax) [eax-10] %ds:-10(%ebp) [ds:ebp-10]Example instructions,
mov %ax, 100 mov %eax, -100(%eax)
The first instruction moves the value in register AX into offset 100 of the data segment register (by default), and the second one moves the value in eax register to [eax-100].
At times, especially when moving literal values to memory, it becomes neccessary to specify the size-of-transfer or the operand-size. For example the instruction,
mov $10, 100
only specfies that the value 10 is to be moved to the memory offset 100, but not the transfer size. In NASM this is done by adding the casting keyword byte/word/dword etc. to any of the operands. In AT&T syntax, this is done by adding a suffix - b/w/l - to the instruction. For example,
movb $10, %es:(%eax)
moves a byte value 10 to the memory location [ea:eax], whereas,
movl $10, %es:(%eax)
moves a long value (dword) 10 to the same place.
A few more examples,
movl $100, %ebx pushl %eax popw %ax
The jmp, call, ret, etc., instructions transfer the control from one part of a program to another. They can be classified as control transfers to the same code segment (near) or to different code segments (far). The possible types of branch addressing are - relative offset (label), register, memory operand, and segment-offset pointers.
Relative offsets, are specified using labels, as shown below.
label1: . . jmp label1
Branch addressing using registers or memory operands must be prefixed by a '*'. To specify a "far" control tranfers, a 'l' must be prefixed, as in 'ljmp', 'lcall', etc. For example,
GAS syntax NASM syntax ========== =========== jmp *100 jmp near [100] call *100 call near [100] jmp *%eax jmp near eax jmp *%ecx call near ecx jmp *(%eax) jmp near [eax] call *(%ebx) call near [ebx] ljmp *100 jmp far [100] lcall *100 call far [100] ljmp *(%eax) jmp far [eax] lcall *(%ebx) call far [ebx] ret retn lret retf lret $0x100 retf 0x100
Segment-offset pointers are specified using the following format:
jmp $segment, $offset
For example:
jmp $0x10, $0x100000If you keep these few things in mind, you'll catch up real soon. As for more details on the GNU assembler, you could try the documentation.
By vivek on 2003-09-01
메일을 받았는데 나중에 보려고 올려둡니다.
출처를 몰라서 표시 못합니다.
-----------------------------------------------------------------------------
CARES 여러분, 영호입니다.
며칠 전 shell script 문법 때문에 여기저기 사이트를 찾다가 프로그래머에게 유용한 사이트들을 모아놓은 블로그가 있어, 여러분도 참고하시라고 보내드립니다.
유용한 사이트들이 많으니 참고하세요
C/C++
ACM - The ABCs of Writing C++ Classes
Guru of the Week - Guru of the Week
STL - Standard C++ Library Tutorial 한글
STL - Standard C++ Library (SGI)
STL - Visual C++ 의 STL Sample
C++ FAQ - C++ FAQ
MSJ - Microsoft Systems Journal
VC++ STL Reference - VC++ STL Reference
Thinking in C++ - Thinking in C++ 온라인 북
코드 구루 - 코드 샘플이 많은 개발관련 사이트
OpenIL - Open Image Library
Win32ASM - Iczelion's Win32 Assembly Homepage
Priority Que & STL - by Mark Nelson (Dr. Dobb's Journal)
STLPort - 범용, 공개, 오픈소스 STL
데이타 압축 - 데이타 압축 관련 링크 모음
C++ Optimize - C++ 최적화 방법에 대한 내용
STL newbie - STL 초보자를 위한 문서
코드프로젝트 - 다양한 장르의 프로그래밍 강좌
MTL - Matrix Template Library
몇몇책들 - Effective C++, More Effective C++, Design Patterns
CPlusPlus - C++ Tutorial
AssemRef - Assembler Programmer's Reference
공짜 C/C++ 컴파일러들 - 공개 C/C++ 컴파일러들에 대한 상세한 목록
어셈러브 - 국내 어셈블리 관련 홈페이지
C++ Online Books - C++ 관련 공짜 온라인 북 링크
STL Document - RogueWave Software 의 STL 튜토리얼 및 레퍼런스
Blitz++ - 객체지향 공학용 수치계산 라이브러리(C++)
행렬 라이브러리 비교 - C/C++ 용 행렬 라이브러리 비교평가
GNU/Linux
리눅스사랑넷 - 리눅스사용자라면 꼭 가봐야 할 사이트
KLDP - 리눅스사용자라면 꼭 가봐야 할 사이트
리눅스시스템관리 - 리눅스 시스템 관리자를 위한 홈페이지
Debian-KR 메일링 리스트 - 데비안-KR 메일링 리스트 아카이브
Trinux - 디스켓 3장에 들어가는 리눅스
linux-firewall - linux-firewall
certcc.or.kr - 한국정보보호센터
쉘프로그래밍 - 쉘프로그래밍매뉴얼
SAINT - 보안분석툴
securityfocus.com - 보안관련정보
정규표현식설명 - 정규표현식에 대한 설명
Thinkpad Tool - Thinkpad Notebook Linux Configuration Tool
네트워크프로그래밍 - BeeJ's Guide to Network Programming
webalizer - 웹 로그 분석툴
MRTG - 서버 네트워크 통계 프로그램
VirtualPC - 윈도우에서 리눅스를 깔 수 있다는
IBM Linux - 한국 IBM의 리눅스 기술문서 번역 및 자료
Linux C - Linux C 프로그래밍에 관한
헤커되기 - 헤커가 되는 법(에릭.S.레이몬드)
데비안사용자 - 국내 데비안 사용자 그룹
리눅스 가제트 - 리눅스관련 웹진
프렉 - 헤킹관련 웹진
CygWin - GNU + Cygnus + Windows
GNU GPL FAQ - GPL 에 관련된 빈번한 질문과 답
phpBB - PHP, MySQL 로 만드는 커뮤니티. 디자인이 깔끔.
KTUG - Korea TeX Users Group
KLDP 닷넷 - 소스포지와 같은 국내의 오픈소스 프로젝트 서비스 제공
Emacs
Emacs-KR 홈페이지 - 최고의 에디터인 Emacs 의 사용자 모임
Emacs 설치 - 이맥스 윈도우즈용/유닉스용 설치에 관한...
정재목씨의 Emacs - 정재목씨의 Emacs 관련 페이지
NT Emacs - Windows 95/98/NT 용 Emacs 설명
Elisp Manual - Emacs Lisp Programming
Elisp intro page - Elisp Introduction & link
Elisp Reference - Elisp Reference Manual
VisEmacs - Visual Studio 내장 에디터로 Emacs 를 사용하게 해 줌
개발툴
Doxygen - C++ 개발 문서화 도구
HeaderDoc - C/C++ 헤더파일을 HTML 로 문서화(펄)
CVS Home - 버젼 컨트롤 시스템
RCS - GNU Revision Control System
CS-RCS - 윈도우용 RCS. 1인용은 공짜
ViewCVS - CVS 레파지터리를 웹상에서 보기
ActiveState - ActivePerl, ActiveTCL 등등
Cetus링크 - 프로그래밍관련 방대한 링크
ZipArchive - C++ Zip 압축 라이브러리(소스동봉, zlib 사용)
zlib - zip 라이브러리
Data Compression Lib - 데이터 압축 관련 정보(인덱스, 링크, 소스)
GNU Win32 - Win32 용 각종 GNU 라이브러리 소스(libjpeg, crypt, freetype, zlib 등등)
루아 - 엘레강트하고, 심플하며, 빠르고, 가벼우며, 확장성이 용이한 스크립트 언어
공개 컴파일러들 - 각종 언어에 대한 공개 컴파일러 목록
CGShaders - C for Graphics. 사용자그룹을 가장한 공식홈
UPX - 실행 프로그램 압축 유틸리티. 여러분의 프로그램이 작아집니다.
oggvorbis - 오그 보비스 플러그인, SDK 및 소스 다운로드
MATH
Math Fun Facts - 수학의 재미있는 사실들
AKROWNE's Home - 수학관련 유용한 정보(책 미러 많다)
Math of DFD - Mathematics of DFD(Discrete Fourier Transform)
그리스문자 읽는법 - 그리스 문자를 읽는 방법
Numerican Recipes in C - Numerican Recipes in C 온라인 북
Mech-World - 기계공학관련(동역학,수치해석) 강의노트 및 관련정보
Forgodot - 미적분학 원격 강의
NetLib - 수학관련 문서 및 소프트웨어 모음집
MathBook - Online Books and Lecture Notes in Mathematics
물리의 이해 - 경상대학교의 물리학 노트. 플래쉬 및 자바애플릿.
수학사랑 - 수학교사들의 연구단체
대한 수학회 - 대한민국 수학 학회.
PlanetMath - Wikipedia+MathWorld+Slashdot
MathWorld - 수학 백과 사전. 방대한 자료 상세한 설명.
GIF math - 각종 수학 식에 대한 GIF 이미지 모음
개발자개인홈
Kano - 실시간그래픽스에 관심있는 일본개발자. GPG 일본판 번역자
Ádám Moravánszky - ShaderX 에 Bump Mapped BRDF Rendering 파트집필
Thomas Jakobsen - Game Engine, Physics, Character Animation, Denmark
Pion - 파연님 개인홈. 게임 개발 및 번역.
Cass Everitte - nVidia 사에서 일하는 카스 에버릿. 몇몇 오픈지엘 예제
Hoppe - 메쉬 최적화 및 기타 유용한 자료들. MS 사 댕김.
Newtype - MaxScript, D3D 등등의 여러 개발관련 정보.
Kaliver - 이주형님 개인홈. ASE Viewer 및 Qu Engine
OS가 지원해야 하며 일단 제가 알기로는 Linux(2.6부터) gcc와 MSVC에서 지원되는데요,
TLS - Thread Local Storage 라고 불립니다. 시스템콜로 구현되는 것 같더군요.
일단 구현된 내용은 차치하고 사용법은 다음과 같습니다.
gcc의 경우만 설명
| __thread int my_thread_var; static __thread int my_static_thread_var; |
__thread 키워드로 구분되며 이렇게 선언한 변수는 쓰레드마다 가지게 됩니다.
관련 내용 링크
... 또 다른 변화는 TLS (Thread Local Storage) 시스템 호출의 도입이다. 쓰레드 레지스터로 사용될 수 있는 GDT (Global Descriptor Table) 엔트리를 한 개 이상 할당 할 수 있다. GDT는 CPU 기반이고 엔트리는 쓰레드 기반이다.
TLS - Thread Local Storage http://purewell.egloos.com/3398289
* GCC 메뉴얼 보면 TLS가 아니라 TSD(Thread Storage Duration)이라고 표현하였는데 대충 같은 말이다.
이것을 위해 POSIX는 pthread_key_create, pthread_get/setspecific 등 함수를 마련해놨지만 눈만 팽글팽글 돌고, 소스만 지저분해져 보일 것 같다. 귀찮으면 C99, C++98 표준 __thread 키워드를 사용하자.
~$ /lib/libc.so.6
...
Thread-local storage support included. << 이부분이 보인다면 현재 glibc가 TLS를 지원하는버젼입니다.
/dev/mem에 mmap을 쓰면 시스템의 물리 메모리에 접근 가능합니다.
| #include <stdio.h>
#define REALVIEW_SYS_BASE 0x10000000 int main( void ) fd = open("/dev/mem", O_RDONLY | O_SYNC); |
[root@target2 ~]# ./mmaptest
mapped addr= 0x40158000
p_sys100hz = 0x40158024
*p_sys100hz = 0x396fe45
p_sys24mhz = 0x4015805c
*p_sys24mhz = 0x778161b0
이렇게 쓰면 됩니다.
/dev/mem을 open하고 mmap을 이 fd에 대해 하면 됩니다.
gcc에서만 일단 사용이 가능하지만 한가지 흥미있는 키워드를 발견했다. __builtin_expect 라는 키워드다 다음과 같이 사용할 수 있다.
| void* p = malloc(100); // if (p == NULL) // error(); if (__builtin_expect(p == NULL, 0)) error(); |
위와 같은 경우는 아주 많이 접할 것이다. 그런데 사실 포인터 p가 NULL인 경우는 잘 없다. 99.99%는 항상 p != NULL가 될 것이다. 이럴 경우 __builtin_expect는 매우 유용하게 사용할 수 있다.
이 키워드는 p == NULL의 값이 *대부분* false가 될 것이 라고 컴파일러에게 알려준다. 그래서 CPU가 좀 더 효율적으로 명령어를 fetch할 수 있도록 한다. 물론 그렇다고해서 프로그램의 정확성, 즉 p가 NULL이면 error가 실행되라는 것까지 해치지는 않는다. 단순히 프로그램의 성능 향상을 위한 키워드이다.
pthread 소스 보면서 찾았네요.
pthread_mutex_lock 소스가 상당히 기네요..
glibc-2.7/nptl/pthread_mutex_lock.c
| int ....... |
이게 LinuxThreads와 NPTL의 소스가 다 있어서 한참을 검색했는데
이렇게 하면 나오네요. NPTL을 쓰는군요
| $ /lib/libpthread.so.0 Native POSIX Threads Library by Ulrich Drepper et al Copyright (C) 2006 Free Software Foundation, Inc. |
데비안에서 소스 받기는
| $ dpkg-query -S /lib/libpthread.so.0 |
http://barriosstory.blogspot.com/2008/03/arm-linux-kernel-system-call.html
먼저 커널의 다음 3 파일을 수정하여 새로운 system call을 추가하자.
1. arch/arm/kernel/calls.S
2. include/asm-arm/unistd.h
3. 해당 system call을 구현 할 부분(따로 분리해서 구현해도 상관없고 기존의 아무 소스 파일에나 구현해도 상관없다. 단지 makefile만 잘 수정해준다면)
마지막으로 해당 system call을 사용하는 application을 작성하여야 한다.
http://www.4ellene.net/tt/1168
system call 추가 방법
1. vi /usr/src/linux/arch/i386/kernel/entry.S
: system call 번호를 추가
.long SYMBOL_NAME(sys_mysyscall) // 240
2. vi /usr/src/linux/include/asm-i386/unistd.h
#define __NR_mysyscall 240
3. vi /usr/src/linux/kernel/test.c
#include <linux/kernel.h>
asmlinkage int sys_mysyscall()
{
printk("Hello linux\n");
return 2007;
}
4. vi /usr/src/linux/kernel/Makefile
obj-y = sched.o dma.o fork.o exec_domain.o panic.o printk.o \
module.o exit.o itimer.o info.o time.o softirq.o resource.o \
sysctl.o acct.o capability.o ptrace.o timer.o user.o \
signal.o sys.o kmod.o context.o test.o
5. vi app.c
#include <asm-i386/unistd.h>
#include <errno.h>
_syscall0( int, mysyscall );
int main()
{
int i;
i = mysyscall();
printf("%d\n", i );
return 0;
}
6. cc -I/usr/src/linux/include app.c
7. 커널 컴파일
시스템 콜을 추가하는 하고자 하는데 잘안되네요
시스템 콜 함수까지는 제대로 구현되는 거는 같은데
그것을 test하는 user application이 잘되지 않네요
_syscall2(int, pedagogictime, int, flag, struct timeval *, thetime);
이런식으로 해주고 user application을 작성해서 컴파일 해주면
'__NR_pedagogictime' undeclared라고 나오네요
분명히 include/asm/arch/unistd.h에서 추가해줬는데 말입니다.
그런데 여러 자료를 보니까 include/asm-arm/unistd.h를 변경해준다고 하고
그런데 거기에 include에 가보니까 asm~~이런 것들이 굉장히 많은데
이것들의 차이는 무언가요 제가 맞게 바꾼거는 맞나요??
혹시 arch디렉토리가 없지 않나요?
인텔칩을 사용한다면 include/asm-i386/unistd.h 파일을 수정해주세요.
저 파일에
#define __NR_eventfd 323 #define __NR_pedagogictime 324 #ifdef __KERNEL__ #define NR_syscalls 325
asm-???? 들은 각 CPU별로 어셈블리어가 다르기 때문에 CPU별로 나뉘어 있는 것입니다.
프로파일링이나 함수 래핑으로 lock등의 작업을 가로채서 로깅할 때 호출한 쪽의 PC값을 알고 싶을 경우가 있습니다.
현재실행되는 함수 입장에서 보면 리턴 주소이죠
이 때 쓸 수 있는 함수가 gcc의 builtin함수로 __builtin_return_address() 입니다.
__builtin_return_address(level)
여기서 level은 call stack 에서 몇번째를 리턴할 것인지에 대한 값입니다.
__builtin_return_address(0) 하면 현재함수의 리턴주소가 나옵니다.
__builtin_return_address(1) 하면 나를 호출한 함수의 리턴주소가 나옵니다...
예제 코드를 짜봤습니다.
| #include <stdio.h> int my_func1() int main(void) |
이 소스코드를 컴파일해서 objdump해보면
| 080483d1 <my_func0>: 08048403 <main>: 804845c: e8 70 ff ff ff call 80483d1 <my_func0> |
이와 같이 호출하게 되어있고
실행해보면
| backtrace test |
이렇게 나옵니다. call 바로 다음 주소--리턴 주소가 나오지요.
이외에도 몇가지 builtin함수들이 있습니다.
예를 들어 __builtin_frame_address(level)은 프레임 포인터를 반환합니다.
관련 포스트 :
커널 2.6부터 지원됩니다.
프로세스는 sched_setaffinity()가 있습니다.
쓰레드는 pthread_setaffinity_np()가 있습니다
커널의 소스에 보면 migration하는 경우에도 cpu affinity를 보고 설정된 CPU가 아니면 하지 않습니다.
그러므로 …_setaffinity()를 호출하면 해당 CPU에 고정되는 것이 맞습니다.
Hotplug시 affinity 설정된 모든 CPU가 꺼진 경우만 예외적으로 affinity를 변경합니다.
[code]
static void sched_migrate_task(task_t *p, int dest_cpu)
{
migration_req_t req;
runqueue_t *rq;
unsigned long flags;
rq = task_rq_lock(p, &flags);
if (!cpu_isset(dest_cpu, p->cpus_allowed)
|| unlikely(cpu_is_offline(dest_cpu)))
goto out;
....
static
int can_migrate_task(task_t *p, runqueue_t *rq, int this_cpu,
struct sched_domain *sd, enum idle_type idle,
int *all_pinned)
{
/*
* We do not migrate tasks that are:
* 1) running (obviously), or
* 2) cannot be migrated to this CPU due to cpus_allowed, or
* 3) are cache-hot on their current CPU.
*/
if (!cpu_isset(this_cpu, p->cpus_allowed))
return 0;
[/code]
아래 코드를 2.6.24, x86, 4코어에서 테스트했습니다.
( http://www.thinkingparallel.com/2006/08 ··· inity%2F 에 나온 코드를 컴파일 되도록 수정 )
[code]
/* Short test program to test the pthread_setaffinity_np
* (which sets the affinity of threads to processors).
* Compile: gcc pthread_setaffinity_np_test.c
* -o pthread_setaffinity_np_test -lm -lpthread
* Usage: ./pthread_setaffinity_test
*
* Open a "top"-window at the same time and see all the work
* being done on CPU 0 first and after a short wait on CPU 1.
* Repeat with different numbers to make sure, it is not a
* coincidence.
*/
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#define __USE_GNU
#include <pthread.h>
double waste_time(long n)
{
double res = 0;
long i = 0;
while (i <n * 200000) {
i++;
res += sqrt(i);
}
return res;
}
void *thread_func(void *param)
{
unsigned long mask = 1; /* processor 0 */
/* bind process to processor 0 */
if (pthread_setaffinity_np(pthread_self(), sizeof(mask), (cpu_set_t *)&mask) <0) {
perror("pthread_setaffinity_np");
}
/* waste some time so the work is visible with "top" */
printf("result: %f\n", waste_time(2000));
mask = 2; /* process switches to processor 1 now */
sleep(2);
if (pthread_setaffinity_np(pthread_self(), sizeof(mask), (cpu_set_t *)&mask) <0) {
perror("pthread_setaffinity_np");
}
/* waste some more time to see the processor switch */
printf("result: %f\n", waste_time(2000));
return 0;
}
int main(int argc, char *argv[])
{
pthread_t my_thread;
if (pthread_create(&my_thread, NULL, thread_func,
NULL) != 0) {
perror("pthread_create");
}
//pthread_exit(NULL);
pthread_join(my_thread, NULL);
return 0;
}
[/code]
---------------------------------
프로세스의 경우 소스는 다음과 같습니다.
[code]
/* Short test program to test sched_setaffinity
* (which sets the affinity of processes to processors).
* Compile: gcc sched_setaffinity_test.c
* -o sched_setaffinity_test -lm
* Usage: ./sched_setaffinity_test
*
* Open a "top"-window at the same time and see all the work
* being done on CPU 0 first and after a short wait on CPU 1.
* Repeat with different numbers to make sure, it is not a
* coincidence.
*/
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#define __USE_GNU
#include <sched.h>
double waste_time(long n)
{
double res = 0;
long i = 0;
while(i <n * 200000) {
i++;
res += sqrt (i);
}
return res;
}
int main(int argc, char **argv)
{
unsigned long mask = 1; /* processor 0 */
/* bind process to processor 0 */
if (sched_setaffinity(0, sizeof(mask), (cpu_set_t*)&mask) <0) {
perror("sched_setaffinity");
}
/* waste some time so the work is visible with "top" */
printf ("result: %f\n", waste_time (2000));
mask = 2; /* process switches to processor 1 now */
if (sched_setaffinity(0, sizeof(mask), (cpu_set_t*)&mask) <0) {
perror("sched_setaffinity");
}
/* waste some more time to see the processor switch */
printf ("result: %f\n", waste_time (2000));
return 0;
}
[/code]
2010/01/27 01:49 [수정/삭제] [답글]
관리자만 볼 수 있는 댓글입니다.
2010/01/28 19:43 [수정/삭제]
예압