C언어에서 strace로 시스템 콜 디버깅하는 방법

C언어로 개발된 프로그램은 시스템 콜을 통해 운영 체제와 상호작용합니다. 하지만, 예상치 못한 동작이나 오류가 발생했을 때, 이를 정확히 파악하고 해결하는 것은 쉽지 않을 수 있습니다. 이때 strace는 강력한 디버깅 도구로서, 프로그램이 호출하는 시스템 콜과 그 결과를 실시간으로 추적해 문제의 원인을 밝혀냅니다. 본 기사에서는 strace를 사용해 C언어 프로그램의 동작을 분석하고, 시스템 콜 레벨에서 발생할 수 있는 오류를 해결하는 방법을 단계별로 설명합니다.

목차

strace란 무엇인가?


strace는 리눅스 및 유닉스 기반 시스템에서 실행 중인 프로그램이 호출하는 시스템 콜을 추적하는 도구입니다. 시스템 콜은 운영 체제 커널과 사용자 프로그램 간의 인터페이스로, 파일 입출력, 네트워크 통신, 프로세스 관리 등 다양한 작업에 사용됩니다.

strace의 주요 기능

  • 시스템 콜 추적: 프로그램이 호출하는 모든 시스템 콜을 기록합니다.
  • 입출력 디버깅: 파일 또는 네트워크 입출력과 관련된 동작을 분석합니다.
  • 오류 원인 분석: 특정 오류와 관련된 시스템 콜의 결과와 반환값을 파악합니다.
  • 성능 최적화 도움: 불필요한 시스템 콜을 찾아 성능 병목을 제거합니다.

사용 사례

  • 프로그램이 의도한 대로 시스템 리소스를 사용하는지 확인.
  • 특정 파일이나 소켓에 대한 접근 오류 디버깅.
  • 실행 중인 바이너리의 동작을 역추적해 문제 해결.

strace는 디버깅뿐 아니라 시스템 콜 이해를 돕는 학습 도구로도 유용합니다.

strace 설치 및 초기 설정

리눅스에서 strace 설치


대부분의 리눅스 배포판에서 strace는 기본 패키지 관리자에 포함되어 있어 간단히 설치할 수 있습니다.

Ubuntu/Debian

sudo apt update
sudo apt install strace

CentOS/RHEL

sudo yum install strace

Fedora

sudo dnf install strace

설치 확인


strace 설치 후, 아래 명령어로 정상적으로 설치되었는지 확인합니다.

strace -V


이 명령어는 strace의 버전을 출력하며, 성공적으로 설치되었음을 알 수 있습니다.

기본 사용법


strace를 사용하려면 분석하려는 프로그램을 명령어 뒤에 추가합니다. 예를 들어, ls 명령어를 추적하려면:

strace ls


이 명령어는 ls 명령이 호출한 모든 시스템 콜과 결과를 출력합니다.

출력 파일 저장


-o 옵션을 사용해 출력 결과를 파일로 저장할 수 있습니다.

strace -o output.txt ls


이 명령은 ls 명령의 시스템 콜 로그를 output.txt 파일에 기록합니다.

루트 권한 필요성


특정 프로그램이나 프로세스를 추적하려면 루트 권한이 필요할 수 있습니다. 이 경우 sudo를 사용합니다.

sudo strace ./my_program

이렇게 설치와 초기 설정을 완료하면, strace를 활용한 본격적인 디버깅을 시작할 준비가 됩니다.

시스템 콜 추적의 필요성

시스템 콜과 프로그램의 상호작용


C언어 프로그램은 파일 입출력, 메모리 할당, 네트워크 작업 등을 수행하기 위해 시스템 콜을 사용합니다. 이 시스템 콜은 운영 체제와 프로그램 간의 핵심적인 인터페이스로, 프로그램의 동작과 성능에 직접적인 영향을 미칩니다.

