简化移植google profiler

本文介绍了一个简单的性能剖析工具实现方案,通过周期性采样获取调用堆栈信息,并记录到文件中供进一步分析。该工具利用了信号处理机制来触发采样过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

调用ProfilerStart和ProfilerStop就可生成perf文件,然后用pprof解析就行

.h

extern void StartProfiler(const char* fname);
extern void StopProfiler();
extern void ProfilerSignalHandler(int sig, siginfo_t* sinfo, void* ucontext);

class ProfileData 
{
public:    
    static const int kMaxStackDepth = 64;  // Max stack depth stored in profile

    ProfileData();
    ~ProfileData();

    // If data collection is not already enabled start to collect data
    // into fname.  Parameters related to this profiling run are specified
    // by 'options'.
    //
    // Returns true if data collection could be started, otherwise (if an
    // error occurred or if data collection was already enabled) returns
    // false.
    bool Start(const char *fname);

    // If data collection is enabled, stop data collection and write the
    // data to disk.
    void Stop();

    // Stop data collection without writing anything else to disk, and
    // discard any collected data.
    void Reset();

    // If data collection is enabled, record a sample with 'depth'
    // entries from 'stack'.  (depth must be > 0.)  At most
    // kMaxStackDepth stack entries will be recorded, starting with
    // stack[0].
    //
    // This function is safe to call from asynchronous signals (but is
    // not re-entrant).
    void Add(int depth, const void* const* stack);

    // Is data collection currently enabled?
    bool enabled() const { return out_ >= 0; }

private:
    static const int kAssociativity = 4;          // For hashtable
    static const int kBuckets = 1 << 10;          // For hashtable
    static const int kBufferLength = 1 << 18;     // For eviction buffer

    // Type of slots: each slot can be either a count, or a PC value
    typedef uintptr_t Slot;

    // Hash-table/eviction-buffer entry (a.k.a. a sample)
    struct Entry 
    {
        Slot count;                  // Number of hits
        Slot depth;                  // Stack depth
        Slot stack[kMaxStackDepth];  // Stack contents
    };

    // Hash table bucket
    struct Bucket 
    {
        Entry entry[kAssociativity];
    };

    Bucket*       hash_;          // hash table
    Slot*         evict_;         // evicted entries
    int           num_evicted_;   // how many evicted entries?
    int           out_;           // fd for output file.
    int           count_;         // How many samples recorded
    int           evictions_;     // How many evictions
    size_t        total_bytes_;   // How much output
    char*         fname_;         // Profile file name
    time_t        start_time_;    // Start time, or 0

    // Move 'entry' to the eviction buffer.
    void Evict(const Entry& entry);

    // Write contents of eviction buffer to disk.
    void FlushEvicted();

};

class ProcMapsIterator 
{
public:
    struct Buffer 
    {
        static const size_t kBufSize = PATH_MAX + 1024;
        char buf_[kBufSize];
    };

    // Create an iterator with specified storage (for use in signal
    // handler). "buffer" should point to a ProcMapsIterator::Buffer
    // buffer can be NULL in which case a bufer will be allocated.
    ProcMapsIterator(pid_t pid, Buffer *buffer);

    // Writes the "canonical" form of the /proc/xxx/maps info for a single
    // line to the passed-in buffer. Returns the number of bytes written,
    // or 0 if it was not able to write the complete line.  (To guarantee
    // success, buffer should have size at least Buffer::kBufSize.)
    // Takes as arguments values set via a call to Next().  The
    // "canonical" form of the line (taken from linux's /proc/xxx/maps):
    //    <start_addr(hex)>-<end_addr(hex)> <perms(rwxp)> <offset(hex)>   +
    //    <major_dev(hex)>:<minor_dev(hex)> <inode> <filename> Note: the
    // eg
    //    08048000-0804c000 r-xp 00000000 03:01 3793678    /bin/cat
    // If you don't have the dev_t (dev), feel free to pass in 0.
    // (Next() doesn't return a dev_t, though NextExt does.)
    //
    // Note: if filename and flags were obtained via a call to Next(),
    // then the output of this function is only valid if Next() returned
    // true, and only until the iterator is destroyed or Next() is
    // called again.  (Since filename, at least, points into CurrentLine.)
    static int FormatLine(char* buffer, int bufsize,
                        uint64_t start, uint64_t end, const char *flags,
                        uint64_t offset, int64 inode, const char *filename,
                        dev_t dev);

