# LLDB 디버깅을 위한 세팅

예제 프로젝트 (opens new window) 다운로드를 받는다. 맥OS 가상 머신을 실리콘 맥에서 돌리는 예제 앱이다.

Finder와 같은 기본 앱에 대해 lldb 디버거를 활용하여 호출 함수들을 모니터링 하기 위해서는 System Integrity Protection(SIP)을 비활성화 해야 한다. 해당 기능은 애플이 멀웨어 차단을 위해 도입한 기능이며, 앱 무결성을 보장해주는 기능이다. 이 기능으로 인해 LLDB나 DTrace 등을 활용할 수 없다. 아래 과정을 따라가며 실습해보자.

앱 스킴을 InstallationTool로 지정하고 Run한다. 앱을 실행하면 MacOS 이미지가 자동으로 설치된다. 설치가 완료되면 startVirtualMachine 함수를 수정한 뒤, 앱 스킴을 MacOSVirtualMachineSampleApp으로 수정하고 실행한다.

func startVirtualMachine() {
    let options = VZMacOSVirtualMachineStartOptions()
    options.startUpFromMacOSRecovery = true
    virtualMachine.start(options: options, completionHandler: { (result) in
        // .. 아무 코드나 삽입
    })
}

위 코드의 옵션은 맥OS 가상머신 실행 시 복구모두로 실행하는 옵션이다. 가상머신 실행 뒤 맥OS를 초기 세팅하고 나면 OS에 대한 사용자 정보들이 저장된다. 가상머신에 대해 세팅을 하지 않으면 _mbsetupuser 기반으로 csrutil 비활성화를 해야 하는데, 하드웨어 단의 계정 정보이기 때문에 알 수 있는 방법이 없다.

사용자 정보를 설정한 뒤, xcode Run을 다시 해주면 기존 코드에서 지정해놓은 대로 복구모드에서 MacOS가 실행된다. 복구모드에서 터미널을 켜주고 아래 코드를 입력한다.

csrutil disable && reboot

위의 csrutil disable 명령어를 통해 SIP을 비활성화 할 수 있다. 재부팅 후 평소 모드에 진입하여 csrutil status 명령어를 입력하면 SIP이 비활성화 되었다는 문구를 볼 수 있다. 이후 lldb -n Finder를 입력하면, 현재 Finder 프로세스에 lldb 디버거가 연결되게 된다.

터미널에서 lldb에 연결된 프로세스 로그들을 출력하려면 터미널 탭을 두개 열어서 확인해볼 수 있다. 먼저 하나의 탭에서 lldb를 실행하고 다른 하나의 터미널에서는 아래 명령어를 입력한다.

tty

tty명령어는 현재 터미널의 주소값을 알려준다. 나의 경우 /dev/ttys001로 되어 있는데, 이건 터미널 생성 상황에 따라 각자 다르다. 해당 터미널 주소로 명령어 전송이 가능한지 확인하기 위해서 다른 터미널에서 echo명령어로 문자열을 전달할 수 있다.

# 다른 터미널 탭
echo "HELLO" 1>/dev/ttys001

# lldb와 프로세스 연결하기

lldb를 실행해둔 터미널 탭에서 아래 명령어를 실행한다.

(lldb) file /System/Applications/Notes.app

위의 file 명령어를 통해 실행 대상을 Notes앱으로 지정한다. 이후 아래 명령어를 이어 실행한다.

(lldb) process launch -e /dev/ttys001

lldb와 연결된 프로세스 로그들을 -e 옵션 파라미터인 /dev/ttys001주소값을 가진 터미널로 전송하는 것을 의미한다.

리눅스 명령어 몇가지

grep [옵션][패턴][파일명]

# 특정 파일에서 'error' 문자열 찾기
grep 'error' 파일명

# 여러개의 파일에서 'error' 문자열 찾기
grep 'error' 파일명1 파일명2

# 현재 디렉토리내에 있는 모든 파일에서 'error' 문자열 찾기
grep 'error' *

# 특정 확장자를 가진 모든 파일에서 'error' 문자열 찾기
grep 'error' *.log

grep명령어는 특정 파일에서 패턴에 해당하는 행을 출력해주는 명령어이다.

  • c : 일치하는 행의 수를 출력한다.
  • i : 대소문자를 구별하지 않는다.
  • v : 일치하지 않는 행만 출력한다.
  • n : 포함된 행의 번호를 함께 출력한다.
  • l : 패턴이 포함된 파일의 이름을 출력한다.
  • w : 단어와 일치하는 행만 출력한다.
  • x : 라인과 일치하는 행만 출력한다.
  • r : 하위 디렉토리를 포함한 모든 파일에서 검색한다.
  • m 숫자 : 최대로 표시될 수 있는 결과를 제한한다.
  • E : 찾을 패턴을 정규 표현식으로 찾는다.
  • F : 찾을 패턴을 문자열로 찾는다.

# 터미널에서 중단점 설정하기

터미널에서 lldb를 실행한다. file 실행할_앱.app으로 실행할 프로세스를 세팅하고, 아래 명령어를 디버거에 입력한다.

(lldb) b -[NSView hitTest:]

위의 b 명령어는 브레이크 포인트를 지정하는 것을 의미한다. Cocoa SDK에서 -[NSView hitTest:]는 이벤트 실행 루프에서 화면 탭이나 제스처를 처리하는 클래스가 무엇인지 알려주는 오브젝티브-C 메서드이다.

(lldb) continue

continue로 프로세스를 시작하고 실행한 앱을 탭하면 탭된 뷰 중단점에서 실행되는 메서드를 확인할 수 있다.

(lldb) po $arg1

위 명령어를 실행하면 중단점에서 히트된 뷰 주소와 정보를 확인할 수 있다. po는 print object를 의미하는 명령어이다. swift 객체를 쉽게 출력하기 위한 명령어이다. NSObject의 description 혹은 debugDescription 메서드를 제공한다.

인스턴스 출력 후에 continue, po $arg1 명령어를 계속 번갈아 사용하게 되면 UI 리스폰더 체인에 따라 내부 하위계층 서브뷰들이 계속해서 출력되는 것을 볼 수 있다. arg1은 브레이크 포인트로 영향받는 코드의 가상주소를 나타낸다.

(lldb) breakpoint delete

위 명령어는 설정해둔 중단점들을 삭제하는 명령어이다.

(lldb) breakpoint set -n "-[NSView hitTest:]" -C "po $arg1" -G1

위 명령어를 입력하면 설정한 오브젝티브 C 메서드에 대해 po $arg1명령어를 재귀적으로 계속 실행한다는 것을 의미한다. LLDB 디버깅 시 스위프트 환경에서 할 수도 있는데, Foundation과 같은 모듈들을 불러오는 데 시간이 오래 소요되고 성능도 떨어지기에 오브젝티브C 기반으로 디버깅하는 것이 좋다.