Preserve Debugger.setSkipAllPauses for internal use

MERGED2026v8inspector
2026. 9. 15.tomatozil 프로필 이미지tomatozil

문제 설명

많은 보안 사이트는 개발자가 디버깅을 못 하도록 코드에 무한 루프로 debugger; 코드를 심어놓습니다. 이를 막기 위해 사용자가 '모든 중단점을 비활성화'하면, 크롬 개발자 도구는 Chromium 내부 V8 엔진에 setSkipAllPauses(true) 명령을 날려 브라우저가 절대 멈추지 않도록 설정합니다. 문제는 페이지를 이동할 때 발생합니다.

기존 페이지가 중단점에서 멈춰 있으면 페이지 전환이 안 될 수 있기 때문에, Chromium은 탐색을 진행하기 위해 임시로 setSkipAllPauses(true)를 실행하고 기존 멈춤을 강제로 풀어 종료시킵니다. 새 페이지로의 탐색이 끝나면 Chromium은 안전하게 디버거를 복구하기 위해 이 임시 설정을 해제하는 setSkipAllPauses(false)를 실행합니다. 이 자체는 필요한 동작입니다.

그런데 만약 debugger; 에 걸려 브라우저가 멈춘 후에 사용자가 뒤늦게 중단점을 비활성화 했다면? 그리고 새 페이지로 이동한다면? 사용자가 설정한 값과 Chromium의 내부 임시 설정값이 setSkipAllPauses라는 단 하나의 변수를 공유하고 있기 때문에, 결국 탐색 종료 시 실행되는 Chromium의 false가 사용자의 기존 true 설정을 강제로 덮어쓰게 됩니다. 그 결과 최종 V8 엔진의 상태는 false가 되며, 사용자는 중단점을 비활성화했다고 생각하지만 새 페이지의 debugger; 코드를 만나는 순간 브라우저가 다시 멈춰버리는 일이 발생합니다.

해결 내용

사용자가 설정한 값과 Chromium의 내부 임시 설정값을 각각 다른 변수로 관리하도록 분리했습니다.

bool m_skipAllPauses = false;

bool m_skipAllPauses = false;
bool m_skipAllPausesFromEmbedder = false;

그리고 두 값을 조합해서 실제 pause 판단에 사용되는 최종값을 설정합니다.

void V8DebuggerAgentImpl::updateSkipAllPauses() {
  m_skipAllPauses = //최종값
      m_skipAllPausesFromEmbedder || 
      m_state->booleanProperty(DebuggerAgentState::skipAllPauses, false);
}

테스트 방법

  • autoninja -C out/Default inspector-test: 성공
  • inspector/debugger/set-skip-all-pauses: 1개 통과
  • inspector/debugger/*: 253개 전체 통과
  • V8 단위 테스트 (해당 테스트코드는 커밋하지 않음)
    └─ internal-use 호출이 protocol 세션 상태를 훼손하지 않는지 검사

배운 점 (Key Takeaways)

  • 상태 전이(State Transition)의 엄격한 분리 필요성: 시스템의 주체별로 명화한 변수(State) 분리가 시스템의 안정성을 높인다는 점을 배웠습니다.
  • GoogleTest(GTest) 테스트 코드 작성법

참고 자료