썬 스튜디오 dbx 디버거의 가장 유용한 디버깅 기능중에 하나는 프로그램 실행 중에 감시점(Watchpoint) 를 활성화 할 수 있다는 것입니다. 감시점은 또한 데이타 변경 중지점(breakpoint) 라고도 불리며 dbx 에서 변수의 값이 변하거나 표현이 변했을때 프로그램을 정지 시킬 수 있습니다. 감시점은 중지점과 비슷한데 감시점은 주소 위치가 읽히거나 수정될때 실행을 멈추는 반면 중지점은 특정 장소의 명령이 실행될때 멈춘 다는 것만이 다릅니다.
이 글은 썬 스튜디오 dbx 디버거에서 감시점 설비를 사용할 수 있도록 여러분을 교육시키려는 의도로 쓰여 졌습니다. dbx 디버거는 소스 레벨과 명령어 레벨의 디버깅 모두에서 사용될 수 있습니다.
이와 덧붙여서 솔라리스 커널의 내부 상태가 간단한 D 스크립트로 추적될 수 있는 지를 보여주기 위해 dtrace 가 사용되었습니다.
내부 설명
역사적으로 감시점은 소프트웨어내에 구현되어졌습니다. 그리고 어느정도 프로그램의 실행을 저하 시켰습니다. 새로운 버전의 마이크로프로세서들은 디버그 레지스터가 내장되어져서 dbx 같은 현대의 소프트웨어 디버거들이 하드웨어 감시점을 생성하는 것이 가능해 졌습니다. 하드웨어 감시점은 매우 빠르고 프로그램의 실행 속도를 저하시키지도 않습니다.
예를 들어 인텔과 AMD 아키텍쳐는 8개의 디버그 레지스터를 가지고 있고 이것은 DR0 에서 DR7 까지 입니다. DR0 부터 DR3 까지의 레지스터는 주소 브레이크포인트를 생성하는데 사용될 수 있습니다. 소프트웨어는 가상 (순차적인) 주소를 4개의 레지스터에 적재(load) 할 수 있습니다. 그리고 주소가 명령어 혹은 데이타 참조주소와 일치할때 중지점을 활성화 할 수 있습니다. 디버그 컨트롤 레지스터 (DR7) 는 주소 중지점 레지스터(DR0 부터 DR3 까지) 의 중지점 조건을 생성하는데 사용될 수 있고 각 주소 중지점 레지스터 각각 마다의 디버그 예외를 지정할 수도 있습니다. DR6 은 디버그 상태 레지스터 입니다. 마이크로프로세서는 활성화 디버그 조건이 충족되어서 디버그 예외가 유발될때 디버그 상태를 DR6 에 적재 합니다. 이 레지스터는 프로세서에 의해 절대 초기화 되지 않고 소프트웨어에 의해 내용이 읽혀진 다음에 초기화 되어야 합니다. DR4 와 DR5 레지스터는 예약되었고 소프트웨어에 의해 사용될 수 없습니다.
다행이도 솔라리스 운영체제는 /proc 라고 불리는 잘 정의된 인터페이스를 제공함으로써 개발자가 서로 다른 마이크로프로세서 아키텍쳐의 복잡함으로 부터 지켜 줍니다. /proc 인터페이스를 사용함으로써 dbx 디버거 같은 어플리케이션이 솔라리스 플랫폼상에서 극단적으로 이식성이 좋아 집니다. 솔라리스는 SPARC, x86, 그리고 x64 아키텍쳐 상에서 실행 됩니다.
/proc 인터페이스는 파일 시스템으로 시스템의 각 프로세스와 경량 프로세스(LWP) 의 상태에 접근 할 수 있는 기능을 제공 합니다. 감시점은 /proc 파일 시스템 인터페이스를 통해서 프로세스의 컨트롤 파일을 열은 다음 PCWATCH 커맨드(proc (4) 메인 페이지를 참고하시기 바랍니다)를 전송 함으로써 설정되고 설정 해지 될 수 있습니다. .
PCWATCH 커맨드는 prwatch 자료 구조를 동반하는데 이것은 주소, 영향을 받는 구역의 길이, 그리고 감시될 접근의 종류를 포함 하고 있습니다: 읽기, 쓰기, 실행, 그리고 접근 이전의 정지, 접근 이후의 정지 등.
감시점은 쓰레드 프로세스의 LWP 가 감시되고 있는 구역의 적어도 한 바이트 이상을 메모리 참조 할때 혹은 PCWATCH 커맨드에 의해 지정된 접근 모드와 메모리 참조가 일치 할때 트리거 됩니다.
LWP 가 감시점을 트리거 하면 솔라리스 커널에 의해 생성되는 감시점 트랩 (FLTWATCH) 을 발생시킵니다. 만약 FLTWATCH 가 추적 되면 LWP 는 정지 합니다; 혹은 SIGTRAP 시그널을 전송합니다. 만약 SIGTRAP 이 추적 되고 블럭되지 않는 다면 이때 LWP 가 정지 합니다. 이 지점에서 dbx 디버거가 제어권을 갖고 여러분은 dbx 커맨드를 사용해서 추적되고 있는 프로세스의 상태를 검사 할 수 있습니다.
dbx 디버거에서 감시점 설정하기
메모리 주소가 접근될때 실행을 멈추려면:
(dbx) stop access mode address-expression [, byte-size-expression ]mode 는 메모리가 어떻게 접근 되었는지를 나타냅니다. 이것은 다음의 문자들 중 하나 혹은 복수개로 구성될 수 있습니다:
| r | 지정된 주소의 메모리가 읽혀졌을때 |
| w | 메모리가 쓰여졌을 때 |
| x | 메모리가 실행되었을 때 |
mode 는 또한 다음의 문자들도 포함할 수 있습니다:
| a | 접근 이후 프로세스를 정지 (기본값) |
| b | 접근 이전에 프로세스를 정지 |
address-expression 은 주소를 생성할 수 있는 어떠한 표현식도 지정 될 수 있습니다. 만약 심볼릭 표현식을 주었다면 감시되는 구역의 사이즈가 자동적으로 추론되어 집니다; 여러분은 byte-size-expression 을 지정함으로써 오버라이드 할 수 있습니다. 또한 사이즈가 필수 조건일때 비심볼릭의 비타입지정 적인 주소 표현식을 사용할 수 있습니다.
만약 다음의 커맨드를 입력한다면 실행은 0xfffffd7fffdff7a 가 읽혀 진 다음 정지할 것입니다:
만약 다음의 커맨드를 입력한다면 실행은 변수 local 이 쓰여지기 전에 정지할 것입니다:(dbx) stop access r 0xfffffd7fffdff7a8, 4
(dbx) stop access wb &local
stop 접근 커맨드를 사용할때에는 아래 사항들을 기억하시기 바랍니다:
- 이벤트는 동일한 값이 쓰여지더라도 발생합니다.
- 기본적으로 이벤트는 변수에 쓰는 작업을 하는 명령 실행 이후에 발생됩니다. 모드를 b 로 지정함으로써 명령이 실행되기 전에 이벤트를 발생시키도록 할 수 있습니다.
예전의 stop modify 커맨드가 호환성을 위해서 여전히 수용 됩니다. 그리고 적절한 stop access 커맨드로 매핑됩니다:
(dbx) stop modify address-expression [, byte-size-expression ]
다음의 a.cc 예제에서 우리는 local 변수가 쓰기 작업을 위해 접근 될때마다 프로세스를 정지하려고 합니다.
a.cc 예제 프로그램
#include <stdio.h>
int global = 0;
static int stat = 0;
void poker(int *ip)
{
*ip = 5;
}
main()
{
static int flocal;
int local;
global = 0;
stat = 0;
flocal = 0;
local = 0;
poker(&global);
poker(&stat);
poker(&flocal);
poker(&local);
}
a.cc 예제 프로그램은 아래와 같이 컴파일 될 수 있습니다:
CC -g -m64 a.cc
기본적으로 C++ 컴파일러는 a.out 실행파일을 생성합니다.
이제 a.out 실행 파일에 dbx 디버거를 실행 시키고 local 변수에 데이타 변경 중지점(감시점) 을 지정합니다.
아래는 dbx 디버거의 출력 결과 입니다.
% dbx a.out
For information about new features see `help changes'
To remove this message, put `dbxenv suppress_startup_message 7.6'in your .dbxrc
Reading a.out
Reading ld.so.1
Reading libCstd.so.1
Reading libCrun.so.1
Reading libm.so.2
Reading libc.so.1
(dbx) stop in main
(2) stop in main
Running: a.out
(process id 10452)
stopped in main at line 16 in file "a.cc"
16 global = 0;
(dbx) stop access w &local
(3) stop access wa &local, 4
(dbx) cont
watchpoint wa &local (0xfffffd7fffdff7c8[4]) at line 19 in file "a.cc"
19 local = 0;
(dbx) cont
watchpoint wa &local (0xfffffd7fffdff7c8[4]) at line 8 in file "a.cc"
8 *ip = 5;
(dbx)
위에서 볼 수 있듯이 stop access 커맨드와 쓰기 접근 모드가 local 변수의 감시점을 지정하기 위해 사용되었습니다. &local 구문은 local 변수의 주소를 나타냅니다. local 변수는 4바이트 정수로 정의되었고 그러므로 감시될 구역의 사이즈가 자동적으로 추론되고 커맨드 구문에 추가되어 집니다.
local 변수를 위한 감시점 트랩이 2번 트리거 되어 집니다. 첫번째는 main 메소드에서 local 변수에 0 이 지정됩니다. 두번째에는 poker local 메소드에서 5 가 지정됩니다.
DTrace를 이용하여 감시점 트랩 모니터링하기
이 섹션에서는 DTrace 설비를 사용해서 솔라리스 커널내에서 감시점 트랩이 어떻게 추적 되는지 살펴 봅니다. 여기서는 여러분이 D 스크립트, probe 와 구문들에 익숙하다고 가정합니다. 그렇지 않다면 이 섹션을 더 읽기 전에 다음의 글들을 읽어 보시기 바랍니다: Using DTrace with Sun Studio Tools to Understand, Analyze, Debug, and Enhance Complex Applications./usr/include/sys/falut.h 헤더 파일은 프로세스에서 추적될 수 있는 모든 하드웨어 오류(fault)의 이름들을 포함하고 있습니다. 이 글의 특정한 상황을 위해 우리는 오직 FLTWATCH 오류에 집중할 것입니다. FLTWATCH 오류 혹은 숫자 12가 감시점 트랩 입니다.
다음의 D 스크립트는 하드웨어 오류를 모니터 하기 위해 proc 프로바이더에서 fault probe 를 사용하는 법을 보여 줍니다.
fault.d D 스크립트
#pragma D option quiet
dtrace:::BEGIN {
printf("Tracing hardware faults. Enter <control-c> to end.\n");
}
proc:::fault
{
@[execname, args[0], args[1]->__data.__fault.__addr,
args[1]->__data.__fault.__pc] = count();
}
END
{
printf("%10s %10s %16s %16s %10s\n",
"EXECUTABLE", "FAULT", "ADDRESS", "PC", "COUNT");
printa("%10s %10d %16p %16p %8@d\n", @);
}
fault probe 는 쓰레드가 머신 오류를 만나면 구동 됩니다. fault probe 는 두개의 매개변수를 가지고 있습니다: 오류 코드는 args[0] 에 지정 됩니다. 오류에 맞는 커널 siginfo 구조체가 args[1] 에 의해 지정됩니다.
커널 siginfo_t 구조체는 /usr/include/sys/siginfo.h 헤더 파일에 정의되어 있습니다. siginf_t 구조체는 몇가지 구조체의 union 으로 구성되어 있습니다. 어쨌든 우리들의 예제에서는 __addr 와 __fault 구조체의 __pc 필드만을 살펴 볼 것입니다.
fault.d D 스크립트에서 보듯이 proc::fault 절에서 다음의 표현식들을 기반으로 데이타를 수집하는데 aggregation 이 사용되었습니다:
| execname | 실행파일 이름 |
| args[0] | 오류 코드 |
| args[1]->__data.__fault.__addr | args[1] 는 siginfo_t 구조체의 포인터 입니다. __addr 은 메모리 내의 감시되는 구역의 주소 입니다. |
| args[1]->__data.__fault.__pc | args[1] 는 siginfo_t 구조체의 포인터 입니다. __pc 는 메모리 내의 감시되는 구역을 접근하는 명령의 주소 입니다. |
count() 함수는 프로세스내의 각 오류가 트리거 되는 횟수를 보여 줍니다.
fault.d 스크립트는 개별 터미널 윈도우에서 실행되어야 합니다.
다음의 dtrace 커맨드는 솔라리스 커널의 fault probe 를 활성화 시킵니다:
dtrace -s fault.d
이 지점에서 proc 프로바이더의 fault probe 가 활성화 되고 데이타 수집을 기다 립니다.
이제 개별 터미널 윈도우에서 a.out 실행파일에 dbx 를 실행시키고 이전 섹션에서 살펴본대로 동일한 커맨드의 순서를 입력해서 local 변수의 데이타 변경에 대한 정지점을 지정합니다.
dtrace 커맨드가 실행된 터미널 윈도우에서 보여지는 대로 <control-c> 커맨드는 fault.d 스크립트의 실행을 종료 합니다. DTrace는 다음의 출력을 생성합니다:
% dtrace -s fault.d
Tracing hardware faults. Enter <control-c> to end.
^C
EXECUTABLE FAULT ADDRESS PC COUNT
a.out 3 401048 0 1
a.out 3 fffffd7fff3ce570 0 1
a.out 4 401053 0 1
a.out 4 fffffd7fff3ce571 0 1
a.out 12 fffffd7fffdff7c8 401030 1
a.out 12 fffffd7fffdff7c8 401069 1
a.out 3 fffffd7fff3ce540 0 2
FAULT 컬럼은 a.out 프로세스에서 추적된 모든 하드웨어 오류들을 나타 냅니다. FLTBPT 오류 혹은 숫자 3은 정지점 트랩입니다. FLTTRACE 오류 혹은 숫자 4는 추적 트랩(single-step) 입니다. 그러나 이전에 언급했던 대로 우리는 오직 FLTWATCH 오류 혹은 숫자 12 에만 집중할 것입니다.
fault.d 스크립트의 출력을 바탕으로 a.out 프로세스는 0xfffffd7fffdff7c8 주소에 감시접 트랩을 두번 발생 시켰습니다. 이미 예상 했듯이 0xfffffd7fffdff7c8 는 메모리 내의 local 변수의 주소 입니다 (이전 섹션의 dbx 출력 결과를 보시기 바랍니다).
두개의 명령어 주소, 0x401030 와 0x401069 가 PC (Program Counter) 컬럼에 나타납니다. 이 두개의 명령어들은 감시된 구역(0xfffffd7fffdff7c8)을 참조하는 메모리 참조를 포함하고 있습니다. 그러므로 감시점 트랩이 이 명령어들에 대해 트리거 되었습니다.
다음 단계로는 이러한 명령어들이 어떠한 것들인지 확인하는 것입니다. dbx 를 통해 코드를 디스어셈블 해서 0x401030 와 0x401069 명령어 주소의 어셈블리 코드를 검사할 수 있습니다.
여기서는 여러분이 dbx 의 명령어 레벨 디버깅 커맨드에 익숙하다고 가정합니다. 그렇지 않다면 이 섹션의 나머지 부분을 진행 하기 전에 다음의 글을 읽어보시기 바랍니다: AMD64 Instruction-Level Debugging with Sun Studio dbx.
아래는 dbx 의 출력 결과 입니다. dis 커맨드가 0x401030 와 0x401069 명령어 주소에 해당하는 코드들을 디스어셈블 하는데 사용 되었습니다. regs 커맨드는 일반적인 범용 레지스터를 출력하는데 사용 됩니다.
(dbx) cont
watchpoint wa &local (0xfffffd7fffdff7c8[4]) at line 8 in file "a.cc"
8 *ip = 5;
(dbx) dis main
0x0000000000401040: main pushq %rbp
0x0000000000401041: main+0x0001: movq %rsp,%rbp
0x0000000000401044: main+0x0004: subq $0x0000000000000010,%rsp
0x0000000000401048: main+0x0008: movl $0x0000000000000000,global
0x0000000000401053: main+0x0013: movl $0x0000000000000000,stat
0x000000000040105e: main+0x001e: movl $0x0000000000000000,__1fEmain1AGflocal_
0x0000000000401069: main+0x0029: movl
$0x0000000000000000,0xfffffffffffffff8(%rbp)
0x0000000000401070: main+0x0030: movq $global,%rdi
0x0000000000401077: main+0x0037: movl $0x0000000000000000,%eax
0x000000000040107c: main+0x003c: call poker [ 0x401020, .-0x5c ]
(dbx) dis poker
0x0000000000401020: poker : pushq %rbp
0x0000000000401021: poker+0x0001: movq& %rsp,%rbp
0x0000000000401024: poker+0x0004: subq $0x0000000000000010,%rsp
0x0000000000401028: poker+0x0008: movq %rdi,0xfffffffffffffff8(%rbp)
0x000000000040102c: poker+0x000c: movq 0xfffffffffffffff8(%rbp),%r8
0x0000000000401030: poker+0x0010: movl
$0x0000000000000005,0x0000000000000000(%r8)
0x0000000000401038: poker+0x0018: leave
0x0000000000401039: poker+0x0019: ret
0x000000000040103a: poker+0x001a: nop
0x000000000040103c: _ex_deregister+0x01f4: nop
(dbx) regs
current frame: [1]
r15 0x0000000000000000
r14 0x0000000000000000
r13 0x0000000000000000
r12 0x0000000000000000
r11 0xfffffffffbc01ec8
r10 0x0000000048fe9d0a
r9 0x00000000000015da
r8 0xfffffd7fffdff7c8
rdi 0xfffffd7fffdff7c8
rsi 0xfffffd7fffdff7f8
rbp 0xfffffd7fffdff7b0
rbx 0xfffffd7fff3fac40
rdx 0xfffffd7fffdff808
rcx 0x0000000000093182
rax 0x0000000000000000
trapno 0x0000000000000001
err 0x0000000000000000
rip 0x0000000000401030:poker+0x10 movl
$0x0000000000000005,0x0000000000000000(%r8)
cs 0x0000000000000053
eflags 0x0000000000000286
rsp 0xfffffd7fffdff7a0
ss 0x000000000000004b
fs 0x0000000000000000
gs 0x0000000000000000
es 0x000000000000004b
ds 0x000000000000004b
fsbase 0xfffffd7fff382000
gsbase 0x0000000000000000
(dbx)
위에서 보듯이 a.cc 프로그램의 8번째 줄에 있는 poker 메소드 내에 *ip 포맷 파라미터에 숫자 5 가 지정 될때 감시점이 트리거 되었습니다.
유사하게 동일한 할당 작업이 어셈블리 레벨에서 관찰될 수 있습니다. 0x401030 의 movl 명령이 %r8 레지스터를 참조 해제 하고 주소가 0xfffffd7fffdff7c8 (local 변수) 인 변수에 5를 할당 했습니다.
결론
하드웨어가 지원하는 dbx 의 감시점은 매우 빠르고 아주 어려운 소프트웨어 결점을 찾아 내는데에 매우 유용합니다. 감시점은 데이타 변경 중지점 이라고도 불리며 dbx 에서 변수 혹은 표현식이 변경되었을때 프로그램을 정지시키는데 사용될 수 있습니다.
DTrace 설비는 이전에는 절대로 할 수 없었던 방법으로 솔라리스 커널의 내부 상태를 모니터링 할 수 있도록 도와 줍니다. 이글에서 볼 수 있듯이 간단한 D 스크립트는 실행 시에 어플리케이션과 솔라리스 커널관 상호 작용이 어떻게 이루어 지는지 볼 수 있습니다.
마지막으로 dbx 와 DTrace 를 동시에 사용함으로써 여러분 어플리케이션에 존재하는 불확실한 소프트웨어 결점 혹은 솔라리스 커널 그 자체의 결점의 실마리를 풀 수 있는 환상적인 디버깅 환경을 만들수 있습니다.
이 아티클의 영문 원본은
http://developers.sun.com
에서 볼수 있습니다.



전체

댓글을 달아 주세요
댓글을 쓰시려면 로그인해주세요.