Home > OS >  Valgrind ClientCheck uninitialized string
Valgrind ClientCheck uninitialized string

Time:04-03

I'm new to Valgrind, and I've had some trouble finding the source of some of it's warnings. I've been using the VALGRIND_CHECK_VALUE_IS_DEFINED macro from memcheck.h to try and locate the exact source of the error, which has led me to wonder if I am using the tool correctly.

Here is a sample program that I run with Valgrind:

#include <valgrind/memcheck.h>
    
int main() {
    std::string str("test");
    VALGRIND_CHECK_VALUE_IS_DEFINED(str);
    return 0;
}

Which results in the following warnings:

==9612== Uninitialised byte(s) found during client check request
==9612==    at 0x11EB45: main (main.cpp:5)
==9612==  Address 0x1ffefffd35 is on thread 1's stack
==9612==  in frame #0, created by main (main.cpp:3)
==9612==  Uninitialised value was created by a stack allocation
==9612==    at 0x11EA8E: main (main.cpp:3)

A very similar program:

#include <valgrind/memcheck.h>
    
int main() {
    int x = 0;
    VALGRIND_CHECK_VALUE_IS_DEFINED(x);
    return 0;
}

has no such issue. I am using the flag --track-origins=yes for line tracing, and compiling as c 17 using g 9.4.0 (although I received the same warning with clang 14.0.0) on Ubuntu 20.04 LTS.

Is this a mistake on my end, or is it an issue with Valgrind?

CodePudding user response:

The reason for this is fairly straightforward. An std::string roughly consists of a pointer, length and allocated_capacity members. On 64bit Unix-like platforms they are all 8 bytes. The "SSO" [Small String Optimization] will recycle (and extend) the allocated capacity member via a union to store the string if it is short enough.

Note that clang libc does things a bit differently (I think that the union there recycles the pointer and the capacity to allow larger "small" strings).

This means that sizeof(std::string) is 32. There are then two possibilities.

  1. If the string is not "short" then the pointer, length and allocated_capacity are all used and the last 8 bytes will be uninitialized.
  2. If the string is "short", then any spare capacity in the local_buf (16 bytes for libstdc ) will be uninitialized.

Consider the following code. I've used VALGRIND_GET_VBITS which will get a byte with bits set to zero for defined memory and set to 1 for undefined memory.

#include "valgrind/memcheck.h"
#include <string>
#include <iostream>

int main() {
    const auto size = sizeof(std::string);
    unsigned char bits[size];
    // SSO, local buf not filled
    std::string str("test");
    VALGRIND_CHECK_VALUE_IS_DEFINED(str);
    VALGRIND_GET_VBITS(&str, bits, size);
    for (int i = 0; i < size;   i)
       std::cout << "byte " << std::dec << i << " bits " << std::hex << static_cast<int>(bits[i]) << '\n';

    // SSO, local buf illed
    std::string str2("123456789012345");
    VALGRIND_CHECK_VALUE_IS_DEFINED(str2);
    VALGRIND_GET_VBITS(&str2, bits, size);
    for (int i = 0; i < size;   i)
       std::cout << "byte " << std::dec << i << " bits " << std::hex << static_cast<int>(bits[i]) << '\n';

    // not SSO
    std::string str3("12345678901234567890");
    VALGRIND_CHECK_VALUE_IS_DEFINED(str3);
    VALGRIND_GET_VBITS(&str3, bits, size);
    for (int i = 0; i < size;   i)
       std::cout << "byte " << std::dec << i << " bits " << std::hex << static_cast<int>(bits[i]) << '\n';
    return 0;
}

For str (used 4 bytes plus one for nul, so 5 bytes of the SSO buffer). The output after the "Uninitialized bytes" message is

byte 0 bits 0
[same 1 to 13]
byte 14 bits 0
byte 15 bits ff
[same 16 to 30]
byte 31 bits ff

So you can see bytes 15 to 31 are undefined. That's 11 bytes, which is what I expected. 16 bytes for the SSO local_buf, the first 5 bytes of which are initialized by "test\0".

For str2 there is no "Uninitialized bytes" message, and the VBITS are all defined.

Lastly for str3, too long for SSO, again there is an "Uninitialized bytes" message and this time the output is

byte 0 bits 0
[same 1 to 22]
byte 23 bits 0
byte 24 bits ff
[same 25 to 30]
byte 31 bits ff

Again this is as expected. Now the last 8 bytes of the string are acting like unused padding and are uninitialized.

  • Related