After way too long debugging a Fast-DDS subscriber that kept returning RETCODE_NO_DATA, I traced the problem back to assigning an uninitialized bool field to the published data type. The value had garbage in memory, and in a release build that garbage byte ends up written verbatim onto the wire.
I think that the issue is in Cdr::serialize(bool), the normalization step gets optimized away by the compiler in release builds.
Looking at the source:
|
Cdr& Cdr::serialize( |
|
const bool bool_t) |
|
{ |
|
if (((end_ - offset_) >= sizeof(uint8_t)) || resize(sizeof(uint8_t))) |
|
{ |
|
// Save last datasize. |
|
last_data_size_ = sizeof(uint8_t); |
|
|
|
if (bool_t) |
|
{ |
|
offset_++ << static_cast<uint8_t>(1); |
|
} |
|
else |
|
{ |
|
offset_++ << static_cast<uint8_t>(0); |
|
} |
|
|
|
return *this; |
|
} |
|
|
|
throw NotEnoughMemoryException(NotEnoughMemoryException::NOT_ENOUGH_MEMORY_MESSAGE_DEFAULT); |
|
} |
The release build compiles it down to a single mov %bl, (%rax).
This is the release version decompiled for reference:
/* eprosima::fastcdr::Cdr::serialize(bool) */
Cdr * __thiscall eprosima::fastcdr::Cdr::serialize(Cdr *this,bool bool_t)
{
char cVar1;
long lVar2;
// Omit exception handling
*(undefined8 *)(this + 0x88) = 1;
*(long *)(this + 0x98) = lVar2 + 1;
*(bool *)lVar2 = bool_t; // Here we assign directly bool_t
return this;
}
I know this is technically UB on my side, but right now that check is effectively a no-op.
This looks like the same issue addressed in #155, is there a better way to approach it?
I thought about introducing a function like the following:
bool force_bool(uint8_t value) {
return !!value;
}
Then the serialize method would be:
Cdr& Cdr::serialize(
const bool bool_t)
{
if (((end_ - offset_) >= sizeof(uint8_t)) || resize(sizeof(uint8_t)))
{
// Save last datasize.
last_data_size_ = sizeof(uint8_t);
offset_++ << force_bool(static_cast<uint8_t>(bool_t));
return *this;
}
throw NotEnoughMemoryException(NotEnoughMemoryException::NOT_ENOUGH_MEMORY_MESSAGE_DEFAULT);
}
This compiles to the following (as before, I'm showing the decompilation from the library compiled in release mode):
/* eprosima::fastcdr::force_bool(unsigned char) */
bool eprosima::fastcdr::force_bool(uchar param_1)
{
return param_1 != '\0';
}
/* eprosima::fastcdr::Cdr::serialize(bool) */
Cdr * __thiscall eprosima::fastcdr::Cdr::serialize(Cdr *this,bool bool_t)
{
undefined1 uVar1;
char cVar2;
undefined1 *puVar3;
// Omit exception handling
*(undefined8 *)(this + 0x88) = 1;
*(undefined1 **)(this + 0x98) = puVar3 + 1;
uVar1 = force_bool(bool_t);
*puVar3 = uVar1;
return this;
}
Version used:
- Fast-CDR v2.3.0
- GCC 11.5.0
After way too long debugging a Fast-DDS subscriber that kept returning RETCODE_NO_DATA, I traced the problem back to assigning an uninitialized bool field to the published data type. The value had garbage in memory, and in a release build that garbage byte ends up written verbatim onto the wire.
I think that the issue is in
Cdr::serialize(bool), the normalization step gets optimized away by the compiler in release builds.Looking at the source:
Fast-CDR/src/cpp/Cdr.cpp
Lines 816 to 837 in a9bd70c
The release build compiles it down to a single
mov %bl, (%rax).This is the release version decompiled for reference:
I know this is technically UB on my side, but right now that check is effectively a no-op.
This looks like the same issue addressed in #155, is there a better way to approach it?
I thought about introducing a function like the following:
Then the serialize method would be:
This compiles to the following (as before, I'm showing the decompilation from the library compiled in release mode):
Version used: