1000sj
SJ CODE
1000sj
전체 방문자
오늘
어제
  • 분류 전체보기
    • Algorithms
      • Crypto
    • Security
      • 네트워크
      • 보안
      • CTF
      • Exploit
    • System Programming
      • Operating System
      • Compiler
      • Device Driver
      • Emulator
      • Parrelel Processing
      • Assembly
    • Application Programming
      • Script
      • Android
    • Cloud Computing
      • Cloud Native
      • Public Cloud
      • Infrastructure
      • Database
      • DevOps
    • TroubleShooting
    • ETC
      • 문화 생활
      • 커뮤니티

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
1000sj

SJ CODE

Security/Exploit

GO runtime 해부 #0 Monkey Patching

2025. 10. 12. 16:22

 

Go 프로그램 부트스트랩

go 프로그램 시작 과정은 다음과 같다.

_rt0 (Entry Point)
  ↓
_main
  ↓
runtime.rt0_go (스케줄러 및 첫 고루틴 설정)
  ↓
runtime.main
  ├→ runtime.init
  ├→ main.init
  └→ main.main (우리가 작성한 코드)
  ↓
exit(0)

 

핵심 개념은 다음과 같다.

 

  • 우리가 작성하는 main.main()은 진짜 시작점이 아님
  • Go 런타임이 먼저 초기화 작업 수행:
    • 스케줄러 설정
    • 메모리 할당자 초기화
    • GC 설정
    • 고루틴 스택 준비

 

진입점이 main 위에서 시작한다는 점이 중요하다. 진짜 시작점은 _rt0 이다.  main()은 런타임 초기화의 끝 에 해당한다.

이게 왜 중요할까

 

panic과 defer의 내부 구조

panic은 함수가 아니다

grep -r "func panic\b" .
./src/builtin/builtin.go:func panic(v any)
./doc/go_spec.html:func panic(interface{})

 grep -r "func gopanic\b" .
./src/runtime/panic.go:func gopanic(e any) {

 

panic은 built-in 함수로(컴파일러 내장) 실제 구현은 runtime.gopanic (어셈블리)에서 수행된다.

컴파일러가 panic() 호출을 runtime.gopanic() 호출로 변환한다.

 

gopanic 구현

// runtime/panic.go
func gopanic(e any) {
    gp := getg()  // 현재 고루틴 가져오기
    if gp.m.curg != gp {
        // 시스템 스택에서는 panic 불가
        throw("panic on system stack")
    }
    
    // panic 체인에 추가
    // defer 함수들 실행
    // recover() 체크
    // 스택 트레이스 출력
}

 

panic 실행 흐름

panic() 호출
  ↓
Run defers (역순으로 실행) → recover() 발견 시 복구
  ↓
Print stack traces (복구 안 되면)
  ↓
Die (프로그램 종료)

 

defer의 동작 방식

사용자 코드

func main() {
    defer fmt.Println("cleanup")
    // ... 작업
}

 

컴파일러 변환

func main() {
    runtime.deferproc(size, funcptr)
    // ... 작업
    runtime.deferreturn()
}

 

deferproc 구현

func deferproc(siz int32, fn *funcval) {
    if getg().m.curg != getg() {
        throw("defer on system stack")
    }
    
    // defer 구조체 생성
    d := newdefer(siz)
    d.fn = fn
    d.link = gp._defer    // 기존 defer 체인 연결
    gp._defer = d         // 새 defer를 head로
}

 

defer 체인 구조

gp._defer → [defer 3] → [defer 2] → [defer 1] → nil
           (최근)                             (오래된)

 

함수 종료 시 deferreturn()이 LIFO 순서로 실행한다.

 

고루틴(g) 구조체와 getg()

 

g 구조체

type g struct {
    stack       stack      // 스택 메모리 정보
    stackguard0 uintptr    // 스택 오버플로우 감지
    
    _panic      *_panic    // panic 연결 리스트
    _defer      *_defer    // defer 연결 리스트
    
    m           *m         // 현재 실행 중인 OS 스레드(M)
    sched       gobuf      // 스케줄링 정보 (PC, SP 등)
    
    atomicstatus uint32   // 고루틴 상태
    goid        int64     // 고루틴 ID
    
    preempt     bool      // 선점 신호
    
    // ... 더 많은 필드들
}

주요 필드

  • _panic: 현재 panic 체인 (중첩 panic 지원)
  • _defer: defer 함수 체인
  • m: 이 고루틴을 실행하는 머신(OS 스레드)
  • stack: 고루틴의 스택 메모리 영역

 

getg() - 초고속 구현

어셈블리 구현

TEXT ·GetG(SB),NOSPLIT,$0-8
    get_tls(CX)           // TLS 주소를 CX에
    MOVQ g(CX), AX        // g 포인터를 AX로
    MOVQ AX, ret+0(FP)    // 리턴값에 저장
    RET

매크로 정의

#define get_tls(r)  MOVQ TLS, r
#define g(r)        0(r)(CTLS*1)

 

  • TLS (Thread Local Storage)에서 직접 g 포인터 가져옴
  • 함수 호출 오버헤드 없음 (인라인 어셈블리)
  • 레지스터 접근만으로 완료 → 극도로 빠름!

 

Stop The World 메커니즘

 

stopTheWorld 함수

// runtime/proc.go
func stopTheWorld(reason string) {
    semacquire(&worldsema, false)        // 전역 세마포어 획득
    getg().m.preemptoff = reason         // 선점 비활성화
    systemstack(stopTheWorldWithSema)    // 시스템 스택에서 실행
}

목적

  • 모든 P(프로세서)가 고루틴 실행 중지
  • GC safe point에서 모든 고루틴 인터럽트
  • 현재 고루틴의 P만 실행 상태 유지

제약사항

  • 시스템 스택에서 호출 불가
  • worldsema를 이미 보유하면 안 됨
  • 완료 후 반드시 startTheWorld() 호출

 

STW 사용 예시

func TestStop(t *testing.T) {
    success := true
    
    StopTheWorld("testing")
    
    go func() {
        success = false  // 이 코드는 실행되지 않음!
    }()
    
    for j := 0; j < 100000000; j++ {
        if !success {
            t.Error("Other goroutine woke up")
            return
        }
    }
    
    StartTheWorld()
}

 

 

    1000sj
    1000sj

    티스토리툴바