시스템 콜 추적의 중요성

  • 오류 원인 파악: 프로그램에서 발생하는 문제는 종종 시스템 콜 단계에서 발생합니다. 이를 추적하면 문제의 근본 원인을 빠르게 찾아낼 수 있습니다.
  • 퍼포먼스 최적화: 불필요하거나 반복적으로 호출되는 시스템 콜을 줄임으로써 프로그램의 성능을 개선할 수 있습니다.
  • 운영 환경 문제 해결: 특정 운영 체제나 환경에서만 발생하는 문제를 디버깅할 때 유용합니다.

문제 상황의 예시

  1. 파일 입출력 오류: 프로그램이 특정 파일에 접근할 수 없거나, 읽기/쓰기가 실패하는 경우.
  2. 네트워크 문제: 서버와의 연결이 끊기거나 데이터 전송이 중단되는 경우.
  3. 리소스 누수: 파일 디스크립터나 메모리 리소스가 제대로 해제되지 않아 시스템 성능이 저하되는 경우.

strace의 역할

  • 각 시스템 콜의 호출 순서와 반환값을 실시간으로 확인할 수 있습니다.
  • 실패한 시스템 콜과 그에 따른 에러 코드(EINTR, ENOENT 등)를 상세히 보여줍니다.
  • 시스템 리소스 사용 상태를 추적해 병목 지점을 파악할 수 있습니다.

시스템 콜 추적은 문제를 정확히 이해하고 해결책을 제시하는 디버깅 과정의 핵심 요소입니다. strace를 활용하면 이러한 작업을 효율적으로 수행할 수 있습니다.

strace의 주요 명령어

기본 명령어


strace의 기본 사용법은 단순합니다. 프로그램 실행 시 호출된 모든 시스템 콜을 출력합니다.

strace <명령어>


예:

strace ls


ls 명령이 실행되며, 호출된 시스템 콜과 그 결과가 출력됩니다.

특정 시스템 콜 필터링


-e 옵션을 사용하면 추적할 시스템 콜을 선택할 수 있습니다.

strace -e open,read,write ./my_program


이 명령은 open, read, write 시스템 콜만 추적합니다.

출력 결과 파일로 저장


-o 옵션으로 출력 결과를 파일에 저장할 수 있습니다.

strace -o trace_log.txt ./my_program


결과는 trace_log.txt 파일에 기록됩니다.

기존 프로세스에 연결


이미 실행 중인 프로세스를 추적하려면 -p 옵션을 사용합니다.

strace -p <PID>


예:

strace -p 12345


PID 12345인 프로세스를 추적합니다.

시스템 콜 실패만 출력


-z 옵션을 사용하면 실패한 시스템 콜만 출력합니다.

strace -z ./my_program

호출 시간 측정


-T 옵션을 사용하면 각 시스템 콜의 실행 시간을 출력합니다.

strace -T ./my_program


예제 출력:

write(1, "Hello\n", 6) = 6 <0.000012>


여기서 <0.000012>는 실행 시간이 0.000012초임을 나타냅니다.

호출 시간 및 상대 시간 출력


-tt 옵션은 시스템 콜 호출 시간, -r 옵션은 상대 시간을 표시합니다.

strace -tt -r ./my_program

실행 중 바이너리의 라이브러리 호출 추적


-f 옵션으로 자식 프로세스의 시스템 콜까지 추적할 수 있습니다.

strace -f ./my_program

결합 예제


모든 주요 옵션을 결합한 명령어:

strace -o trace_log.txt -e open,read -f -T ./my_program


이 명령은 open, read 시스템 콜만 자식 프로세스까지 추적하며, 실행 시간을 포함한 결과를 trace_log.txt에 저장합니다.

이 명령어를 통해 다양한 상황에서 strace를 효과적으로 사용할 수 있습니다.

strace 출력 결과 해석 방법

출력 형식 이해


strace의 출력은 시스템 콜 호출과 그 결과를 포함한 상세 정보를 제공합니다. 기본적인 출력 형식은 다음과 같습니다:

시스템콜명(인자1, 인자2, ...) = 반환값 <시간>

예제 출력:

open("example.txt", O_RDONLY) = 3 <0.000012>
  • open: 호출된 시스템 콜 이름
  • “example.txt”: 첫 번째 인자 (파일 이름)
  • O_RDONLY: 두 번째 인자 (읽기 전용 모드)
  • 3: 반환값 (파일 디스크립터)
  • <0.000012>: 실행 시간 (초 단위)