    // Find the next entry in /proc/maps; return true if found or false
    // if at the end of the file.
    //
    // Any of the result pointers can be NULL if you're not interested
    // in those values.
    //
    // If "flags" and "filename" are passed, they end up pointing to
    // storage within the ProcMapsIterator that is valid only until the
    // iterator is destroyed or Next() is called again. The caller may
    // modify the contents of these strings (up as far as the first NUL,
    // and only until the subsequent call to Next()) if desired.

    // The offsets are all uint64_t in order to handle the case of a
    // 32-bit process running on a 64-bit kernel
    //
    // IMPORTANT NOTE: see top-of-class notes for details about what
    // mapped regions Next() iterates over, depending on O/S.
    // TODO(csilvers): make flags and filename const.
    bool Next(uint64_t *start, uint64_t *end, char **flags,
            uint64_t *offset, int64 *inode, char **filename);

    bool NextExt(uint64_t *start, uint64_t *end, char **flags,
               uint64_t *offset, int64 *inode, char **filename,
               uint64_t *file_mapping, uint64_t *file_pages,
               uint64_t *anon_mapping, uint64_t *anon_pages,
               dev_t *dev);

    ~ProcMapsIterator();

private:
    void Init(pid_t pid, Buffer *buffer, bool use_maps_backing);

    char *ibuf_;        // input buffer
    char *stext_;       // start of text
    char *etext_;       // end of text
    char *nextline_;    // start of next line
    char *ebuf_;        // end of buffer (1 char for a nul)

    int fd_;            // filehandle on /proc/*/maps

    pid_t pid_;
    char flags_[10];
    Buffer* dynamic_buffer_;  // dynamically-allocated Buffer
    bool using_maps_backing_; // true if we are looking at maps_backing instead of maps.
};

.cpp

ProfileData g_ProfileData;

void StartProfiler(const char* fname)
{
    g_ProfileData.Start(fname);

    // 定时器
    struct itimerval timer;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 1000000 / 100; // 0.01秒
    timer.it_value = timer.it_interval;
    setitimer(ITIMER_PROF, &timer, 0);

    // 回调
    struct sigaction sa;
    sa.sa_sigaction = ProfilerSignalHandler;
    sa.sa_flags = SA_RESTART | SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    const int signal_number = SIGPROF;
    sigaction(signal_number, &sa, NULL);
}

void StopProfiler()
{
    // 定时器
    struct itimerval timer;
    memset(&timer, 0, sizeof timer);
    setitimer(ITIMER_PROF, &timer, 0);
  
    // 回调
    struct sigaction sa;
    sa.sa_handler = SIG_IGN;
    sa.sa_flags = SA_RESTART;
    sigemptyset(&sa.sa_mask);
    const int signal_number = SIGPROF;
    sigaction(signal_number, &sa, NULL);

    g_ProfileData.Stop();
}

void ProfileData::Add(int depth, const void* const* stack) 
{
    if (!enabled()) 
    {
        return;
    }
    
    if (depth > kMaxStackDepth) 
    {
        depth = kMaxStackDepth;
    }

    // Make hash-value
    Slot h = 0;
    for (int i = 0; i < depth; i++) 
    {
        Slot slot = reinterpret_cast<Slot>(stack[i]);
        h = (h << 8) | (h >> (8*(sizeof(h)-1)));
        h += (slot * 31) + (slot * 7) + (slot * 3);
    }

    count_++;

    // See if table already has an entry for this trace
    bool done = false;
    Bucket* bucket = &hash_[h % kBuckets];
    for (int a = 0; a < kAssociativity; a++) 
    {
        Entry* e = &bucket->entry[a];
        if ((int)e->depth == depth) 
        {
            bool match = true;
            for (int i = 0; i < depth; i++) 
            {
                if (e->stack[i] != reinterpret_cast<Slot>(stack[i])) 
                {
                    match = false;
                    break;
                }
            }
            if (match) 
            {
                e->count++;
                done = true;
                break;
            }
        }
    }

    if (!done) 
    {
        // Evict entry with smallest count
        Entry* e = &bucket->entry[0];
        for (int a = 1; a < kAssociativity; a++) 
        {
            if (bucket->entry[a].count < e->count) 
            {
                e = &bucket->entry[a];
            }
        }
        if (e->count > 0) 
        {
            evictions_++;
            Evict(*e);
        }

        // Use the newly evicted entry
        e->depth = depth;
        e->count = 1;
        for (int i = 0; i < depth; i++) 
        {
            e->stack[i] = reinterpret_cast<Slot>(stack[i]);
        }
    }
}

