전체 화면 모드로 C 프로그램 디버깅

C에 대한 기본 디버깅 태스크에 대한 설명은 다음 C 프로그램을 참조하십시오.

예제: 디버깅을 위한 샘플 C 프로그램

이 절에서 설명하는 자료와 연관된 자세한 정보에 대해서는 다음 주제를 참조하십시오.

예제: 디버깅을 위한 샘플 C 프로그램

아래 프로그램은 디버깅 태스크를 보여 주기 위해 다양한 주제에서 사용됩니다.

이 프로그램은 문자 버퍼에서 입력을 읽는 단순한 계산기입니다. 정수를 읽은 경우에는 스택에 밀어넣어집니다. 연산자(+ - * /) 중 하나를 읽은 경우에는 맨 위에 있는 두 요소가 스택에서 나가고, 해당 연산자에 대해 연산이 수행된 다음 그 결과가 스택에 밀어넣어집니다. = 연산자는 스택의 맨 위에 있는 요소의 값을 버퍼에 씁니다.

CALC.H

/*----- FILE CALC.H --------------------------------------------------*/
/*                                                                    */
/* Header file for CALC.C PUSHPOP.C READTOKN.C                        */
/* a simple calculator                                                */
/*--------------------------------------------------------------------*/
typedef enum toks {
  T_INTEGER,
  T_PLUS,
  T_TIMES,
  T_MINUS,
  T_DIVIDE,
  T_EQUALS,
  T_STOP
} Token;
Token read_token(char buf[]);
typedef struct int_link  {
  struct int_link * next;
  int i;
} IntLink;
typedef struct int_stack {
  IntLink * top;
} IntStack;
extern void  push(IntStack *, int);
extern int pop(IntStack *);

CALC.C

/*----- FILE CALC.C --------------------------------------------------*/
/*                                                                    */
/* A simple calculator that does operations on integers that          */
/* are pushed and popped on a stack                                   */
/*--------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include "calc.h"
IntStack stack = { 0 };
main()
{
  Token tok;
  char word[100];
  char buf_out[100];
  int num, num2;
  for(;;)
  {
    tok=read_token(word);
    switch(tok)
    {
      case T_STOP:
        break;
      case T_INTEGER:
        num = atoi(word);
        push(&stack,num);     /*  CALC1  statement */
        break;
      case T_PLUS:
        push(&stack, pop(&stack)+pop(&stack) );
        break;
      case T_MINUS:
        num = pop(&stack);
        push(&stack, num-pop(&stack));
        break;
      case T_TIMES:
        push(&stack, pop(&stack)*pop(&stack));
        break;
      case T_DIVIDE:
        num2 = pop(&stack);
        num = pop(&stack);
        push(&stack, num/num2);   /* CALC2  statement */
        break;
      case T_EQUALS:
        num = pop(&stack);
        sprintf(buf_out,"= %d ",num);
        push(&stack,num);
        break;
    }
    if (tok==T_STOP)
      break;
  }
  return 0;
}

PUSHPOP.C

/*----- FILE PUSHPOP.C -----------------------------------------------*/
/*                                                                    */
/* A push and pop function for a stack of integers                    */
/*--------------------------------------------------------------------*/
#include <stdlib.h>
#include "calc.h"
/*--------------------------------------------------------------------*/
/* input:  stk - stack of integers                                    */
/*         num - value to push on the stack                           */
/* action: get a link to hold the pushed value, push link on stack    */
/*                                                                    */
extern void push(IntStack * stk, int num)
{
  IntLink * ptr;
  ptr       = (IntLink *) malloc( sizeof(IntLink));  /*  PUSHPOP1   */
  ptr–>i    = num;                /*  PUSHPOP2  statement */
  ptr–>next = stk–>top;
  stk–>top  = ptr;

}
/*--------------------------------------------------------------------*/
/* return: int value popped from stack                                */
/* action: pops top element from stack and gets return value from it  */
/*--------------------------------------------------------------------*/
extern int pop(IntStack * stk)
{
  IntLink * ptr;
  int num;
  ptr      = stk–>top;
  num      = ptr–>i;
  stk–>top = ptr–>next;
  free(ptr);
  return num;
}

READTOKN.C

