src/runtime/pprof/proto.go GO 770 lines View on github.com → Search inside
1// Copyright 2016 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package pprof67import (8	"bytes"9	"compress/gzip"10	"fmt"11	"internal/abi"12	"io"13	"runtime"14	"strconv"15	"strings"16	"time"17	"unsafe"18)1920// lostProfileEvent is the function to which lost profiling21// events are attributed.22// (The name shows up in the pprof graphs.)23func lostProfileEvent() { lostProfileEvent() }2425// A profileBuilder writes a profile incrementally from a26// stream of profile samples delivered by the runtime.27type profileBuilder struct {28	start      time.Time29	end        time.Time30	havePeriod bool31	period     int6432	m          profMap3334	// encoding state35	w         io.Writer36	zw        *gzip.Writer37	pb        protobuf38	strings   []string39	stringMap map[string]int40	locs      map[uintptr]locInfo // list of locInfo starting with the given PC.41	funcs     map[string]int      // Package path-qualified function name to Function.ID42	mem       []memMap43	deck      pcDeck44}4546type memMap struct {47	// initialized as reading mapping48	start   uintptr // Address at which the binary (or DLL) is loaded into memory.49	end     uintptr // The limit of the address range occupied by this mapping.50	offset  uint64  // Offset in the binary that corresponds to the first mapped address.51	file    string  // The object this entry is loaded from.52	buildID string  // A string that uniquely identifies a particular program version with high probability.5354	funcs symbolizeFlag55	fake  bool // map entry was faked; /proc/self/maps wasn't available56}5758// symbolizeFlag keeps track of symbolization result.59//60//	0                  : no symbol lookup was performed61//	1<<0 (lookupTried) : symbol lookup was performed62//	1<<1 (lookupFailed): symbol lookup was performed but failed63type symbolizeFlag uint86465const (66	lookupTried  symbolizeFlag = 1 << iota67	lookupFailed symbolizeFlag = 1 << iota68)6970const (71	// message Profile72	tagProfile_SampleType        = 1  // repeated ValueType73	tagProfile_Sample            = 2  // repeated Sample74	tagProfile_Mapping           = 3  // repeated Mapping75	tagProfile_Location          = 4  // repeated Location76	tagProfile_Function          = 5  // repeated Function77	tagProfile_StringTable       = 6  // repeated string78	tagProfile_DropFrames        = 7  // int64 (string table index)79	tagProfile_KeepFrames        = 8  // int64 (string table index)80	tagProfile_TimeNanos         = 9  // int6481	tagProfile_DurationNanos     = 10 // int6482	tagProfile_PeriodType        = 11 // ValueType (really optional string???)83	tagProfile_Period            = 12 // int6484	tagProfile_Comment           = 13 // repeated int6485	tagProfile_DefaultSampleType = 14 // int648687	// message ValueType88	tagValueType_Type = 1 // int64 (string table index)89	tagValueType_Unit = 2 // int64 (string table index)9091	// message Sample92	tagSample_Location = 1 // repeated uint6493	tagSample_Value    = 2 // repeated int6494	tagSample_Label    = 3 // repeated Label9596	// message Label97	tagLabel_Key = 1 // int64 (string table index)98	tagLabel_Str = 2 // int64 (string table index)99	tagLabel_Num = 3 // int64100101	// message Mapping102	tagMapping_ID              = 1  // uint64103	tagMapping_Start           = 2  // uint64104	tagMapping_Limit           = 3  // uint64105	tagMapping_Offset          = 4  // uint64106	tagMapping_Filename        = 5  // int64 (string table index)107	tagMapping_BuildID         = 6  // int64 (string table index)108	tagMapping_HasFunctions    = 7  // bool109	tagMapping_HasFilenames    = 8  // bool110	tagMapping_HasLineNumbers  = 9  // bool111	tagMapping_HasInlineFrames = 10 // bool112113	// message Location114	tagLocation_ID        = 1 // uint64115	tagLocation_MappingID = 2 // uint64116	tagLocation_Address   = 3 // uint64117	tagLocation_Line      = 4 // repeated Line118119	// message Line120	tagLine_FunctionID = 1 // uint64121	tagLine_Line       = 2 // int64122123	// message Function124	tagFunction_ID         = 1 // uint64125	tagFunction_Name       = 2 // int64 (string table index)126	tagFunction_SystemName = 3 // int64 (string table index)127	tagFunction_Filename   = 4 // int64 (string table index)128	tagFunction_StartLine  = 5 // int64129)130131// stringIndex adds s to the string table if not already present132// and returns the index of s in the string table.133func (b *profileBuilder) stringIndex(s string) int64 {134	id, ok := b.stringMap[s]135	if !ok {136		id = len(b.strings)137		b.strings = append(b.strings, s)138		b.stringMap[s] = id139	}140	return int64(id)141}142143func (b *profileBuilder) flush() {144	const dataFlush = 4096145	if b.pb.nest == 0 && len(b.pb.data) > dataFlush {146		b.zw.Write(b.pb.data)147		b.pb.data = b.pb.data[:0]148	}149}150151// pbValueType encodes a ValueType message to b.pb.152func (b *profileBuilder) pbValueType(tag int, typ, unit string) {153	start := b.pb.startMessage()154	b.pb.int64(tagValueType_Type, b.stringIndex(typ))155	b.pb.int64(tagValueType_Unit, b.stringIndex(unit))156	b.pb.endMessage(tag, start)157}158159// pbSample encodes a Sample message to b.pb.160func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) {161	start := b.pb.startMessage()162	b.pb.int64s(tagSample_Value, values)163	b.pb.uint64s(tagSample_Location, locs)164	if labels != nil {165		labels()166	}167	b.pb.endMessage(tagProfile_Sample, start)168	b.flush()169}170171// pbLabel encodes a Label message to b.pb.172func (b *profileBuilder) pbLabel(tag int, key, str string, num int64) {173	start := b.pb.startMessage()174	b.pb.int64Opt(tagLabel_Key, b.stringIndex(key))175	b.pb.int64Opt(tagLabel_Str, b.stringIndex(str))176	b.pb.int64Opt(tagLabel_Num, num)177	b.pb.endMessage(tag, start)178}179180// pbLine encodes a Line message to b.pb.181func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) {182	start := b.pb.startMessage()183	b.pb.uint64Opt(tagLine_FunctionID, funcID)184	b.pb.int64Opt(tagLine_Line, line)185	b.pb.endMessage(tag, start)186}187188// pbMapping encodes a Mapping message to b.pb.189func (b *profileBuilder) pbMapping(tag int, id, base, limit, offset uint64, file, buildID string, hasFuncs bool) {190	start := b.pb.startMessage()191	b.pb.uint64Opt(tagMapping_ID, id)192	b.pb.uint64Opt(tagMapping_Start, base)193	b.pb.uint64Opt(tagMapping_Limit, limit)194	b.pb.uint64Opt(tagMapping_Offset, offset)195	b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file))196	b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID))197	// TODO: we set HasFunctions if all symbols from samples were symbolized (hasFuncs).198	// Decide what to do about HasInlineFrames and HasLineNumbers.199	// Also, another approach to handle the mapping entry with200	// incomplete symbolization results is to duplicate the mapping201	// entry (but with different Has* fields values) and use202	// different entries for symbolized locations and unsymbolized locations.203	if hasFuncs {204		b.pb.bool(tagMapping_HasFunctions, true)205	}206	b.pb.endMessage(tag, start)207}208209func allFrames(addr uintptr) ([]runtime.Frame, symbolizeFlag) {210	// Expand this one address using CallersFrames so we can cache211	// each expansion. In general, CallersFrames takes a whole212	// stack, but in this case we know there will be no skips in213	// the stack and we have return PCs anyway.214	frames := runtime.CallersFrames([]uintptr{addr})215	frame, more := frames.Next()216	if frame.Function == "runtime.goexit" {217		// Short-circuit if we see runtime.goexit so the loop218		// below doesn't allocate a useless empty location.219		return nil, 0220	}221222	symbolizeResult := lookupTried223	if frame.PC == 0 || frame.Function == "" || frame.File == "" || frame.Line == 0 {224		symbolizeResult |= lookupFailed225	}226227	if frame.PC == 0 {228		// If we failed to resolve the frame, at least make up229		// a reasonable call PC. This mostly happens in tests.230		frame.PC = addr - 1231	}232	ret := []runtime.Frame{frame}233	for frame.Function != "runtime.goexit" && more {234		frame, more = frames.Next()235		ret = append(ret, frame)236	}237	return ret, symbolizeResult238}239240type locInfo struct {241	// location id assigned by the profileBuilder242	id uint64243244	// sequence of PCs, including the fake PCs returned by the traceback245	// to represent inlined functions246	// https://github.com/golang/go/blob/d6f2f833c93a41ec1c68e49804b8387a06b131c5/src/runtime/traceback.go#L347-L368247	pcs []uintptr248249	// firstPCFrames and firstPCSymbolizeResult hold the results of the250	// allFrames call for the first (leaf-most) PC this locInfo represents251	firstPCFrames          []runtime.Frame252	firstPCSymbolizeResult symbolizeFlag253}254255// newProfileBuilder returns a new profileBuilder.256// CPU profiling data obtained from the runtime can be added257// by calling b.addCPUData, and then the eventual profile258// can be obtained by calling b.finish.259func newProfileBuilder(w io.Writer) *profileBuilder {260	zw, _ := gzip.NewWriterLevel(w, gzip.BestSpeed)261	b := &profileBuilder{262		w:         w,263		zw:        zw,264		start:     time.Now(),265		strings:   []string{""},266		stringMap: map[string]int{"": 0},267		locs:      map[uintptr]locInfo{},268		funcs:     map[string]int{},269	}270	b.readMapping()271	return b272}273274// addCPUData adds the CPU profiling data to the profile.275//276// The data must be a whole number of records, as delivered by the runtime.277// len(tags) must be equal to the number of records in data.278func (b *profileBuilder) addCPUData(data []uint64, tags []unsafe.Pointer) error {279	if !b.havePeriod {280		// first record is period281		if len(data) < 3 {282			return fmt.Errorf("truncated profile")283		}284		if data[0] != 3 || data[2] == 0 {285			return fmt.Errorf("malformed profile")286		}287		// data[2] is sampling rate in Hz. Convert to sampling288		// period in nanoseconds.289		b.period = 1e9 / int64(data[2])290		b.havePeriod = true291		data = data[3:]292		// Consume tag slot. Note that there isn't a meaningful tag293		// value for this record.294		tags = tags[1:]295	}296297	// Parse CPU samples from the profile.298	// Each sample is 3+n uint64s:299	//	data[0] = 3+n300	//	data[1] = time stamp (ignored)301	//	data[2] = count302	//	data[3:3+n] = stack303	// If the count is 0 and the stack has length 1,304	// that's an overflow record inserted by the runtime305	// to indicate that stack[0] samples were lost.306	// Otherwise the count is usually 1,307	// but in a few special cases like lost non-Go samples308	// there can be larger counts.309	// Because many samples with the same stack arrive,310	// we want to deduplicate immediately, which we do311	// using the b.m profMap.312	for len(data) > 0 {313		if len(data) < 3 || data[0] > uint64(len(data)) {314			return fmt.Errorf("truncated profile")315		}316		if data[0] < 3 || tags != nil && len(tags) < 1 {317			return fmt.Errorf("malformed profile")318		}319		if len(tags) < 1 {320			return fmt.Errorf("mismatched profile records and tags")321		}322		count := data[2]323		stk := data[3:data[0]]324		data = data[data[0]:]325		tag := tags[0]326		tags = tags[1:]327328		if count == 0 && len(stk) == 1 {329			// overflow record330			count = uint64(stk[0])331			stk = []uint64{332				// gentraceback guarantees that PCs in the333				// stack can be unconditionally decremented and334				// still be valid, so we must do the same.335				uint64(abi.FuncPCABIInternal(lostProfileEvent) + 1),336			}337		}338		b.m.lookup(stk, tag).count += int64(count)339	}340341	if len(tags) != 0 {342		return fmt.Errorf("mismatched profile records and tags")343	}344	return nil345}346347// build completes and returns the constructed profile.348func (b *profileBuilder) build() error {349	b.end = time.Now()350351	b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano())352	if b.havePeriod { // must be CPU profile353		b.pbValueType(tagProfile_SampleType, "samples", "count")354		b.pbValueType(tagProfile_SampleType, "cpu", "nanoseconds")355		b.pb.int64Opt(tagProfile_DurationNanos, b.end.Sub(b.start).Nanoseconds())356		b.pbValueType(tagProfile_PeriodType, "cpu", "nanoseconds")357		b.pb.int64Opt(tagProfile_Period, b.period)358	}359360	values := []int64{0, 0}361	var locs []uint64362363	for e := b.m.all; e != nil; e = e.nextAll {364		values[0] = e.count365		values[1] = e.count * b.period366367		var labels func()368		if e.tag != nil {369			labels = func() {370				for _, lbl := range (*labelMap)(e.tag).Set.List {371					b.pbLabel(tagSample_Label, lbl.Key, lbl.Value, 0)372				}373			}374		}375376		locs = b.appendLocsForStack(locs[:0], e.stk)377378		b.pbSample(values, locs, labels)379	}380381	for i, m := range b.mem {382		hasFunctions := m.funcs == lookupTried // lookupTried but not lookupFailed383		b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(m.start), uint64(m.end), m.offset, m.file, m.buildID, hasFunctions)384	}385386	// TODO: Anything for tagProfile_DropFrames?387	// TODO: Anything for tagProfile_KeepFrames?388389	b.pb.strings(tagProfile_StringTable, b.strings)390	_, err := b.zw.Write(b.pb.data)391	if err != nil {392		return err393	}394	return b.zw.Close()395}396397// appendLocsForStack appends the location IDs for the given stack trace to the given398// location ID slice, locs. The addresses in the stack are return PCs or 1 + the PC of399// an inline marker as the runtime traceback function returns.400//401// It may return an empty slice even if locs is non-empty, for example if locs consists402// solely of runtime.goexit. We still count these empty stacks in profiles in order to403// get the right cumulative sample count.404//405// It may emit to b.pb, so there must be no message encoding in progress.406func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) (newLocs []uint64) {407	b.deck.reset()408409	// The last frame might be truncated. Recover lost inline frames.410	origStk := stk411	stk = runtime_expandFinalInlineFrame(stk)412413	for len(stk) > 0 {414		addr := stk[0]415		if l, ok := b.locs[addr]; ok {416			// When generating code for an inlined function, the compiler adds417			// NOP instructions to the outermost function as a placeholder for418			// each layer of inlining. When the runtime generates tracebacks for419			// stacks that include inlined functions, it uses the addresses of420			// those NOPs as "fake" PCs on the stack as if they were regular421			// function call sites. But if a profiling signal arrives while the422			// CPU is executing one of those NOPs, its PC will show up as a leaf423			// in the profile with its own Location entry. So, always check424			// whether addr is a "fake" PC in the context of the current call425			// stack by trying to add it to the inlining deck before assuming426			// that the deck is complete.427			if len(b.deck.pcs) > 0 {428				if added := b.deck.tryAdd(addr, l.firstPCFrames, l.firstPCSymbolizeResult); added {429					stk = stk[1:]430					continue431				}432			}433434			// first record the location if there is any pending accumulated info.435			if id := b.emitLocation(); id > 0 {436				locs = append(locs, id)437			}438439			// then, record the cached location.440			locs = append(locs, l.id)441442			// Skip the matching pcs.443			//444			// Even if stk was truncated due to the stack depth445			// limit, expandFinalInlineFrame above has already446			// fixed the truncation, ensuring it is long enough.447			if len(l.pcs) > len(stk) {448				panic(fmt.Sprintf("stack too short to match cached location; stk = %#x, l.pcs = %#x, original stk = %#x", stk, l.pcs, origStk))449			}450			stk = stk[len(l.pcs):]451			continue452		}453454		frames, symbolizeResult := allFrames(addr)455		if len(frames) == 0 { // runtime.goexit.456			if id := b.emitLocation(); id > 0 {457				locs = append(locs, id)458			}459			stk = stk[1:]460			continue461		}462463		if added := b.deck.tryAdd(addr, frames, symbolizeResult); added {464			stk = stk[1:]465			continue466		}467		// add failed because this addr is not inlined with the468		// existing PCs in the deck. Flush the deck and retry handling469		// this pc.470		if id := b.emitLocation(); id > 0 {471			locs = append(locs, id)472		}473474		// check cache again - previous emitLocation added a new entry475		if l, ok := b.locs[addr]; ok {476			locs = append(locs, l.id)477			stk = stk[len(l.pcs):] // skip the matching pcs.478		} else {479			b.deck.tryAdd(addr, frames, symbolizeResult) // must succeed.480			stk = stk[1:]481		}482	}483	if id := b.emitLocation(); id > 0 { // emit remaining location.484		locs = append(locs, id)485	}486	return locs487}488489// Here's an example of how Go 1.17 writes out inlined functions, compiled for490// linux/amd64. The disassembly of main.main shows two levels of inlining: main491// calls b, b calls a, a does some work.492//493//   inline.go:9   0x4553ec  90              NOPL                 // func main()    { b(v) }494//   inline.go:6   0x4553ed  90              NOPL                 // func b(v *int) { a(v) }495//   inline.go:5   0x4553ee  48c7002a000000  MOVQ $0x2a, 0(AX)    // func a(v *int) { *v = 42 }496//497// If a profiling signal arrives while executing the MOVQ at 0x4553ee (for line498// 5), the runtime will report the stack as the MOVQ frame being called by the499// NOPL at 0x4553ed (for line 6) being called by the NOPL at 0x4553ec (for line500// 9).501//502// The role of pcDeck is to collapse those three frames back into a single503// location at 0x4553ee, with file/line/function symbolization info representing504// the three layers of calls. It does that via sequential calls to pcDeck.tryAdd505// starting with the leaf-most address. The fourth call to pcDeck.tryAdd will be506// for the caller of main.main. Because main.main was not inlined in its caller,507// the deck will reject the addition, and the fourth PC on the stack will get508// its own location.509510// pcDeck is a helper to detect a sequence of inlined functions from511// a stack trace returned by the runtime.512//513// The stack traces returned by runtime's trackback functions are fully514// expanded (at least for Go functions) and include the fake pcs representing515// inlined functions. The profile proto expects the inlined functions to be516// encoded in one Location message.517// https://github.com/google/pprof/blob/5e965273ee43930341d897407202dd5e10e952cb/proto/profile.proto#L177-L184518//519// Runtime does not directly expose whether a frame is for an inlined function520// and looking up debug info is not ideal, so we use a heuristic to filter521// the fake pcs and restore the inlined and entry functions. Inlined functions522// have the following properties:523//524//	Frame's Func is nil (note: also true for non-Go functions), and525//	Frame's Entry matches its entry function frame's Entry (note: could also be true for recursive calls and non-Go functions), and526//	Frame's Name does not match its entry function frame's name (note: inlined functions cannot be directly recursive).527//528// As reading and processing the pcs in a stack trace one by one (from leaf to the root),529// we use pcDeck to temporarily hold the observed pcs and their expanded frames530// until we observe the entry function frame.531type pcDeck struct {532	pcs             []uintptr533	frames          []runtime.Frame534	symbolizeResult symbolizeFlag535536	// firstPCFrames indicates the number of frames associated with the first537	// (leaf-most) PC in the deck538	firstPCFrames int539	// firstPCSymbolizeResult holds the results of the allFrames call for the540	// first (leaf-most) PC in the deck541	firstPCSymbolizeResult symbolizeFlag542}543544func (d *pcDeck) reset() {545	d.pcs = d.pcs[:0]546	d.frames = d.frames[:0]547	d.symbolizeResult = 0548	d.firstPCFrames = 0549	d.firstPCSymbolizeResult = 0550}551552// tryAdd tries to add the pc and Frames expanded from it (most likely one,553// since the stack trace is already fully expanded) and the symbolizeResult554// to the deck. If it fails the caller needs to flush the deck and retry.555func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symbolizeFlag) (success bool) {556	if existing := len(d.frames); existing > 0 {557		// 'd.frames' are all expanded from one 'pc' and represent all558		// inlined functions so we check only the last one.559		newFrame := frames[0]560		last := d.frames[existing-1]561		if last.Func != nil { // the last frame can't be inlined. Flush.562			return false563		}564		if last.Entry == 0 || newFrame.Entry == 0 { // Possibly not a Go function. Don't try to merge.565			return false566		}567568		if last.Entry != newFrame.Entry { // newFrame is for a different function.569			return false570		}571		if runtime_FrameSymbolName(&last) == runtime_FrameSymbolName(&newFrame) { // maybe recursion.572			return false573		}574	}575	d.pcs = append(d.pcs, pc)576	d.frames = append(d.frames, frames...)577	d.symbolizeResult |= symbolizeResult578	if len(d.pcs) == 1 {579		d.firstPCFrames = len(d.frames)580		d.firstPCSymbolizeResult = symbolizeResult581	}582	return true583}584585// emitLocation emits the new location and function information recorded in the deck586// and returns the location ID encoded in the profile protobuf.587// It emits to b.pb, so there must be no message encoding in progress.588// It resets the deck.589func (b *profileBuilder) emitLocation() uint64 {590	if len(b.deck.pcs) == 0 {591		return 0592	}593	defer b.deck.reset()594595	addr := b.deck.pcs[0]596	firstFrame := b.deck.frames[0]597598	// We can't write out functions while in the middle of the599	// Location message, so record new functions we encounter and600	// write them out after the Location.601	type newFunc struct {602		id         uint64603		name, file string604		startLine  int64605	}606	newFuncs := make([]newFunc, 0, 8)607608	id := uint64(len(b.locs)) + 1609	b.locs[addr] = locInfo{610		id:                     id,611		pcs:                    append([]uintptr{}, b.deck.pcs...),612		firstPCSymbolizeResult: b.deck.firstPCSymbolizeResult,613		firstPCFrames:          append([]runtime.Frame{}, b.deck.frames[:b.deck.firstPCFrames]...),614	}615616	start := b.pb.startMessage()617	b.pb.uint64Opt(tagLocation_ID, id)618	b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC))619	for _, frame := range b.deck.frames {620		// Write out each line in frame expansion.621		funcName := runtime_FrameSymbolName(&frame)622		funcID := uint64(b.funcs[funcName])623		if funcID == 0 {624			funcID = uint64(len(b.funcs)) + 1625			b.funcs[funcName] = int(funcID)626			newFuncs = append(newFuncs, newFunc{627				id:        funcID,628				name:      funcName,629				file:      frame.File,630				startLine: int64(runtime_FrameStartLine(&frame)),631			})632		}633		b.pbLine(tagLocation_Line, funcID, int64(frame.Line))634	}635	for i := range b.mem {636		if b.mem[i].start <= addr && addr < b.mem[i].end || b.mem[i].fake {637			b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1))638639			m := b.mem[i]640			m.funcs |= b.deck.symbolizeResult641			b.mem[i] = m642			break643		}644	}645	b.pb.endMessage(tagProfile_Location, start)646647	// Write out functions we found during frame expansion.648	for _, fn := range newFuncs {649		start := b.pb.startMessage()650		b.pb.uint64Opt(tagFunction_ID, fn.id)651		b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name))652		b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name))653		b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file))654		b.pb.int64Opt(tagFunction_StartLine, fn.startLine)655		b.pb.endMessage(tagProfile_Function, start)656	}657658	b.flush()659	return id660}661662var space = []byte(" ")663var newline = []byte("\n")664665func parseProcSelfMaps(data []byte, addMapping func(lo, hi, offset uint64, file, buildID string)) {666	// $ cat /proc/self/maps667	// 00400000-0040b000 r-xp 00000000 fc:01 787766                             /bin/cat668	// 0060a000-0060b000 r--p 0000a000 fc:01 787766                             /bin/cat669	// 0060b000-0060c000 rw-p 0000b000 fc:01 787766                             /bin/cat670	// 014ab000-014cc000 rw-p 00000000 00:00 0                                  [heap]671	// 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064                    /usr/lib/locale/locale-archive672	// 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so673	// 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so674	// 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so675	// 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so676	// 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0677	// 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so678	// 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0679	// 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0680	// 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so681	// 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so682	// 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0683	// 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0                          [stack]684	// 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0                          [vdso]685	// ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]686687	var line []byte688	// next removes and returns the next field in the line.689	// It also removes from line any spaces following the field.690	next := func() []byte {691		var f []byte692		f, line, _ = bytes.Cut(line, space)693		line = bytes.TrimLeft(line, " ")694		return f695	}696697	for len(data) > 0 {698		line, data, _ = bytes.Cut(data, newline)699		addr := next()700		loStr, hiStr, ok := strings.Cut(string(addr), "-")701		if !ok {702			continue703		}704		lo, err := strconv.ParseUint(loStr, 16, 64)705		if err != nil {706			continue707		}708		hi, err := strconv.ParseUint(hiStr, 16, 64)709		if err != nil {710			continue711		}712		perm := next()713		if len(perm) < 4 || perm[2] != 'x' {714			// Only interested in executable mappings.715			continue716		}717		offset, err := strconv.ParseUint(string(next()), 16, 64)718		if err != nil {719			continue720		}721		next()          // dev722		inode := next() // inode723		if line == nil {724			continue725		}726		file := string(line)727728		// Trim deleted file marker.729		deletedStr := " (deleted)"730		deletedLen := len(deletedStr)731		if len(file) >= deletedLen && file[len(file)-deletedLen:] == deletedStr {732			file = file[:len(file)-deletedLen]733		}734735		if len(inode) == 1 && inode[0] == '0' && file == "" {736			// Huge-page text mappings list the initial fragment of737			// mapped but unpopulated memory as being inode 0.738			// Don't report that part.739			// But [vdso] and [vsyscall] are inode 0, so let non-empty file names through.740			continue741		}742743		// TODO: pprof's remapMappingIDs makes one adjustment:744		// 1. If there is an /anon_hugepage mapping first and it is745		// consecutive to a next mapping, drop the /anon_hugepage.746		// There's no indication why this is needed.747		// Let's try not doing this and see what breaks.748		// If we do need it, it would go here, before we749		// enter the mappings into b.mem in the first place.750751		buildID, _ := elfBuildID(file)752		addMapping(lo, hi, offset, file, buildID)753	}754}755756func (b *profileBuilder) addMapping(lo, hi, offset uint64, file, buildID string) {757	b.addMappingEntry(lo, hi, offset, file, buildID, false)758}759760func (b *profileBuilder) addMappingEntry(lo, hi, offset uint64, file, buildID string, fake bool) {761	b.mem = append(b.mem, memMap{762		start:   uintptr(lo),763		end:     uintptr(hi),764		offset:  offset,765		file:    file,766		buildID: buildID,767		fake:    fake,768	})769}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.