void ProfileData::Evict(const Entry& entry) 
{
    const int d = entry.depth;
    const int nslots = d + 2;     // Number of slots needed in eviction buffer
    if (num_evicted_ + nslots > kBufferLength) 
    {
        FlushEvicted();
        assert(num_evicted_ == 0);
        assert(nslots <= kBufferLength);
    }
    evict_[num_evicted_++] = entry.count;
    evict_[num_evicted_++] = d;
    memcpy(&evict_[num_evicted_], entry.stack, d * sizeof(Slot));
    num_evicted_ += d;
}

static void FDWrite(int fd, const char* buf, size_t len) 
{
    while (len > 0) 
    {
        ssize_t r;
        r = write(fd, buf, len);
        if (r == -1)
        {
            return;
        }
        buf += r;
        len -= r;
    }
}

static void DumpProcSelfMaps(int fd) 
{
    ProcMapsIterator::Buffer iterbuf;
    ProcMapsIterator it(0, &iterbuf);   // 0 means "current pid"

    uint64_t start, end, offset;
    int64 inode;
    char *flags, *filename;
    ProcMapsIterator::Buffer linebuf;
    while (it.Next(&start, &end, &flags, &offset, &inode, &filename)) 
    {
        int written = it.FormatLine(linebuf.buf_, sizeof(linebuf.buf_),
                                    start, end, flags, offset, inode, filename,
                                    0);
        FDWrite(fd, linebuf.buf_, written);
    }
}

void ProfileData::FlushEvicted() 
{
    if (num_evicted_ > 0) 
    {
        const char* buf = reinterpret_cast<char*>(evict_);
        size_t bytes = sizeof(evict_[0]) * num_evicted_;
        total_bytes_ += bytes;
        FDWrite(out_, buf, bytes);
    }
    num_evicted_ = 0;
}

void ProfileData::Stop() 
{
    if (!enabled()) 
    {
        return;
    }

    // Move data from hash table to eviction buffer
    for (int b = 0; b < kBuckets; b++) 
    {
        Bucket* bucket = &hash_[b];
        for (int a = 0; a < kAssociativity; a++) 
        {
            if (bucket->entry[a].count > 0) 
            {
                Evict(bucket->entry[a]);
            }
        }
    }

    if (num_evicted_ + 3 > kBufferLength) 
    {
        // Ensure there is enough room for end of data marker
        FlushEvicted();
    }

    // Write end of data marker
    evict_[num_evicted_++] = 0;         // count
    evict_[num_evicted_++] = 1;         // depth
    evict_[num_evicted_++] = 0;         // end of data marker
    FlushEvicted();

    // Dump "/proc/self/maps" so we get list of mapped shared libraries
    DumpProcSelfMaps(out_);

    Reset();
    fprintf(stderr, "PROFILE: interrupts/evictions/bytes = %d/%d/%zu\n",
          count_, evictions_, total_bytes_);
}

void ProfileData::Reset() 
{
    if (!enabled()) 
    {
        return;
    }

    // Don't reset count_, evictions_, or total_bytes_ here.  They're used
    // by Stop to print information about the profile after reset, and are
    // cleared by Start when starting a new profile.
    close(out_);
    delete[] hash_;
    hash_ = 0;
    delete[] evict_;
    evict_ = 0;
    num_evicted_ = 0;
    free(fname_);
    fname_ = 0;
    start_time_ = 0;

    out_ = -1;
}

ProfileData::ProfileData()
    : hash_(0),
      evict_(0),
      num_evicted_(0),
      out_(-1),
      count_(0),
      evictions_(0),
      total_bytes_(0),
      fname_(0),
      start_time_(0) 
{
}

ProfileData::~ProfileData() 
{
    Stop();
}