/*----- FILE READTOKN.C ----------------------------------------------*/
/*                                                                    */
/* A function to read input and tokenize it for a simple calculator   */
/*--------------------------------------------------------------------*/
#include <ctype.h>
#include <stdio.h>
#include "calc.h"
/*--------------------------------------------------------------------*/
/* action: get next input char, update index for next call            */
/* return: next input char                                            */
/*--------------------------------------------------------------------*/
static char nextchar(void)
{
/*--------------------------------------------------------------------*/
/* input  action:                                                     */
/*    2      push 2 on stack                                          */
/*    18     push 18                                                  */
/*    +      pop 2, pop 18, add, push result (20)                     */
/*    =      output value on the top of the stack (20)                */
/*    5      push 5                                                   */
/*    /      pop 5, pop 20, divide, push result (4)                   */
/*    =      output value on the top of the stack (4)                 */
/*--------------------------------------------------------------------*/
  char * buf_in  = "2 18 + = 5 / = ";
  static int index;     /* starts at 0 */
  char ret;
  ret = buf_in[index];
  ++index;
  return ret;
}
/*--------------------------------------------------------------------*/
/* output: buf - null terminated token                                */
/* return: token type                                                 */
/* action: reads chars through nextchar() and tokenizes them          */
/*--------------------------------------------------------------------*/
Token read_token(char buf[])
{
  int i;
  char c;
  /* skip leading white space */
  for( c=nextchar();
       isspace(c);
       c=nextchar())
    ;
  buf[0] = c;  /* get ready to return single char e.g."+" */
  buf[1] = 0;
  switch(c)
  {
    case '+' : return T_PLUS;
    case '-' : return T_MINUS;
    case '*' : return T_TIMES;
    case '/' : return T_DIVIDE;
    case '=' : return T_EQUALS;
    default:
      i = 0;
      while (isdigit(c)) {
        buf[i++] = c;
        c = nextchar();
      }
      buf[i] = 0;
      if (i==0)
        return T_STOP;
      else
        return T_INTEGER;
  }
}

이 절에서 설명하는 자료와 연관된 자세한 정보에 대해서는 다음 주제를 참조하십시오.

C에서 특정 함수가 호출될 때 정지

이 주제에서는 AT CALLAT ENTRY 명령을 사용하여 루틴을 호출한 직후에 정지하는 방법을 설명합니다. 예제: 디버깅을 위한 샘플 C 프로그램은 이 명령을 설명하는 데 사용됩니다.

AT CALL 명령을 사용하려면 TEST 컴파일러 옵션과 함께 호출 프로그램을 컴파일해야 합니다.

read_token이 호출되기 직전에 정지하려면 다음 명령을 입력하십시오.

AT CALL read_token ;

AT ENTRY 명령을 사용하려면 TEST 컴파일러 옵션과 함께 호출된 프로그램을 컴파일해야 합니다.

read_token이 호출된 직후에 정지하려면 다음 명령을 입력하십시오.

AT ENTRY read_token ;

push가 호출된 직후 num이 16인 경우에만 정지하려면 다음 명령을 입력하십시오.

AT ENTRY push WHEN num=16;

C 변수 값 수정

단일 변수의 컨텐츠를 나열하려면 변수명으로 커서를 이동하고 PF4(LIST)를 누르십시오. 로그 창에 값이 표시됩니다. 이는 명령행에 LIST TITLED variable을 입력하는 것과 같습니다.

예제: 디버깅을 위한 샘플 C 프로그램

 CALC1  명령문까지 위의 CALC 프로그램을 실행한 다음 NUM 위로 커서를 이동하고 PF4(LIST)를 누르십시오. 로그 창에 다음이 표시됩니다.

LIST ( num ) ;
num = 2

num 값을 22로 수정하려면 num = 2 행 위에 num = 22를 입력하고 Enter를 눌러 명령행에 놓은 다음 다시 Enter를 눌러 명령을 실행하십시오.

명령행에 대부분의 C 표현식을 입력할 수 있습니다.

이제 PF2(STEP)를 눌러 push()에 대한 호출로 step into한 다음 PUSHPOP2 명령문에 도달할 때까지 step하십시오. ptr 변수의 속성을 보려면 다음 Debug Tool 명령을 실행하십시오.

DESCRIBE ATTRIBUTES *ptr;

로그 창에 다음과 같은 결과가 표시됩니다.

속성: * ptr
주소: 0BB6E010, 길이: 8
  struct int_link
    struct int_link *next;
    int i;

이 액션을 사용하여 구조와 유니온을 찾아볼 수 있습니다.

다음 명령을 실행하여 ptr이 가리키는 구조의 모든 멤버 값을 나열할 수 있습니다.

LIST *ptr ;

그러면 로그 창에 다음과 같은 결과가 표시됩니다.

