github.com/grafana/pyroscope-go/godeltaprof@v0.1.8-0.20240513050943-1b1f97373e2a/internal/pprof/proto.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package pprof 6 7 import ( 8 "bytes" 9 "io" 10 "os" 11 "runtime" 12 "strconv" 13 "strings" 14 "time" 15 ) 16 17 // lostProfileEvent is the function to which lost profiling 18 // events are attributed. 19 // (The name shows up in the pprof graphs.) 20 func lostProfileEvent() { lostProfileEvent() } 21 22 type ProfileBuilderOptions struct { 23 // for go1.21+ if true - use runtime_FrameSymbolName - produces frames with generic types, for example [go.shape.int] 24 // for go1.21+ if false - use runtime.Frame->Function - produces frames with generic types ommited [...] 25 // pre 1.21 - always use runtime.Frame->Function - produces frames with generic types ommited [...] 26 GenericsFrames bool 27 LazyMapping bool 28 } 29 30 // A profileBuilder writes a profile incrementally from a 31 // stream of profile samples delivered by the runtime. 32 type profileBuilder struct { 33 start time.Time 34 end time.Time 35 havePeriod bool 36 period int64 37 38 // encoding state 39 w io.Writer 40 zw gzipWriter 41 pb protobuf 42 strings []string 43 stringMap map[string]int 44 locs map[uintptr]locInfo // list of locInfo starting with the given PC. 45 funcs map[string]int // Package path-qualified function name to Function.ID 46 mem []memMap 47 deck pcDeck 48 49 opt ProfileBuilderOptions 50 } 51 52 type memMap struct { 53 // initialized as reading mapping 54 start uintptr // Address at which the binary (or DLL) is loaded into memory. 55 end uintptr // The limit of the address range occupied by this mapping. 56 offset uint64 // Offset in the binary that corresponds to the first mapped address. 57 file string // The object this entry is loaded from. 58 buildID string // A string that uniquely identifies a particular program version with high probability. 59 60 funcs symbolizeFlag 61 fake bool // map entry was faked; /proc/self/maps wasn't available 62 } 63 64 // symbolizeFlag keeps track of symbolization result. 65 // 66 // 0 : no symbol lookup was performed 67 // 1<<0 (lookupTried) : symbol lookup was performed 68 // 1<<1 (lookupFailed): symbol lookup was performed but failed 69 type symbolizeFlag uint8 70 71 const ( 72 lookupTried symbolizeFlag = 1 << iota 73 lookupFailed symbolizeFlag = 1 << iota 74 ) 75 76 const ( 77 // message Profile 78 tagProfile_SampleType = 1 // repeated ValueType 79 tagProfile_Sample = 2 // repeated Sample 80 tagProfile_Mapping = 3 // repeated Mapping 81 tagProfile_Location = 4 // repeated Location 82 tagProfile_Function = 5 // repeated Function 83 tagProfile_StringTable = 6 // repeated string 84 tagProfile_DropFrames = 7 // int64 (string table index) 85 tagProfile_KeepFrames = 8 // int64 (string table index) 86 tagProfile_TimeNanos = 9 // int64 87 tagProfile_DurationNanos = 10 // int64 88 tagProfile_PeriodType = 11 // ValueType (really optional string???) 89 tagProfile_Period = 12 // int64 90 tagProfile_Comment = 13 // repeated int64 91 tagProfile_DefaultSampleType = 14 // int64 92 93 // message ValueType 94 tagValueType_Type = 1 // int64 (string table index) 95 tagValueType_Unit = 2 // int64 (string table index) 96 97 // message Sample 98 tagSample_Location = 1 // repeated uint64 99 tagSample_Value = 2 // repeated int64 100 tagSample_Label = 3 // repeated Label 101 102 // message Label 103 tagLabel_Key = 1 // int64 (string table index) 104 tagLabel_Str = 2 // int64 (string table index) 105 tagLabel_Num = 3 // int64 106 107 // message Mapping 108 tagMapping_ID = 1 // uint64 109 tagMapping_Start = 2 // uint64 110 tagMapping_Limit = 3 // uint64 111 tagMapping_Offset = 4 // uint64 112 tagMapping_Filename = 5 // int64 (string table index) 113 tagMapping_BuildID = 6 // int64 (string table index) 114 tagMapping_HasFunctions = 7 // bool 115 tagMapping_HasFilenames = 8 // bool 116 tagMapping_HasLineNumbers = 9 // bool 117 tagMapping_HasInlineFrames = 10 // bool 118 119 // message Location 120 tagLocation_ID = 1 // uint64 121 tagLocation_MappingID = 2 // uint64 122 tagLocation_Address = 3 // uint64 123 tagLocation_Line = 4 // repeated Line 124 125 // message Line 126 tagLine_FunctionID = 1 // uint64 127 tagLine_Line = 2 // int64 128 129 // message Function 130 tagFunction_ID = 1 // uint64 131 tagFunction_Name = 2 // int64 (string table index) 132 tagFunction_SystemName = 3 // int64 (string table index) 133 tagFunction_Filename = 4 // int64 (string table index) 134 tagFunction_StartLine = 5 // int64 135 ) 136 137 // stringIndex adds s to the string table if not already present 138 // and returns the index of s in the string table. 139 func (b *profileBuilder) stringIndex(s string) int64 { 140 id, ok := b.stringMap[s] 141 if !ok { 142 id = len(b.strings) 143 b.strings = append(b.strings, s) 144 b.stringMap[s] = id 145 } 146 return int64(id) 147 } 148 149 func (b *profileBuilder) flush() { 150 const dataFlush = 4096 151 if b.pb.nest == 0 && len(b.pb.data) > dataFlush { 152 b.zw.Write(b.pb.data) 153 b.pb.data = b.pb.data[:0] 154 } 155 } 156 157 // pbValueType encodes a ValueType message to b.pb. 158 func (b *profileBuilder) pbValueType(tag int, typ, unit string) { 159 start := b.pb.startMessage() 160 b.pb.int64(tagValueType_Type, b.stringIndex(typ)) 161 b.pb.int64(tagValueType_Unit, b.stringIndex(unit)) 162 b.pb.endMessage(tag, start) 163 } 164 165 // pbSample encodes a Sample message to b.pb. 166 func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) { 167 start := b.pb.startMessage() 168 b.pb.int64s(tagSample_Value, values) 169 b.pb.uint64s(tagSample_Location, locs) 170 if labels != nil { 171 labels() 172 } 173 b.pb.endMessage(tagProfile_Sample, start) 174 b.flush() 175 } 176 177 // pbLabel encodes a Label message to b.pb. 178 func (b *profileBuilder) pbLabel(tag int, key, str string, num int64) { 179 start := b.pb.startMessage() 180 b.pb.int64Opt(tagLabel_Key, b.stringIndex(key)) 181 b.pb.int64Opt(tagLabel_Str, b.stringIndex(str)) 182 b.pb.int64Opt(tagLabel_Num, num) 183 b.pb.endMessage(tag, start) 184 } 185 186 // pbLine encodes a Line message to b.pb. 187 func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) { 188 start := b.pb.startMessage() 189 b.pb.uint64Opt(tagLine_FunctionID, funcID) 190 b.pb.int64Opt(tagLine_Line, line) 191 b.pb.endMessage(tag, start) 192 } 193 194 // pbMapping encodes a Mapping message to b.pb. 195 func (b *profileBuilder) pbMapping(tag int, id, base, limit, offset uint64, file, buildID string, hasFuncs bool) { 196 start := b.pb.startMessage() 197 b.pb.uint64Opt(tagMapping_ID, id) 198 b.pb.uint64Opt(tagMapping_Start, base) 199 b.pb.uint64Opt(tagMapping_Limit, limit) 200 b.pb.uint64Opt(tagMapping_Offset, offset) 201 b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file)) 202 b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID)) 203 // TODO: we set HasFunctions if all symbols from samples were symbolized (hasFuncs). 204 // Decide what to do about HasInlineFrames and HasLineNumbers. 205 // Also, another approach to handle the mapping entry with 206 // incomplete symbolization results is to dupliace the mapping 207 // entry (but with different Has* fields values) and use 208 // different entries for symbolized locations and unsymbolized locations. 209 if hasFuncs { 210 b.pb.bool(tagMapping_HasFunctions, true) 211 } 212 b.pb.endMessage(tag, start) 213 } 214 215 func allFrames(addr uintptr) ([]runtime.Frame, symbolizeFlag) { 216 // Expand this one address using CallersFrames so we can cache 217 // each expansion. In general, CallersFrames takes a whole 218 // stack, but in this case we know there will be no skips in 219 // the stack and we have return PCs anyway. 220 frames := runtime.CallersFrames([]uintptr{addr}) 221 frame, more := frames.Next() 222 if frame.Function == "runtime.goexit" { 223 // Short-circuit if we see runtime.goexit so the loop 224 // below doesn't allocate a useless empty location. 225 return nil, 0 226 } 227 228 symbolizeResult := lookupTried 229 if frame.PC == 0 || frame.Function == "" || frame.File == "" || frame.Line == 0 { 230 symbolizeResult |= lookupFailed 231 } 232 233 if frame.PC == 0 { 234 // If we failed to resolve the frame, at least make up 235 // a reasonable call PC. This mostly happens in tests. 236 frame.PC = addr - 1 237 } 238 ret := []runtime.Frame{frame} 239 for frame.Function != "runtime.goexit" && more { 240 frame, more = frames.Next() 241 ret = append(ret, frame) 242 } 243 return ret, symbolizeResult 244 } 245 246 type locInfo struct { 247 // location id assigned by the profileBuilder 248 id uint64 249 250 // sequence of PCs, including the fake PCs returned by the traceback 251 // to represent inlined functions 252 // https://github.com/golang/go/blob/d6f2f833c93a41ec1c68e49804b8387a06b131c5/src/runtime/traceback.go#L347-L368 253 pcs []uintptr 254 255 // firstPCFrames and firstPCSymbolizeResult hold the results of the 256 // allFrames call for the first (leaf-most) PC this locInfo represents 257 firstPCFrames []runtime.Frame 258 firstPCSymbolizeResult symbolizeFlag 259 } 260 261 // newProfileBuilder returns a new profileBuilder. 262 // CPU profiling data obtained from the runtime can be added 263 // by calling b.addCPUData, and then the eventual profile 264 // can be obtained by calling b.finish. 265 func newProfileBuilder(w io.Writer, opt ProfileBuilderOptions, mapping []memMap) *profileBuilder { 266 zw := newGzipWriter(w) 267 b := &profileBuilder{ 268 w: w, 269 zw: zw, 270 start: time.Now(), 271 strings: []string{""}, 272 stringMap: map[string]int{"": 0}, 273 locs: map[uintptr]locInfo{}, 274 funcs: map[string]int{}, 275 opt: opt, 276 } 277 b.mem = mapping 278 return b 279 } 280 281 // build completes and returns the constructed profile. 282 func (b *profileBuilder) build() { 283 b.end = time.Now() 284 285 b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano()) 286 if b.havePeriod { // must be CPU profile 287 b.pbValueType(tagProfile_SampleType, "samples", "count") 288 b.pbValueType(tagProfile_SampleType, "cpu", "nanoseconds") 289 b.pb.int64Opt(tagProfile_DurationNanos, b.end.Sub(b.start).Nanoseconds()) 290 b.pbValueType(tagProfile_PeriodType, "cpu", "nanoseconds") 291 b.pb.int64Opt(tagProfile_Period, b.period) 292 } 293 294 for i, m := range b.mem { 295 hasFunctions := m.funcs == lookupTried // lookupTried but not lookupFailed 296 b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(m.start), uint64(m.end), m.offset, m.file, m.buildID, hasFunctions) 297 } 298 299 // TODO: Anything for tagProfile_DropFrames? 300 // TODO: Anything for tagProfile_KeepFrames? 301 302 b.pb.strings(tagProfile_StringTable, b.strings) 303 b.zw.Write(b.pb.data) 304 b.zw.Close() 305 } 306 307 // appendLocsForStack appends the location IDs for the given stack trace to the given 308 // location ID slice, locs. The addresses in the stack are return PCs or 1 + the PC of 309 // an inline marker as the runtime traceback function returns. 310 // 311 // It may return an empty slice even if locs is non-empty, for example if locs consists 312 // solely of runtime.goexit. We still count these empty stacks in profiles in order to 313 // get the right cumulative sample count. 314 // 315 // It may emit to b.pb, so there must be no message encoding in progress. 316 func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) (newLocs []uint64) { 317 b.deck.reset() 318 319 // The last frame might be truncated. Recover lost inline frames. 320 stk = runtime_expandFinalInlineFrame(stk) 321 322 for len(stk) > 0 { 323 addr := stk[0] 324 if l, ok := b.locs[addr]; ok { 325 // When generating code for an inlined function, the compiler adds 326 // NOP instructions to the outermost function as a placeholder for 327 // each layer of inlining. When the runtime generates tracebacks for 328 // stacks that include inlined functions, it uses the addresses of 329 // those NOPs as "fake" PCs on the stack as if they were regular 330 // function call sites. But if a profiling signal arrives while the 331 // CPU is executing one of those NOPs, its PC will show up as a leaf 332 // in the profile with its own Location entry. So, always check 333 // whether addr is a "fake" PC in the context of the current call 334 // stack by trying to add it to the inlining deck before assuming 335 // that the deck is complete. 336 if len(b.deck.pcs) > 0 { 337 if added := b.deck.tryAdd(addr, l.firstPCFrames, l.firstPCSymbolizeResult); added { 338 stk = stk[1:] 339 continue 340 } 341 } 342 343 // first record the location if there is any pending accumulated info. 344 if id := b.emitLocation(); id > 0 { 345 locs = append(locs, id) 346 } 347 348 // then, record the cached location. 349 locs = append(locs, l.id) 350 351 // Skip the matching pcs. 352 // 353 // Even if stk was truncated due to the stack depth 354 // limit, expandFinalInlineFrame above has already 355 // fixed the truncation, ensuring it is long enough. 356 stk = stk[len(l.pcs):] 357 continue 358 } 359 360 frames, symbolizeResult := allFrames(addr) 361 if len(frames) == 0 { // runtime.goexit. 362 if id := b.emitLocation(); id > 0 { 363 locs = append(locs, id) 364 } 365 stk = stk[1:] 366 continue 367 } 368 369 if added := b.deck.tryAdd(addr, frames, symbolizeResult); added { 370 stk = stk[1:] 371 continue 372 } 373 // add failed because this addr is not inlined with the 374 // existing PCs in the deck. Flush the deck and retry handling 375 // this pc. 376 if id := b.emitLocation(); id > 0 { 377 locs = append(locs, id) 378 } 379 380 // check cache again - previous emitLocation added a new entry 381 if l, ok := b.locs[addr]; ok { 382 locs = append(locs, l.id) 383 stk = stk[len(l.pcs):] // skip the matching pcs. 384 } else { 385 b.deck.tryAdd(addr, frames, symbolizeResult) // must succeed. 386 stk = stk[1:] 387 } 388 } 389 if id := b.emitLocation(); id > 0 { // emit remaining location. 390 locs = append(locs, id) 391 } 392 return locs 393 } 394 395 // Here's an example of how Go 1.17 writes out inlined functions, compiled for 396 // linux/amd64. The disassembly of main.main shows two levels of inlining: main 397 // calls b, b calls a, a does some work. 398 // 399 // inline.go:9 0x4553ec 90 NOPL // func main() { b(v) } 400 // inline.go:6 0x4553ed 90 NOPL // func b(v *int) { a(v) } 401 // inline.go:5 0x4553ee 48c7002a000000 MOVQ $0x2a, 0(AX) // func a(v *int) { *v = 42 } 402 // 403 // If a profiling signal arrives while executing the MOVQ at 0x4553ee (for line 404 // 5), the runtime will report the stack as the MOVQ frame being called by the 405 // NOPL at 0x4553ed (for line 6) being called by the NOPL at 0x4553ec (for line 406 // 9). 407 // 408 // The role of pcDeck is to collapse those three frames back into a single 409 // location at 0x4553ee, with file/line/function symbolization info representing 410 // the three layers of calls. It does that via sequential calls to pcDeck.tryAdd 411 // starting with the leaf-most address. The fourth call to pcDeck.tryAdd will be 412 // for the caller of main.main. Because main.main was not inlined in its caller, 413 // the deck will reject the addition, and the fourth PC on the stack will get 414 // its own location. 415 416 // pcDeck is a helper to detect a sequence of inlined functions from 417 // a stack trace returned by the runtime. 418 // 419 // The stack traces returned by runtime's trackback functions are fully 420 // expanded (at least for Go functions) and include the fake pcs representing 421 // inlined functions. The profile proto expects the inlined functions to be 422 // encoded in one Location message. 423 // https://github.com/google/pprof/blob/5e965273ee43930341d897407202dd5e10e952cb/proto/profile.proto#L177-L184 424 // 425 // Runtime does not directly expose whether a frame is for an inlined function 426 // and looking up debug info is not ideal, so we use a heuristic to filter 427 // the fake pcs and restore the inlined and entry functions. Inlined functions 428 // have the following properties: 429 // 430 // Frame's Func is nil (note: also true for non-Go functions), and 431 // Frame's Entry matches its entry function frame's Entry (note: could also be true for recursive calls and non-Go functions), and 432 // Frame's Name does not match its entry function frame's name (note: inlined functions cannot be directly recursive). 433 // 434 // As reading and processing the pcs in a stack trace one by one (from leaf to the root), 435 // we use pcDeck to temporarily hold the observed pcs and their expanded frames 436 // until we observe the entry function frame. 437 type pcDeck struct { 438 pcs []uintptr 439 frames []runtime.Frame 440 symbolizeResult symbolizeFlag 441 442 // firstPCFrames indicates the number of frames associated with the first 443 // (leaf-most) PC in the deck 444 firstPCFrames int 445 // firstPCSymbolizeResult holds the results of the allFrames call for the 446 // first (leaf-most) PC in the deck 447 firstPCSymbolizeResult symbolizeFlag 448 } 449 450 func (d *pcDeck) reset() { 451 d.pcs = d.pcs[:0] 452 d.frames = d.frames[:0] 453 d.symbolizeResult = 0 454 d.firstPCFrames = 0 455 d.firstPCSymbolizeResult = 0 456 } 457 458 // tryAdd tries to add the pc and Frames expanded from it (most likely one, 459 // since the stack trace is already fully expanded) and the symbolizeResult 460 // to the deck. If it fails the caller needs to flush the deck and retry. 461 func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symbolizeFlag) (success bool) { 462 if existing := len(d.frames); existing > 0 { 463 // 'd.frames' are all expanded from one 'pc' and represent all 464 // inlined functions so we check only the last one. 465 newFrame := frames[0] 466 last := d.frames[existing-1] 467 if last.Func != nil { // the last frame can't be inlined. Flush. 468 return false 469 } 470 if last.Entry == 0 || newFrame.Entry == 0 { // Possibly not a Go function. Don't try to merge. 471 return false 472 } 473 474 if last.Entry != newFrame.Entry { // newFrame is for a different function. 475 return false 476 } 477 if last.Function == newFrame.Function { // maybe recursion. 478 return false 479 } 480 } 481 d.pcs = append(d.pcs, pc) 482 d.frames = append(d.frames, frames...) 483 d.symbolizeResult |= symbolizeResult 484 if len(d.pcs) == 1 { 485 d.firstPCFrames = len(d.frames) 486 d.firstPCSymbolizeResult = symbolizeResult 487 } 488 return true 489 } 490 491 // emitLocation emits the new location and function information recorded in the deck 492 // and returns the location ID encoded in the profile protobuf. 493 // It emits to b.pb, so there must be no message encoding in progress. 494 // It resets the deck. 495 func (b *profileBuilder) emitLocation() uint64 { 496 if len(b.deck.pcs) == 0 { 497 return 0 498 } 499 defer b.deck.reset() 500 501 addr := b.deck.pcs[0] 502 firstFrame := b.deck.frames[0] 503 504 // We can't write out functions while in the middle of the 505 // Location message, so record new functions we encounter and 506 // write them out after the Location. 507 type newFunc struct { 508 id uint64 509 name, file string 510 startLine int64 511 } 512 newFuncs := make([]newFunc, 0, 8) 513 514 id := uint64(len(b.locs)) + 1 515 b.locs[addr] = locInfo{ 516 id: id, 517 pcs: append([]uintptr{}, b.deck.pcs...), 518 firstPCSymbolizeResult: b.deck.firstPCSymbolizeResult, 519 firstPCFrames: append([]runtime.Frame{}, b.deck.frames[:b.deck.firstPCFrames]...), 520 } 521 522 start := b.pb.startMessage() 523 b.pb.uint64Opt(tagLocation_ID, id) 524 b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC)) 525 for _, frame := range b.deck.frames { 526 // Write out each line in frame expansion. 527 funcID := uint64(b.funcs[frame.Function]) 528 if funcID == 0 { 529 funcID = uint64(len(b.funcs)) + 1 530 b.funcs[frame.Function] = int(funcID) 531 var name string 532 if b.opt.GenericsFrames { 533 name = runtime_FrameSymbolName(&frame) 534 } else { 535 name = frame.Function 536 } 537 newFuncs = append(newFuncs, newFunc{ 538 id: funcID, 539 name: name, 540 file: frame.File, 541 startLine: int64(runtime_FrameStartLine(&frame)), 542 }) 543 } 544 b.pbLine(tagLocation_Line, funcID, int64(frame.Line)) 545 } 546 for i := range b.mem { 547 if b.mem[i].start <= addr && addr < b.mem[i].end || b.mem[i].fake { 548 b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1)) 549 550 m := b.mem[i] 551 m.funcs |= b.deck.symbolizeResult 552 b.mem[i] = m 553 break 554 } 555 } 556 b.pb.endMessage(tagProfile_Location, start) 557 558 // Write out functions we found during frame expansion. 559 for _, fn := range newFuncs { 560 start := b.pb.startMessage() 561 b.pb.uint64Opt(tagFunction_ID, fn.id) 562 b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name)) 563 b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name)) 564 b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file)) 565 b.pb.int64Opt(tagFunction_StartLine, fn.startLine) 566 b.pb.endMessage(tagProfile_Function, start) 567 } 568 569 b.flush() 570 return id 571 } 572 573 func readMapping() []memMap { 574 data, _ := os.ReadFile("/proc/self/maps") 575 var mem []memMap 576 parseProcSelfMaps(data, func(lo, hi, offset uint64, file, buildID string) { 577 mem = append(mem, memMap{ 578 start: uintptr(lo), 579 end: uintptr(hi), 580 offset: offset, 581 file: file, 582 buildID: buildID, 583 fake: false, 584 }) 585 }) 586 if len(mem) == 0 { // pprof expects a map entry, so fake one. 587 mem = []memMap{{ 588 start: uintptr(0), 589 end: uintptr(0), 590 offset: 0, 591 file: "", 592 buildID: "", 593 fake: true, 594 }} 595 } 596 return mem 597 } 598 599 var space = []byte(" ") 600 var newline = []byte("\n") 601 602 func parseProcSelfMaps(data []byte, addMapping func(lo, hi, offset uint64, file, buildID string)) { 603 // $ cat /proc/self/maps 604 // 00400000-0040b000 r-xp 00000000 fc:01 787766 /bin/cat 605 // 0060a000-0060b000 r--p 0000a000 fc:01 787766 /bin/cat 606 // 0060b000-0060c000 rw-p 0000b000 fc:01 787766 /bin/cat 607 // 014ab000-014cc000 rw-p 00000000 00:00 0 [heap] 608 // 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064 /usr/lib/locale/locale-archive 609 // 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 610 // 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 611 // 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 612 // 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 613 // 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0 614 // 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 615 // 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0 616 // 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0 617 // 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 618 // 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 619 // 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0 620 // 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0 [stack] 621 // 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0 [vdso] 622 // ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] 623 624 var line []byte 625 // next removes and returns the next field in the line. 626 // It also removes from line any spaces following the field. 627 next := func() []byte { 628 var f []byte 629 f, line, _ = bytesCut(line, space) 630 line = bytes.TrimLeft(line, " ") 631 return f 632 } 633 634 for len(data) > 0 { 635 line, data, _ = bytesCut(data, newline) 636 addr := next() 637 loStr, hiStr, ok := stringsCut(string(addr), "-") 638 if !ok { 639 continue 640 } 641 lo, err := strconv.ParseUint(loStr, 16, 64) 642 if err != nil { 643 continue 644 } 645 hi, err := strconv.ParseUint(hiStr, 16, 64) 646 if err != nil { 647 continue 648 } 649 perm := next() 650 if len(perm) < 4 || perm[2] != 'x' { 651 // Only interested in executable mappings. 652 continue 653 } 654 offset, err := strconv.ParseUint(string(next()), 16, 64) 655 if err != nil { 656 continue 657 } 658 next() // dev 659 inode := next() // inode 660 if line == nil { 661 continue 662 } 663 file := string(line) 664 665 // Trim deleted file marker. 666 deletedStr := " (deleted)" 667 deletedLen := len(deletedStr) 668 if len(file) >= deletedLen && file[len(file)-deletedLen:] == deletedStr { 669 file = file[:len(file)-deletedLen] 670 } 671 672 if len(inode) == 1 && inode[0] == '0' && file == "" { 673 // Huge-page text mappings list the initial fragment of 674 // mapped but unpopulated memory as being inode 0. 675 // Don't report that part. 676 // But [vdso] and [vsyscall] are inode 0, so let non-empty file names through. 677 continue 678 } 679 680 // TODO: pprof's remapMappingIDs makes one adjustment: 681 // 1. If there is an /anon_hugepage mapping first and it is 682 // consecutive to a next mapping, drop the /anon_hugepage. 683 // There's no indication why this is needed. 684 // Let's try not doing this and see what breaks. 685 // If we do need it, it would go here, before we 686 // enter the mappings into b.mem in the first place. 687 688 buildID, _ := elfBuildID(file) 689 addMapping(lo, hi, offset, file, buildID) 690 } 691 } 692 693 // Cut slices s around the first instance of sep, 694 // returning the text before and after sep. 695 // The found result reports whether sep appears in s. 696 // If sep does not appear in s, cut returns s, nil, false. 697 // 698 // Cut returns slices of the original slice s, not copies. 699 func bytesCut(s, sep []byte) (before, after []byte, found bool) { 700 if i := bytes.Index(s, sep); i >= 0 { 701 return s[:i], s[i+len(sep):], true 702 } 703 return s, nil, false 704 } 705 706 // Cut slices s around the first instance of sep, 707 // returning the text before and after sep. 708 // The found result reports whether sep appears in s. 709 // If sep does not appear in s, cut returns s, "", false. 710 func stringsCut(s, sep string) (before, after string, found bool) { 711 if i := strings.Index(s, sep); i >= 0 { 712 return s[:i], s[i+len(sep):], true 713 } 714 return s, "", false 715 }