본문 바로가기

카테고리 없음

디버거에 대해

디버거가 작동하는 순서(?)

 

디버기(디버깅 당하는 프로세스)에 디버거(디버그 하는 프로세스)가 붙거나 디버기를 생성해서 디버깅 한다.

 

디버거는 디버기의 메모리, 레지스터를 read,write할 수 있는데,

 

디버기가 실행되다가 제어가 디버거로 넘어오면 그떄 위와같은 다양한 조작을 할 수 있다.

 

구체적으로 windows에서는 

 

DebugActiveProcess함수로 실행중인 프로세스에 디버거가 붙는다.

 

DebugActiveProcessStop함수로 디버깅을 끝낸다.(디버거를 뗀다)

 

WaitForDebugEvent 디버기에 특정 이벤트가 발생하면 제어를 디버기에서 디버거로 준다(중요)

 

ContinueDebugEvent 디버거로 제어가 넘어왔으면 예외를 처리하고, 조작을 마치고 이 함수로 디버기에게 제어를 다시 넘겨준다.(디버기 계속 실행)

 

ReadProcessMemory  디버기 메모리를 읽는다.

 

WriteProcessMemory 디버기 메모리에 쓴다.

 

GetThreadContext  디버기의 레지스터 값을 가져온다.

 

SetThreadContext 디버기의 레지스터 값을 수정한다.

 

WaitForDebugEvent로 특정 이벤트를 설정하면 그 이벤트가 디버기에서 발생하면 디버거로 제어가 넘어오는데 그떄 위의 함수를 통해 디버기의 메모리, 레지스터를 조작하고 디버기에게 제어 넘기는 함수로 넘겨준다.

 

WaitForDebugEvent 로 등록할 수 있는 이벤트는

 

CREATE_PROCESS_DEBUG_EVENT

EXIT_PROCESS_DEBUG_EVENT

CREATE_THREAD_DEBUG_EVENT

EXIT_THREAD_DEBUG_EVENT

EXCEPTION_DEBUG_EVENT

LOAD_DLL_DEBUG_EVENT 

UNLOAD_DLL_DEBUG_EVENT 

등등 다양한 이벤트가 존재한다.

 

이러한 이벤트를 등록해두면 디버기에서 해당 이벤트가 발생할때(ex. EXCEPTION_DEBUG_EVENT -> breakpoint 등 예외 발생 할 때) 디버거로 제어가 넘어와서 처리를 할 수 있다.

 

 

Linux에는 ptrace라는 함수로 이를 처리하는데,

 

 

long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

 

이렇게 생긴 함수고, 각 인자의 설명은

 

  • request: ptrace의 동작을 지정하는 요청 코드 어떤 작업을 수행할지를 정의함. 예를 들면, 프로세스 추적, 메모리 읽기/쓰기, 레지스터 접근 등
  • pid: 제어하려는 대상 프로세스의 프로세스 ID. 예를 들어, 추적할 자식 프로세스나 다른 프로세스의 pid
  • addr: 특정 요청에 대해 메모리 주소나 레지스터 위치 등을 지정하는 주소 값. 요청에 따라 사용되거나 무시됨. 예를 들어, 메모리에서 값을 읽거나 쓸 때 이 값을 사용.
  • data: addr에 있는 값을 읽거나 쓰거나, 요청에 따라 추가 데이터로 사용. 예를 들어, 프로세스 메모리에 값을 쓰거나, 특정 레지스터 값을 설정할 때 사용됨.

 

request에 따라서 함수가 다양한 일을 함.

 

  1. PTRACE_TRACEME
    • 설명: 자식 프로세스가 자신이 부모 프로세스에 의해 추적될 수 있도록 함.
    • 용도: 자식 프로세스가 디버거에 의해 추적되도록 함.
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
     
  2. PTRACE_PEEKDATA
    • 설명: 다른 프로세스의 메모리에서 데이터를 읽음
    • 용도: 디버거가 추적 중인 프로세스의 메모리 값을 읽을 때 사용.
    long data = ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
     
  3. PTRACE_POKEDATA
    • 설명: 다른 프로세스의 메모리에 데이터를 write.
    • 용도: 추적 중인 프로세스의 메모리에 값을 기록할 때 사용.
    ptrace(PTRACE_POKEDATA, pid, addr, value);
     
  4. PTRACE_CONT
    • 설명: 중단된 프로세스를 계속 실행.
    • 용도: SIGTRAP이나 다른 시그널로 중단된 프로세스를 다시 실행할 때 사용.
    ptrace(PTRACE_CONT, pid, NULL, NULL);
     
  5. PTRACE_SINGLESTEP
    • 설명: 프로세스를 한 명령어만 실행하고 중단.
    • 용도: 디버깅 중 한 단계씩 실행할 때 사용.
    ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL);
     
  6. PTRACE_ATTACH
    • 설명: 현재 실행 중인 프로세스를 추적.
    • 용도: 현재 실행 중인 다른 프로세스에 디버거를 붙여 추적을 시작할 때 사용.
    ptrace(PTRACE_ATTACH, pid, NULL, NULL);
     
  7. PTRACE_DETACH
    • 설명: 추적을 중단하고 프로세스의 실행을 계속.
    • 용도: 프로세스를 더 이상 추적하지 않도록 설정하고, 실행을 계속할 때 사용.
    ptrace(PTRACE_DETACH, pid, NULL, NULL);
     
  8. PTRACE_GETREGS
    • 설명: 레지스터 값을 읽습니다.
    • 용도: 프로세스의 현재 레지스터 상태를 확인할 때 사용.
    struct user_regs_struct regs;
    ptrace(PTRACE_GETREGS, pid, NULL, &regs);
     
  9. PTRACE_SETREGS
    • 설명: 레지스터 값을 설정.
    • 용도: 특정 레지스터 값을 설정하여 프로세스의 실행 흐름을 조정할 때 사용.
    struct user_regs_struct regs;
    ptrace(PTRACE_SETREGS, pid, NULL, &regs);
     
     
     
    ptrace하나로 request 인자를 다르게 설정해주어 windows의 다양한 디버깅 함수처럼 사용 할 수 있다.
     
    windows와 다르게
     
    waitpid(pid, &status, 0);로 프로세스의 중단을 대기하고 중단되면 디버거로 제어가 넘어온다.
     
    제어가 넘어오는 상황으로는 bp를 만나거나, 프로세스 시작, PTRACE_SINGLESTEP, 시그널 발생, 프로세스 중단, 시스템콜 호출 등이 있다.
     
    이럴떄 제어가 넘어와서 관심없는 이벤트면 cont로 디버기로 넘겨주면 된다.
     
     

전체적으로 정리하면 디버거가 프로세스에 붙어서 디버기의 제어를 넘겨받으면 처리하고 다시 디버기에게 넘겨주고 반복해서 원하는 행위를 하면 되는것.