bool ProfileData::Start(const char* fname) 
{
    if (enabled()) 
    {
        return false;
    }
  
    // Open output file and initialize various data structures
    int fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if (fd < 0) 
    {
        // Can't open outfile for write
        return false;
    }

    start_time_ = time(NULL);
    fname_ = strdup(fname);

    // Reset counters
    num_evicted_ = 0;
    count_       = 0;
    evictions_   = 0;
    total_bytes_ = 0;

    hash_ = new Bucket[kBuckets];
    evict_ = new Slot[kBufferLength];
    memset(hash_, 0, sizeof(hash_[0]) * kBuckets);

    // Record special entries
    evict_[num_evicted_++] = 0;                     // count for header
    evict_[num_evicted_++] = 3;                     // depth for header
    evict_[num_evicted_++] = 0;                     // Version number
    
    int period = 1000000 / 100;
    evict_[num_evicted_++] = period;                // Period (microseconds)
    evict_[num_evicted_++] = 0;                     // Padding

    out_ = fd;

    return true;
}

void ProfilerSignalHandler(int sig, siginfo_t* sinfo, void* ucontext)
{
    const int maxLevel = 200;    
    void* buffer[maxLevel];    
    int level = backtrace(buffer, maxLevel);

    g_ProfileData.Add(level - 2, buffer + 2);
}

static void ConstructFilename(const char* spec, pid_t pid, char* buf, int buf_size)
{
    snprintf(buf, buf_size, spec, static_cast<int>(pid ? pid : getpid()));
}

ProcMapsIterator::ProcMapsIterator(pid_t pid, Buffer *buffer) 
{
    Init(pid, buffer, false);
}

void ProcMapsIterator::Init(pid_t pid, Buffer *buffer, bool use_maps_backing) 
{
    pid_ = pid;
    using_maps_backing_ = use_maps_backing;
    dynamic_buffer_ = NULL;
    if (!buffer) 
    {
        // If the user didn't pass in any buffer storage, allocate it
        // now. This is the normal case; the signal handler passes in a
        // static buffer.
        buffer = dynamic_buffer_ = new Buffer;
    } 
    else 
    {
        dynamic_buffer_ = NULL;
    }

    ibuf_ = buffer->buf_;

    stext_ = etext_ = nextline_ = ibuf_;
    ebuf_ = ibuf_ + Buffer::kBufSize - 1;
    nextline_ = ibuf_;

    if (use_maps_backing) 
    {  // don't bother with clever "self" stuff in this case
        ConstructFilename("/proc/%d/maps_backing", pid, ibuf_, Buffer::kBufSize);
    } 
    else if (pid == 0) 
    {
        // We have to kludge a bit to deal with the args ConstructFilename
        // expects.  The 1 is never used -- it's only impt. that it's not 0.
        ConstructFilename("/proc/self/maps", 1, ibuf_, Buffer::kBufSize);
    } 
    else 
    {
        ConstructFilename("/proc/%d/maps", pid, ibuf_, Buffer::kBufSize);
    }
    
    // No error logging since this can be called from the crash dump
    // handler at awkward moments. Users should call Valid() before
    // using.
    fd_ = open(ibuf_, O_RDONLY);
    
}

ProcMapsIterator::~ProcMapsIterator() 
{
    if (fd_ >= 0) 
    {
        close(fd_);
    }
    delete dynamic_buffer_;
}

bool ProcMapsIterator::Next(uint64_t *start, uint64_t *end, char **flags, uint64_t *offset, int64 *inode, char **filename) 
{
    return NextExt(start, end, flags, offset, inode, filename, NULL, NULL, NULL, NULL, NULL);
}


// Finds |c| in |text|, and assign '\0' at the found position.
// The original character at the modified position should be |c|.
// A pointer to the modified position is stored in |endptr|.
// |endptr| should not be NULL.
static bool ExtractUntilChar(char *text, int c, char **endptr) 
{
    char *found;
    found = strchr(text, c);
    if (found == NULL) 
    {
        *endptr = NULL;
        return false;
    }

    *endptr = found;
    *found = '\0';
    return true;
}

template<class T>
static T StringToInteger(char *text, char **endptr, int base) 
{
    assert(false);
    return T();
}

template<>
int StringToInteger<int>(char *text, char **endptr, int base) 
{
    return strtol(text, endptr, base);
}

template<>
int64 StringToInteger<int64>(char *text, char **endptr, int base) 
{
    return strtoll(text, endptr, base);
}