출력에서 중요한 부분

  1. 시스템 콜 이름
    호출된 시스템 콜의 이름으로, 어떤 작업이 수행되었는지 알 수 있습니다.
  2. 인자 값
    함수 호출에 사용된 값으로, 파일 경로나 플래그 등을 확인할 수 있습니다.
  3. 반환값
    성공 또는 실패 여부를 나타냅니다. 양수는 성공, -1은 실패를 의미하며, 실패 시 오류 코드가 표시됩니다.
  4. 실행 시간
    각 시스템 콜이 소요한 시간을 확인할 수 있어 성능 병목을 파악할 수 있습니다.

실패한 시스템 콜 분석


시스템 콜 실패 시 오류 코드와 메시지가 함께 출력됩니다.
예:

open("nonexistent.txt", O_RDONLY) = -1 ENOENT (No such file or directory)
  • -1: 시스템 콜 실패를 의미
  • ENOENT: 오류 코드 (파일 없음)
  • (No such file or directory): 오류 메시지

필터링을 통한 분석 효율화


출력 데이터를 효율적으로 분석하기 위해 grep 명령어와 함께 사용합니다.

strace ./my_program | grep "open"


이 명령은 open 시스템 콜에 관련된 출력만 표시합니다.

자주 등장하는 시스템 콜과 의미

  • open: 파일 열기
  • read/write: 데이터 읽기/쓰기
  • close: 파일 닫기
  • socket/connect: 네트워크 소켓 열기 및 연결
  • fork/execve: 프로세스 생성 및 실행

출력 저장 및 분석


출력을 파일로 저장한 후, 텍스트 편집기나 분석 도구를 사용해 세부적으로 검토할 수 있습니다.

strace -o trace_output.txt ./my_program


이후, trace_output.txt 파일을 열어 필요한 정보를 확인합니다.

출력 결과 활용

  • 프로그램의 시스템 리소스 사용 현황 파악
  • 특정 시스템 콜 호출 빈도 확인
  • 오류 원인과 성능 병목 지점 분석

strace 출력은 프로그램 동작을 상세히 이해하고 문제를 해결하는 강력한 도구입니다.

특정 시스템 콜 디버깅 방법

특정 시스템 콜 필터링


strace에서 -e 옵션을 사용하면 특정 시스템 콜만 추적할 수 있어 출력 데이터를 간결하게 만듭니다. 예를 들어, 파일 입출력과 관련된 시스템 콜을 추적하려면:

strace -e open,read,write ./my_program


이 명령은 open, read, write 시스템 콜만 출력합니다.

특정 파일 접근 추적


특정 파일에 대한 접근을 추적하려면 grep 명령어를 사용해 출력 결과를 필터링합니다.

strace ./my_program 2>&1 | grep "example.txt"


이 명령은 example.txt에 접근하는 시스템 콜만 표시합니다.

네트워크 관련 시스템 콜 디버깅


네트워크 소켓 관련 동작을 추적하려면 소켓 시스템 콜을 필터링합니다.

strace -e socket,connect,sendto,recvfrom ./my_program


출력 예시:

socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("93.184.216.34")}, 16) = 0
sendto(3, "GET / HTTP/1.1\r\n", 16, 0, NULL, 0) = 16
recvfrom(3, "HTTP/1.1 200 OK\r\n", 1024, 0, NULL, NULL) = 17


이를 통해 네트워크 연결 문제를 분석할 수 있습니다.

파일 디스크립터 누수 디버깅


파일 디스크립터가 제대로 닫히지 않아 리소스 누수가 발생할 수 있습니다. close 시스템 콜을 추적해 문제를 확인합니다.

strace -e close ./my_program


출력 예시:

close(3) = 0
close(4) = -1 EBADF (Bad file descriptor)


위 출력은 파일 디스크립터 4가 이미 닫혔거나 잘못된 상태임을 나타냅니다.

시스템 콜 실패 분석


