Nan Xiao's Blog

A system software / performance engineer's home

Tag: c-plus-plus (Page 1 of 2)

Postmortem of NTL::Vec type

I am playing with NTL, and come across a core dump issue which is related with NTL::ZZX variable:

(gdb) p msg
$2 = (NTL::ZZX &) @0x7fff680601f0: {rep = {_vec(long double,...)( *) = {rep = 0xab629d0}}}

The NTL::ZZX actually contains one member, rep:

class ZZX {

public:

vec_ZZ rep;

......

ZZ& operator[](long i) { return rep[i]; }
const ZZ& operator[](long i) const { return rep[i]; }

......
}

The vec_ZZ is a vector (not std::vector, NTL::Vec instead) in fact:

typedef Vec<ZZ> vec_ZZ;

The error occurs when getting the 8191-st element. Unfortunately, I can’t use gdb to access the element in vector directly:

(gdb) p i
$3 = 8191
(gdb) p msg[i]
You can't do that without a process to debug.

After referring this doc, it gives me the idea that seems be the gdb‘s limitation of accessing container. So I try to access the member straightaway.NTL::Vec is just a template class containing one public member:

template<class T>
class Vec {  
public:  
    ......
    WrappedPtr<T, _vec_deleter> _vec__rep;
    ......
};

While WrappedPtr is nothing but another template class:

template<class T, class Deleter>
class WrappedPtr {
   ......
public:
   typedef T * raw_ptr;

   raw_ptr rep;
   ......
}

We can see the rep member in WrappedPtr points to the start address of the content in vector. Read the 8191-st element’s value:

(gdb) p sizeof(*msg.rep._vec__rep.rep)
$23 = 8
(gdb) x/16xb msg.rep._vec__rep.rep+8191
0xab729c8:      0x77    0x01    0x00    0x00    0x00    0x00    0x00    0x00
0xab729d0:      0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00

The valid value should be a memory address, 0x177 is definitely not. So the next thing is to find out why this isn’t correct …

Message length setting in gRPC

The default send/receive message length of gRPC is defined here:

/** Default send/receive message size limits in bytes. -1 for unlimited. */
/** TODO(roth) Make this match the default receive limit after next release */
#define GRPC_DEFAULT_MAX_SEND_MESSAGE_LENGTH -1
#define GRPC_DEFAULT_MAX_RECV_MESSAGE_LENGTH (4 * 1024 * 1024)

We can know that send message length is no limited (-1), while Server/Client can only receive 4 Mi bytes by default.

You can change receive message length to unlimited in Client:

grpc::ChannelArguments ch_args;
ch_args.SetMaxReceiveMessageSize(-1);
std::shared_ptr<grpc::Channel> ch = 
        grpc::CreateCustomChannel("localhost:50051", grpc::InsecureChannelCredentials(), ch_args);

But this doesn’t work in Server:

ServerBuilder builder;
builder.SetMaxReceiveMessageSize(-1);

Because in server_builder.cc, the parameter only takes effect when it is positive:

std::unique_ptr<Server> ServerBuilder::BuildAndStart() {
    ......
    if (max_receive_message_size_ >= 0) {
      args.SetInt(GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH, max_receive_message_size_);
    }
    ......
}

So we can use builder.SetMaxReceiveMessageSize(INT_MAX); as a work-around.

BTW, check message length limit is in get_message_size_limits function.

The tips of using and debugging C++ std::iostream

(1) Be cautious of '\n' and std::endl.

The std::endl will flush the output buffer while '\n' not. For example, in socket programming, if client sends message to server using '\n', like this:

out << "Hello World!" << '\n';
out << "I am coming" << '\n';

The server may still block in reading operation and no data is fetched. So you should use std::endl in this case:

out << "Hello World!" << std::endl;
out << "I am coming" << std::endl;

(2) Use std::ios::rdstate.

std::ios::rdstate is a handy function to check the stream state. You can use it in gdb debugging:

(gdb) p in.rdstate()
$45 = std::_S_goodbit
(gdb) n
350             return in;
(gdb) p in.rdstate()
$46 = std::_S_failbit

Through step-mode, we can see which operation crashes the stream.

(3) Serialize the data into file.

No matter you want to do test or debug issue, dump the data into a file and read it back is a very effective method:

std::ofstream ofs("data.txt");
ofs << output;
ofs.close();

std::ifstream ifs("data.txt");
ifs >> input;
ifs.close();

The above simple code can verify whether your serialization functions are correct or not. Trust me, it is a very brilliant trouble-shouting std::iostream issue trick, and it saved me just now!

A “std::bad_alloc” issue caused by typo

Last week, I fixed a bug which was caused by a typo. The simplified code is like this:

#include <vector>
using namespace std;

class A {
    int i;
public:
    A(int i): i(i) {}
};

class B {
    vector<A> v;
public:
    B(vector<A> v1): v(v) {}
};

int main() {
    vector<A> a(1, 0);
    B b(a);
    return 0;
}

Please note the constructor of B:

B(vector<A> v1): v(v) {}

It was supposed to use v1 to initialize v, while I misspelled: v(v). My compiler is gcc 6.3.1, compile and run it:

# g++ -g test.cpp
# ./a.out
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Aborted (core dumped)

Change B(vector<A> v1): v(v) to B(vector<A> v1): v(v1), then all is OK.

What is the effect of extern “C”?

I often see the following code in C header files:

#ifdef __cplusplus
extern "C" {
#endif

......

#ifdef __cplusplus
}
#endif

What does it mean? Since there is __cplusplus marco, it must be related to C++ compilation. Let’s see a simple program (print.c):

$ cat print.c
#include <stdio.h>

void print(void)
{
        printf("Hello world!\n");
}

Use gcc to generate object file:

$ gcc -c print.c
$ 

Then create a main.cpp file which calls print() in its main() function:

$ cat main.cpp
extern void print(void);

int main(void)
{
        print();
        return 0;
}

Compile main.cpp and link with print.o:

$ g++ main.cpp print.o
/tmp/cc60fu19.o: In function `main':
main.cpp:(.text+0x5): undefined reference to `print()'
collect2: error: ld returned 1 exit status

It is weird, right? the print() function must be defined in print.o, why can’t g++ find it? Let’s do a simple magic, add "C" in extern void print(void);:

$ cat main.cpp
extern "C" void print(void);

int main(void)
{
        print();
        return 0;
}

Try compile main.cpp again:

$ g++ main.cpp print.o
$ ./a.out
Hello world!

It is OK now! The root cause is related to name mangling. To be simplified, when compile C++ code, the names of functions, global variables, etc will be modified, not the same as original format. While compile C code, this won’t happen. The function of extern "C" is to tell C++ compiler search the original name, not the mangled ones. To get a sense of name mangling, you can check the print() name in object file:

$ readelf -s print.o | grep print
 1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS print.c
 9: 0000000000000000    17 FUNC    GLOBAL DEFAULT    1 print

Then use g++ to compile print.c, and check function name again:

$ g++ -c print.c
$ readelf -s print.o | grep print
 1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS print.c
 9: 0000000000000000    17 FUNC    GLOBAL DEFAULT    1 _Z5printv

You can see the print() function name is actually _Z5printv when use g++ to generate the object file.

References:
Why do we use extern “C”?;
In C++ source, what is the effect of extern ā€œCā€?.

Page 1 of 2

Powered by WordPress & Theme by Anders Norén