template<>
uint64_t StringToInteger<uint64_t>(char *text, char **endptr, int base) 
{
    return strtoull(text, endptr, base);
}

// Increments |*text_pointer| while it points a whitespace character.
// It is to follow sscanf's whilespace handling.
static void SkipWhileWhitespace(char **text_pointer, int c) 
{
    if (isspace(c)) 
    {
        while (isspace(**text_pointer) && isspace(*((*text_pointer) + 1))) 
        {
            ++(*text_pointer);
        }
    }
}

template<typename T>
static T StringToIntegerUntilChar(char *text, int base, int c, char **endptr_result) 
{
    *endptr_result = NULL;

    char *endptr_extract;
    if (!ExtractUntilChar(text, c, &endptr_extract))
        return 0;

    T result;
    char *endptr_strto;
    result = StringToInteger<T>(text, &endptr_strto, base);
    *endptr_extract = c;

    if (endptr_extract != endptr_strto)
        return 0;

    *endptr_result = endptr_extract;
    SkipWhileWhitespace(endptr_result, c);

    return result;
}

template<typename T>
static bool StringToIntegerUntilCharWithCheck(T *outptr, char *text, int base, int c, char **endptr) 
{
    *outptr = StringToIntegerUntilChar<T>(*endptr, base, c, endptr);
    if (*endptr == NULL || **endptr == '\0') return false;
        ++(*endptr);
    return true;
}

static char *CopyStringUntilChar(char *text, unsigned out_len, int c, char *out)
{
    char *endptr;
    if (!ExtractUntilChar(text, c, &endptr))
        return NULL;

    strncpy(out, text, out_len);
    out[out_len-1] = '\0';
    *endptr = c;

    SkipWhileWhitespace(&endptr, c);
    return endptr;
}

static bool ParseProcMapsLine(char *text, uint64_t *start, uint64_t *end,
                              char *flags, uint64_t *offset,
                              int *major, int *minor, int64 *inode,
                              unsigned *filename_offset) 
{
  /*
   * It's similar to:
   * sscanf(text, "%"SCNx64"-%"SCNx64" %4s %"SCNx64" %x:%x %"SCNd64" %n",
   *        start, end, flags, offset, major, minor, inode, filename_offset)
   */
  char *endptr = text;
  if (endptr == NULL || *endptr == '\0')  return false;

  if (!StringToIntegerUntilCharWithCheck(start, endptr, 16, '-', &endptr))
    return false;

  if (!StringToIntegerUntilCharWithCheck(end, endptr, 16, ' ', &endptr))
    return false;

  endptr = CopyStringUntilChar(endptr, 5, ' ', flags);
  if (endptr == NULL || *endptr == '\0')  return false;
  ++endptr;

  if (!StringToIntegerUntilCharWithCheck(offset, endptr, 16, ' ', &endptr))
    return false;

  if (!StringToIntegerUntilCharWithCheck(major, endptr, 16, ':', &endptr))
    return false;

  if (!StringToIntegerUntilCharWithCheck(minor, endptr, 16, ' ', &endptr))
    return false;

  if (!StringToIntegerUntilCharWithCheck(inode, endptr, 10, ' ', &endptr))
    return false;

  *filename_offset = (endptr - text);
  return true;
}