LIST *ptr ;
(* ptr).next = 0x00000000
(* ptr).i = 0

다음 예제에서와 같이 명령으로 대입을 실행하여 구조의 멤버 값을 변경할 수 있습니다.

(* ptr).i = 33 ;

조건이 true인 경우에만 C 행에서 정지

프로그램의 특정 부분은 처음에는 대부분 잘 작동하지만 나중에 특정 조건이 발생하여 실패하는 경우가 많습니다. 이때 단순히 행 중단점만 설정하면 프로그램을 디버깅할 때 특정 조건에 도달할 때까지 계속해서 GO 명령을 실행해야 하므로 효율적이지 않습니다. 특정 조건이 나타날 때까지 프로그램 실행을 계속하도록 Debug Tool에 지시할 수 있습니다.

예제: 디버깅을 위한 샘플 C 프로그램

예를 들어, 위 프로그램의 main 프로시저에서 나누기가 0인 경우에만(예외가 발생하기 전에) T_DIVIDE에서 중지하려고 합니다. 다음과 같이 중단점을 설정하십시오.

AT 40 { if(num2 != 0) GO; }

40행은  CALC2  명령문입니다. 이 명령을 실행하면 Debug Tool이 40행에서 중지합니다. num2 값이 0이 아니면 프로그램이 계속 실행됩니다. Debug Tool 명령을 사용하여 num2 값을 0이 아닌 값으로 변경할 수 있습니다.

일부분만 TEST를 사용하여 컴파일된 C 디버깅

예제: 디버깅을 위한 샘플 C 프로그램

PUSHPOP.C 파일의 push() 함수에 대한 시작점에 중단점을 설정하려 한다고 가정해 보십시오. PUSHPOP.C는 TEST를 사용하여 컴파일되었지만 다른 파일은 그렇지 않습니다. Debug Tool에 빈 소스 창이 표시됩니다. 컴파일 단위를 표시하려면 다음 명령을 입력하십시오.

LIST NAMES CUS

LIST NAMES CUS 명령은 Debug Tool에 알려진 모든 컴파일 단위 목록을 표시합니다. 사용 중인 컴파일러에 따라 또는 "USERID.MFISTART.C(PUSHPOP)"가 애플리케이션에 의해 나중에 페치된 경우, 이 컴파일 단위가 Debug Tool에 알려지지 않았을 수 있습니다. 표시되는 경우 다음 명령을 입력하십시오.

SET QUALIFY CU "USERID.MFISTART.C(PUSHPOP)"
AT ENTRY push;
GO ;

또는

AT ENTRY "USERID.MFISTART.C(PUSHPOP)":>push
GO;

표시되지 않는 경우 다음과 같이 appearance 중단점을 설정하십시오.

AT APPEARANCE "USERID.MFISTART.C(PUSHPOP)" ;
GO ;

이 appearance 중단점은 PUSHPOP 컴파일 단위의 함수가 처음 실행될 때 제어를 가져오기 위해 필요합니다. 제어를 가져오면, 다음과 같이 push()에 대한 시작점에 중단점을 설정할 수 있습니다.

AT ENTRY push;

다음과 같이 중단점을 결합할 수도 있습니다.

AT APPEARANCE "USERID.MFISTART.C(PUSHPOP)" AT ENTRY push; GO;

stdout에 C 출력 캡처

로그 창으로 stdout의 방향을 바꾸려면 다음 명령을 실행하십시오.

SET INTERCEPT ON FILE stdout ;

SET 명령을 사용하면 프로그램뿐만 아니라 대화식 함수 호출에서도 stdout을 캡처할 수 있습니다. 예를 들어, 다음 명령을 입력하여 명령행에서 대화식으로 printf를 호출하면 널(null) 종료 문자열을 표시할 수 있습니다.

printf(sptr);

이 방법을 사용하면 LIST STORAGE를 사용할 때보다 쉽습니다.

stdin에 C 입력 캡처

명령 프롬프트에서 입력할 수 있도록 stdin 입력의 방향을 바꾸려면 다음 단계를 수행하십시오.

  1. 다음 명령을 입력하십시오. SET INTERCEPT ON FILE stdin ;
  2. Debug Tool이 scanf와 같은 C문을 발견하면 로그 창에 다음 메시지가 표시됩니다.
    EQA1290I 프로그램이 stdin에서 입력 대기 중입니다.
    EQA1292I INPUT 명령을 사용하여 인터셉트된 가변 형식 파일에
             최대 1000 문자 이상을 입력할 수 있습니다. 
  3. INPUT 명령을 입력하여 입력 데이터를 입력하십시오.