실패한 시스템 콜만 추적하려면 -z 옵션을 사용합니다.

strace -z ./my_program


출력 예시:

open("nonexistent.txt", O_RDONLY) = -1 ENOENT (No such file or directory)


이는 nonexistent.txt 파일이 없음을 나타냅니다.

실시간 디버깅과 로그 분석


strace 출력 데이터를 파일로 저장하고 분석하면 효율적으로 문제를 해결할 수 있습니다.

strace -o trace_log.txt -e open ./my_program


이후, trace_log.txt 파일을 열어 세부적인 정보를 검토합니다.

특정 상황별 디버깅

  1. 프로그램이 파일에 접근하지 못할 때
  • open 호출을 추적해 파일 경로와 접근 권한을 확인합니다.
  1. 네트워크 연결이 끊길 때
  • connectsendto 호출을 추적해 IP와 포트를 확인합니다.
  1. 성능 병목이 발생할 때
  • -T 옵션으로 각 시스템 콜의 실행 시간을 측정합니다.

결합된 디버깅 사례


여러 옵션을 결합해 특정 상황을 디버깅할 수 있습니다.

strace -e open,read,write -T -o debug_log.txt ./my_program


이 명령은 파일 입출력 관련 시스템 콜의 실행 시간을 기록해 성능 문제를 분석합니다.

특정 시스템 콜을 디버깅하면 문제의 원인을 정확히 파악하고, 프로그램의 안정성을 크게 향상시킬 수 있습니다.

strace와 C코드 디버깅 연계

strace로 C코드 동작 확인하기


strace는 실행 중인 C 프로그램에서 호출되는 시스템 콜을 추적해 코드와 실제 실행 사이의 차이를 분석할 수 있습니다. 이를 통해 코드 상의 오류를 발견하거나 예상하지 못한 동작을 파악할 수 있습니다.

예제 코드와 strace 적용


다음은 파일을 읽는 간단한 C 프로그램입니다:

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file = fopen("example.txt", "r");
    if (!file) {
        perror("Failed to open file");
        return EXIT_FAILURE;
    }

    char buffer[100];
    while (fgets(buffer, sizeof(buffer), file)) {
        printf("%s", buffer);
    }

    fclose(file);
    return EXIT_SUCCESS;
}

이 프로그램을 example.c로 저장한 뒤 컴파일합니다.

gcc -o example example.c

strace를 사용해 실행합니다:

strace ./example

출력 분석


strace의 출력은 프로그램의 실제 동작을 보여줍니다.

open("example.txt", O_RDONLY) = 3
read(3, "Hello, World!\n", 100) = 14
write(1, "Hello, World!\n", 14) = 14
close(3) = 0
  • open: 파일이 성공적으로 열렸습니다.
  • read: 파일에서 데이터를 읽어옵니다.
  • write: 데이터를 출력합니다.
  • close: 파일 디스크립터를 닫습니다.

코드와 동작 비교

  1. 파일 열기 확인
    open 호출이 실패하면 fopen 함수에서 발생한 문제를 찾을 수 있습니다.
  2. 데이터 읽기 문제
    read 호출에서 읽은 바이트 수를 확인해 데이터 읽기가 제대로 이루어졌는지 분석합니다.
  3. 출력 문제 파악
    write 호출을 확인해 출력이 예상대로 이루어졌는지 점검합니다.

문제 상황 디버깅

  1. 파일이 없는 경우
    프로그램이 존재하지 않는 파일을 열려고 시도하면:
   open("missing.txt", O_RDONLY) = -1 ENOENT (No such file or directory)


오류 코드 ENOENT를 통해 파일이 없음을 알 수 있습니다.

  1. 파일 접근 권한 문제
    읽기 권한이 없는 파일을 열려고 시도하면:
   open("example.txt", O_RDONLY) = -1 EACCES (Permission denied)


오류 코드 EACCES는 권한 문제가 있음을 나타냅니다.

복잡한 프로그램 분석


멀티프로세스 또는 멀티스레드 프로그램에서는 -f 옵션으로 자식 프로세스까지 추적합니다.

strace -f ./example

