This Time Self-Hosted
dark mode light mode Search

For elves, size matters

This weekend I was supposed to take it slow, relax, and spend it rebuilding my strength, playing and stuff. At the end I wasn’t able to do much of that, and instead I spent it looking at projects I left dangling for a while, like my very own Ruby-Elf.

One beef I always had with ELF analysis tools was with the size command, that is used to get some quick statistics about the internal size distribution of an ELF file. I was interested in that because it’s tightly related to what cowstats tells you already. Indeed if you look at the following output, the data column is the total size of Copy on Write sections for the file.

% size /usr/lib/libxml2.so              
   text	   data	    bss	    dec	    hex	filename
1425219	  35772	   5368	1466359	 165ff7	/usr/lib/libxml2.so

You’d expect me to be happy that there is already a tool that extracts that information out of compiled ELF files, but I really am not because this does not actually provide enough granularity for what my targets are, usually.

First of all, you have to learn that the three names for the columns are not the actual names of the involved sections, even though .text, .data and .bss are three common ELF sections. The actual meanings are for bss, the sum of the sizes of all the allocated sections which are not mapped to the zero page (like .bss and .tbss), for data the sum of the sizes of all the allocated writeable sections, and for text the sum of the sizes of all the remaining allocated sections. This means that .rodata sections are counted in text rather than data, which can easily be confusing.

I blame this confusion also on the size(1) man page that comes from binutils, since it contains this:

Here is an example of the Berkeley (default) format of output from size:

$ size --format=Berkeley ranlib size
text data bss dec hex filename
294880 81920 11592 388392 5ed28 ranlib
294880 81920 11888 388688 5ee50 size

This is the same data, but displayed closer to System V conventions:

$ size --format=SysV ranlib size
ranlib :
section size addr
.text 294880 8192
.data 81920 303104
.bss 11592 385024
Total 388392
size :
section size addr
.text 294880 8192
.data 81920 303104
.bss 11888 385024
Total 388688

This would make you expect that the three columns map directly to the three named sections. Instead if you actually used the System V format for size on libxml2 you’d get a result much different:

% size --format=SysV /usr/lib/libxml2.so
/usr/lib/libxml2.so  :
section             size      addr
.gnu.hash          13012       456
.dynsym            43464     13472
.dynstr            35235     56936
.gnu.version        3622     92172
.gnu.version_r       128     95800
.rela.dyn          76872     95928
.rela.plt           3216    172800
.init                 24    176016
.plt                2160    176040
.text            1011448    178208
.fini                 14   1189656
.rodata           132592   1189696
.eh_frame_hdr      19508   1322288
.eh_frame          83924   1341800
.ctors                16   3526016
.dtors                16   3526032
.jcr                   8   3526048
.data.rel.ro       28056   3526080
.dynamic             448   3554136
.got                 720   3554584
.got.plt            1096   3555304
.data               5412   3556416
.bss                5368   3561856
.gnu_debuglink        28         0
Total            1466387

It counts all the sections one by one rather than grouping them. I’m still not sure on how it decides whether to print or not a given section, I’d have to look at the actual code from binutils to say that. It’s not counting just the allocated sections (so the ones that count in for the vmsize once the file is loaded to spawn a process), or otherwise .gnu_debuglink wouldn’t be in the list, but it’s also not showing all of the sections, since .shstrtab is not there at all. Interestingly enough, if you use the eu-size program that comes with Elfutils rather than the version coming from GNU Binutils, you’ll see that .gnu_debuglink is not in the list, which suggests a bug in Binutils.

But it’s not just this inconsistency in naming that bothers me, it’s more that the output information is almost entirely pointless to make actual guesses about the behaviour of a program, although that’s the most common usage of size. The problem is that you cannot distinguish between increases in the size of the code, or in the size of the constant tables it uses. It also does not allow to easily know whether the amount of data is or not mitigated by prelink.

The SysV-style output is even more useless since if you want to know the full data of the sections you can just use the readelf -S command, which reports among other things the size and the address it is loaded at, which is just what SysV style does. The only difference is that you cannot change radix and that it does not give you a total, but even then I don’t see what’s the point after all. By the way if you think that readelf -S is too difficult to read, try eu-readelf -S, it’s nicer.

So at the end of the day what am I supposed to do? Well at this point I’m writing my own script, rbelf-size, which shows a different set of headers, which allows me to actually distinguish between the different sections so I can judge the changes in software and libraries in different versions and with different patches. I hope to have it available on Ruby-Elf’s repository by the end of next week, maybe it can be useful to others too.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.