Search
Duplicate

디버거 lldb 를 써보자

간단소개
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
개발지식
Scrap
태그
9 more properties
lldb는 Mac os에서 기본적으로 제공되는 디버거이다. 일반적으로 다른 디버거를 쓸 수 없는 환경에서 이를 활용하면 좋다.

실행

lldb는 컴파일된 파일에 대해 디버깅을 해줄 수 있다. 따라서 먼저 컴파일 작업을 하는 것이 필요하다.
중요한 점 하나는 컴파일을 해 줄 때에 -g 옵션을 주고 컴파일을 해야한다. (그렇지 않으면 c 가 아니라 어셈블리 단위로 디버깅을 하게 될 것이다) 또한 함수 동작 전체를 디버깅하기 때문에 의존성이 있는 라이브러리 등이 있다면 같이 컴파일을 해주어야 한다.
debug : fclean #이전에 만들어놓은 목적파일과 a파일 out파일 등을 삭제한다. make all -C "./libft" #의존성이 있는 라이브러리들을 make 한 뒤 cp ./libft/$(LIB_NAME) $(LIB_NAME) #생성한 라이브러리 파일을 루트 디렉토리로 복사하고 gcc -g ${SRCS} $(LIB_NAME) main.c #컴파일을 한다. lldb a.out #생성된 out파일로 lldb를 실행한다.
Makefile
복사
이렇게 컴파일을 한다면 정상적으로 lldb 가 가동되는 것을 볼 수 있다.
이 상태로 코드를 돌려보고 싶다면 run 명령어나 r 명령어를 입력하면 된다. 매개변수를 넣어주고 싶으면 한 칸을 띄우고 run "hello" 와 같이 입력하면 argv[n]으로 매개변수를 받아 작동하게 할 수 있다. 그러나 이 상태에서는 코드의 세부적인 내역을 볼 수 없다. 우리가 보고 싶은 것은 더 자세한 부분이지 코드의 동작 여부가 아닐 것이다.

브레이킹 포인트 - b

코드의 진행 중간에 더 자세한 부분을 보고 싶으면 먼저 코드가 run 하는 도중에 멈추어서 조정할 수 있는 구간인 브레이킹 포인트를 설정하여야 한다. 브레이킹 포인트는 b 명령어를 이용해서 설정할 수 있으며, 아래의 규칙을 따른다.
b [filename]:[line_number]
Shell
복사
브레이킹 포인트를 설정하고 run 을 실행하면 그 라인이 실행되기 에 멈추어서 유저의 지시를 기다리게 된다. 이렇게만 이야기하면 애매하니 구체적인 상황을 상정하여 해보자.
나는 ft_printf.c 파일 안에 위와 같이 파일을 작성했다. 함수 vsprintf는 fmt에서 '%'가 있을 때, 80번 라인에서 ft_call_parserset 를 실행한다. ft_call_parserset 함수는 파싱함수들을 호출해서, 첫번째 매개변수로 들어온 fmt 문자열을 파싱하여 구조체 info에 정보를 기록한다. 그런 뒤에 81번째 라인에서 ft_printf_call_handler를 호출하여 구조체 info 에서 기록된 정보에 따라 출력한다. 그 뒤 해당 함수는 표준출력으로 출력한 문자열의 길이만큼 ret 에 정수를 더해준다. vsprintf 는 이렇게 매개변수 첫번째인 문자열 전체를 출력하여 반환된 ret 을 반환한다.
이제 나는 구조체 info 에 이 함수가 fmt 를 잘 파싱하여 기록하고 있는지 보고싶다. 그러면 이 구조체에 정보가 기록되기 전 시점에 동작을 멈추고, 기록을 하는 과정과정마다 변수를 확인하면 된다. 그러면 최초로 구조체에 정보가 기록되기 전에 브레이킹 포인트를 설정하면 된다. 그렇다면 80번 라인에 브레이킹 포인트를 걸어 놓으면 된다.
br ft_printf.c:80
Shell
복사
이렇게 되면 lldb는 다음과 같은 출력을 보여준다.
이렇게 몇번째 브레이킹 포인트가 걸렸는지, 그리고 어떤 파일과 함수에 몇 번 라인과 주소에 브레이킹 포인트가 걸렸는지 알려준다. 이 상태로 다른 브레이킹 포인트를 걸고 싶다면 또 브레이킹 포인트를 추가해주면 된다.
이렇게 두번째 브레이킹 포인트를 추가하고 run을 하면 첫번째 브레이킹 포인트로 가서 멈춘다. 이후의 브레이킹 포인트로 갈려면 continue 혹은 c 명령어를 사용하면 된다.
continue (== c)
Shell
복사
브레이킹 포인트를 지울려면 아래와 같이 입력하면 된다.
# 전체 브레이킹 포인트 지우기 br del # n번째 브레이킹 포인트 지우기 br del [breaking_point_number]
Shell
복사