코드와 시스템 콜 최적화


strace 결과를 통해 불필요한 시스템 콜을 확인하고, 이를 줄이는 방향으로 코드를 수정합니다. 예를 들어, 불필요한 open 호출이나 과도한 read 호출을 최소화할 수 있습니다.

결합 사용 사례


다음 명령으로 실행 시간을 포함한 상세 로그를 분석합니다:

strace -T -o trace_log.txt ./example

trace_log.txt 파일에서 실행 시간을 분석해 성능 병목을 파악하거나 디버깅 효율성을 높일 수 있습니다.

결론


strace를 C코드 디버깅에 활용하면 프로그램의 시스템 콜 레벨 동작을 명확히 파악할 수 있어, 코드의 문제를 빠르고 효과적으로 해결할 수 있습니다.

응용 예제와 연습 문제

응용 예제: 네트워크 요청 디버깅


다음은 HTTP 요청을 보내는 C 프로그램입니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    char *request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
    char response[4096];

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        return EXIT_FAILURE;
    }

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(80);
    inet_pton(AF_INET, "93.184.216.34", &server_addr.sin_addr);

    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Connection failed");
        close(sockfd);
        return EXIT_FAILURE;
    }

    send(sockfd, request, strlen(request), 0);
    recv(sockfd, response, sizeof(response) - 1, 0);

    printf("Response:\n%s\n", response);
    close(sockfd);
    return EXIT_SUCCESS;
}

컴파일 후 실행:

gcc -o http_request http_request.c
strace -e socket,connect,sendto,recvfrom ./http_request

출력 예시

socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("93.184.216.34")}, 16) = 0
sendto(3, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", 40, 0, NULL, 0) = 40
recvfrom(3, "HTTP/1.1 200 OK\r\nDate: Mon, 14 Jan 2025...\n", 4096, 0, NULL, NULL) = 4096
close(3) = 0

이를 통해 네트워크 연결 및 데이터 송수신 과정을 추적할 수 있습니다.

연습 문제

문제 1: 파일 입출력 디버깅
다음 프로그램을 작성하고 strace로 디버깅하세요:

  • 프로그램이 “test.txt” 파일을 열고, 문자열 “Hello, World!”를 작성한 뒤 닫습니다.
  • strace로 프로그램 실행 시 호출된 시스템 콜을 확인합니다.

문제 2: 시스템 콜 실패 디버깅
다음 시나리오를 분석하세요:

  • 프로그램이 존재하지 않는 파일 “missing.txt”를 열려고 시도합니다.
  • strace를 사용해 실패 원인과 반환값을 확인하세요.

문제 3: 자식 프로세스 추적
C 프로그램에서 fork()를 사용해 자식 프로세스를 생성한 뒤 execve()를 호출합니다.

  • strace -f를 사용해 부모와 자식 프로세스의 시스템 콜을 모두 추적하세요.

정답 예시


문제 1 예시 출력

open("test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
write(3, "Hello, World!", 13) = 13
close(3) = 0

문제 2 예시 출력

open("missing.txt", O_RDONLY) = -1 ENOENT (No such file or directory)

문제 3 예시 출력

fork() = 12345
[pid 12345] execve("/bin/ls", ["ls"], 0x7ffd12345678 /* 18 vars */) = 0

활용 팁


strace와 연습 문제를 통해 다양한 디버깅 기술을 익히면, 시스템 콜 단위에서 문제를 해결하는 능력을 크게 향상시킬 수 있습니다.

요약


본 기사에서는 strace를 활용한 C언어 프로그램의 시스템 콜 디버깅 방법을 다뤘습니다. strace 설치 및 설정부터 시스템 콜 추적, 출력 결과 해석, 특정 시스템 콜 디버깅, 그리고 실제 C코드와의 연계 방법까지 단계별로 설명했습니다. 이를 통해 오류 원인을 파악하고 성능을 최적화할 수 있는 강력한 디버깅 도구로서의 strace 사용법을 익힐 수 있습니다. 실습 예제와 연습 문제를 통해 strace 활용 능력을 심화하세요.

목차