Upgrade to Pro — share decks privately, control downloads, hide ads and more …

За пару мгновений до main() [RUS]

За пару мгновений до main() [RUS]

Oleg Kovalov

April 23, 2021
Tweet

More Decks by Oleg Kovalov

Other Decks in Programming

Transcript

  1. За пару мгновений до main()
    GopherCon Russia 2021
    Oleg Kovalov
    gogoapps.io
    olegk.dev

    View Slide

  2. - Опенсорс-зависимый гофер
    Олег
    2

    View Slide

  3. - Опенсорс-зависимый гофер
    - Фанат линтеров (соавтор go-critic)
    Олег
    3

    View Slide

  4. - Опенсорс-зависимый гофер
    - Фанат линтеров (соавтор go-critic)
    - Один из ведущих подкаста Generic Talks
    Олег
    4

    View Slide

  5. Олег
    5
    - Опенсорс-зависимый гофер
    - Фанат линтеров (соавтор go-critic)
    - Один из ведущих подкаста Generic Talks
    - Веселый и полезный @oleg_log

    View Slide

  6. Олег
    6
    - Опенсорс-зависимый гофер
    - Фанат линтеров (соавтор go-critic)
    - Один из ведущих подкаста Generic Talks
    - Веселый и полезный @oleg_log
    - А еще Twitter/Telegram @go_perf

    View Slide

  7. - Опенсорс-зависимый гофер
    - Фанат линтеров (соавтор go-critic)
    - Один из ведущих подкаста Generic Talks
    - Веселый и полезный @oleg_log
    - А еще Twitter/Telegram @go_perf
    - ...
    olegk.dev
    Олег
    7

    View Slide

  8. План доклада
    8

    View Slide

  9. - Зачем???
    План доклада
    9

    View Slide

  10. - Зачем???
    - У нас есть *.exe
    - Попробуем получить код
    - Пробежимся по коду
    План доклада
    10

    View Slide

  11. - Зачем???
    - У нас есть *.exe
    - Попробуем получить код
    - Пробежимся по коду
    - Пробежимся по коду
    - Пробежимся по коду
    - Пробежимся по коду
    - Пробежимся по коду
    - Пробежимся по коду
    - Пробежимся по коду
    План доклада
    11

    View Slide

  12. - Зачем???
    - У нас есть *.exe
    - Попробуем получить код
    - Пробежимся по коду
    - Пробежимся по коду
    - Пробежимся по коду
    - Пробежимся по коду
    - Пробежимся по коду
    - Пробежимся по коду
    - Пробежимся по коду
    - Интересное из stdlib
    - Exit
    Go 1.16.3, OS Darwin (macOS Big Sur), YMMV
    План доклада
    12

    View Slide

  13. Зачем???
    13

    View Slide

  14. Зачем???
    14

    View Slide

  15. Зачем???
    15

    View Slide

  16. Зачем???
    16

    View Slide

  17. Зачем???
    17

    View Slide

  18. Правда из жизни
    18

    View Slide

  19. Правда из жизни
    19
    Сгенерировано вот этим https://github.com/knz/go-binsize-viz

    View Slide

  20. Правда из жизни рантайма
    20
    Сгенерировано вот этим https://github.com/knz/go-binsize-viz
    pclntab
    rodata
    types

    View Slide

  21. Правда из жизни рантайма
    21
    Хорошая тулза от Brad Fitzpatrick https://github.com/bradfitz/shotizam
    (нооо пока не работает с Go 1.16)
    pclntab
    rodata
    types

    View Slide

  22. Да что это такое этот ваш pclntab
    22

    View Slide

  23. Это все pclntab, расходимся
    23

    View Slide

  24. Хотя pclntab не во всём виноват
    24
    https://lobste.rs/s/gvtstv/my_go_executable_files_are_still_getting#c_dbfije

    View Slide

  25. Так о чём это я
    25
    -

    View Slide

  26. - Проблема вроде бы есть
    Так о чём это я
    26

    View Slide

  27. - Проблема вроде бы есть
    - Но действительно проблематит не всех
    Так о чём это я
    27

    View Slide

  28. - Проблема вроде бы есть
    - Но действительно проблематит не всех
    - размер Docker image чаще выступает проблемой
    - если вы на Ubuntu/Debian строитесь
    Так о чём это я
    28

    View Slide

  29. - Проблема вроде бы есть
    - Но действительно проблематит не всех
    - размер Docker image чаще выступает проблемой
    - если вы на Ubuntu/Debian строитесь
    - + можно через UPX пожать бинарь
    Так о чём это я
    29

    View Slide

  30. Так о чём это я
    30
    - Проблема вроде бы есть
    - Но действительно проблематит не всех
    - размер Docker image чаще выступает проблемой
    - если вы на Ubuntu/Debian строитесь
    - + можно через UPX пожать бинарь
    - Но что там минимально-нужного?

    View Slide

  31. Так о чём это я
    31
    - Проблема вроде бы есть
    - Но действительно проблематит не всех
    - размер Docker image чаще выступает проблемой
    - если вы на Ubuntu/Debian строитесь
    - + можно через UPX пожать бинарь
    - Но что там минимально-нужного?
    - Про это и поговорим

    View Slide

  32. Куда уж проще?
    32

    View Slide

  33. ❯ cat main.go
    package main
    func main() {
    // ʕ◔ϖ◔ʔ
    }
    Куда уж проще?
    33

    View Slide

  34. ❯ cat main.go
    package main
    func main() {
    // ʕ◔ϖ◔ʔ
    }
    ❯ go build -o main.exe main.go
    Куда уж проще?
    34

    View Slide

  35. ❯ cat main.go
    package main
    func main() {
    // ʕ◔ϖ◔ʔ
    }
    ❯ go build -o main.exe main.go
    ❯ du -sh main.exec
    1.1M main.exec
    Куда уж проще?
    35

    View Slide

  36. ❯ cat main.go
    package main
    func main() {
    // ʕ◔ϖ◔ʔ
    }
    ❯ go build -o main.exe main.go
    ❯ du -sh main.exec
    1.1M main.exec
    ❯ 👀
    Куда уж проще?
    36

    View Slide

  37. ❯ go build -o main.exe main.go
    ❯ cat main.exe
    Что в нашем *.exe? /1
    37

    View Slide

  38. cat
    38

    View Slide

  39. ❯ go build -o main.exe main.go
    ❯ go list -json . > main.json
    Что в нашем *.exe? /2
    39

    View Slide

  40. go list -json
    40
    {
    "Dir": "/Users/olegkovalov/go/src/github.com/cristaloleg/funf",
    "ImportPath": "github.com/cristaloleg/funf",
    "Name": "main",
    "Module": {...},
    "GoFiles": [
    "main.go"
    ],
    "Deps": [
    "internal/bytealg",
    "internal/cpu",
    "runtime",
    "runtime/internal/atomic",
    "runtime/internal/math",
    "runtime/internal/sys",
    "unsafe"
    ]
    }

    View Slide

  41. ❯ go build -o main.exe main.go
    ❯ go tool objdump -S main.exe > main.dump
    Что в нашем *.exe? /3
    41

    View Slide

  42. - Дизассемблирует исполняемые файлы
    go tool objdump
    42

    View Slide

  43. - Дизассемблирует исполняемые файлы
    var printCode = flag.Bool("S", false, "print Go code alongside assembly")
    go tool objdump
    43

    View Slide

  44. - Дизассемблирует исполняемые файлы
    var printCode = flag.Bool("S", false, "print Go code alongside assembly")
    - Еще есть флаг symregexp
    flag.String("s", "", "only dump symbols matching this regexp")
    ❯ go tool objdump -S -s runtime.close main.exec
    go tool objdump
    44

    View Slide

  45. Пример из main.dump
    45

    View Slide

  46. ❯ go build -o main.exe main.go
    ❯ go tool objdump -S main.exe > main.dump
    ❯ du -sh main.dump
    5.1M main.dump
    Итог objdump
    46

    View Slide

  47. ❯ go build -o main.exe main.go
    ❯ go tool objdump -S main.exe > main.dump
    ❯ du -sh main.dump
    5.1M main.dump
    ❯ rg "TEXT runtime." -c main.dump
    939
    Итог objdump
    47

    View Slide

  48. ❯ go build -o main.exe main.go
    ❯ go tool objdump -S main.exe > main.dump
    ❯ du -sh main.dump
    5.1M main.dump
    ❯ rg "TEXT runtime." -c main.dump
    939
    ❯ 🔥🔥🔥
    Итог objdump
    48

    View Slide

  49. Пойдем в main.dump
    49

    View Slide

  50. - Обычный текстовый файл
    Пойдем в main.dump
    50

    View Slide

  51. - Обычный текстовый файл
    - Со всеми возможными объявлениями
    Пойдем в main.dump
    51

    View Slide

  52. Пойдем в main.dump
    52
    - Обычный текстовый файл
    - Со всеми возможными объявлениями
    - Полезно, если делаете низкоуровневые оптимизации
    - Или какой-то там доклад

    View Slide

  53. Так и запишем /0
    53
    - ...
    - main()

    View Slide

  54. ...124k lines later
    TEXT main.main(SB) /Users/olegkovalov/go/src/cristaloleg/funf/main.go
    }
    0x105e180 c3 RET
    Вот и любимый main()
    54

    View Slide

  55. ...124k lines later
    TEXT main.main(SB) /Users/olegkovalov/go/src/cristaloleg/funf/main.go
    }
    0x105e180 c3 RET
    Эм...main.main ?
    55

    View Slide

  56. Настоящий main
    56
    ...60k lines
    0x102fc2b 488b6c2448 MOVQ 0x48(SP), BP
    0x102fc30 4883c450 ADDQ $0x50, SP
    0x102fc34 c3 RET
    func main() {
    0x102fc35 e846980200 CALL runtime.morestack_noctxt(SB)
    0x102fc3a e941fcffff JMP runtime.main(SB)
    0x102fc3f cc INT $0x3
    ...60k lines

    View Slide

  57. Настоящий main...уже ближе
    57
    ...60k lines
    0x102fc2b 488b6c2448 MOVQ 0x48(SP), BP
    0x102fc30 4883c450 ADDQ $0x50, SP
    0x102fc34 c3 RET
    func main() {
    0x102fc35 e846980200 CALL runtime.morestack_noctxt(SB)
    0x102fc3a e941fcffff JMP runtime.main(SB)
    0x102fc3f cc INT $0x3
    ...60k lines

    View Slide

  58. runtime.main
    58
    TEXT runtime.main(SB) /usr/local/Cellar/go/1.16/libexec/src/runtime/proc.go
    func main() {
    0x102f880 65488b0c2530000000 MOVQ GS:0x30, CX
    0x102f889 483b6110 CMPQ 0x10(CX), SP
    0x102f88d 0f86a2030000 JBE 0x102fc35
    0x102f893 4883ec50 SUBQ $0x50, SP
    0x102f897 48896c2448 MOVQ BP, 0x48(SP)
    0x102f89c 488d6c2448 LEAQ 0x48(SP), BP
    0x102f8a1 0f57c0 XORPS X0, X0
    0x102f8a4 0f11442438 MOVUPS X0, 0x38(SP)
    0x102f8a9 c644242700 MOVB $0x0, 0x27(SP)
    g := getg()
    0x102f8ae 65488b042530000000 MOVQ GS:0x30, AX
    0x102f8b7 4889442430 MOVQ AX, 0x30(SP)
    g.m.g0.racectx = 0

    View Slide

  59. Что к чему в runtime/proc.go
    59
    //go:linkname main_main main.main
    func main_main()
    // ...
    // The main goroutine.
    func main() {
    g := getg()
    // ...
    fn := main_main // make an indirect call,
    // as the linker doesn't know the address
    // of the main package when laying down the runtime
    fn()
    // ...

    View Slide

  60. Так и запишем /1
    60
    - ...
    - runtime.main()
    - main_main ~ main.main // магия линковщика
    - main.main // это мы!

    View Slide

  61. Вот только это не всё
    61

    View Slide

  62. - Есть странные runtime/asm_.s файлы
    Вот только это не всё
    62

    View Slide

  63. - Есть странные runtime/asm_.s файлы
    - где arch - некая архитектура (amd64, arm64, mips, …)
    - *.s - Assembler
    Вот только это не всё
    63

    View Slide

  64. - Есть странные runtime/asm_.s файлы
    - где arch - некая архитектура (amd64, arm64, mips, …)
    - *.s - Assembler
    - 👀
    Вот только это не всё
    64

    View Slide

  65. - Есть странные runtime/asm_.s файлы
    - где arch - некая архитектура (amd64, arm64, mips, …)
    - *.s - Assembler
    - 👀
    - Значит пойдём разбираться
    Вот только это не всё
    65

    View Slide

  66. - Есть странные runtime/asm_.s файлы
    - где arch - некая архитектура (amd64, arm64, mips, …)
    - *.s - Assembler
    - 👀
    - Значит пойдём разбираться
    - Посмотрим на asm_amd64.s
    - всего лишь 2к строк
    Вот только это не всё
    66

    View Slide

  67. - Есть странные runtime/asm_.s файлы
    - где arch - некая архитектура (amd64, arm64, mips, …)
    - *.s - Assembler
    - 👀
    - Значит пойдём разбираться
    - Посмотрим на asm_amd64.s
    - всего лишь 2к строк
    - в остальных + - 1к
    - arm64.s тоже тяжелый
    Вот только это не всё
    67

    View Slide

  68. - FP: Frame pointer: arguments and locals.
    - PC: Program counter: jumps and branches.
    - SB: Static base pointer: global symbols.
    - SP: Stack pointer: top of stack.
    Словарик читателя Go assembler
    68

    View Slide

  69. - FP: Frame pointer: arguments and locals.
    - PC: Program counter: jumps and branches.
    - SB: Static base pointer: global symbols.
    - SP: Stack pointer: top of stack.
    - JMP: jump
    - LEA: load effective address
    - MOV dst, src: move src
    - CALL: invoke function
    - DATA(+GLOBL): data symbols (global)
    Словарик читателя Go assembler
    69

    View Slide

  70. // _rt0_amd64 is common startup code for most amd64 systems when using
    // internal linking. This is the entry point for the program from the
    // kernel for an ordinary -buildmode=exe program. The stack holds the
    // number of arguments and the C-style argv.
    TEXT _rt0_amd64(SB),NOSPLIT,$-8
    MOVQ 0(SP), DI // argc
    LEAQ 8(SP), SI // argv
    JMP runtime·rt0_go(SB)
    Ах вот оно что!
    70

    View Slide

  71. // Defined as ABIInternal since it does not use the stack-based Go ABI (and
    // in addition there are no calls to this entry point from Go code).
    TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // copy arguments forward on an even stack
    MOVQ DI, AX // argc
    MOVQ SI, BX // argv
    // ...
    // create istack out of the given (operating system) stack.
    MOVQ $runtime·g0(SB), DI
    // ...
    // find out information about the processor we're on
    MOVL $0, AX
    CPUID
    // ...
    Пойдем дальше
    71

    View Slide

  72. Пойдем дальше
    72
    TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // Figure out how to serialize RDTSC.
    // On Intel processors LFENCE is enough. AMD requires MFENCE.
    // Don't know about the rest, so let's do MFENCE.
    // ...
    ok:
    // set the per-goroutine and per-mach "registers"
    get_tls(BX)
    LEAQ runtime·g0(SB), CX
    MOVQ CX, g(BX)
    LEAQ runtime·m0(SB), AX
    // save m->g0 = g0
    MOVQ CX, m_g0(AX)
    // save m0 to g0->m
    MOVQ AX, g_m(CX)

    View Slide

  73. Пойдем дальше
    73
    TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // ...
    CALL runtime·check(SB)
    MOVL 16(SP), AX // copy argc
    MOVL AX, 0(SP)
    MOVQ 24(SP), AX // copy argv
    MOVQ AX, 8(SP)
    CALL runtime·args(SB)
    CALL runtime·osinit(SB)
    CALL runtime·schedinit(SB)

    View Slide

  74. - Да, содержат платформозависимые вещи
    runtime/os_.go
    74

    View Slide

  75. - Да, содержат платформозависимые вещи
    - Обработка сигналов
    - Иногда таймеры
    runtime/os_.go
    75

    View Slide

  76. - Да, содержат платформозависимые вещи
    - Обработка сигналов
    - Иногда таймеры
    - Рандом
    - Размеры тредов и прочих системных вещей
    - Энвы и аргументы программы
    - речь о environment variables и argc & argv
    runtime/os_.go
    76

    View Slide

  77. - Да, содержат платформозависимые вещи
    - Обработка сигналов
    - Иногда таймеры
    - Рандом
    - Размеры тредов и прочих системных вещей
    - Энвы и аргументы программы
    - речь о environment variables и argc & argv
    - Проверка адекватности машины
    - o!
    runtime/os_.go
    77

    View Slide

  78. - В файле runtime1.go есть интересная функция
    - check()
    Проверка адекватности машины
    78

    View Slide

  79. - В файле runtime1.go есть интересная функция
    - check()
    - проверяем размеры примитивных типов
    - d uint16
    - if unsafe.Sizeof(d) != 2 { throw("bad d") }
    Проверка адекватности машины
    79

    View Slide

  80. - В файле runtime1.go есть интересная функция
    - check()
    - проверяем размеры примитивных типов
    - d uint16
    - if unsafe.Sizeof(d) != 2 { throw("bad d") }
    - check() вызывает testAtomic64()
    Проверка адекватности машины
    80

    View Slide

  81. - В файле runtime1.go есть интересная функция
    - check()
    - проверяем размеры примитивных типов
    - d uint16
    - if unsafe.Sizeof(d) != 2 { throw("bad d") }
    - check() вызывает testAtomic64()
    - Как можно догадаться, testAtomic64 про атомики
    - test_z64 = 42, test_x64 = 0
    - if atomic.Cas64(&test_z64, test_x64, 1) { throw("cas64 failed") }
    Проверка адекватности машины
    81

    View Slide

  82. Пойдем дальше
    82
    TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // ...
    CALL runtime·check(SB)
    MOVL 16(SP), AX // copy argc
    MOVL AX, 0(SP)
    MOVQ 24(SP), AX // copy argv
    MOVQ AX, 8(SP)
    CALL runtime·args(SB)
    CALL runtime·osinit(SB)
    CALL runtime·schedinit(SB)
    // при помощи магии и директив линкера это и будет os.Args

    View Slide

  83. /Users/olegkovalov/go/src/golang/go/src/runtime/runtime1.go
    func args(c int32, v **byte) {
    argc = c
    argv = v
    sysargs(c, v)
    }
    Пойдем дальше
    83

    View Slide

  84. Пойдем дальше
    84
    /Users/olegkovalov/go/src/golang/go/src/runtime/runtime1.go
    func args(c int32, v **byte) {
    argc = c
    argv = v
    sysargs(c, v)
    }
    // os_darwin.go
    func sysargs(argc int32, argv **byte) {
    // skip over argv, envv and the first string will be the path
    n := argc + 1
    for argv_index(argv, n) != nil {
    n++
    }
    executablePath = gostringnocopy(argv_index(argv, n+1))
    // ...
    }

    View Slide

  85. Пойдем дальше
    85
    TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // ...
    CALL runtime·check(SB)
    MOVL 16(SP), AX // copy argc
    MOVL AX, 0(SP)
    MOVQ 24(SP), AX // copy argv
    MOVQ AX, 8(SP)
    CALL runtime·args(SB)
    CALL runtime·osinit(SB)
    CALL runtime·schedinit(SB)

    View Slide

  86. Пойдем дальше
    86
    // os_darwin.go
    // BSD interface for threading.
    func osinit() {
    // pthread_create delayed until end of goenvs so that we
    // can look at the environment first.
    ncpu = getncpu()
    physPageSize = getPageSize()
    }

    View Slide

  87. Пойдем дальше
    87
    TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // ...
    CALL runtime·check(SB)
    MOVL 16(SP), AX // copy argc
    MOVL AX, 0(SP)
    MOVQ 24(SP), AX // copy argv
    MOVQ AX, 8(SP)
    CALL runtime·args(SB)
    CALL runtime·osinit(SB)
    CALL runtime·schedinit(SB)

    View Slide

  88. Так и запишем /2
    88
    - runtime·_rt0_amd64 // или какая там у вас архитектура
    - runtime·rt0_go
    - создадим Вселенную
    - check
    - args
    - osinit
    - schedinit
    - runtime.main()
    - main_main ~ main.main // магия линковщика
    - main.main // это мы!

    View Slide

  89. Пойдем дальше
    89
    // The bootstrap sequence is:
    //
    // call osinit
    // call schedinit
    // make & queue new G
    // call runtime·mstart
    //
    // The new G calls runtime·main.
    func schedinit() {
    // ...

    View Slide

  90. - G - goroutine
    - M - worker thread, or machine.
    - P - processor, a resource that is required to execute Go code.
    - getg() - returns the pointer to the current g
    - _g_ := getg()
    - p := getg().m.p.ptr()
    - tls - thread-local storage
    Остальное это if-ы, атомики и непонятные алгоритмы. Вроде изян.
    Словарик читателя runtime
    90

    View Slide

  91. func schedinit() {
    // ...
    _g_ := getg()
    sched.maxmcount = 10000 // maximum number of m's allowed (or die)
    // The world starts stopped.
    worldStopped()
    moduledataverify() // pclntab is correct
    stackinit() //
    mallocinit()
    fastrandinit() // must run before mcommoninit
    mcommoninit(_g_.m, -1)
    schedinit /1
    91

    View Slide

  92. func schedinit() {
    // ...
    _g_ := getg()
    sched.maxmcount = 10000 // maximum number of m's allowed (or die)
    // The world starts stopped.
    worldStopped()
    moduledataverify() // pclntab is correct
    stackinit() //
    mallocinit()
    fastrandinit() // must run before mcommoninit
    mcommoninit(_g_.m, -1)
    cpuinit() // must run before alginit
    alginit() // maps must not be used before this call
    modulesinit() // provides activeModules
    typelinksinit() // uses maps, activeModules
    itabsinit() // uses activeModules
    schedinit /1
    92

    View Slide

  93. func schedinit() {
    // ...
    goargs()
    goenvs()
    parsedebugvars()
    gcinit()
    lock(&sched.lock)
    // ...
    if procresize(procs) != nil {
    throw("unknown runnable goroutine during bootstrap")
    }
    unlock(&sched.lock)
    // World is effectively started now, as P's can run.
    worldStarted()
    // ...
    }
    schedinit /2
    93

    View Slide

  94. TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // ...
    // create a new goroutine to start program
    MOVQ $runtime·mainPC(SB), AX // entry
    PUSHQ AX
    PUSHQ $0 // arg size
    CALL runtime·newproc(SB)
    POPQ AX
    POPQ AX
    ...
    // mainPC is a function value for runtime.main, to be passed to newproc.
    // The reference to runtime.main is made via ABIInternal, since the
    // actual function (not the ABI0 wrapper) is needed by newproc.
    DATA runtime·mainPC+0(SB)/8,$runtime·main(SB)
    GLOBL runtime·mainPC(SB),RODATA,$8
    Еще чуть-чуть Ассемблера
    94

    View Slide

  95. Последнее из Ассемблера, честно
    95
    TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // ...
    // create a new goroutine to start program
    MOVQ $runtime·mainPC(SB), AX // entry
    PUSHQ AX
    PUSHQ $0 // arg size
    CALL runtime·newproc(SB)
    POPQ AX
    POPQ AX
    // start this M
    CALL runtime·mstart(SB)
    CALL runtime·abort(SB)// mstart should never return
    RET
    TEXT runtime·mstart(SB),NOSPLIT|TOPFRAME,$0
    CALL runtime·mstart0(SB)
    RET // not reached

    View Slide

  96. newproc
    96
    // Create a new g running fn with siz bytes of arguments.
    // Put it on the queue of g's waiting to run.
    // The compiler turns a go statement into a call to this.
    // ...
    func newproc(siz int32, fn *funcval) {
    argp := add(unsafe.Pointer(&fn), sys.PtrSize)
    gp := getg()
    pc := getcallerpc()
    systemstack(func() {
    newg := newproc1(fn, argp, siz, gp, pc)
    _p_ := getg().m.p.ptr()
    runqput(_p_, newg, true)
    if mainStarted {
    wakep()
    }
    })
    }

    View Slide

  97. // Create a new g running fn with siz bytes of arguments.
    // Put it on the queue of g's waiting to run.
    // The compiler turns a go statement into a call to this.
    // ...
    func newproc(siz int32, fn *funcval) {
    argp := add(unsafe.Pointer(&fn), sys.PtrSize)
    gp := getg()
    pc := getcallerpc()
    systemstack(func() {
    newg := newproc1(fn, argp, siz, gp, pc)
    _p_ := getg().m.p.ptr()
    runqput(_p_, newg, true)
    if mainStarted {
    wakep()
    }
    })
    }
    newproc
    97

    View Slide

  98. newproc
    98
    // Create a new g running fn with siz bytes of arguments.
    // Put it on the queue of g's waiting to run.
    // The compiler turns a go statement into a call to this.
    // ...
    func newproc(siz int32, fn *funcval) {
    argp := add(unsafe.Pointer(&fn), sys.PtrSize)
    gp := getg()
    pc := getcallerpc()
    systemstack(func() {
    newg := newproc1(fn, argp, siz, gp, pc)
    _p_ := getg().m.p.ptr()
    runqput(_p_, newg, true)
    if mainStarted {
    wakep()
    }
    })
    }

    View Slide

  99. systemstack
    99
    // systemstack runs fn on a system stack.
    // If systemstack is called from the per-OS-thread (g0) stack, or
    // if systemstack is called from the signal handling (gsignal) stack,
    // systemstack calls fn directly and returns.
    //
    // Otherwise, systemstack is being called from the limited stack
    // of an ordinary goroutine. In this case, systemstack switches
    // to the per-OS-thread stack, calls fn, and switches back.
    //
    //go:noescape
    func systemstack(fn func())

    View Slide

  100. Наконец-то scheduler
    // mstart is the entry-point for new Ms.
    func mstart()
    // mstart0 is the Go entry-point for new Ms.
    func mstart0() {
    _g_ := getg()
    // ...
    mstart1()
    }
    func mstart1() {
    _g_ := getg()
    // ...
    schedule()
    }
    100

    View Slide

  101. schedule
    101
    // One round of scheduler: find a runnable goroutine and execute it.
    // Never returns.
    func schedule() {
    _g_ := getg()
    top:
    pp := _g_.m.p.ptr()
    pp.preempt = false
    var gp *g
    // get some gp
    execute(gp, inheritTime)
    }

    View Slide

  102. Так и запишем /3
    102
    - runtime·_rt0_amd64 // или что там у вас по архитектуре и ОС
    - runtime·rt0_go
    - check
    - args
    - osinit
    - schedinit
    - ...
    - newproc создаст горутину для runtime.main
    - mstart для выполнения горутины выше
    - mstart0 -> mstart1
    - schedule
    - runtime.main()
    - main_main ~ main.main // магия линковщика
    - main.main // это мы!

    View Slide

  103. Так и запишем /3.1 (WebAssembly)
    103

    View Slide

  104. - Все озвученное ранее это 250 линий
    Оке, а почему там 2к линий?
    104

    View Slide

  105. - Все озвученное ранее это 250 линий
    - Остальное это runtime-овые вещи (кэп)
    Оке, а почему там 2к линий?
    105

    View Slide

  106. - Все озвученное ранее это 250 линий
    - Остальное это runtime-овые вещи (кэп)
    - управлением стеком
    - выполнение defer
    - различные паники
    Оке, а почему там 2к линий?
    106

    View Slide

  107. - Все озвученное ранее это 250 линий
    - Остальное это runtime-овые вещи (кэп)
    - управлением стеком
    - выполнение defer
    - различные паники
    - помощь для дебага
    - любимый cgo
    - gc barriers
    Оке, а почему там 2к линий?
    107

    View Slide

  108. - Все озвученное ранее это 250 линий
    - Остальное это runtime-овые вещи (кэп)
    - управлением стеком
    - выполнение defer
    - различные паники
    - помощь для дебага
    - любимый cgo
    - gc barriers
    - таймеры
    - сигналы
    - хеши
    Оке, а почему там 2к линий?
    108

    View Slide

  109. Пойдем дальше
    // func memhash(p unsafe.Pointer, h, s uintptr) uintptr
    // hash function using AES hardware instructions
    TEXT runtime·memhash(SB),NOSPLIT,$0-32
    CMPB runtime·useAeshash(SB), $0
    JEQ noaes
    MOVQ p+0(FP), AX // ptr to data
    MOVQ s+16(FP), CX // size
    LEAQ ret+24(FP), DX
    JMP aeshashbody<>(SB)
    noaes:
    JMP runtime·memhashFallback(SB)
    /Users/olegkovalov/go/src/golang/go/src/runtime/hash64.go
    func memhashFallback(p unsafe.Pointer, seed, s uintptr) uintptr {
    109

    View Slide

  110. - add GOAMD64 architecture variants #25489
    - https://github.com/golang/go/issues/25489
    - Сейчас можно найти похожие строки
    - x86HasSSE41 = cpu.X86.HasSSE41
    - на самом деле их сильно больше
    - Из-за чего мы теряем такты процессора
    - каждая наносекунда на счету :(
    GOAMD64 proposal
    110

    View Slide

  111. Что там в init()-ах??
    111

    View Slide

  112. - В runtime init-ов не много
    - Большинство из них это просто проверки машины
    - какие размеры типов
    - какие флаги у процессора
    - Но есть 1 интересный в runtime
    (потом обсудим некоторые пакеты из stdlib)
    Так что там в init()-ах??
    112

    View Slide

  113. /Users/olegkovalov/go/src/golang/go/src/runtime/proc.go
    // start forcegc helper goroutine
    func init() {
    go forcegchelper()
    }
    Популярная претензия к GC
    113

    View Slide

  114. /Users/olegkovalov/go/src/golang/go/src/runtime/proc.go
    // start forcegc helper goroutine
    func init() {
    go forcegchelper()
    }
    // 5k lines later...
    Популярная претензия к GC
    114

    View Slide

  115. Популярная претензия к GC
    115
    /Users/olegkovalov/go/src/golang/go/src/runtime/proc.go
    // start forcegc helper goroutine
    func init() {
    go forcegchelper()
    }
    // 5k lines later...
    // forcegcperiod is the maximum time in nanoseconds between garbage
    // collections. If we go this long without a garbage collection, one
    // is forced to run.
    //
    // This is a variable for testing purposes. It normally doesn't change.
    var forcegcperiod int64 = 2 * 60 * 1e9

    View Slide

  116. func init() {
    http.HandleFunc("/debug/vars", expvarHandler)
    Publish("cmdline", Func(cmdline))
    Publish("memstats", Func(memstats))
    }
    Так что там в expvar
    116

    View Slide

  117. func init() {
    crypto.RegisterHash(crypto.MD5, New)
    }
    Так что там в crypto/*
    117

    View Slide

  118. func init() {
    crypto.RegisterHash(crypto.MD5, New)
    }
    // Именно поэтому в импортах мы делаем:
    import (
    _ "crypto/sha256" // to register a sha256
    _ "crypto/sha512" // to register a sha384/512
    )
    Так что там в crypto/*
    118

    View Slide

  119. func init() {
    crypto.RegisterHash(crypto.MD5, New)
    }
    // Именно поэтому в импортах мы делаем:
    import (
    _ "crypto/sha256" // to register a sha256
    _ "crypto/sha512" // to register a sha384/512
    )
    // а НЕ это:
    import (
    _ "crypto/sha256" //nolint
    "crypto/sha512"
    )
    var _ = sha512.
    Так что там в crypto/*
    119

    View Slide

  120. func init() {
    // ...
    registerBasics()
    // ...
    }
    Так что там в encoding/gob
    120

    View Slide

  121. func init() {
    // ...
    registerBasics()
    // ...
    }
    func registerBasics() {
    Register(int(0))
    Register(int8(0))
    Register(int16(0))
    Register(int32(0))
    Register(int64(0))
    // ...
    Так что там в encoding/gob
    121

    View Slide

  122. Так что там в net
    122
    // net/conf_netcgo.go
    //go:build netcgo
    // +build netcgo
    package net
    // The build tag "netcgo" forces use of the cgo DNS resolver.
    // It is the opposite of "netgo".
    func init() { netCgo = true }
    // net/conf.go
    netGo bool // go DNS resolution forced
    netCgo bool // cgo DNS resolution forced

    View Slide

  123. Так что там в net
    123
    // net/conf_netcgo.go
    //go:build netcgo
    // +build netcgo
    package net
    // The build tag "netcgo" forces use of the cgo DNS resolver.
    // It is the opposite of "netgo".
    func init() { netCgo = true }
    // net/conf.go
    netGo bool // go DNS resolution forced
    netCgo bool // cgo DNS resolution forced

    View Slide

  124. // When debugging a particular http server-based test,
    // this flag lets you run
    // go test -run=BrokenTest -httptest.serve=127.0.0.1:8000
    // to start the broken server so you can interact with it manually.
    // ...
    // isn't really part of our API. Don't depend on this.
    var serveFlag string
    func init() {
    if has(os.Args, "-httptest.serve=") {
    flag.StringVar(&serveFlag, "httptest.serve", "", "...")
    }
    }
    Так что там в net/http/httptest
    124

    View Slide

  125. func init() {
    http.HandleFunc("/debug/pprof/", Index)
    http.HandleFunc("/debug/pprof/cmdline", Cmdline)
    http.HandleFunc("/debug/pprof/profile", Profile)
    http.HandleFunc("/debug/pprof/symbol", Symbol)
    http.HandleFunc("/debug/pprof/trace", Trace)
    }
    Так что там в net/http/pprof
    125

    View Slide

  126. // mime/type_freebsd.go
    func init() {
    typeFiles = append(typeFiles, "/usr/local/etc/mime.types")
    }
    // mime/type_openbsd.go
    func init() {
    typeFiles = append(typeFiles, "/usr/share/misc/mime.types")
    }
    ...
    Так что там в mime
    126
    MIME == Multipurpose Internet Mail Extensions

    View Slide

  127. func init() {
    runtime_registerPoolCleanup(poolCleanup)
    }
    func poolCleanup() {
    for _, p := range oldPools {
    // ...
    }
    for _, p := range allPools {
    // ...
    }
    // The pools with non-empty primary caches now have non-empty
    // victim caches and no pools have primary caches.
    oldPools, allPools = allPools, nil
    }
    Так что там в sync
    127

    View Slide

  128. Так что там в sync /2
    // sync/runtime.go
    // Ensure that sync and runtime agree on size of notifyList.
    func runtime_notifyListCheck(size uintptr)
    func init() {
    var n notifyList
    runtime_notifyListCheck(unsafe.Sizeof(n))
    }
    // runtime/sema.go
    //go:linkname notifyListCheck sync.runtime_notifyListCheck
    func notifyListCheck(sz uintptr) {
    if sz != unsafe.Sizeof(notifyList{}) {
    print("runtime: bad notifyList")
    throw("bad notifyList size")
    }
    } 128

    View Slide

  129. Так что там в time/tzdata
    129

    View Slide

  130. // Package tzdata provides an embedded copy of the timezone database.
    // If this package is imported anywhere in the program, then if
    // the time package cannot find tzdata files on the system,
    // it will use this embedded information.
    //
    // Importing this package will increase the size of a program by about
    // 450 KB.
    //
    Так что там в time/tzdata
    130

    View Slide

  131. // Package tzdata provides an embedded copy of the timezone database.
    // If this package is imported anywhere in the program, then if
    // the time package cannot find tzdata files on the system,
    // it will use this embedded information.
    //
    // Importing this package will increase the size of a program by about
    // 450 KB.
    //
    Так что там в time/tzdata
    131

    View Slide

  132. Так что там в time/tzdata
    132
    // Package tzdata provides an embedded copy of the timezone database.
    // If this package is imported anywhere in the program, then if
    // the time package cannot find tzdata files on the system,
    // it will use this embedded information.
    //
    // Importing this package will increase the size of a program by about
    // 450 KB.
    //
    // This package should normally be imported by a program's main package,
    // not by a library. Libraries normally shouldn't decide whether to
    // include the timezone database in a program.
    //
    // This package will be automatically imported if you build with
    // -tags timetzdata.
    package tzdata

    View Slide

  133. Так что там в time/tzdata
    133
    // Package tzdata provides an embedded copy of the timezone database.
    // If this package is imported anywhere in the program, then if
    // the time package cannot find tzdata files on the system,
    // it will use this embedded information.
    //
    // Importing this package will increase the size of a program by about
    // 450 KB.
    //
    // This package should normally be imported by a program's main package,
    // not by a library. Libraries normally shouldn't decide whether to
    // include the timezone database in a program.
    //
    // This package will be automatically imported if you build with
    // -tags timetzdata.
    package tzdata

    View Slide

  134. Интересности в 1 слайд
    134

    View Slide

  135. - Часто можно увидеть объявление функции перед использованием
    - мы ведь помним, что когда-то Go был написан на Си :)
    Интересности в 1 слайд
    135

    View Slide

  136. - Часто можно увидеть объявление функции перед использованием
    - мы ведь помним, что когда-то Go был написан на Си :)
    - Также достаточное количество _ в именах переменных и функций
    - опять же наследие Си
    Интересности в 1 слайд
    136

    View Slide

  137. - Часто можно увидеть объявление функции перед использованием
    - мы ведь помним, что когда-то Go был написан на Си :)
    - Также достаточное количество _ в именах переменных и функций
    - опять же наследие Си
    - Windows, NetBSD, Wasm, JS и Plan9 особенные
    - часто из-за них есть осознанные костыли
    Интересности в 1 слайд
    137

    View Slide

  138. - Часто можно увидеть объявление функции перед использованием
    - мы ведь помним, что когда-то Go был написан на Си :)
    - Также достаточное количество _ в именах переменных и функций
    - опять же наследие Си
    - Windows, NetBSD, Wasm, JS и Plan9 особенные
    - часто из-за них есть осознанные костыли
    - Cgo is not Go, как говорил Пайк
    - и этим все сказано
    Интересности в 1 слайд
    138

    View Slide

  139. - Часто можно увидеть объявление функции перед использованием
    - мы ведь помним, что когда-то Go был написан на Си :)
    - Также достаточное количество _ в именах переменных и функций
    - опять же наследие Си
    - Windows, NetBSD, Wasm, JS и Plan9 особенные
    - часто из-за них есть осознанные костыли
    - Cgo is not Go, как говорил Пайк
    - и этим все сказано
    - Runtime is all about corner cases
    - такое моё ИМХО
    Интересности в 1 слайд
    139

    View Slide

  140. Вывод
    140

    View Slide

  141. Вывод
    141
    - Близко знакомимся с машиной
    - Создаём горутину
    - Создаём тред, который её выполнит
    - Выполняем и смотрим что выполнить еще
    - Всё

    View Slide

  142. - A Quick Guide to Go's Assembler
    - https://golang.org/doc/asm
    - The Go Memory Model
    - https://golang.org/ref/mem
    - Go 1.2 Runtime Symbol Information (2013)
    - https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZO
    z_o/pub
    - runtime/HACKING.md
    - https://github.com/golang/go/blob/master/src/runtime/HACKING.md
    Материалы
    142

    View Slide

  143. - Anton Repushko
    - Bogdan Storozhuk
    - Iskander Sharipov
    Благодарности
    143

    View Slide

  144. - Anton Repushko
    - Bogdan Storozhuk
    - Iskander Sharipov
    - И тебе тоже
    Благодарности
    144

    View Slide

  145. - Anton Repushko
    - Bogdan Storozhuk
    - Iskander Sharipov
    - И тебе тоже
    GopherCon team 👌
    Благодарности
    145

    View Slide

  146. Thank you
    Questions?
    Telegram: @olegkovalov
    Twitter: @oleg_kovalov
    That’s all folks

    View Slide