BugNet - Continuously Recording Program Execution for Deterministic Replay Debugging
랩세미나 시간에 발표한 Record & Replay 에 대한 논문 중 하나다.
Abstract
공장에서 출시되는 코드를 위해서 행해지는 버그를 고치고 재생산하는 것은 중요한 시간이다. 개발자들을 돕기 위해서, 우리는 생산품이 동작 상에서 끊임없는(계속되는) 정보를 record 하기 위한 아키텍처를 제안한다. 프로그램이 크래쉬 되기 전에 모은 정보들은 크래시전에 최근의 몇 백만 명령어의 수행을 결정적으로 replay 하기 위한 실행환경에서 개발자들의 수행에 의해 사용할 수 있다.
bugnet 은 어떤 시간의 포인트에 레지스터 파일의 내용을 record 하는 것을 기반으로 하고, 그리고 나서 로드한 값들은 record 한 후에 포인트는 프로그램의 실행의 결정적인 replay 를 허용할 수 있다. bugnet 은 애플리케이션의 실행과 그것의 라이브러리의 사용을 replay 하는 것에 중점을 두고 있다. 하지만, OS 는 아니다. 그러나 우리의 제안은 애플리케이션의 실행을 넘어 컨텍스트 스위칭과 인터럽트들을 replay 할 수 있는 기능을 제공한다.
따라서 bugnet 은 프로그램 I/O, 인터럽트, DMA 전송을 추적하는 것은 필요가 없다. 따라서 만일 그랬다면, 더 복잡한 하드웨어 지원을 필요로 했을 것이다.
게다가, bugnet 은 replay 를 위한 시스템 상태의 마지막 core dump 를 요구하지 않는다. 그리고 중요한 개발자에게 보내져야할 데이터의 양을 감소시킨다.
Introduction
지난 10 년간 프로세서의 성능이 빠르게 증가되는 것을 목격해왔다. 고성능 소프트웨어 시스템은 성능의 증가가 영향을 미쳤다. 하지만 소프트웨어 신뢰도의 비용이 영향을 미친다. 1986 년에 Tandem OS 는 4 백만 라인을 가지고 있었고, 2003 년의 윈도우 XP 는 4 ~ 5 천만 라인을 가지고 있다. 불행하게도 소프트웨어 공학과 검증 기술은 복잡하고 그리고 소프트웨어 시스템은 후에 버그가 있는지를 제한하는 엄격한 QA 과정을 거친다. 게다가 인터넷의 급증이 빈번한 소프트웨어 패치의 배포를 만들었고, 벤더들이 좀더 빠른 릴리즈 일정을 요구하고 이로 인해 릴리즈된 소프트웨어에 좀더 많은 버그가 포함되게 되었다.
릴리즈된 소프트웨어 버그를 추적하고 고치는 것은 악몽이다. 중요한 시간과 돈을 낭비하는 것이다. 곤란한 것 중에 주요한 것은 개발자 관점에서의 버그를 재생산시키는 것이다. 대부분의 현재 디버깅 시스템은 core dump 에 의존한다. 이것은 크래쉬전의 마지막 시스템의 상태를 표현한다. 불행하게도 이 방법은 core dump 에 의해서 프로그램이 끝났을 때의 상태를 표현하는 것은 실제로 책임이 있었던 에러의 근원을 결정하는 데 어렵기 때문에 불충분하다.
이문제의 좋은 방법은 생산 동안에 충분한 정보를 캡쳐하기 위한 아키텍처 지원을 제공하는 것이다. 프로그램이 크래쉬되기 전에 일어난 순간 같은 결정적인 replay 를 개발자에 의해서 사용할 수 있는 기능.
결정적인 replay 디버깅은 버그에 이르게 하는 명령어의 정확히 같은 순서로 replay 하는 기능을 말한다. 우리는 DRD 가 문제의 근원을 고립시키고 고칠 수 있는 효율적인 방법이라고 믿는다. Flight Data Recorder 시스템은 DRD 를 활성화 하는 아키텍처의 첫번째 제안 중 하나다.
FDR 은 SafetyNet 의 가장 높은 곳을 토대로 한다. SafetyNet 은 공유된 메모리 멀티 프로세서 시스템에서 신뢰성을 제공하는 체크포인트 방법이다.
FDR 은 추적 데이터의 race, 프로그램 I/O, 인터럽트, DMA 엑세스를 추가적으로 채용한다. 이것들은 체크포인트의 시작으로 부터 전체 시스템의 결정적인 replay 를 위해서 모두 요구되어 진다. FDR 에서 record 된 정보를 사용하는 것은 인터럽트 핸들러와 시스템 콜 루틴, 추가적인 유저코드가 포함된 전체 시스템 실행을 replay 하기 위해서 가능하다.
이 논문에서 우리는 bugnet 이라고 불리는 계속적으로 프로그램을 추적하는 것에 초점을 맞추는 아키텍처에 대해서 설명할 것이다. bugnet 은 공유된 라이브러리와 유저코드 안에서 실행되는 명령어들을 결정적으로 replay 하는 데 초점을 맞추고 있다. 애플리케이션 레벨의 버그들의 중요한 숫자는 우리의 제안을 사용함으로서 replay 될 수 있고, 우리의 제안은 매력적인 개발 툴 소프트웨어이다.
bugnet 은 오직 애플리케이션 레벨의 버그에 초점을 맞추기 때문에, 그것은 FDR 에서 전체 시스템 실행을 replay 할 수 없다. 비록 bugnet 이 인터럽트와 시스템 콜을 record 하는 것은 허용한다. 애플리케이션 레벨의 버그에 초점을 맞춤으로서 FDR 보다 생성되는 로그 사이즈와 하드웨어의 사용되는 양이 적다.
Related Work
마이크로소프트의 Dr.Watson 툴과 모질라의 Talkback 은 프로그램 크래쉬를 위한 이유를 모아서 분석하고 최근의 방법의 예이다. 모든 이런 툴들은 정보를 수집하고, 프로그램이 크래쉬 되었을 때의 실행상태의 마지막 스냅샷을 나타낸다. 이 크래쉬된 리포트들은 어떤 유틸리티를 가지고 있고 그것은 크래쉬 전의 수행된 명령어의 순서를 정확히 replay 하는 기능을 가지는 것은 매우 바람직하다. 이런 replay 하는 예는 Ronsse 와 De Bosschere 가 있다. 그들은 명령어들의 같은 순서 통해 시간안에 되돌아 갈 수 있는 것을 가능하게 하고 프로그램이 반복적으로 재실행하는 모든 정보를 로깅하는 소프트웨어 디버거를 만들었다.
Checkpoint Schemes
replay 를 허용하기 위해서는, 이전의 작업에서는 체크포인트와 같은 형식을 사용했다. 이전 작업의 본체는 다양한 체크포인트 방법상에서 였다. 체크포인트를 디자인하고 재시작(Restart) 시스템의 신뢰성을 증대시키기 위해.(신뢰성을 높여 CPR 시스템을 디자인하기 위해)
시스템 실패(일시적으로 하드웨어 또는 소프트웨어 에러)를 만나면, 예전의 체크포인트로 롤백할 수 있고, 그후 즉시로 부터 계속 실행할 수 있다.
CPR 시스템에서 체크포인트 방법의 목적은 에러없이 계속되는 실행으로부터 일관된 전체 시스템 상태를 복구하기 위한 매커니즘을 제공하기 위함이다. 그것은 보통 체크포인트의 시작할 때 전체 시스템 상태를 복구하는 매커니즘을 포함한다. CPR 은 safetynet 과 re-vive 에서 OS 지원 또는 하드웨어 지원을 한다.
FDR 은 시간안에 이전 단계에 상응하는 일관된 전체 시스템 상태를 복구하기 위한 safetynet 체크포인트 매커니즘을 채택했다. 추가적으로 그것은 시스템에서 입력되는 replay 를 가능하게 하는 (I/O, 인터럽트, DMA 전송)을 record 한다.
이런 record 되는 정보들과 함께 복구된 전체 시스템 상태로 부터 시작하면서, 최초(오리지널) 프로그램 실행은 replay 될 수 있다.
우리가 제안하는 체크포인트 방식은 일관된 전체 시스템 상태를 복구하지 않는다. 대신에 그것은 단지 로드(load) 명령어를 통해 프로그램이 읽은 입력을 추적하는 것에 집중한다. 우리는 체크포인트의 처음에 레지스터의 상태를 record 하여 보여준다. 이것은 실행된 명령어를 replay 하는 데 충분하다. 우리의 체크포인트 방법은 일관된 전체 시스템 상태를 재생성하지 않는다. 그것은 CPR 시스템을 사용하는데 적당하지 않다. 그러나 애플리케이션 레벨의 버그를 재생성하는 데는 유용하다.
Deterministic Replayers for Debugging Multithreaded Applications
과거에는 멀티쓰레딩 애플리케이션을 디버깅하기 위해 지원을 제공하는 것이 결정적인 replayer 를 만드는 것이 목표였던 연구가 있었다. InstantReplay 는 Bacon 과 Goldstein 에 의해서 제안되었으며, 메모리 엑세스 순서를 record 하는 소프트웨어 기반의 결정적인 replayer, 그리고 하드웨어를 돕는 버전을 만들었다. 메모리 엑세스 순서를 record 하여 로깅하는 필요가 있는 정보의 양은 옮아가는 속성을 적용함으로서 줄일 수 있다.
만일 멀티쓰레드 애플리케이션이 싱글 프로세서 상에서 실행하고 있다면, 단지 스케줄러 결정을 record 하는 양은 제한할 수 있다. 다른 여러가지 방법은 쓰레드들의 콘트롤 플로우를 단지 record 하는 것이다.
data race 를 찾아내기 위해 필요한 정보를 record 하는 제안이 또한 있다. RecPlay logs synchronization race 와 race 는 오프라인 분석을 통해 찾아낸다. Eraser 은 약간 다른 방법을 가진다. 그것은 공유된 변수을 위한 잠금의 잘못된 사용 때문에 발생하는 에러를 찾아낸다.
ReEnact 는 rolling back 을 위한 방법을 제공하고, 쓰레드 레벨의 추론을 지원함으로서 실행을 replay 하는 것을 제공한다. ReEnact 는 data race 를 찾아내고, 예전에 로그한 체크포인트를 롤백할 수 있고, 실행을 replay 할 수 있다. Flashback 은 매끈한 롤백과 프로그램 실행을 replay 하기 위해 가벼운 OS 지원을 제공한다.
이런 모든 방법들에 대한 반대로서, 오직 FDR 은 프로그램 실행을 결정적으로 replay 하는 것을 가능하게 하는 프로그램 크래쉬 전에 발생한 완전한 기록(히스토리)을 제공한다. 이것은 문제를 정확하게 재생산하기 위해 개발자와 커뮤니케이션할 필요가 있다.
Architecture Support for Debugging
최근에 더 나은 소프트웨어 신뢰성과 정확성을 가능하게 하는 것을 지원하는데 큰 흥미를 가지고 있다. iWatcher 은 애플리케이션을 디버깅하기 위해 복잡한 와치포인트를 제공한다. 그것은 메모리 위치와 함께 태그를 결부시킨다. 그리고 이 메모리 위치에 접근했을 때, 특별한 함수가 모니터링을 수행하기 위해 실행된다. SafeMem 과 AccMon 은 최근에 프로그램의 수행중에 동적으로 침입하는 메모리를 잡아내는 기능을 아키텍처 상에서 제공하는 제안을 했다. 오프라인 디버거에서 효율적인 브레이크 포인트와 와치포인트의 실행을 아키텍처 지원하는 것은 Corliss 에 의해 또한 논의되어 졌다.
Flight Data Recorder
FDR 의 목적은 충분한 정보를 모으기 위한 아키텍처 지원을 제공하는 것이다. 크래쉬 전에 전체 시스템 실행의 마지막 1초를 결정적으로 replay 를 가능하게 하기 위한 충분한 정보이다. FDR 은 인터럽트, 시스템 콜 루틴을 replay 하는 것을 지원하는 것을 가짐으로서 전체 시스템을 replay 하기 위해 노력한다. 이와 대조를 이루어, bugnet 은 오직 유저 코드와 공유된 라이브러리에서의 실행을 replay 하는데 초점을 맞춘다. 그러나 그것은 여전히 결정적으로 애플리케이션을 넘어서는 인터럽트를 replay 할 수 있다. FDR 은 계속적으로 정보의 3 가지 종류를 record 한다.
- Checkpoint : FDR 은 safetynet 체크포인트 메커니즘에 사용한다. 실행의 replay 시작하는 일관된 시스템 상태를 복구하기 위해.
- Interrupts and External Inputs : 결정적인 replay 를 수행하기 위해, FDR 는 모든 인터럽트, memory mapped I/O, DMA 전송을 record 한다.
- Memory Races : 멀티쓰레드 애플리케이션에서 data race 를 디버깅하는 것을 지원하기 위해서 공유되는 메모리 접근 순서는 추가적인 MRB 를 사용함으로서 record 한다.
FDR 에서 replay 하기 위해 필요한 로그의 결합된 사이즈는 약 34 MB 정도이다. 전체 시스템 replay 를 가능하게 하기 위해 요구되는 모든 정보를 모으기 위한 FDR 의 하드웨어 복잡도는 온-칩 하드웨어의 약 1.3 MB 그리고 메인 메모리 영역의 34 MB 이다. 추가적으로 개발자와 커뮤니케이션하기 위한 전체 물리적 메모리 이미지의 마지막 스냅샷은 1 GB 와 비슷할 수 있다. 이것은 애플리케이션의 메모리 footprint 와 메인 메모리의 사용하는 것에 따라 다를 수 있다. bugnet 은 단지 애플리케이션 레벨의 버그를 캡처하는 것에 초점을 맞추는 이래로, replay 를 위한 record 되는 데 필요한 정보의 양이 줄어들 수 있다.
게다가, 더 작은 트레이스(추적) 크기는 심지어 사용자가 개발자가 트레이스를 커뮤니케이션하기위한 네트워크 대역폭 강제와 함께 권할 것이다. 심지어 사용자가
더 작은 트레이스 크기는 개발자가 뒤로 추적하는 것을 커뮤니케이션하기 위해 네트워크 대역폭 제한하는 것과 함께 권할 것이다.
BugNet Architecture
bugnet 은 프로그램 크래쉬의 앞선 명령어들의 윈도우(창)을 결정적으로 replay 할 수 있는 충분한 정보를 record 하는 것을 아키텍처 지원하는 것을 제공한다. 우리는 처음에 아키텍처의 개요를 준다. 그리고 나서 각 자세한 컴포넌트에 대해 의논해볼 것이다.
BugNet Architecture Overview
bugnet 의 목적은 정보를 record 하기 위함이다. 결정적인 replay 디버깅을 가능하게 하기위해 개발자에 뒤로 커뮤니케이션이 필요한 정보.
FDR 과 같지 않은, 우리는 애플리케이션 레벨 버그를 디버깅을 가능하게 하기 위해서 공유된 라이브러리와 유저 코드를 단지 replay 하는 데 초점을 제한한다. bugnet 은 인터럽트와 시스템콜을 결정적인 replay 를 지원한다. 그러나 개발자는 인터럽트와 시스템 콜 루틴의 실행하는 동안에 분석을 할 수 없다.
새로운 체크포인트는 각 체크포인트 인터벌의 시작에 인터벌의 첫번째 명령에서 시작되면서 실행이 replay 되는 것을 허락하기 위해 만들어 진다.
그러므로 체크포인트 인터벌은 로그된 체크포인트에 의해서 캡쳐된 커밋된 명령어의 윈도우를 나타낸다. 트래이스하는 동안, 가장 큰 사이즈(커밋된 명령어들의 갯수)는 체크포인트 인터벌을 위해 명기된다. 이 제한에 도달하면, 새로운 체크포인트 인터벌은 초기화되어지고, 그러나 체크포인트는 인터럽트나 컨텍스트 스위치를 만나면 때이르게 종료될 수 있다. 체크포인트 인터벌 동안, 충분한 정보는 체크포인트 인터벌의 시작에 프로그램 실행의 replay 를 시작하기 위해 로그에 record 된다.
bugnet 은 로드 명령어를 수행할 때, 값을 읽음으로서 본질적으로 프로그램의 수행을 관찰하는 것을 기반으로 한다. 따라서, 체크포인트 인터벌을 replay 하기 위해서 단지 초기 레지스터 상태를 record 할 필요가 있고, 그런 후에 인터벌에서 로드 명령어의 값을 record 한다.
체크포인트 인터벌 동안, 메모리 값은 인터럽트, 특히 I/O 인터럽트와 DMA 전송에 의해서 변경될 수 있다. 게다가 공유 메모리 멀티 쓰레드 프로그램의 경우, 공유메모리는 체크포인트 인터벌 동안 다른 쓰레드에 의해 변경될 수 있다. 그러나 로드 값을 로깅하는 것에 의해, 우리는 어떤 메모리 값이 인터럽트 핸들러 또는 공유 메모리 프로세서의 다른 쓰레드에 의해서 업데이트 되는 결정적인 replay 를 위해 요구되는 정보를 record 하는 것을 안전하게 한다.
그림 1은 bugnet 아키텍처의 주요 컴포넌트를 표현하고 있다. 회색으로 칠한 컴포넌트들은 기본 아키텍처에서 새로 추가된 것이다. bugnet 은 체크포인트 인터벌의 처음에 체크포인트를 생성함으로서 운영된다. 체크포인트 인터벌의 시작에 아키텍처 상태의 스냅샷을 CB 에 record 한다. record 된 아키텍처 상태는 프로그램 카운터와 레지스터 값을 포함한다. 초기화 후에, 로드 명령어가 실행될 때마다 새로운 로그 엔트리는 로드(load) 값을 record 하기 위해 만들어진다. 그리고 이것은 또한 CB 로 저장된다.
모든 로드 명령어의 결과값을 record 하는 것은 명확히 비용이 많이 든다. 그러나 우리는 체크포인트 인터벌에 있는 메모리에 첫번째로 엑세스하는 경우에만 로드한 값을 record 할 필요가 있다는 것에 주의한다. 다른 로드한 것의 결과값은 replay 하는 동안 중요하지 않게 생성될 수 있다. 그러므로 우리는 임시적인 히트를 확인하기 위해 캐쉬안의 모든 워드와 함께 한 비트를 연합시킨다. 그것은 record 되지 않는다. 이 최적화를 적용할 때, 특별한 걱정(care)은 먼 쓰레드에 의해서 공유메모리를 엑세스하고 인터럽트를 콘트롤하는 필요로 한다는 것이다.
만일 외부 엔티티들에 의해서 메모리 주소가 변경된다면, 우리는 장래의 로드 참조하는 그 주소가 로그되는지 확인한다.
트레이스 크기를 더욱 최적화하기 위해서 그림 1 에서 보는 바와 같이 우리는 사전기반의 압축기를 사용한다. 이것은 로드한 값에서 빈번한 값의 지역성을 이용한다.
로드 값과 체크포인트 인터벌을 위한 초기 아키텍처 상태정보를 포함하는 로그는 FLL 이라 한다. 체크포인트 인터벌을 위한 FLL 에서 record 되는 정보는 인터벌을 replay 하기 위해 충분하다. 이것은 정확히 같은 입출력 레지스터와 원래(오리지널) 실행에서 메모리 값과 함께 명령어들을 재실행하는 것을 가능하게 한다.
이것은 쓰레드를 위한 FLL 이 다른 쓰레드들이 독립해 있는 각 쓰레드를 replay 하기 위해 필요한 정보를 가지고 있기 때문에 멀티쓰레드 프로그램의 경우 사실이다. 쓰레드들 사이의 data race 를 디버깅하기 위해서, 우리는 쓰레드를 가로지르는 메모리 오퍼레이션의 순서를 재생산하기 위한 동기화 정보를 record 할 필요가 있다. 우리는 FDR 에서 사용되고 있는 방법인 동기화 정보를 record 하기위해 MRB 를 사용한다. 그것은 MRL 에서 담당한다.
CB 와 MRB 는 있을 수 있는 것보다 큰 로그의 모음이 전용 하드웨어 버퍼에 저장되는 것을 허락하기 위해 뒷면의 메모리인 FIFO 큐이다. 운영체제는 bugnet 의 사용을 위해 할당되는 데 필요한 메모리 영역의 관리를 위한 지원을 제공한다.
버그가 발견될 때, 목표는 메모리에서 더 낡았던 로그에 덮어쓰고 있는 메모리에 프로그램의 실행을 연속적으로 기록하고 디스크에 로그를 덤프시키는 것이다.
그러므로, 메모리는 많은 다른 쓰레드들로부터의 로그들과 다양한 연속 체크포인트에 일치하는 로그들을 포함할 수 있다.
할당된 메모리 영역이 가득 차면, 가장 오래된 쓰레드의 체크포인트에 대응하는 로그는 버린다.
충분한 FLL 들은 각 쓰레드를 위한 적어도 명령어의 수행이 수천만의 명령어를 replay 하기 위해 메모리에 유지된다. 개발자는 버그의 원인을 결정하기 위해 충분한 명령어들을 replay 하는데 흥미를 느낀다. 이 명령어의 숫자(번호)는 replay 윈도우보다 적어야 하며, bugnet 에 의해서 캡쳐된다.
replay 윈도우는 메모리에 저장되는 FLLs 과 MRLs 의 순서를 주는 쓰레드를 위해 replay 할 수 있는 명령들의 숫자(번호)이다.
운영체제가 프로그램이 실패를 접했음을 발견했을 때, 애플리케이션이 종료되기 전에, 처음으로 현재 FLL 의 명령 카운터를 record 하고, 현재 FLL 에서 실패한 명령의 프로그램 카운터를 record 하고, 그런 후에 계속적으로(영속적인) 저장 장치에 애플리케이션을 위해 모든 로그를 모아 저장한다.
로그들은 디버깅을 위해 개발자에게 되돌려 보내진다.
Checkpoint Scheme
체크포인팅을 위해서, 프로그램의 실행은 다양한 체크포인트 인터벌로 분할 된다. 거기서 인터벌의 길이는 실행된 명령의 숫자에 의해서 지정받는다.
프로그램 인터벌의 끝에는 현재 체크포인트가 종료되어지고, 새로운 하나가 생성될 것이다. 게다가 인터럽트와 시스템 콜들은 또한 체크포인트 인터벌을 종료할 수 있다. 우리는 그것을 나중에 설명할 것이다. 최종적으로 실행의 실패는 체크포인트 인터벌을 종료할 것이고, 디버깅을 위해서 개발자에게 로그들의 시작하는 모음을 되돌려 보내진다.
새로운 체크포인트는 CB 에서 새로운 FLL deliminator 을 생성하고, 그리고 체크포인트 인터벌의 FLL 과 함께 다음의 헤더정보를 초기화 함으로서 record 된다.
- Process ID and Thread ID - 그것이 만들어졌던 실행의 쓰레드와 FLL 가 결부시켜야 한다.
- Program Counter and Register File contents - 체크 포인트 인터벌의 처음에 아키텍처 상태를 다시 표현하기 위해서 필요하다. 이 정보는 record 된 로드 값을 사용하여 프로그램 실행을 replay 시작하기 전에 아키텍처 상태를 초기화하기 위해 replayer 에 의해서 나중에 사용되어질 것이다.
- Checkpoint Interval Identifier(C-ID) - 이 FLL 에 대응하는 체크포인트를 확인하기 위해 사용된다.
- Timestamp - 체크포인트가 생성되었을 때, 시스템 클럭 타이머가 타임스탬프이다. 이것은 생성된 시간에 의한 쓰래드를 위해 모아지는 FLLs 를 순서를 정하는데 유용하다.
체크포인트 인터벌이 생성되면, C-ID 는 인터벌을 위한 유일한 확인자를 제공하기 위해 만들어진다. 새로운 체크포인트 인터벌이 생성될 때마다 C-ID 는 증가한다. 이 카운터와 C-ID 를 위해 사용되는 비트들의 수는 시간의 어떤 순간이라도 메모리에 존재할 수 있는 체크포인트의 최대 수에 의존한다. 그것이 오버플로우 되면, 카운터는 0 으로 설정된다.
위의 정보와 함께 초기화되고 난 후의 FLL 은 체크포인트 인터벌 동안 실행되는 로드 명령의 출력(output) 값과 함께 추가될 것이다.
Tracking Load Values
체크포인트 인터벌 이내에 그것이 그 메모리 위치에 첫번째로 접근한 경우에만 메모리 위치를 접근하는 load 는 기록될 필요가 있다. 다른 load 들의 값은 replay 동안 재 생성될 수 있다. 단지 메모리 위치에 첫번째 로드를 기록하는 것은 record 될 필요가 있는 load 값들의 수를 상당히 줄일 수 있을 것이다.
이 최적화를 수행하기 위해, 우리는 L1 과 L2 캐쉬에서 첫번째 load bit 를 모든 워드와 결부(연합) 시킨다. 체크포인트 인터벌의 시작에 모든 이들 비트들은 클리어 될 것이다. load 가 비트가 설정되지 않은 워드에 접근할 때, load 값은 체크포인트 버퍼에서 기록될 것이다. 그리고 비트는 켜질 것이다(turn on).
만일 비트가 캐쉬안에 워드 때문에 set 되면, 그것은 워드의 값이 이미 기록되었던 것을 의미한다. 그러므로 장래의 load 접근은 기록될 필요가 없다.
이 방법은 FDR 로 부터 개조되었다. FDR 의 목적은 체크포인트 인터벌동안 첫번째로 저장한 특별한 위치까지 추적하는 것이고, 덮어써지는 값을 기록하는 것이다.
그들은 체크포인트 인터벌의 시작에 메모리 상태를 되찾기 위해 마지막 core 이미지를 교정하기 위해 저장된 값을 사용하는 이래로 저장에 초점을 둔다.
bugnet 에서는 만일 특별한 메모리 위치의 첫번째 접근이 메모리라면(저장되면), 우리는 비트를 설정하고 메모리의(저장된) 값을 기록하지 않는다.
비트가 설정될 것인가에 따라 메모리 위치에서 장래의 load 접근은 기록되지 않을 것이다. replay 동안 명령어들의 실행에 의해 만들어 지기 때문에 메모리(저장된) 값들은 기록되지 않는다.
캐쉬 블럭이 L2 캐쉬로 부터 교체되면, 캐쉬 블럭의 워드와 함께 연관된 모든 첫번째 load 비트는 클리어 된다.
그래서 L2 로 부터 회복된 주소(블럭)을 위한 기록된 값은 재 기록 될 것이다. 블럭이 같은 주소들로 다시 접근하게 될 것이다.
그러므로 블럭이 뒤로 가져오게 될때, L2 에서 회복된 주소(블럭)을 위해 기록된 값은 재 기록 될 것이다. 그리고 그 같은 주소들은 다시 접근하게 된다.
L2 에서 L1 까지 블럭을 가지고 들어올 때 L2 캐쉬 안에 첫번째 load 비트는 L1 캐쉬에 첫번째 load 비트를 초기화하는 데 사용한다.
L1 블럭이 축출되면, 첫번째 load 비트는 L2 캐쉬의 첫번째 load bit 에 저장된다. 위의 첫번째 load 최적화는 체크포인트 인터벌을 위해 효과적일 것이다. 이것은 실행된 load/store 가 많을 수록 이미 기록되었던 메모리 위치일 확률이 더 높기 때문이다.
결과적으로 명령 replay 를 위해 record 된 정보의 양은 더 긴 체크포인트 인터벌과 함께 감소될 것이다.
replay 동안, 우리는 load 명령을 위한 값이 log 에서 record 되어야 하는지 말지에 대해 결정할 필요가 있다. 만일 그것이 record 된다면, replay 하는 동안 실행되는 load 는 FLL 에서 그 값을 얻을 필요가 있다. 만일 record 되지 않는 다면, 그것은 접근하고 있는 메모리 위치가 첫번째 접근이 확실히 아니다.
replay 동안 메모리 상태를 시뮬레이팅에 의해, 값은 시뮬레이트된 메모리 상태로 부터 읽음으로서 얻을 수 있다. 언제 replay 동안 load 값을 소비하는 지를 결정하기 위해, 각 로그 엔트리의 부분으로서 우리는 지난 load 명령이 기록되었던 이래로 지나친 load 명령의 수(번호)를 지정할 필드를 가진다. 다음은 load 명령을 위한 정보를 record 하기 위하여 체크포인트의 각 로그 엔트리의 포맷이다.
(LC-Type, Reduced/Full L-Count, LV-Type, Encoded/Full Load-Value)
두번째 필드인, Reduced/Full L-Count 는 현재 기록되는 load 명령과 지난 로그된 load 명령 사이의 지나친(기록되지 않은) load 명령의 숫자를 나타낸다. 모든 L-Count 값을 record 하기 위해서는 log(checkpoint interval length) bit 가 필요하다. L-Count 가 사용되는 최대한의 체크포인트 인터벌 사이즈보다 클 수 없다는 것 이래로.
우리는 L-Count 값의 대부분이 단지 5 bit 를 사용해서 표현할 수 있다는 것을 알아냈다. 그러므로 우리는 그 값이 32 미만일 때마다 5 bit 를 사용하는 L-Count 를 record 한다.
만일 L-Count 의 값이 32 를 넘으면, 전체 L-Count 값을 record 하기 위해 재분류한다.
전체 L-Count 값을 포함하는 로그는 하나의 추가적인 비트(LC-Type) 을 사용하는 것에 의해 포함하는 로그를 구별한다.
4 번째 필드인 Encoded/Full Load-Value 는 load 값을 record 하기 위해 사용된다. 다시 우리는 전체 32 비트 load 값을 record 하는 것을 피하기 위해서 시도했다. 이것을 달성하기 위해서, 우리는 가장 자주 발생하는 load 값을 포착하는 64 엔트리 사전을 사용한다.
만일 load 값이 사전에서 찾는다면, 우리는 사전의 값의 위치를 표현하기 위해 6 bit 를 사용한다. 만일 load 값이 사전에 없다면 전체 32 bit 값을 record 한다. LV-Type 은 두 가지의 케이스 사이에서 구분하기 위해 사용되는 bit 이다.
load 명령을 추적하기 위해 우리는 단지 그것의 log 의 출력 값을 record 한다. 그들이 쓰레드의 실행을 replay 하는 동안 생산될 수 있는 이래로 load 명령의 PC 의 주소나 효율적인 주소도 모두 기록되지 않는다. 5 섹션에서는 우리는 replay 방법에 대해 더 구체적으로 설명한다.
Dictionary-based Compressor
bugnet 에서 load 값은 사전기반의 압축기를 사용하여 압축시킨다. 그것은 load 값이 자주 나타나는 값의 지역성을 보여준다.
즉, 모든 load 값의 50% 이상은 자주 발생하는 값의 작은 숫자를 사용함으로서 포착할 수 있다. 게다가 값 예측자는 엄숙한 압축 비율을 제공하는 것을 나타낸다.
우리의 방법은 사전 테이블이라 불리는 64 엔트리 완전한 연관 테이블은 자주 발생하는 load 값을 포착하기 위해서 사용한다.
사전 테이블은 체크포인트 인터벌의 처음에 비워진다. 그리고 각 load 명령의 실행과 함께 업데이트 된다. FLL 로 load 값을 기록하기 전에 값은 사전테이블을 조사한다. 만일 hit 되면, 전체 32 비트 값을 저장하는 대신에 우리는 6 비트 인코딩을 저장할 것이다.
6 비트 인코딩은 사전테이블의 값의 계층(랭크)에 대응한다. 우리의 설계에서 계층은 매칭되는 값을 찾기위해서 사용하는 사전 테이블에 대응된다.
체크포인트 인터벌에서 사전 테이블은 load 명령들이 실행되어짐으로서 계속적으로 갱신될 것이다. 결과적으로 사전테이블의 값의 위치는 인터벌 동안 바뀌고 있을 수 있다. 그러므로 우리가 값을 압축하기 위해 사용하는 인코딩은 FLL 의 과정 위에서 바뀔 수 있다. 이것은 우리가 replay 동안에 사전 상태를 시뮬레이트 하는 이래로 유효하다. replay 동안 우리는 체크포인트 인터벌의 처음 그리고 초기 사전 상태(빈 상태)를 알고 있다. 그리고 모든 이후의 실행되었던 load 명령은 테이블을 갱신한다.
그러므로 시간의 어떤 순간이라도, replay 동안 load 명령을 실행하는 동안, 사전 테이블의 상태는 최초의 실행 동안 그 상태와 같을 것이다.
인터벌 내에서 실행되는 모든 load 를 위해, 사전 테이블은 다음과 같이 갱신될 것이다. 테이블에 각 엔트리는 엔트리에 저장된 값의 빈번함을 추적하기위해서 3 비트 saturating 카운터를 가진다. 사전테이블의 엔트리에서 load 값을 찾았을 때, 3bit saturating 카운터는 그것이 포화될 때까지 증가되는 엔트리에 대응시킬 수 있다.
만일 갱신된 카운터 값이 테이블 예전 엔트리의 카운터 값보다도 크거나 같다면, 두개의 값은 테이블에서 그들의 포지션(계층)을 스왑한다. 이 것은 아주 자주 발생하는 값이 테이블의 상위(top) 에서 여과할 것을 확실하게 한다.
사전 테이블에서 load 값을 찾을 수 없다면, 그것은 가장 작은 카운터 값과 함께 엔트리에 추가된다. 만일 그것이 복수의 후보라면, 테이블에서 가장 낮은 포지션을 차지하고 있는 엔트리는 교체를 위해 선택된다.
Handlng Interrupts and Context Switches
인터럽트는 동기와 비동기가 있다. 비동기적 인터럽트는 I/O 와 타이머 인터럽트와 같은 애플리케이션 코드를 수행하면서 외부에 소스에 의해 원인이 있다. 반면에 동기적 인터럽트(또한 트랩과 관련된) 는 프로그램 명령을 실행하면서 트리거(유발)되어 진다.
트랩의 이유는 산술적인 오버플로우 익셉션, 시스템 콜 또는 페이지 폴트 같은 이벤트가 포함된다.
우리의 목적은 오직 애플리케이션 코드를 replay 하고 디버깅하는 것이다. 우리는 어떤 인터럽트 동안에 실행하는 것은 record 하지 않는다. 그래서 우리는 인터럽트 핸들러의 부분으로서 실행되는 load 명령의 출력, 운영체제의 인터럽트 서비스 루틴은 record 하지 않는다. 그럼에도 불구하고 우리는 인터럽트가 애플리케이션 실행에 영향을 주는 방법을 추적하기 위한 필요가 있다.
인터럽트는 메모리의 상태를 수정한다.(예를 들면 I/O 인터럽트) 그리고 그들은 심지어 프로그램 카운터나 레지스터를 수정함으로서 프로그램 실행의 아키텍처 상태를 변화시킬 수 있다.
제어가 애플리케이션 코드로 리턴될 때, 똑바른 방법은 인터럽트를 만난 상에서 현재 체크포인트 인터벌이 너무 이르게 종료되는 것에 의한 문제를 해결하고 새로운 체크포인트(?) 를 생성하는 것이다.
만일 우리가 인터럽트 처리 후에 새로운 체크포인트를 생성한다면, 새로운 FLL 의 헤더에 초기화될 것인가에 따라 우리는 올바른 프로그램 카운터 값을 보장할 수 있다.
또한 첫번째 load 를 추적하기 위해서 사용하는 비트는 reset 이었을 것이다. 그러므로 필요한 load 값을 확실하게 하는 것은 인터럽트 이후에 실행되었던 명령을 replay 하기 위해 기록되는 것이다.
우리는 섹션 6 에서 결과를 논의하는 동안 모델을 구조화한다.
좀더 적극적인 방법은 체크포인트와 인터럽트를 넘어 추적하기 위해 첫번째 load 비트를 허용하는 것이다.(첫번째 load 비트에게 체크포인트와 인터럽트를 가로질러 추적되는 것을 허용하는 것이다)
인터럽트 후에 새로운 체크포인트를 재시작할 때, 이것은 기록되는 load 명령의 숫자를 줄이는데 도움을 준다.
해결책은 인터럽트 또는 컨텍스트 스위칭 중에 메모리 상태가 업데이트 될 때, 첫번째 load 비트는 정확히 무효로 되는지 확인할 필요가 있다.
좀더 자세하게 이 방법의 시험하는 것은 미래의 연구를 위해 남겨둔다.
Handling External Input
인터럽트를 콘트롤하기 위해 이전 섹션에서 설명한 방법은 I/O 인터럽트를 제어하기에 충분하다. 메모리 매핑 I/O 방법은 프로그램의 가상 주소 공간에 디바이스의 주소 공간을 매핑함으로서 동작한다. 이 값들은 프로그램의 주소 공간에 대응하는 가상 주소를 사용함으로서 load 명령을 통해서 디바이스로 부터 읽는다.
OS 는 I/O 시스템 콜을 서비스 하기 위해 DMA 전송을 초기화 할 수 있다. 그런 경우에 제어는 애플리케이션 코드로 리턴될 것이다. 그러나 DMA 전송은 병렬로 계속 수행할 수 있다. FDR 와 같이, 우리는 DMA write 가 사전 기반의 캐쉬 결합 프로토콜을 사용하고 애플리케이션을 실행하고 있는 프로세서에서 캐쉬 블럭을 무효화한다. 이것은 첫번째 load 최적화를 위해 사용되는 비트가 리셋 되는 것을 확실하게 할 것이다. 그리고 변경된 메모리 위치의 값들은 애플리케이션 load 에 의해 참조되어 얻을 때, record 된다.
그것이 애플리케이션에서 참조될 때까지 단지 첫번째 load 값을 기록하는 우리의 계획은 프로세스의 주소 공간으로 복사되는 데이터를 기록하는 것을 피한다.
비록 커다란 양의 데이터가 프로세스의 주소 공간으로 복사될 수 있다고 해도, 그것의 모두는 크래쉬 앞에 프록그램 실행에 의해 반드시 사용되지 않을 것이다. 우리의 계획은 나중에 replay 할 필요가 있는 그 명령에 의해 사실 소비하게 되는 바로 필요한 값을 기록하는 것을 확실하게 한다.
Support for Multithreaded Applications
우리는 공유 메모리 멀티 프로세서 시스템에서 멀티쓰레드 프로그램을 실행시킨다고 가정한다. 공유 메모리 멀티 쓰레드 애플리케이션에서 다른 프로세서의 외부 쓰레드는 실행시에 체크포인트 인터벌의 안에서 공유 데이터를 수정할 수 있다.
이 문제는 우리가 DMA 전송에 관해서 논의했던 것과 같은 것이다. 외부 쓰레드에 의해 공유 메모리 위치가 변경되면, 대응하는 캐쉬 블럭은 갱신되기 전에 무효화될 것이다. 이것은 첫번째 load 를 캐쉬 블럭까지 추적하기 위해 사용되는 비트를 리셋 할 것이다.
결과로서, 같은 캐쉬 블럭에서 장래 load 참조는 FLL 에 외부 쓰레드에 의해 값이 record 하는 결과를 가져온다.
체크포인트 인터벌에 대응하는 FLL 은 인터벌에서 명령어를 replay 하기 위해서 충분하다. 이것은 멀티쓰레드 애플리케이션의 경우 사실이다. 우리는 쓰레드를 실행하기 위해 요청되는 모든 입력 값을 record 함으로서 어떤 쓰레드도 다른 쓰레드와 독립적으로 replay 할 수 있다.
그러나, 데이터 race 디버깅을 돕기 위해서 쓰레드를 가로질러 실행되는 명령의 순서를 추론하기 위한 기능이 요구된다. 동기화 정보를 record 하기 위해서 우리는 FDR 에서 제안하는 방법을 개조하고, 분리된 MRB 에서 그것을 record 한다.
Memory Model
FDR 에서 우리는 디렉토리 기반의 캐쉬 결합 프로토콜을 사용하는 순차적인 일관된 메모리 모델과 함께 공유 메모리 멀티 프로세서를 가정한다. 순차적 일관 메모리 모델에서 모든 메모리 접근은 차례로 발생하는 것처럼 나타난다.
그러므로 프로그램의 실행은 순차적인 순서로 다른 쓰레드에서 명령을 인터리빙 하는 것에 의해 표현될 수 있다. replay 동안 유효한 순차적 순서를 얻기 위해서는 우리는 쓰레드를 가로넘어 메모리 오퍼레이션의 순서를 record 하기 위한 FDR 에서 사용되는 방법을 개조했다.
Asynchronous Checkpointing in Multithreaded programs
FDR 의 체크포인트 방법은 공유 메모리 멀티쓰레드 애플리케이션을 지원하기 위한 장벽(?) 동기화를 사용한다. 이 방법은 모든 쓰레드를 가로지르는 체크포인트 인터벌이 시간의 같은 순간에 시작되는 것을 확실하게 한다. 이 방법은 bugnet 아키텍처에서는 바람직하지 않다. 특히 우리가 작은 인터벌 길이의 체크포인트를 만들기 원했을 때, 성능에 의한 오버헤드 때문에.
더구나, 우리는 인터럽트 이벤트를 만났을 때와 같은 다른 쓰레드로 부터 독립적인 체크포인트 종료의 유연성을 가지기 원했다.
그러므로 우리는 쓰레드에게 다른 쓰레드로 부터 독립적인 체크포인트 인터벌의 생성과 종료하는 것을 허용했다. 결과로서, 다른 쓰레드를 가로지르는 체크포인트 인터벌은 같은 시간에 시작하지 않을 수 있다. 쓰레드를 가로지르는 비동기 체크포인트를 지원하기 위해서 다음 섹션에서 설명할 우리는 모든 메모리 race 로그 엔트리의 부분으로서 체크포인트 식별자를 record 한다.
Memory Race Log
새로운 체크포인트가 생성될 때마다, 새로운 FLL 을 만드는 것에 더하여 설명하기 전에 우리는 또한 memory race buffer 에 저장되는 새로운 MRL 을 만든다. 모든 쓰레드는 그것의 로컬 MRL 에서 동기화 정보를 record 한다. 주어진 쓰레드를 위해, MRL 은 FLL 과 함께 동기화에서 유지한다. 새로운 체크포인트 인터벌이 생성될 때, 더하여 새로운 FLL 이 만들어지고 새로운 MRL 은 또한 만들어지고, 다음의 헤더 정보와 함께 초기화 된다.
- Process ID and Thread ID(T-ID) : 이에 대응되는 쓰레드와 함께 메모리 race 를 결합하기 위해 사용되어 진다.
- Checkpoint Interval Identifier(C-ID) : 이 메모리 race 로그에 대응되는 체크포인트 인터벌을 식별하기 위해 사용된다.
- Timestamp : 체크포인트가 생성될 때 시스템 클럭 타이머이다. 타임스탬프는 다른 쓰레드를 가로지르는 replay 동안 MRL 의 정렬하기 위해 사용한다.
메모리 race 로그의 목적은 쓰레드 중에서 공유 메모리 순서를 추적하기위함이다. MRL 생성을 위한 FDR 의 제안은 실행 상태와 함께 외부 쓰레드로 부터 결합 응답 메세지(write 를 실행하는 동안, 무효 승인을 write 하고 그리고 load 를 실행하는 동안, update 응답을 write 한다)에 편승하는 것이다.
공유 메모리 접근을 위해 결합 응답 메세지 마다 MRL 의 엔트리는 만들어질 것이다. 어떤 결합 응답도 그 메모리 오퍼레이션을 위해 받지않을 것에 따라 공유되지 않거나, 배타적인 상태에 있는 메모리 위치에 load 또는 저장이 실행되는 동안 어떤 MRL 엔트리도 기록되지 않을 것이다. bugnet 을 위해 MRL 은 체크포인트 인터벌의 시작에 위의 정보와 함께 초기화 된다. 이후에 결합 응답 메셎를 받았을 때, 로그는 다음의 정보를 덧붙인다.
(local.IC, remote.TID, remote.CID, remote.IC)
각 레코드의 목적은 외부 쓰레드의 실행을 동기화하기 위함이다. 그들의 명령 카운터의 둘다를 record 함으로서 로컬 쓰레드의 실행과 함께 결합 응답 메세지를 보낸다. 이 정보를 가지고 있는 것은 우리에게 모든 쓰레드를 가로질러 메모리 오퍼레이션의 순서를 되찾는 것을 허락한다.
외부 쓰레드는 그것의 실행 상태를 보낸다. 결합 응답의 일부로서, 메모리 오퍼레이션을 실행하는 로컬 쓰레드에.
(메모리 작업을 실행했던 로컬 쓰레드에서 그 결합 응답의 일부로서, 외부 쓰레드는 그 실행 상태를 보낸다)
local.IC 는 로컬 쓰레드에서 현재 메모리 오퍼레이션까지 현재 체크포인트 인터벌을 시작하는 것에서 실행되는 명령의 숫자를 표현한다. 그러므로 local.IC 의 사이즈는 오직 log 만큼 커야할 필요가 있다.(체크포인트 인터벌 길이). 다른 3개의 필드는 외부 쓰레드의 상태를 표현하는데 사용한다. remote.TID 는 thread ID 를 식별한다. 이 필드의 크기는 log 만큼 크다.(최대 현재 살아있는 스레드). remote.CID 는 일반적으로 외부 쓰레드의 active 한 체크포인트 인터벌에 대응하고 있는 체크포인트 인터벌 식별자를 표현한다.
체크포인트 식별자를 위해 요구되는 bit 수는 메모리에 동시에 존재할 수 있는 체크포인트의 수에 의존한다.
remote.IC 는 결합 응답을 보내질 때, 외부 쓰레드의 명령 카운터를 표현한다.
멀티 쓰레드의 replay 순서 하기위한 MRL 과 FLL 을 어떻게 사용하는 지의 자세한 설명은 섹션 5.2 에서 한다.
FDR 은 Netzer 의 알고리즘 실행을 지원하는 아키텍처를 제공함으로서 메모리 race 로그의 크기를 최적화 한다. 우리도 또한 가정한다. 이 메모리 race 로그 최적화에 대한 자세한 내용은 FDR(24) 를 보자.
Memory Backing
체크포인트 버퍼에 저장되는 첫번째 load 로그와 MRB 에 저장되는 메모리 race 로그는 메모리에서 2 개의 다른 위치에서 지원하는 메모리이다.
이 목적을 위해 바쳐지는 메모리 공간의 양은 사용자나 운영체제에 의해 성능 효과(충격)이 허용한도 내에 있다는 것을 확실하게 하기위해 관리될 수 있다. 메모리 공간와 바쳐지는 디스크 공간의 양은 replay 할 수 있는 명령의 숫자를 결정할 것이다.
메모리 버스가 놀고 있을 때마다 버퍼의 내용은 느리게 메모리로 써진다. 우리가 있는 그대로의 생성되는 로그 엔트리를 압축할 수 있는 이래로, 버퍼의 내용은 어떤 포인트에서라도 메모리에 느리게 write 될 수 있다.
이 메모리를 지원하는 방법은 메인 메모리로 추가적인 트래픽 때문에 메모리 대역폭 요구에 영향을 줄 수 있다. 프로세서가 메인 메모리에 접근해서 캐쉬미스를 되었을 때, 데이터가 메인 메모리로 부터 얻게 되는 것은 기다리면서 지연시키게 될 것이다.
그래서 load 가 FLL 에서 기록되는 비율은 줄어들게 될 것이다. 우리는 메모리 버스가 idel 상태일 때 메모리로 로그를 쓰는 것이 충분한 대역폭이라는 것을 SPEC 벤치마크를 시뮬레이트 할 때 알아냈다. 그리고 온 칩 버퍼는 오직 로깅에서 버스트를 잡을 만큼 충분히 클 필요가 있다.
On Detecting Fault
운영체제는 언제 프로그램이 쓰레드가 종료되게 하는 명령을 실행하는지 알 것이다. 0 으로 나누거나 또는 유효하지 않은 주소로 메모리 오퍼레이션을 접근하는 것 때문에 산술적 익셉션은 프로그램이 크래쉬 되면 트리거 할 수 있는 약간의 예이다.
운영체제가 프로그램이 실패하는 명령이 실행되는 것을 감지하면, FLL 에 record 된다. 체크포인트 인터벌에 현재 명령 카운터와 실패 명령의 프로그램 카운터를 record.
게다가 OS 는 메인 메모리와 하드웨어 버퍼로 부터 애플리케이션에 대응하는 FLL 과 MRL 을 모은다. 그것은 모든 로그의 헤더를 통해서 검사한다. 그리고 애플리케이션에 대응하는 로그를 확인하기 위해서 헤더안의 프로세스 식별자를 사용한다.
모아진 로그는 영속적인 저장장치에 저장되고 그리고 디버깅을 위해 개발자에게 보내진다.
Replayer
Virtutech Simics 를 사용한 bugnet 을 위한 개념의 증명으로서 replayer 의 프로토타입을 실행했다. 이 섹션에서 우리는 어떻게 bugnet replayer 가 동작하고 simics 를 이용한 replayer 시스템을 구축함에 있어서 우리의 경험을 설명할 것이다.
Replaying Single Thread
우리는 페도라 리눅스 상에서 Pin Dynamic Instrumentation 툴을 사용했다. 표1 에서 말하는 프로그램이 섹션 4.2 에서 기술되었던 FLL 을 모으기 위해.
여러개의 FLL 은 프로그램 크래쉬 전에(까지) 스레드마다 모은다. replayer 의 목적은 FLL 를 사용하는 것과 bug 까지의 체크포인트 인터벌에서 명령을 실행하는 데 있다. 프로그램의 실행을 replay 하기 위해, 우리의 replayer 는 애플리케이션의 정확히 같은 바이너리와 FLL 을 만들때 사용한 공유 라이브러리를 접근하는 것이다.(프로그램을 재실행하기 위해, FLL 을 만들 때 우리의 replayer 는 애플리케이션과 공유 라이브러리를 위해 정확히 같은 바이너리까지 접근을 가지도록 해야 한다.
FLL 을 replay 하기 위해서, 우리는 첫번째 명령을 실행하기 전에
Simics 에 의해 시뮬레이트되는 레드햇 리눅스 하에서 프로그램 실행을 시작했다. 그리고 첫번째 명령을 실행하기 전에 실행을 break 시켰다. 그다음 우리는 데이터 메모리 위치의 모두를 클리어시키고, 가상 주소 영역으로 로드되는 replay 를 위해 필요한 공유 라이브러리의 모두를 확인했다. 그 다음 우리느 레지스터 값과 프로그램 카운터를 초기화하기 위해 FLL 에 헤더 정보를 이용했다. 마지막으로 애플리케이션의 실행은 계속하는 것을 허락받는다. 모든 load 명령의 실행는 전에 멈춘다.(break)
load 명령을 만나면 replay 는 load 에 의해 접근되는 메모리 위치가 옳은 값을 포함하는지 확인해야 한다. 이것을 하기 위해, 포맷(형식)이 4.3 절에서 설명했던 FLL 에서 다음 레코드의 압축을 풀 필요가 있다.
만일 load 가 FLL 또는 메모리로 부터 그것의 값을 되찾아야 한다면 LC-type bit 를 사용하고, 우리는 FLL 에 record 의 두번재 필드의 record 된 LC-Count 의 값을 디코드(암호화)한다. 그리고 그것을 결정하기 위해 사용한다.
LC-type bit 를 사용하면서, 우리는 FLL 에서 레코드의 두번째 필드에서 record 되는 LC-Count 의 값을 부호화하고, 그것을 load 가 그 값을 FLL 이나 메모리로부터 되찾아야 한다고 결정하기 위해 사용된다.
만일 load 값이 로그에서 부터 오면, 만일 LV-Type 비트가 설정되면 우리는 로그에서 다음 완전한 32 bit 값을 사용한다.
만일 그렇지 않다면, 4.3.1 절에서 기술되는 것에 따라, 다음 6 비트는 사전 엔트리가 값을 제공한다고 설명한다.
대응하는 디렉토리 엔트리는 그다음에 읽는다. 그리고 그 값은 load 를 위해 사용된다.
주의. 사전 테이블에서 정확한 값을 생성하기 위해서는 우리는 모든 실행되었던 load 에 사전을 업데이트 했다. 그리고 4.3.1 절에서 언급한 것처럼 replay 중에 시뮬레이트 했다.
위에 이어서 우리는 정확한 값을 load 명령에 얻게 할 수 있다. 그리고 replay 가 시작하면, 보통의 실행에 실행의 쓰레드를 결정적으로 replay 하는 것을 허락하면서 실행되는 모든 명령은 레지스터 파일과 메모리를 업데이트할 것이다.
replay 동안, 우리는 동기 인터럽트를 만날 것이다. (우리가 인터럽트 중에 계속하는 실행하는 것을 시뮬레이트 할 필요가 없는 이래로, 그것은 NOP 로 바꾸게 될 것이다.)
지난 인터럽트를 replay 하기 위해서, 우리는 단지 쓰레드의 실행을 위해 record 되는 다음 FLL 를 replay 하는 것을 계속한다.
Replaying Multiple Threads and Inferring Data Races
개별적인 쓰레드는 이전 섹션에서 설명되는 절차를 사용하고 있는 멀티 쓰레드 애플리케이션에서 replay 될 수 있다. 그런나 멀티 쓰레드 프로그램을 디버깅하기 위해서 우리는 모든 쓰레드를 가로지르는 메모리 오퍼레이션의 유효한 순차적인 순서를 되찾을 수 있는 필요가 있다. 이것은 MRL 에서 기록되는 모든 정보에 의해 제공된다.
각 쓰레드를 위해 우리는 FLL 과 MRL 를 replay 되는 다양한 체크포인트 인터벌에 대응하도록 한다. 우리는 FLL 를 체크포인트 인터벌 ID 를 사용하고 있는 같은 체크포인트 인터벌과 이 로그의 헤더에 저장되는 타임 스탬프 동안 모아졌던 MRL 과 결부시킬 수 있다.
FLL 을 사용함으로서 우리는 앞에서 설명한 것과 같이 싱글(하나) 쓰레드 애플리케이션을 replay 하는 것으로서 FLL 의 체크포인트 인터벌 동안 실행된 명령을 추적할 수 있다. (트레이스를 발생시킨다.)
이것은 각 쓰레드에서 각 메모리 오퍼레이션을 위해 명령 카운터를 생성한다. 그리고 이것은 각 쓰레드에서 MRL 엔트리를 동기화 포인트를 매핑하기 위해 사용된다. 효과적으로 MRL 은 메모리 오퍼레이션을 위해 쓰레드를 가로질러 순서를 추론하기 위해 사용된다.
MRL 에서 체크포인트 식별자(remote.CID) 와 thread ID(remote.TID) 를 사용하면서 외부 쓰레드의 체크포인트 인터벌은 식별할 수 있다.
그리고 remote.IC 를 사용하면서 우리는 로컬 메모리 오퍼레이션이 실행되기 전에 외부 쓰레드의 마지막 커밋된 명령을 결정할 수 있다. 이런 식으로 우리는 외부 쓰레드를 위해 MRL 엔트리에서 발견되는 명령 카운터와 관계가 있는 로컬 쓰레드에서 메모리 오퍼레이션을 위해 순서 제약을 추론할 수 있다. 유효한 순차적 순서가 되찾아지면, 우리는 얼마나 다른 쓰레드들이 같은 동기화 포인트를 가지기(얻기) 위해 기다리기 전에 각 쓰레드를 replay 하기위해 어느정도 해야 하는지 안다.
Replay Implementation Issues
replay 동안 하나의 이슈는 FLL 이 생성되었을 때, 사용자와 공유된 라이브러리 코드의 명령이 같은 가상 주소로 로드되는 것을 확실하게 할 필요가 있다. 이것은 프로그램 카운터 값이 FLL 참조에서 옳은 명령을 record 했는지 보장하는 것이 요구된다.
이 정보를 제공하기 위해서 체크포인트 로그를 관리하기 위해 사용되는 운영체제 드라이버는 프로그램 실행을 위해, record 를 하기 위해 load 라이브러리 루틴을 또한 hook 하는데 사용할 수 있다. 현재 시작 위치는 바이너리, 각 공유 라이브러리, 그리고 유저 영역 운영체제 코드를 위함이다.
이것은 쓰레드를 위해 FLL 과 관련될 수 있다. 그 때문에 만일 버그가 발생된다면, 바이너리 시작주소 로그는 replay 에 의해 실행을 시작하기에 앞서 가상 주소 영역에서 코드를 바르게 셋업하기위해 사용될 수 있다.
관련된 이슈로는 수정된 코드를 자신을 replay 하는 것이다.
코드가 아닌 오직 load 된 데이터를 포함한 우리의 체크포인트와 메모리 race 로그로 부터, replay 인터벌 밖에서 수정된 코드는 다시 만들어 낼 수 없다.
하나의 솔루션은 첫번째 load 명령을 또한 기록하는 것이다. 다른 옵션은 산업에서 많은 프로파일링 툴에 의해 실행되는 self-modifying code 를 지원하지 않는 것이다.
Result
bugnet 은 프로그램이 크래쉬까지 순간에 대응하는 실행의 윈도우를 replay 함으로서 버그를 재생산하고, 격리시키고, 고치는 원리를 기본으로 한다. 프로그램을 replay 에 의한 디버깅이 소프트웨어 공학 커뮤니티에서 효과적인 기술로 생각되지만, 버그의 대부분 캡쳐하기 위해 요구되는 실행 replay 윈도우의 길이 상에 명백한 결과가 없다. 이 섹션에서 첫번째로 우리는 유명한 데스크탑 애플리케이션을 연구함으로서 이 길이를 재려고 한다. 우리는 버그의 중요한 번호에 특성을 충분히 기술하기 위해서는 천만개의 명령을 replay 해야 됨을 발견했다. 이 결과를 바탕으로 우리는 bugnet 에서 필요한 하드웨어의 양과 트레이스 크기를 연구할 것이고 또한 FDR 과 함께 비교할 것이다.
Methodology
bugnet 을 실험하기 위해 우리는 SPEC 2000 suite 로 부터 소수의 프로그램을 온라인 압축기를 평가하기 위해 사용한다. 그리고 다른 인터벌 사이즈를 위해 요구되는 로그의 사이즈를 분석했다. 이것은 art, bzip, crafty, gzip, mcf, parser, vpr 을 포함한다.
이 프로그램들은 -O3 최적화를 사용하여 x86 플랫폼에서 컴파일 되어 졌다. 우리는 또한 AccMon 연구에서 5 개의 프로그램을 위한 결과를 제공한다. 그리고 sourceforge.net 으로 부터 상위 100 안에 드는 다른 소수의 프로그램 다운로드 했다. AccMon 프로그램은 bc, gzip, ncompress, polymorph, tar 이 사용된다. 싱글 쓰레드인 소스 포지 프로그램은 ghostscript, gnuplot, tidy, xv 이다. 멀티 쓰레드 소스포지 프로그램들은 gaim, napster, python, w3m 이다. 우리는 이 섹션에서 실험하는 로그를 생성하기 위한 x86 바이너리 rewriting 툴인 Pin 을 사용한다.
Bug Characteristics
표 1 은 우리가 연구한 알려진 버그들과 함께 애플리케이션을 리스트한 것이다. 여기 하나의 목적은 버그를 재 생산하고 고치기 위해 replay 에 요구되는 명령의 양을 측정하기 위함이다. 애플리케이션의 소스코드에 위치에 대해 자세히 주어진 테이블의 두번째 컬럼은 버그를 고치기 위해 변경할 필요가 있다.(표의 두번째 컬럼은 버그를 고치기 위해 변경할 필요가 있는 애플리케이션의 소스코드에서 위치에 관해 명세를 준다) 세번째 컬럼은 버그의 성질을 기술한다. 네번째 컬럼은 버그를 캡쳐하기위해 요구되는 replay 윈도우의 길이를 측정한다. 우리는 이 윈도우의 크기를 버그의 근본적인 원인이었던 프로그램에 포인트와 프로그램이 크래쉬 된 곳의 포인트 사이에서 실행되는 동적인 명령의 수를 계산하는 것에 의해 결정한다.
이 분석을 위해 우리는 프로그램 실행에서 버그의 근본적인 원인이 버그 수정(두번째 컬럼의 소스코드 위치)에 대응하고 있는 명령의 지난 동적인 예라고 생각했다. 표 1 에서 리스트된 버그의 셋은 갖가지의 버그를 포함한다. 그것은 끝에 매달려 접근(ghostscript), 버퍼 오버플로우(gzip), 널 포인터 참조(gnuplot) 와 같은 메모리 오염 버그를 포함한다. 그것은 또한 산술적인 오퍼플로우(python) 로 인한 결과 같은 버그도 포함한다. 우리의 연구에서 가장 않좋은 경우는 ghostscript 에서 찾아낸 끝 포인터 참조였다. 이 버그를 캡쳐하기 위해 우리는 1800 만 명령 길이의 replay 윈도우를 필요로 한다.
그러나 표 1 에서 리스트된 다른 버그들의 대부분은 1000 만 명령어보다 적은 replay 하기 위한 지원함으로서 캡쳐할 수 있다.
그림 2 에서 표 1 에 리스트되는 버그를 캡쳐하고, replay 하기 위해 필요한 FLL 의 크기를 나타낸다.
이 결과는 1000 만 체크 포인트 인터벌 길이와 함께 bugnet 을 수행하는 것을 가정한다.
그들에게서 버그를 재생산하기 위해 replay 될 필요가 있는 명령의 번호가 약 수천개의 명령의 순서 상에 있기 때문에 몇 개의 프로그램을 위한 FLL 크기는 1KB 아래에 있다.
3 개의 프로그램(ghostscript, tidy 그리고 xv) 를 제외하고, 나머지는 FLL 정보의 100KB 보다 적게요구한다.
가장 않좋은 경우, 우리는 1MB 의 데이터를 요구했다.
Sensitivity Analysis
이 섹션에서 우리는 체크포인트 인터벌 길이와 replay 윈도우 길이 기반의 어떻게 FLL 사이즈를 바꿀 것인가에 대해 의논한다.
또한 우리는 섹션 4.3.1 에서 논의했던 압축기 알고리즘 기반의 사전의 효율성을 연구할 것이다. 그들이 표준입력을 가지고 있는 이래로 이 연구를 위해서 우리는 SPEC 프로그램을 사용한다. 그것은 아주 분석되었다.
그림 3은 x 축을 따러서 표현된 10K 부터 1억까지 범위의 다른 체크포인트 인터벌 길이를 사용함으로서 1 억개의 명령의 replay 윈도우를 위해 모아진 FLL 의 크기를 나타낸다.
명확히 인터벌 사이즈가 증가함으로서 FLL 크기는 줄어든다. 이것은 4.3 절에서 설명한 우리의 첫번째 load 최적화를 적용한 결과다.
더 긴 체크포인트 인터벌 길이를 위해, load 에 의해 참조되어지는 특별한 메모리 위치가 이미 record 되었던 것은 더 있음직하다.
그러므로 load 명령을 record 하는 것의 빈번함은 더 작은 FLL 사이즈의 결과가 줄어든다.
그림 4 에서는 10 억의 명령에 천만의 윈도우를 replay 하기위한 필요한 FLL 의 크기를 보여준다.
이런 결과에서 우리는 천만 명령의 일정한 체크포인트 인터벌 길이를 가정한다. 평균적으로 225KB 크기의 FLL 은 1000 만 명령과 10억 명령을 replay 하기 위해약 18.86 MB가 요구된다.
지금까지 이 결과는 압축을 위해 64 엔트리 사전 테이블을 가정한다. 우리는 섹션 4.3.1 에서 설명한 우리 압축 기술의 효율성을 현재 의논할 것이다.
그림 5 는 압축했던 (표에서 발견한)값의 퍼센테이지에 우리의 사전 테이블 접근을 사용하는 것은 사전 크기가 변하는 것을 보여준다. 크기가 64 인 사전은 값의 50% 를 평균으로 압축할 수 있다. 그것은 이 논문에서 결과로서 나머지의 사용되는 크기다.
그림 6 은 우리가 여러가지의 사전 크기를 위해 달성한 FLL 의 압축 비율을 보여준다. 평균적으로 우리는 64 엔트리 사전을 사용해서 약 50% 의 압축을 달성했다. 더 큰 사전 테이블 결과로서 높은 압축 비율을 갖는 동안, 그것은 하드웨어 비용이 증가한다. 특히 주어진 사전 테이블은 완전히 결합된다.
마지막으로 우리는 bugnet 의 성능 오버헤드를 검사하는 SimpleScalar x86 을 사용했고 그리고 이 SPEC 프로그램을 위한 0.01% 미만인것을 발견했다.(SPEC 프로그램은 0.01% 미만이다)
우리는 bugnet 의 오버헤드가 0.01% 보다 적은 것을 SPEC 프로그램을 위해 발견했다. 버스가 free 일 때, 우리가 메모리에 압축된 로그 엔트리를 느리게 write 하는 것을 허락하는 증가하는 압축 설계를 사용하는 사실 때문에 발견했다.
그리고 SPEC 프로그램은 많은 인터럽트 또는 시스템 콜을 가지고 있지 않다.
Complexityy of FDR Vs Bugnet
FDR 의 계획은 실행의 마지막 1 초를 replay 하는 기능을 가진다. 그것은 10 억개 명령 길이의 replay 윈도우에 접근할 수 있다.
그것은 프로세서의 속도와 프로그램의 IPC 에 따라 변할 것이다.
FDR 과 공정한 비교를 위해, 우리는 bugnet 아키텍처를 사용하여 10억개 명령를 캡쳐하는 것을 논의한다.
그러나 표 1 에서 보여주는 결과로부터 1000 만 명령의 replay 인터벌은 우리가 실험했던 애플리케이션에서 버그 중의 대다수를 고치는데 충분해야 한다.
우리도 그러므로 1000 만 명령을 replay 하기 위해 로그를 생성하기 위해 bugnet 을 사용하는 것을 논의한다.
bugnet 을 위해 나타내는 이 섹션 모든 결과의 나머지는 1000 만 명령 크기의 체크포인트 인터벌을 가정한다.
주의. 체크포인트 인터벌은 replay 윈도우와 다르다. 실행의 윈도우를 replay 하기 위해서 만일 체크포인트 인터벌 길이가 바란(예상한) replay 윈도우 길이보다 적으면 우리는 복수의 체크포인트에서 로그를 사용할 것이다.
또한 인터럽트를 만나면, 4.4 절에서 설명한 바와 같이 우리는 새로운 체크포인트를 종료하고 생성한다.
Log Size Comlexity
표 2 는 bugnet 과 FDR 로그의 크기를 비교한다. 우리는 bugnet 에서 1000 만 과 10 억의 명령을 replay 하기 위해 필요한 메모리 저장의 양을 비교한다.
FDR 에서 10억 명령을 replay 하기 위한 결과는 실행의 1초를 replay 하는 것에 대응된다. 만일 엔트리가 테이블의 NIL 이면, 그것은 로그가 매커니즘에서 사용되지 않은 것을 의미한다.
1000 만과 10 억의 명령을 replay 하는 것이 요구되는 FLL 사이즈는 SPEC 애플리케이션을 위해 약 225 KB 와 18,86 MB 에 있다.
이것은 1000 만개의 체크포인트 인터벌을 가정한다. 게다가 data race 를 디버깅하기 위해 우리는 FDR 에서 같은 크기의 대략적으로 있는 메모리 race 로그를 요구한다.
2MB 크기의 메모리 race 로그를 더하여 (24) 에서 기술함에 따라 10 억의 명령을 replay 지원하는 FDR 은 18 MB 의 캐쉬와 메모리 로그를 필요로 한다. 이것의 합쳐진 크기는 대략적으로 FLL 이 10 억개 명령을 캡쳐하기 위해 사용하는 크기와 같은 것이다.
그러나 FDR 은 전체 시스템 replay 를 활성화하기 위해 추가적인 정보를 요구한다. FDR 은 애플리케이션의 성질에 따라 사이즈가 넓게 변하는 인터럽트, I/O, DMA 로그를 record 한다.
I/O 집중적인 애플리케이션을 위해, 이들 로그는 금지의 크기를 가지고 있을 지도 모른다.
게다가 FDR 은 1 GB 까지 변동할 수 있는 사이즈의 코어덤프 이미지를 요구한다. 이것은 애플리케이션의 메모리 풋 프린트와 메인 메모리 사이즈가 기반으로 된다.
우리의 결과는 크기가 225 KB 인 로그가 애플리케이션 실행의 1000 만 명령을 replay 할 수 있다는 것을 나타낸다.
이것은 최소한 우리가 테스트 했던 프로그램에서 버그의 대부분을 디버그하고, 재생성하기 위해 충분하다는 것이다.
게다가 bugnet 의 작은 트레이스(때때로 오직 수백 KB 의 순서로)는 사용자와 개발자에게 로그를 커뮤니케이션 하기 위해 권해야 한다.
Hardware Complexity
표 3 은 bugnet 과 FDR 의 복잡성을 비교한다. 이전 섹션과 같이 여기서 다시 우리는 1000 만과 10 억 명령을 캡처하기 위해 bugnet 의 설정을 비교한다.
bugnet 에서 사용하는 주 하드웨어 구조는 그림 1 에서 나타나는 바와 같이 CB, MRB 하드웨어 버퍼와 충분한 결합된 64 엔트리 사전 테이블이다.
CB 의 사이즈는 우리의 로깅에서 버스트(파열, 폭발)를 너그럽게 볼 만큼 충분히 클 필요가 있다. 게다가 우리는 각 로그 엔트리의 증가하는 압축을 수행한다. 그것은 우리에게 CB 에서 메인 메모리와 해방시키는 영역에 느리게 write 하는 것을 허락한다.
CB, MRB, 사전 테이블의 크기는 우리가 로그는 메모리의 뒤(백업)인 이래로 캡쳐하려고 시도하고 있는 replay 윈도웅의 길이에 상관없이 일정하다.
비교에서 FDR 은 약 1416 KB 의 온 칩 하드웨어에게 전체 시스템 replay 를 위해 충분한 정보를 record 하는 것을 요구한다.
FDR 은 LZ 압축의 하드웨어 실행을 가정한다. LZ 압축기는 블럭 기반이다. 그래서 하드웨어 버퍼 크기는 메인 메모리 압축하고, 뒤로 저장하기 전에 정보의 블럭을 모을 만큼 충분히 클 필요가 있다. 그리고 그것은 버스트(파열, 폭발)를 너그럽게 볼 만큼 충분히 클 필요가 있다.
캐쉬와 메모리 체크포인트 버퍼는 SafetyNet 체크포인트 매커니즘에 의한 요청된 정보를 record 하는 데 사용된다. 체크포인트 버퍼 크기는 각각 1MB 와 256KB 인 SafetyNet 체크포인트 매커니즘.
FDR 가 전체 시스템 replay 를 달성하려는 작정이래로, 그것이 3 개의 추가적인 버퍼를 필요로 하는 모든 외부 입력을 record 한다. 3 개의 추가적인 버퍼는 인터럽트를 record 하기 위한 64KB 인터럽트 버퍼, 프로그램 I/O 를 record 하기 위한 8KB 입력 버퍼, DMA write 를 record 하기 위한 32KB DMA 버퍼이다. 요약적으로 bugnet 을 위해 전체 온 칩 하드웨어 요건은 약 48KB 이다. 이에 반해 FDR 은 1416KB 이다.
Limitations
이 섹션에서 우리는 릴리즈된 소프트웨어에서 버그를 캡처하는 기능을 지원하는 bugnet 아키텍처의 한계에 대해서 논의한다.
주요 제한점은 애플리케이션 프로그램의 복잡한 상호 대화 때문에 발생하는 버그들이다. 우리가 오직 애플리케이션 코드만을 추적하는 이래로, 시스템 코드는 고치는 것은 아주 어려운 일이다.
다른 제한점은 bugnet 을 위해 바쳐지는 메모리 공간의 양이 제한된다는 것이다. 그리고 큰 replay 윈도우를 필요로 하는 버그는 캡쳐되지 않을 수도 있다.
Debugging
bugnet 의 초점은 드라이버와 인터럽트 핸들러와 같은 운영체제 루틴고 복잡한 상호대화가 없는 유저 코드를 디버깅하는 것을 도와준다. 그러므로 우리의 방법은 드라이버 또는 운영체제 또는 이것과 유저 코드 사이의 복잡한 상호 대화에서 문제를 디버깅하는 데 유용하지 않을 것이다.
bugnet 이 시스템 코드를 replay 할 수 없음에도, 그것은 인터럽트를 서비스하고, 컨텍스트 스위칭의 전후 프로그램 실행의 결정적인 replay 를 아직도 제공한다. 그래서 유저는 인터럽트로 넘겨지는 파라미터의 값, load 되는 값, 인터럽트 서비스 후에 소비되는 값을 검사할 수 있다. 이것은 트레이스와 함께 사용자에게 인터럽트와 운영체제 상호 대화를 가지고 있는 어떤 버그를 디버깅하는 것을 허락하 수 있다.
또한 우리는 운영체제 공유하는 라이브러리 코드의 모든것을 replay 한다.
OS 라이브러리 코드와 함께 유저 코드는 프로그램 수행의 중요한 부분으로 이루어져 있다. 이것은 애플리케이션 레벨 버그의 대부분을 추적하여 잡을 수 있게 충분하다.
bugnet 로그는 전 시스템/메인 메모리의 최종 상태를 표현하고 있는 코어덤프를 포함하지 않는 점에 주의하라.
결과적으로 임의의 메모리 위치 또는 데이터 구조를 조사할 수 없다. 만일 명령 윈도우가 replay 했던 동안 메모리 위치가 접근하게 되지 않으면, 그것의 값은 replay 동안 조사할 수 없다.
이것은 버그의 원인을 추론할 때에 약간의 불편을 초래할지도 모른다.
크래쉬는 결점이 있는 실행에 책임이 없기 전에 우리가 프로그램 실행에 의해 손대지 않는 메모리 주소를 예상하는 이래로 그것은 버그를 고립시키는 것으로 부터 유저를 막을 수 없다.(그러나 그것 때문에 사용자는 우리가 크래쉬보다 이전의 프로그램 실행에 의해 손대지 않은 메모리 주소가 결점이 있는 동작에 대해 책임이 없었다라고 예상하는 이래로 버그를 고립 시킬 수 없어서는 안된다)
bugnet 은 운영체제 또는 애플리케이션 자신이 프로그램이 실패 또는 익셉션을 만났던 것을 확인할 수 있을 때만 bugnet 은 버그를 찾을 수 있다. 예를 들면, 그것이 버그를 캡쳐하기 위해 올바른 시간을 알지 못하는 이래로 부정확한 결과를 가져오고 있는 버그는 bugnet 을 통해 캡쳐되지 않을 것이다.
Replay Window Size
만일 버그를 캡쳐하기에는 너무 작다고 밝혀지면 다른 가능한 이슈는 replay 윈도우 사이즈다. replay 윈도우 크기는 운영체제 또는 유저에 의한 조정될 수 있는 손잡이다.
replay 윈도우 크기는 본질적으로 모든 FLL 를 저장하는 것에 할당될 수 있는 메인 메모리 공간의 양과 replay 를 위해 명령의 바라게 되는 숫자를 캡쳐하기 위해 사용되는 MRL 에 의존적이다.
유저는 바라는 replay 윈도우 크기와 값을 치르고 싶어하는 최대 성능 벌칙을 지정할 수 있어야 한다.
이 입력에 기반에서, 운영체제는 메모리 공간 할당을 동적으로 조정할 수 있다.
특별한 순간에 실행하고 있는 애플리케이션이 메모리 집중적이 아니고 그리고 빈 공간의 그 많은 양이 이용할 수 있다라고 결정할 수 있다면, 그것은 bugnet 에 메모리 공간 할당을 늘릴 수 있다.
반면에 만일 성능 저하가 허용할 수 있는 제한위에 가면 그것은 할당되는 공간을 낮출 수 있다. 게다가 애플리케이션을 사용하는 고객은 버그의 대다수를 캡쳐할 수 있는 최소의 replay 윈도우 크기를 지정할 수 있다.
우리는 1000 만 명령 크기의 윈도우가 대다수의 버그를 캡쳐하는데 충분하다는 것을 알았다. 만일 버그가 발생하고 버그를 추적하여 잡아내기위해 충분하지 않은 상태가 지속된다면, 고객은 replay 윈도우 크기를 증가시킬 것을 부탁받을 지도 모른다.
Conclusion
컴퓨터 산업은 릴리즈된 소프트웨어가 버그를 포함하는 사실을 인정해왔다.
오늘날 소프트 개발자에게 직면한 도전 핵심 중의 하나는 고객 쪽에 명백한 그들 자신의 버그를 재 생산하는 것에 있다.
이 문제를 연설하기 위해, 우리는 생산품이 실행하는 중에 연속적으로 정보를 record 하는 bugnet 아키텍처를 제안했다. record 되는 정보(1MB 보다 적은)는 뒤로 개발자에서 의사소통할 수 있게 되고, 크래쉬 전에 프로그램 실행을 결정적으로 replay 하는 것에 의해 버그에 특성을 기술하는데 사용된다. bugnet 은 오직 유저코드와 애플리케이션 레벨 버그를 찾기위한 공유 라이브러리에 초점을 맞춘다. 이것을 달성하기 위해, 체크포인트 인터벌을 위한 bugnet 의 로그는 첫번째 load 메모리 접근의 트레이스와 인터벌의 시작에 레지스터 상태를 포함한다.
이것은 인터럽트와 시스템 콜 동안 계속 실행하는 것을 replay 하지 않고,프로그램의 실행의 결정적인 replay 를 달성하는 충분한 정보이다. 이것은 작은 크기의 로그를 가진다. 이것은 약 500 KB 의 로그 크기는 1000 만 명령의 replay 윈도우 사이즈를 캡쳐하는 데 충분하다. 이것은 사용자각 로그로 개발자에게 알리는 동기를 줄 만큼 충분히 작다. 우리는 bugnet 이 아주 작은 성능 오버헤드를 가지고 있고, 오버헤드의 영역은 약간의 요구되는 하드웨어 버퍼를 위해 약 48KB 정도이다.
내용 요약
이 논문은 기존의 CPR 시스템에서 사용하는 방법에서의 단점(trace data 의 크기, 하드웨어 자원)을 최소한으로 하고 있다.
대신 application level 의 bug 에 초점을 맞추고 있다. 하드웨어적인 버퍼와 사전 테이블을 이용해서 load instruction 만을 버퍼에 저장한다. 이때 특정 메모리 주소의 첫번째로 액세스 하는 경우에만 저장한다. L1, L2 캐쉬에서 구분한다. 그 이후의 값들은 저장하지 않는다. checkpoint interval 마다 사전 테이블은 reset 된다. 멀티 쓰레드 프로그램을 위해서 memory race buffer 를 이용해서 공유된 메모리의 순서를 구분한다.