이 절에서 설명하는 자료와 연관된 자세한 정보에 대해서는 다음 주제를 참조하십시오.

Debug Tool에서 C 함수 호출

명령행에서 대화식으로 라이브러리 함수(예: strlen)나 프로그램 함수 중 하나를 호출하여 함수를 시작할 수 있습니다. 기능은 다음 요구사항을 따라야 합니다.

예제: 디버깅을 위한 샘플 C 프로그램

아래에서는 push()를 대화식으로 호출하여 값 하나가 스택에서 나가기 직전에 스택에 값을 하나 더 밀어 넣습니다.

AT CALL pop  ;
GO ;
push(77);
GO ;

스택에 추가 값이 밀어 넣어졌으므로 계산기가 이전과는 다른 결과를 생성합니다.

C의 원시 스토리지 표시

char * 변수 ptr은 인쇄 가능 문자가 포함된 스토리지의 한 부분을 가리킬 수 있습니다. 처음 20자를 표시하려면 다음 명령을 입력하십시오.

LIST STORAGE(*ptr,20)

문자열이 널(null)로 종료되면 다음과 같이 명령행에서 대화식 함수 호출을 사용할 수도 있습니다.

puts(ptr) ;

이 절에서 설명하는 자료와 연관된 자세한 정보에 대해서는 다음 주제를 참조하십시오.

C DLL 디버깅

예제: 디버깅을 위한 샘플 C 프로그램

push()pop()을 익스포트하여 DLL로 PUSHPOP.C를 빌드하십시오. PUSHPOP DLL에서 push()pop()을 임포트하는 프로그램으로 CALC.C와 READTOKN.C를 빌드하십시오. CALC 애플리케이션이 시작될 때는 PUSHPOP DLL이 Debug Tool에 알려져 있지 않습니다. 다음 예제에서와 같이 AT APPEARANCE 중단점을 사용하여, 이 컴파일 단위의 코드가 처음 나타날 때 DLL을 제어하십시오.

AT APPEARANCE "USERID.MFISTART.C(PUSHPOP)" ;
GO ;

이 appearance 중단점은 PUSHPOP 컴파일 단위의 함수가 처음 실행될 때 제어를 가져오기 위해 필요합니다. 제어를 가져오면, PUSHPOP에 중단점을 설정할 수 있습니다.

C의 함수 역추적 보기

대개 프로그래밍 오류가 발생하면 프로그래밍 오류를 야기한 것이 무엇인지, 특히 호출 함수의 역추적이 무엇인지 파악하려고 합니다. 이 정보를 보려면 다음 명령을 실행하십시오.

LIST CALLS ;

예제: 디버깅을 위한 샘플 C 프로그램

예를 들어, 다음 명령과 함께 CALC 예제를 실행하십시오.

AT ENTRY read_token ;
GO ;
LIST CALLS ;

그러면 로그 창에 다음과 같은 내용이 표시됩니다.

At ENTRY in C function CALC ::> "USERID.MFISTART.C(READTOKN)" :> read_token.
From LINE 18 in C function CALC ::> "USERID.MFISTART.C(CALC)" :> main :> %BLOCK2.

이는 호출자의 역추적을 표시합니다.

TEST를 사용하여 컴파일된 C 코드의 런타임 경로 추적

프로그램을 변경할 필요 없이 시작점과 종료점을 표시하여 프로그램을 추적하려면 파일에 다음 Debug Tool 명령을 배치한 다음 Debug Tool이 처음 프로그램을 표시할 때 이 명령을 사용하십시오. USERID.DTUSE(TRACE)와 다음 Debug Tool 명령이 포함된 데이터셋을 가지고 있다고 가정해 보십시오.

int indent;
indent = 0;
SET INTERCEPT ON FILE stdout;
AT ENTRY * { ₩
  ++indent; ₩
  if (indent < 0) indent = 0; ₩
  printf("%*.s>%s₩n", indent, " ", %block); ₩
  GO; ₩
}
AT EXIT * {₩
  if (indent < 0) indent = 0; ₩
  printf("%*.s<%s₩n", indent, " ", %block); ₩
  --indent; ₩
  GO; ₩
}

다음 명령을 입력하여 이 파일을 Debug Tool에 대한 명령 소스로 사용할 수 있습니다.

USE USERID.DTUSE(TRACE)

