无垠之码

深度剖析代码之道


valgrind高级主题

客户端请求机制


特殊宏定义

Valgrind特殊宏,依据相关功能分别定义于valgrind.h、memcheck.h、callgrind.h、helgrind.h头文件中。

注意:

  1. VALGRIND_CLO_CHANGE宏提交于3a803036f719,此前版本的Valgrind不支持

    valgrind –help-dyn-options: dynamically changeable options: -v –verbose -q –quiet -d –stats –vgdb=no –vgdb=yes –vgdb=full –vgdb-poll –vgdb-error –vgdb-stop-at –error-markers –show-error-list -s –show-below-main –time-stamp –trace-children –child-silent-after-fork –scheduling-quantum –trace-sched –trace-signals –trace-symtab –trace-cfi –debug-dump=syms –debug-dump=line –debug-dump=frames –trace-redir –trace-syscalls –sym-offsets –progress-interval –merge-recursive-frames –vex-iropt-verbosity –suppressions –trace-flags –trace-notbelow –trace-notabove –profile-flags –gen-suppressions=no –gen-suppressions=yes –gen-suppressions=all –errors-for-leak-kinds –show-leak-kinds –leak-check-heuristics –show-reachable –show-possibly-lost –freelist-vol –freelist-big-blocks –leak-check=no –leak-check=summary –leak-check=yes –leak-check=full –ignore-ranges –ignore-range-below-sp –show-mismatched-frees –show-realloc-size-zero

  2. Valgrind错误分类

    • Illegal read / Illegal write errors
    • Use of uninitialised values
    • Use of uninitialised or unaddressable values in system calls
    • Illegal frees
    • When a heap block is freed with an inappropriate deallocation function
    • Overlapping source and destination blocks
    • Fishy argument values
    • Realloc size zero
    • Memory leak detection(a leak is only counted as a true error if –leak-check=full is specified)

valgrind.h相关宏

  • RUNNING_ON_VALGRIND: 判断环境是否运行于Valgrind模拟的CPU环境,返回Valgrind嵌套层数。
  • VALGRIND_DISCARD_TRANSLATIONS:
  • VALGRIND_COUNT_ERRORS: 返回Valgrind发现的错误数目。
  • VALGRIND_MALLOCLIKE_BLOCK: 如果程序的内存管理非标准库函数malloc、new、new[], valgrind memcheck内存追踪将失效。使用该宏追踪非标准分配器分配的内存。
  • VALGRIND_FREELIKE_BLOCK:
  • VALGRIND_RESIZEINPLACE_BLOCK: 通知Valgrind工具分配的块的大小已被修改,但其地址没有变化。
  • VALGRIND_CREATE_MEMPOOL, VALGRIND_DESTROY_MEMPOOL, VALGRIND_MEMPOOL_ALLOC, VALGRIND_MEMPOOL_FREE, VALGRIND_MOVE_MEMPOOL, VALGRIND_MEMPOOL_CHANGE, VALGRIND_MEMPOOL_EXISTS
  • VALGRIND_NON_SIMD_CALL: 命令Valgrind在CPU上执行指定函数。函数支持0-3个参数。
  • VALGRIND_PRINTF: 打印printf风格的消息至Valgrind日志文件。消息使用**pid**作为消息前缀。
  • VALGRIND_PRINTF_BACKTRACE: VALGRIND_PRINTF类似,但在打印消息后立即打印堆栈回溯信息。
  • VALGRIND_MONITOR_COMMAND: 执行Valgrind监控指令。指令有效返回0,否则返回-1。
  • VALGRIND_CLO_CHANGE: 程序中动态改变Valgrind部分参数。(新版本支持)
  • VALGRIND_STACK_DEREGISTER:
  • VALGRIND_STACK_CHANGE: 通知Valgrind之前注册过的堆栈发生改变。
  • VALGRIND_STACK_REGISTER: 通知Valgrind参数start和end之间是唯一的栈空间,返回的标识符作为其他VALGRIND_STACK_*函数的参数使用。Valgrind使用该信息来确定堆栈指针的更改是由于函数调用还是线换,如果堆栈指针的更改发生在注册的堆栈内,则认为是函数调用。如果堆栈指针的更改发生在未注册的堆栈内,则Valgrind将认为该更改是切换到新线程。Valgrind本身是基于操作系统的内存调试工具,可能对协程的堆栈存在一些困难或不完整。这个选项可以帮助Valgrind更准确地识别堆栈信息,从而改善Valgrind对于用户级线程包情境下的堆栈跟踪和错误报告的准确性。(Unfortunately, this client request is unreliable and best avoided)

