clayout, translate C++/Rust type into C type with the same memory layout. Generally, clayout is used together with bpftrace.
clayout is developed on ddbug. THANKS FOR ddbug!
Imagine a scenario where you want to use bpftrace to track the value of S::x
during the running of the following program.
#include <stdio.h>
#include <unistd.h>
struct X {
virtual ~X() {}
int x1;
};
struct S : public X {
S() : x(0) {}
S(const S &other) : x(other.x) {}
S f(int y, int z) {
printf("output from a.out: this.x=%d y=%d z=%d\n", x, y, z);
x += (y + z);
return *this;
}
int x;
};
int main(int argc, char **argv) {
S s;
int i = 0;
while (1) {
s.f(i, i);
++i;
sleep(1);
// break;
}
return 0;
}
clayout can translate S
into a C structure with the same memory layout:
# clayout will generate struct.h, struct.c
$ clayout -i ${binary path} -o struct S
// struct.h
// Generated by hidva/clayout! 大吉大利!
#pragma once
#include <linux/types.h>
struct HidvaStruct2 {
void** __mem1;
int x1;
} __attribute__((__packed__));
struct S {
struct HidvaStruct2 __parent0;
int x;
} __attribute__((__packed__));
So you can easily write the following bpftrace script:
#include "struct.h"
u:/apsara/zhanyi.ww/tmp/bphtrace/x/trace:_ZN1S1fEii {
printf("output from bpftrace: ret=%p this.x=%d y=%d z=%d\n", (int32*)arg0, ((struct S*)arg1)->x, arg2, arg3)
}
$ bpftrace -c ./trace t.bt
Attaching 1 probe...
output from a.out: this.x=0 y=0 z=0
output from bpftrace: ret=0x7ffff3044610 this.x=0 y=0 z=0
output from a.out: this.x=0 y=1 z=1
output from bpftrace: ret=0x7ffff3044610 this.x=0 y=1 z=1
Please note that you may intuitively think that the layout of S is as follows:
struct X {
void** __mem1;
int x1;
}
struct S {
struct X __parent0;
int x;
}
But actually it is wrong! S::x
will reuse the padding part of X
in C++!
clayout supports multiple input files, and type references across files.
// x.h
struct X {
virtual ~X();
int x1;
};
struct S : public X {
S();
S(const S &other);
S f(int y, int z);
int x;
};
// X.cc
#include <stdio.h>
#include "x.h"
X::~X() {}
S::S(): x(0) {}
S::S(const S &other) : x(other.x) {}
S S::f(int y, int z) {
printf("output from a.out: this.x=%d y=%d z=%d\n", x, y, z);
x += (y + z);
return *this;
}
// trace.cc
#include <unistd.h>
#include "x.h"
int main(int argc, char **argv) {
S s;
int i = 0;
while (1) {
s.f(i, i);
++i;
sleep(1);
}
return 0;
}
$ clang++ -fPIC -shared -g -O0 X.cc -o libzh_x.so
$ clang++ -g -O0 trace.cc -o trace -L. -lzh_x
Because of -fstandalone-debug, the trace binary file does not contain any debugging information of X
:
$ readelf --debug-dump=info trace
<1><fc>: Abbrev Number: 13 (DW_TAG_structure_type)
<fd> DW_AT_name : X
<ff> DW_AT_declaration : 1
Because there is no debugging information of X
in the trace binary file, a placeholder __u8 __unknown_type1[12]
is used.
$ clayout -i trace -o output S
// output.h
// Generated by hidva/clayout! 大吉大利!
#pragma once
#include <linux/types.h>
struct S {
__u8 __unknown_type1[12];
int x;
} __attribute__((__packed__));
We can use multi input file to get the detail of X
:
$ clayout -i trace -i libzh_x.so -o output S
// output.h
// Generated by hidva/clayout! 大吉大利!
#pragma once
#include <linux/types.h>
struct HidvaStruct2 {
void** __mem1;
int x1;
} __attribute__((__packed__));
struct S {
struct HidvaStruct2 __parent0;
int x;
} __attribute__((__packed__));