USE 파일을 실행하면 아래 나열된 프로그램의 실행에 대한 추적이 로그 창에 표시됩니다.

int foo(int i, int j) {
  return i+j;
}
int main(void) {
  return foo(1,2);
}

로그 창의 다음 추적은 Debug Tool 명령에 대한 입력 소스로 USE 파일을 사용하여 샘플 프로그램을 실행한 후 표시됩니다.

>main
 >foo
 <foo
<main

USE 파일을 작성하지 않으려면 명령행에서 명령을 입력하십시오. 이렇게 해도 동일한 결과가 나타날 수 있습니다.

C의 예기치 않은 스토리지 겹쳐쓰기 오류 찾기

프로그램이 실행되는 중에 일부 스토리지의 값이 예기치 않게 변경되어 이 값이 언제 어디서 변경되었는지 알아야 할 때가 있습니다. 다음 예제를 참조하십시오. 다음 예제에서는 호출자가 예상하는 것보다 set_i 함수가 더 많이 변경됩니다.

struct s { int i; int j;};
struct s a = { 0, 0 };

/* function sets only field i */
void set_i(struct s * p, int k)
{
  p–>i = k;
  p–>j = k;   /* error, it unexpectedly sets field j also */
}
main() {
  set_i(&a,123);
}

다음 명령을 사용하여 a의 주소를 찾으십시오.

LIST &(a.j) ;

결과가 0x7042A04라고 가정해 보십시오. 이 주소에서 시작하여 다음 4바이트에서 스토리지 값이 변경되었는지 감시하는 중단점을 설정하려면 다음 명령을 실행하십시오.

AT CHANGE %STORAGE(0x7042A04,4)

이 스토리지의 값이 변경된 경우, 프로그램이 실행될 때 Debug Tool이 정지됩니다.

C의 초기화되지 않은 스토리지 오류 찾기

초기화되지 않은 스토리지 오류를 쉽게 찾으려면 Language Environment TEST 런타임 옵션과 STORAGE 옵션을 사용하여 프로그램을 실행하십시오. 다음 예제를 참조하십시오.

TEST STORAGE(FD,FB,F9)

STORAGE의 첫 번째 하위 매개변수는 힙에서 할당된 스토리지에 대한 채우기 바이트입니다. 예를 들어, malloc 연산자를 통해 할당된 스토리지는 0xFD 바이트로 채워집니다. 스토리지에서 이 바이트가 반복되면 초기화되지 않은 힙 스토리지일 수 있습니다.

STORAGE의 두 번째 하위 매개변수는 힙에서 할당된 다음 비워진 스토리지에 대한 채우기 바이트입니다. 예를 들어, free 연산자를 통해 비워진 스토리지는 0xFB바이트로 채워질 수 있습니다. 스토리지에서 이 바이트가 반복되면 힙에 할당되었지만 비워진 스토리지일 수 있습니다.

STORAGE의 세 번째 하위 매개변수는 새 스택 프레임의 자동 스토리지 변수에 대한 채우기 바이트입니다. 스토리지에서 이 바이트가 반복되면 초기화되지 않은 자동 스토리지일 수 있습니다.

이 예제에서는 가능한 한 초기에 문제점을 발견할 수 있도록 큰 홀수 값을 선택했습니다. 예를 들어, 홀수 주소로 분기하려고 하면 즉시 예외가 발생합니다.

예제: 디버깅을 위한 샘플 C 프로그램

초기화되지 않은 스토리지 예제를 실행해 보겠습니다. STORAGE 런타임 옵션을 STORAGE(FD,FB,F9)로 지정하여 PUSHPOP2 행까지 CALC 프로그램을 실행한 후 다음 명령을 실행하십시오.

LIST *ptr ;

다음 예제와 같이 초기화되지 않은 힙 스토리지에 대한 바이트 채우기가 표시됩니다.

LIST *ptr ;
(* ptr).next = 0xFDFDFDFD
(* ptr).i = -33686019

널(null) C 함수를 호출하기 전에 정지

정의되지 않은 함수를 호출하거나 널(null)을 가리키는 함수 포인터를 통해 함수를 호출하면 심각한 오류가 발생할 수 있습니다. 이러한 호출이 실행되기 직전에 정지하려면 다음과 같이 중단점을 설정하십시오.

AT CALL 0

이 중단점에서 Debug Tool이 중지하면 GO BYPASS 명령을 입력하여 이 호출을 생략할 수 있습니다. 이렇게 하면 오류 조건을 발생시키지 않고 디버그 세션을 계속할 수 있습니다.