// This has too many arguments.  It should really be building
// a map object and returning it.  The problem is that this is called
// when the memory allocator state is undefined, hence the arguments.
bool ProcMapsIterator::NextExt(uint64_t *start, uint64_t *end, char **flags,
                               uint64_t *offset, int64 *inode, char **filename,
                               uint64_t *file_mapping, uint64_t *file_pages,
                               uint64_t *anon_mapping, uint64_t *anon_pages,
                               dev_t *dev) 
{
    do 
    {
        // Advance to the start of the next line
        stext_ = nextline_;

        // See if we have a complete line in the buffer already
        nextline_ = static_cast<char *>(memchr (stext_, '\n', etext_ - stext_));
        if (!nextline_) 
        {
            // Shift/fill the buffer so we do have a line
            int count = etext_ - stext_;

            // Move the current text to the start of the buffer
            memmove(ibuf_, stext_, count);
            stext_ = ibuf_;
            etext_ = ibuf_ + count;

            int nread = 0;            // fill up buffer with text
            while (etext_ < ebuf_) 
            {
                nread = read(fd_, etext_, ebuf_ - etext_);
                if (nread > 0)
                    etext_ += nread;
                else
                    break;
            }

            // Zero out remaining characters in buffer at EOF to avoid returning
            // garbage from subsequent calls.
            if (etext_ != ebuf_ && nread == 0) 
            {
                memset(etext_, 0, ebuf_ - etext_);
            }
            *etext_ = '\n';   // sentinel; safe because ibuf extends 1 char beyond ebuf
            nextline_ = static_cast<char *>(memchr (stext_, '\n', etext_ + 1 - stext_));
        }
        *nextline_ = 0;                // turn newline into nul
        nextline_ += ((nextline_ < etext_)? 1 : 0);  // skip nul if not end of text
        // stext_ now points at a nul-terminated line
        uint64_t tmpstart, tmpend, tmpoffset;
        int64 tmpinode;
        int major, minor;
        unsigned filename_offset = 0;

        // for now, assume all linuxes have the same format
        if (!ParseProcMapsLine(
            stext_,
            start ? start : &tmpstart,
            end ? end : &tmpend,
            flags_,
            offset ? offset : &tmpoffset,
            &major, &minor,
            inode ? inode : &tmpinode, &filename_offset)) continue;

        // Depending on the Linux kernel being used, there may or may not be a space
        // after the inode if there is no filename.  sscanf will in such situations
        // nondeterministically either fill in filename_offset or not (the results
        // differ on multiple calls in the same run even with identical arguments).
        // We don't want to wander off somewhere beyond the end of the string.
        size_t stext_length = strlen(stext_);
        if (filename_offset == 0 || filename_offset > stext_length)
            filename_offset = stext_length;

        // We found an entry
        if (flags) *flags = flags_;
        if (filename) *filename = stext_ + filename_offset;
        if (dev) *dev = minor | (major << 8);

        if (using_maps_backing_) 
        {
        // Extract and parse physical page backing info.
        char *backing_ptr = stext_ + filename_offset + strlen(stext_+filename_offset);

        // find the second '('
        int paren_count = 0;
        while (--backing_ptr > stext_) 
        {
            if (*backing_ptr == '(') 
            {
                ++paren_count;
                if (paren_count >= 2) 
                {
                    uint64_t tmp_file_mapping;
                    uint64_t tmp_file_pages;
                    uint64_t tmp_anon_mapping;
                    uint64_t tmp_anon_pages;

                    sscanf(backing_ptr+1, "F %" "llx" " %" "lld" ") (A %" "llx" " %" "lld" ")",
                           (long long unsigned int*)(file_mapping ? file_mapping : &tmp_file_mapping),
                           (long long int*)(file_pages ? file_pages : &tmp_file_pages),
                           (long long unsigned int*)(anon_mapping ? anon_mapping : &tmp_anon_mapping),
                           (long long int*)(anon_pages ? anon_pages : &tmp_anon_pages));
                    // null terminate the file name (there is a space
                    // before the first (.
                    backing_ptr[-1] = 0;
                    break;
                }
            }
        }
    }

    return true;
  } while (etext_ > ibuf_);

  // We didn't find anything
  return false;
}

int ProcMapsIterator::FormatLine(char* buffer, int bufsize,
                                 uint64_t start, uint64_t end, const char *flags,
                                 uint64_t offset, int64 inode,
                                 const char *filename, dev_t dev) 
{
    // We assume 'flags' looks like 'rwxp' or 'rwx'.
    char r = (flags && flags[0] == 'r') ? 'r' : '-';
    char w = (flags && flags[0] && flags[1] == 'w') ? 'w' : '-';
    char x = (flags && flags[0] && flags[1] && flags[2] == 'x') ? 'x' : '-';
    // p always seems set on linux, so we set the default to 'p', not '-'
    char p = (flags && flags[0] && flags[1] && flags[2] && flags[3] != 'p')
      ? '-' : 'p';

    const int rc = snprintf(buffer, bufsize,
        "%08" "llx" "-%08" "llx" " %c%c%c%c %08" "llx" " %02x:%02x %-11" "lld" " %s\n",
        (long long int)start, (long long int)end, r,w,x,p, (long long int)offset,
        static_cast<int>(dev/256), static_cast<int>(dev%256),
        (long long int)inode, filename);
        
    return (rc < 0 || rc >= bufsize) ? 0 : rc;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值