https://tenzir.com/blog/production-debugging-bpftrace-uprobes/
to trace:
void scheduled_actor::enqueue(mailbox_element_ptr ptr, execution_unit* eu);
Every C++ object with virtual functions has a corresponding function table embedded in the binary that is used to determine where to jump for any virtual function calls, and how to perform a dynamic_cast<> between objects. It looks like this:
struct vtable {
long long int parent_offset;
void* typeinfo;
// void* thunks[nfuncs];
char* anchor; // aka thunks[0], this is where the vtbl* in the class points to
};
class mailbox_element : public intrusive::singly_linked<mailbox_element>,
public memory_managed,
public message_view { /* ... */ };
In terms of bpftrace it looks like this:
struct mailbox_element {
char *primary_vtbl; // vtbl for caf::memory_managed and caf::mailbox_element
void* next; // instance of caf::single_linked
char* secondary_vtbl; // vtbl for caf::message
// ---
char data_members[40]; // sender, message id, ...
};
struct vtable {
long long int parent_offset;
void* typeinfo;
// void* thunks[nfuncs];
char* anchor; // aka thunks[0], this is where the vtbl* in the class points to
};
struct vector {
void* first;
void* last;
void* end_of_storage;
};
// -- caf-specific types
struct mailbox_element {
char *primary_vtbl; // vtbl for caf::memory_managed and caf::mailbox_element
void* next; // instance of caf::single_linked
char* secondary_vtbl; // vtbl for caf::message
// ---
char* sender;
char data_members[32]; // sizeof(caf::mailbox_element) == 64
};
struct mailbox_element_ptr {
struct mailbox_element* ptr;
void* dtor;
};
struct message_data {
char* vtbl;
char data_members[16]; // sizeof(caf::message_data) == 24
};
struct message {
char* vtbl; // vtbl for caf::message and caf::type_erased_tuple
struct message_data* data_ptr;
};
struct mailbox_element_wrapper {
struct mailbox_element parent;
struct message msg;
};
// typeinfo for caf::(anonymous namespace)::mailbox_element_wrapper
#define _ZTIN3caf12_GLOBAL__N_123mailbox_element_wrapperE 0x14c03a8
uprobe:/usr/bin/vast:_ZN3caf15scheduled_actor7enqueueESt10unique_ptrINS_15mailbox_elementENS_6detail8disposerEEPNS_14execution_unitE {
$receiver = reg("di"); // "this" pointer
$element_ptr = (struct mailbox_element_ptr*) reg("si");
$element = $element_ptr->ptr;
// 0x40 is the offset between the actor control block and the `this`-pointer of the actor
$sender = $element->sender + 0x40;
$anchor = $element->primary_vtbl;
$vtable = (struct vtable*) ($anchor - 16);
$typeinfo = $vtable->typeinfo;
// caf::mailbox_element_wrapper
if ($vtable->typeinfo == _ZTIN3caf12_GLOBAL__N_123mailbox_element_wrapperE) {
$wrapper = (struct mailbox_element_wrapper*)$element;
$msg_typeinfo = (struct vtable*)($wrapper->msg.vtbl - 16);
$msg_anchor = $wrapper->msg.vtbl;
$mdata = $wrapper->msg.data_ptr;
$data_anchor = $mdata->vtbl;
$data_typeinfo = ((struct vtable*)($mdata->vtbl - 16))->typeinfo;
$mvt = $mdata->vtbl;
// Most likely this will be caf::detail::tuple_vals<Ts...>, so we can get the
// message types by just decoding the typeinfo symbol.
printf("%llx -> %llx (message) 0x%llx\n", $sender, $receiver, $data_typeinfo);
} else {
printf("%llx -> %llx (mailbox element) 0x%llx\n", $sender, $receiver, $vtable->typeinfo);
}
}