memcheck.h相关宏

  • VALGRIND_MAKE_MEM_NOACCESS:
  • VALGRIND_MAKE_MEM_UNDEFINED:
  • VALGRIND_MAKE_MEM_DEFINED:
  • VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE:
  • VALGRIND_CREATE_BLOCK:
  • VALGRIND_DISCARD:
  • VALGRIND_CHECK_MEM_IS_ADDRESSABLE:
  • VALGRIND_CHECK_MEM_IS_DEFINED:
  • VALGRIND_CHECK_VALUE_IS_DEFINED:
  • VALGRIND_DO_LEAK_CHECK:
  • VALGRIND_DO_ADDED_LEAK_CHECK:
  • VALGRIND_DO_CHANGED_LEAK_CHECK:
  • VALGRIND_DO_QUICK_LEAK_CHECK:
  • VALGRIND_COUNT_LEAKS:
  • VALGRIND_COUNT_LEAK_BLOCKS:
  • VALGRIND_GET_VBITS:
  • VALGRIND_SET_VBITS:
  • VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE:
  • VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE:

callgrind相关宏

  • CALLGRIND_DUMP_STATS
  • CALLGRIND_DUMP_STATS_AT
  • CALLGRIND_ZERO_STATS
  • CALLGRIND_TOGGLE_COLLECT
  • CALLGRIND_START_INSTRUMENTATION
  • CALLGRIND_STOP_INSTRUMENTATION

helgrind.h相关宏

  • VALGRIND_HG_MUTEX_INIT_POST
  • VALGRIND_HG_MUTEX_LOCK_PRE
  • VALGRIND_HG_MUTEX_LOCK_POST
  • VALGRIND_HG_MUTEX_UNLOCK_PRE
  • VALGRIND_HG_MUTEX_UNLOCK_POST
  • VALGRIND_HG_MUTEX_DESTROY_PRE
  • VALGRIND_HG_SEM_INIT_POST
  • VALGRIND_HG_SEM_WAIT_POST
  • VALGRIND_HG_SEM_POST_PRE
  • VALGRIND_HG_SEM_DESTROY_PRE
  • VALGRIND_HG_BARRIER_INIT_PRE
  • VALGRIND_HG_BARRIER_WAIT_PRE
  • VALGRIND_HG_BARRIER_RESIZE_PRE
  • VALGRIND_HG_BARRIER_DESTROY_PRE
  • VALGRIND_HG_CLEAN_MEMORY
  • VALGRIND_HG_CLEAN_MEMORY_HEAPBLOCK
  • VALGRIND_HG_DISABLE_CHECKING
  • VALGRIND_HG_ENABLE_CHECKING
  • VALGRIND_HG_GET_ABITS
  • VALGRIND_HG_GNAT_DEPENDENT_MASTER_JOIN
  • ANNOTATE_CONDVAR_LOCK_WAIT
  • ANNOTATE_CONDVAR_WAIT
  • ANNOTATE_CONDVAR_SIGNAL
  • ANNOTATE_CONDVAR_SIGNAL_ALL
  • ANNOTATE_HAPPENS_BEFORE
  • ANNOTATE_HAPPENS_AFTER
  • ANNOTATE_HAPPENS_BEFORE_FORGET_ALL
  • ANNOTATE_PUBLISH_MEMORY_RANGE
  • ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX
  • ANNOTATE_NEW_MEMORY
  • ANNOTATE_PCQ_CREATE
  • ANNOTATE_PCQ_DESTROY
  • ANNOTATE_PCQ_PUT
  • ANNOTATE_PCQ_GET
  • ANNOTATE_BENIGN_RACE
  • ANNOTATE_BENIGN_RACE_SIZED
  • ANNOTATE_IGNORE_READS_BEGIN
  • ANNOTATE_IGNORE_READS_END
  • ANNOTATE_IGNORE_WRITES_BEGIN
  • ANNOTATE_IGNORE_WRITES_END
  • ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN
  • ANNOTATE_IGNORE_READS_AND_WRITES_END
  • ANNOTATE_TRACE_MEMORY
  • ANNOTATE_THREAD_NAME
  • ANNOTATE_RWLOCK_CREATE
  • ANNOTATE_RWLOCK_DESTROY
  • ANNOTATE_RWLOCK_ACQUIRED
  • ANNOTATE_RWLOCK_RELEASED
  • ANNOTATE_BARRIER_INIT
  • ANNOTATE_BARRIER_WAIT_BEFORE
  • ANNOTATE_BARRIER_WAIT_AFTER
  • ANNOTATE_BARRIER_DESTROY
  • ANNOTATE_EXPECT_RACE
  • ANNOTATE_NO_OP
  • ANNOTATE_FLUSH_STATE