디버거 진행 - n / s

이제 우리가 원하는 위치에서 디버거의 진행이 멈추게 만들었으니, 코드를 한 줄 씩 진행시키면서 코드가 어떻게 동작하는지 확인할 수 있다. lldb는 연산되는 라인 단위로 진행할 수 있는데, next 혹은 n 명령어로 진행할 수 있다.
지금 보시면 브레이킹 포인트로 설정한 80번 라인에서 run 이 멈추고, 유저의 명령을 기다리고 있는 시점에 n 명령어를 입력해주었다. 이렇게 해서 다음라인인 81번 라인으로 넘어갔다. 해당 함수의 라인단위로 진행되기 때문에 라인에 걸려있는 함수의 실행은 모두 진행시키고 현재 스코프의 변수들도 모두 연산을 하고 진행한다. 자세히 잘 보면 프로세스 번호와 현재 상태를 볼 수 있고, 그 밑줄에서는 어느 스레드에서 해당 코드를 진행하고 있는지, 왜 진행을 멈추었는지 확인할 수 있다. frame에서는 어떤 파일의 몇 번 라인에 위치하고 있는지, 현재 위치의 스코프를 가지고 있는 함수에서 어떤 매개변수로 실행하고 있는지 확인할 수 있다.
이제 이렇게 걸려있는 함수들이 작동하는 것을 볼려면 어떻게 해야할까? 예를 들어서 위의 예제에서 fmt에 리턴값을 할당하는 ft_call_parserset 의 동작을 디버깅하고 싶을 때가 있을 것이다. 80번에서 n을 누르면 ft_call_parserset 의 모든 동작을 스킵하고 연산을 한 뒤 81라인으로 넘어온다.
만약 이런 상황에서 현재지점을 ft_call_parserset 내부로 옮겨가서 볼려면 어떻게 해야할까? 이 경우에는 step 혹은 s 명령어를 입력하면 된다.
step (== s)
Shell
복사
위의 예제에서 보면 80번 라인에서 s 를 입력했더니 해당 라인에 걸려있는 함수로 step into 해서 현재 디버깅 위치가 ft_call_parserset 로 바뀐 것을 볼 수 있다.

변수 확인 - v

이제 내가 원하는 위치로 한 라인 한 라인 진행하는 방법을 알았으니, 현재 변수들의 상황을 볼 수 있도록 해보자. lldb에서 변수를 확인 할 때는 v 를 활용한다. v 명령어를 입력하면 현재 디버깅 위치에 해당하는 스코프 내에서 어떤 변수가 선언되어 있는지, 그 값은 어떻게 할당되어 있는지 확인할 수 있다.
#해당 위치 스코프 내의 전체변수 확인 v #해당 위치 스코프 내의 특정변수 확인 v [variable_name] #해당 위치 스코프 내의 특정포인터의 실질값 확인 v *[variable_name] #해당 위치 스코프 내의 특정변수의 주소값 확인 v &[variable_name]
Shell
복사
이전 예제에서 v를 눌렀더니, 현재 vsprintf 함수 내에 선언되어 있는 변수들이 쫙 나온다. 이 중에서는 포인터도 있고, 구조체도, 일반적인 변수인 int 변수도 있다. 포인터의 실제값을 볼 수 있지 않을까? 가능하다!
이런 식으로 c 언어에서 포인터의 실질값과 변수의 주소값을 보는 *&를 활용하여 확인해줄 수 있다.
이제 자신의 코드에 속한 변수의 상황과 함수의 진행을 한땀한땀 훑어보면서 디버깅 할 수 있게 되었다. 이렇게 여러분은 기본적인 lldb를 이용한 디버깅을 할 수 있게 되었다. 물론 lldb도 프레임 뿐 아니라 스레드 별로 디버깅이 가능하지만, 그건 다음에 필로소퍼 할 때에 다시 쓰도록 하겠다리!