2

In an answer on a sister site, I'm trying to dump information from the Linux kernel array unix_socket_table@net/unix/af_unix.c which is defined as:

struct hlist_head unix_socket_table[2 * UNIX_HASH_SIZE];

For the moment, I'm hard-coding the size of the array in my stp script:

for (i = 0; i < 512; i++)

How could I avoid that? That information (the size of the array) is stored in the debug information. gdb can tell me it:

$ gdb --batch --ex 'whatis unix_socket_table' "/usr/lib/debug/boot/vmlinux-$(uname -r)"
type = struct hlist_head [512]
$ gdb --batch --ex 'p sizeof(unix_socket_table)/sizeof(*unix_socket_table)' "/usr/lib/debug/boot/vmlinux-$(uname -r)"
$1 = 512

But how would I do it in systemtap? AFAICT, systemtap has no sizeof() operator.

Community
  • 1
  • 1
Stephane Chazelas
  • 5,859
  • 2
  • 34
  • 31

2 Answers2

5

If it were a type, the @cast operator could be used:

size=&@cast(0,"$TYPENAME")[1]

but alas, unix_socket_table isn't a type. So, plan B, use symdata on the variable (in scope of any old kernel function in the vicinity).

probe begin /* kernel.function("*@net/unix/af_unix.c") */ {
    println(symdata(& @var("unix_socket_table")))
    exit()
}

results here:

unix_socket_table+0x0/0x1000 [kernel]

The second hex number is the symbol size, as computed from the ELF symbol tables at script processing time, equivalent to the 4096 figure here:

% readelf -s /usr/lib/debug/lib/modules/`uname -r`/vmlinux | grep unix_socket_table
71901: ffffffff82023dc0  4096 OBJECT  GLOBAL DEFAULT   28 unix_socket_table

You can get the number with for instance:

probe begin {
    tokenize(symdata(@var("unix_socket_table@net/unix/af_unix.c")),"/");
    printf("%d\n", strtol(tokenize("",""), 16));
    exit()
}
fche
  • 2,641
  • 20
  • 28
3

Many thanks to @fche for pointing me in the right direction. As he says, systemtap's symdata() function can be used to retrieve symbol information at a given address including the size. So we can write our own sizeof() function that parses it to extract the size as:

function sizeof(address:long) {
  tokenize(symdata(address), "/");
  return strtol(tokenize("",""),16);
}

If we look at the definition of that symdata() function, we see that it is itself a systemtap function that makes use of the _stp_snprint_addr() C function, itself calling _stp_kallsyms_lookup() to retrieve data. That means we can also define our own sizeof() using stp_kallsyms_lookup() directly:

function sizeof:long (addr:long) %{ /* pure */ /* pragma:symbols */
  STAP_RETVALUE = -1;
  _stp_kallsyms_lookup(STAP_ARG_addr, (unsigned long*)&(STAP_RETVALUE), NULL, NULL, NULL);
%}

(note that we need -g (guru) here as we're using embedded C).

Now, to get the array size, we need the size of the elements of the arrays. One approach can be to use the address offset between 2 elements of the array. So we could define our array_size() function as:

function array_size(first:long, second:long) {
  return sizeof(first) / (second - first);
}

(where sizeof() is one or the other of the functions defined above).

And call it as:

probe begin {
  printf("%d\n", array_size(
    &@var("unix_socket_table@net/unix/af_unix.c")[0],
    &@var("unix_socket_table@net/unix/af_unix.c")[1]));
  exit();
}

Which gives us 512 as expected.

For sizeof(), another approach could be to use the C sizeof() operator:

$ sudo stap -ge '
%{ #include <net/af_unix.h> %}
probe begin {
  printf("%d\n", %{ sizeof(unix_socket_table)/sizeof(unix_socket_table[0]) %} );
  exit();
}'
512

(also needs -g) but then the information is retrieved from the kernel source code (header files), not debug information, so while that would work for kernel arrays that are defined in header files, that approach won't necessary work for all arrays.

Community
  • 1
  • 1
Stephane Chazelas
  • 5,859
  • 2
  • 34
  • 31