用法举例

以下代码片段基本涵盖上述宏如何使用

valgrind.h宏举例

#include <stdlib.h>
#include <string.h>

#include "debug.h"
#include "memp.h"

void definitely_loss() { int* p = malloc(sizeof(int)); }

int main() {
    if (RUNNING_ON_VALGRIND) {
        VALGRIND_PRINTF("valgrind nested: %d\r\n", RUNNING_ON_VALGRIND);
            VALGRIND_CLO_CHANGE("--leak-check=full");
            VALGRIND_CLO_CHANGE("--show-error-list=yes");
    }

    memp_t* memp = pool_create(1 << 20);
    char* name = (char*)pool_alloc(memp, 64);
    int* age = malloc(sizeof(int));
    definitely_loss();
    pool_free(memp, name);
    strncpy(name, "peter", 64);
    pool_destroy(memp);

    if (RUNNING_ON_VALGRIND) {
        VALGRIND_DO_ADDED_LEAK_CHECK;
        if (VALGRIND_COUNT_ERRORS) VALGRIND_PRINTF_BACKTRACE("valgrind errors: %d\r\n", VALGRIND_COUNT_ERRORS);
    }

    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <ucontext.h>
#include <valgrind/valgrind.h>

#define STACK_SIZE   (8*1024)

int n_ucs = 1;
int max_switchs = 10;
int n_switchs = 0;
int tid = 0;

ucontext_t *ucs;
static ucontext_t engine_uc;

void func(int arg)
{
    printf("I am thread %d\n", (int) arg);

    while (n_switchs < max_switchs) {
        int c_tid = tid;
        int n_tid = (tid + 1) % n_ucs;
        n_switchs++;
        tid = n_tid;
        printf("%d ---> %d\n", c_tid, n_tid);
        swapcontext(&ucs[c_tid], &ucs[n_tid]);
    }
}

int main(int argc, char **argv)
{
    int i;
    n_ucs = atoi(argv[1]);
    max_switchs = atoi(argv[2]);
    ucs = malloc(sizeof(ucontext_t) * n_ucs);
    int* valgrind_ret = malloc(n_ucs*sizeof(int));

    for (i = 0; i < n_ucs; i++) {
        getcontext(&ucs[i]);
        void* mystack = malloc(STACK_SIZE);
        valgrind_ret[i] = VALGRIND_STACK_REGISTER(mystack, mystack + STACK_SIZE);
        ucs[i].uc_stack.ss_sp = mystack;
        ucs[i].uc_stack.ss_size = STACK_SIZE;
        ucs[i].uc_stack.ss_flags = 0;
        ucs[i].uc_link = &engine_uc;
        makecontext(&ucs[i], (void (*)())func, 1, i);
    }

    swapcontext(&engine_uc, &ucs[tid]);

    for (i = 0; i < n_ucs; i++) {
        VALGRIND_STACK_DEREGISTER(valgrind_ret[i]);
        free(ucs[i].uc_stack.ss_sp);
    }

    free(ucs);
    free(valgrind_ret);
    return 0;
}

memcheck.h宏举例

/* 摘自 Testing constant-timeness using Valgrind: case of the NSS library */
#include <valgrind/memcheck.h>

int compute(unsigned char secret[32]) {
    if (secret[0] == 0) {
        return 0;
    } else {
        return 1;
    }
}

int main(void) {
    unsigned char buf[32];
    for (int i = 0; i < 32; ++i)
        buf[i] = 0;
    VALGRIND_MAKE_MEM_UNDEFINED(buf, 32);
    compute(buf);
    return 0;
}

callgrind.h宏举例

helgrind.h宏举例

使用Valgrind内置gdbserver

使用Valgrind诊断或测试的程序时,程序并非运行于真实cpu环境。由此当使用Valgrind工具时,并不能直接使用GDB调试你的程序。 本节介绍如何使用Valgrind内置的GDB环境,达到在Valgrind下程序调试目的。Valgrind的内置GDB除提供正常GDB相关命令外,提供Vagrind内存诊断、缓存分析等相关功能。

一般情况下,调试程序和GDB位于相同机器。GDB使用系统调用获取被调试程序的相关信息。当被调试程序于GDB不处于一台机器时,GDB定义"GDB remote debugging", 获取被调试程序的断点,寄存器,内存相关信息。GDB-SERVER是"GDB remote debugging"协议的实现,当远程调试时远程主机必须运行GDB-SERVER。 Valgrind通过参数vgdb开启内置GDB-SERVER。GDB于GDB-SERVER通信方式不限于TCP/IP,串口,管道。 下面例子中vgdb是Valgrind提供的,建立Valgrind与GDB的管道的Helper程序。

用法举例

# a.本地调试
valgrind --vgdb=yes --vgdb-stop-at=startup /usr/bin/sleep 3600
gdb /usr/bin/sleep -ex 'target remote | vgdb'

# b.远程调试
valgrind --vgdb=yes --vgdb-stop-at=startup /usr/bin/sleep 3600
vgdb --port=1024
gdb -ex 'target remote 127.0.0.1:1024'

特殊命令

  1. memcheck相关命令
  2. callgrind相关命令
  3. massif相关命令
  4. helgrind相关命令

函数包裹


Valgrind允许对某些指定函数的调用进行拦截,并重定向到用户提供的另一个函数。这个“包装函数”可以执行任意操作,通常包括:

  1. 检查函数参数
  2. 调用原始函数
  3. 检查原始函数的返回结果

函数包装可以对任意数量的函数进行包装,在对某个API进行监控或检测时非常有用。

int add(int x, int y){
    return x + y ;
}

gcc -fPIC -shared -o libadd.so add.c
#include "add.h"
#include <stdio.h>

int main(){
   printf("%d\r\n", add(1,1));
   return 0;
}

gcc -g -O0 main.c -L. -ladd -Wl,-rpath=/tmp/test/
#include <stdio.h>
#include "valgrind/valgrind.h"

int I_WRAP_SONAME_FNNAME_ZU(NONE,add)(int x, int y)
{
   int    result;
   OrigFn fn;
   VALGRIND_GET_ORIG_FN(fn);
   printf("foo's wrapper: args %d %d\n", x, y);
   CALL_FN_W_WW(result, fn, x,y);
   printf("foo's wrapper: result %d\n", result);
   return result;
}

gcc -shared -fPIC -o add_wrapper.so add_wrapper.c
LD_PRELOAD=./add_wrapper.so valgrind ./a.out

Reference

  1. Valgrind-用户手册
  2. Valgrind-内存监控命令
  3. Valgrind-堆栈监控命令
  4. Valgrind-缓存监控命令
  5. Valgrind-调用监控命令
  6. 协程代码Valgrind示例
  7. Testing constant-timeness using Valgrind: case of the NSS library
  8. Automated dynamic analysis for timing side-channels
  9. 性能优化之vallgrind之callgrind分析瓶颈
comments powered by Disqus