Strange library sizes (multiples of ~64 kB)

Today it struck me that the size of dynamic libraries in OpenWrt are almost always around 65kB, or a multiple of that:

~$ ls -lR /usr | grep ".so$" 
lrwxrwxrwx    1 root     root            17 Jan 29 23:36 ldd -> ../../lib/libc.so
-rw-r--r--    1 root     root         65836 Jan 29 23:36 libuclient.so
-rw-r--r--    1 root     root         65740 Jan 29 23:36 libudebug.so
-rwxr-xr-x    1 root     root         65824 Jan 29 23:36 uhttpd_ubus.so
-rwxr-xr-x    1 root     root        135188 Jan 29 23:36 app_confbridge.so
-rwxr-xr-x    1 root     root         66576 Jan 29 23:36 app_dial.so
-rwxr-xr-x    1 root     root         65744 Jan 29 23:36 app_echo.so
-rwxr-xr-x    1 root     root         65904 Jan 29 23:36 app_mp3.so
-rwxr-xr-x    1 root     root         66008 Jan 29 23:36 app_playback.so
-rwxr-xr-x    1 root     root         66364 Jan 29 23:36 app_stack.so
-rwxr-xr-x    1 root     root         65792 Jan 29 23:36 app_system.so
-rwxr-xr-x    1 root     root         65840 Jan 29 23:36 bridge_builtin_features.so
-rwxr-xr-x    1 root     root         65916 Jan 29 23:36 bridge_simple.so
-rwxr-xr-x    1 root     root         66276 Jan 29 23:36 bridge_softmix.so
-rwxr-xr-x    1 root     root        134144 Jan 29 23:36 chan_pjsip.so
-rwxr-xr-x    1 root     root         66636 Jan 29 23:36 codec_a_mu.so
-rwxr-xr-x    1 root     root         66708 Jan 29 23:36 codec_alaw.so
-rwxr-xr-x    1 root     root         66388 Jan 29 23:36 codec_resample.so
-rwxr-xr-x    1 root     root         66708 Jan 29 23:36 codec_ulaw.so
-rwxr-xr-x    1 root     root         66376 Jan 29 23:36 format_wav.so
-rwxr-xr-x    1 root     root         66136 Jan 29 23:36 func_callerid.so
-rwxr-xr-x    1 root     root         66492 Jan 29 23:36 func_logic.so
-rwxr-xr-x    1 root     root         65872 Jan 29 23:36 func_pjsip_aor.so
-rwxr-xr-x    1 root     root         65872 Jan 29 23:36 func_pjsip_contact.so
-rwxr-xr-x    1 root     root         65848 Jan 29 23:36 func_pjsip_endpoint.so
-rwxr-xr-x    1 root     root         68336 Jan 29 23:36 func_strings.so
-rwxr-xr-x    1 root     root         65888 Jan 29 23:36 func_timeout.so
-rwxr-xr-x    1 root     root         67252 Jan 29 23:36 pbx_config.so
-rwxr-xr-x    1 root     root         66316 Jan 29 23:36 res_crypto.so
-rwxr-xr-x    1 root     root         66264 Jan 29 23:36 res_http_websocket.so
-rwxr-xr-x    1 root     root         66544 Jan 29 23:36 res_phoneprov.so
-rwxr-xr-x    1 root     root         66456 Jan 29 23:36 res_pjproject.so
-rwxr-xr-x    1 root     root        334944 Jan 29 23:36 res_pjsip.so
-rwxr-xr-x    1 root     root         65920 Jan 29 23:36 res_pjsip_acl.so
-rwxr-xr-x    1 root     root         66004 Jan 29 23:36 res_pjsip_authenticator_digest.so
-rwxr-xr-x    1 root     root         65916 Jan 29 23:36 res_pjsip_caller_id.so
-rwxr-xr-x    1 root     root         66228 Jan 29 23:36 res_pjsip_config_wizard.so
-rwxr-xr-x    1 root     root         65904 Jan 29 23:36 res_pjsip_dialog_info_body_generator.so
-rwxr-xr-x    1 root     root         65996 Jan 29 23:36 res_pjsip_diversion.so
-rwxr-xr-x    1 root     root         65800 Jan 29 23:36 res_pjsip_dlg_options.so
-rwxr-xr-x    1 root     root         65824 Jan 29 23:36 res_pjsip_dtmf_info.so
-rwxr-xr-x    1 root     root         65776 Jan 29 23:36 res_pjsip_empty_info.so
-rwxr-xr-x    1 root     root         65788 Jan 29 23:36 res_pjsip_endpoint_identifier_anonymous.so
-rwxr-xr-x    1 root     root         66372 Jan 29 23:36 res_pjsip_endpoint_identifier_ip.so
-rwxr-xr-x    1 root     root         65804 Jan 29 23:36 res_pjsip_endpoint_identifier_user.so
-rwxr-xr-x    1 root     root         66432 Jan 29 23:36 res_pjsip_exten_state.so
-rwxr-xr-x    1 root     root         66328 Jan 29 23:36 res_pjsip_header_funcs.so
-rwxr-xr-x    1 root     root         66660 Jan 29 23:36 res_pjsip_history.so
-rwxr-xr-x    1 root     root         66088 Jan 29 23:36 res_pjsip_logger.so
-rwxr-xr-x    1 root     root         66164 Jan 29 23:36 res_pjsip_messaging.so
-rwxr-xr-x    1 root     root         66456 Jan 29 23:36 res_pjsip_mwi.so
-rwxr-xr-x    1 root     root         65760 Jan 29 23:36 res_pjsip_mwi_body_generator.so
-rwxr-xr-x    1 root     root         66008 Jan 29 23:36 res_pjsip_nat.so
-rwxr-xr-x    1 root     root         66268 Jan 29 23:36 res_pjsip_notify.so
-rwxr-xr-x    1 root     root         65812 Jan 29 23:36 res_pjsip_one_touch_record_info.so
-rwxr-xr-x    1 root     root         65836 Jan 29 23:36 res_pjsip_outbound_authenticator_digest.so
-rwxr-xr-x    1 root     root         66204 Jan 29 23:36 res_pjsip_outbound_publish.so
-rwxr-xr-x    1 root     root         67080 Jan 29 23:36 res_pjsip_outbound_registration.so
-rwxr-xr-x    1 root     root         65936 Jan 29 23:36 res_pjsip_path.so
-rwxr-xr-x    1 root     root         65832 Jan 29 23:36 res_pjsip_phoneprov_provider.so
-rwxr-xr-x    1 root     root         65816 Jan 29 23:36 res_pjsip_pidf_body_generator.so
-rwxr-xr-x    1 root     root         65764 Jan 29 23:36 res_pjsip_pidf_digium_body_supplement.so
-rwxr-xr-x    1 root     root         65760 Jan 29 23:36 res_pjsip_pidf_eyebeam_body_supplement.so
-rwxr-xr-x    1 root     root         66104 Jan 29 23:36 res_pjsip_publish_asterisk.so
-rwxr-xr-x    1 root     root        132812 Jan 29 23:36 res_pjsip_pubsub.so
-rwxr-xr-x    1 root     root         66516 Jan 29 23:36 res_pjsip_refer.so
-rwxr-xr-x    1 root     root         66256 Jan 29 23:36 res_pjsip_registrar.so
-rwxr-xr-x    1 root     root         65872 Jan 29 23:36 res_pjsip_rfc3326.so
-rwxr-xr-x    1 root     root         66644 Jan 29 23:36 res_pjsip_sdp_rtp.so
-rwxr-xr-x    1 root     root         65856 Jan 29 23:36 res_pjsip_send_to_voicemail.so
-rwxr-xr-x    1 root     root        132648 Jan 29 23:36 res_pjsip_session.so
-rwxr-xr-x    1 root     root         65800 Jan 29 23:36 res_pjsip_sips_contact.so
-rwxr-xr-x    1 root     root         66220 Jan 29 23:36 res_pjsip_t38.so
-rwxr-xr-x    1 root     root         66096 Jan 29 23:36 res_pjsip_transport_websocket.so
-rwxr-xr-x    1 root     root         65852 Jan 29 23:36 res_pjsip_xpidf_body_generator.so
-rwxr-xr-x    1 root     root        199208 Jan 29 23:36 res_rtp_asterisk.so
-rwxr-xr-x    1 root     root         65936 Jan 29 23:36 res_sorcery_astdb.so
-rwxr-xr-x    1 root     root         65984 Jan 29 23:36 res_sorcery_config.so
-rwxr-xr-x    1 root     root         65852 Jan 29 23:36 res_sorcery_memory.so
-rwxr-xr-x    1 root     root         65920 Jan 29 23:36 res_sorcery_realtime.so
-rwxr-xr-x    1 root     root         66012 Jan 29 23:36 res_srtp.so
-rwxr-xr-x    1 root     root         65812 Jan 29 23:36 res_timing_timerfd.so
-rw-r--r--    1 root     root         65736 Jan 29 23:36 lucihttp.so
-rwxr-xr-x    1 root     root         66480 Jan 29 23:36 nixio.so
-rwxr-xr-x    1 root     root         65872 Jan 29 23:36 ubus.so
-rwxr-xr-x    1 root     root         65848 Jan 29 23:36 ip.so
-rwxr-xr-x    1 root     root         65848 Jan 29 23:36 jsonc.so
-rwxr-xr-x    1 root     root         65812 Jan 29 23:36 output_alsa.so
-rwxr-xr-x    1 root     root         65652 Jan 29 23:36 output_dummy.so
-rwxr-xr-x    1 root     root         65940 Jan 29 23:36 pppoatm.so
-rwxr-xr-x    1 root     root         66588 Jan 29 23:36 pppoe.so
-rwxr-xr-x    1 root     root         65932 Jan 29 23:36 file.so
-rwxr-xr-x    1 root     root         65852 Jan 29 23:36 iwinfo.so
-rwxr-xr-x    1 root     root         66056 Jan 29 23:36 luci.so
-rwxr-xr-x    1 root     root         65900 Jan 29 23:36 rpcsys.so
-rwxr-xr-x    1 root     root         65844 Jan 29 23:36 rrdns.so
-rwxr-xr-x    1 root     root         65964 Jan 29 23:36 ucode.so
-rwxr-xr-x    1 root     root         65980 Jan 29 23:36 digest.so
-rwxr-xr-x    1 root     root         66028 Jan 29 23:36 fs.so
-rwxr-xr-x    1 root     root        131244 Jan 29 23:36 html.so
-rwxr-xr-x    1 root     root         65688 Jan 29 23:36 log.so
-rw-r--r--    1 root     root         65740 Jan 29 23:36 lucihttp.so
-rwxr-xr-x    1 root     root         65724 Jan 29 23:36 math.so
-rwxr-xr-x    1 root     root         65944 Jan 29 23:36 nl80211.so
-rwxr-xr-x    1 root     root        131488 Jan 29 23:36 rtnl.so
-rwxr-xr-x    1 root     root         65980 Jan 29 23:36 ubus.so
-rwxr-xr-x    1 root     root         65864 Jan 29 23:36 uci.so
-rwxr-xr-x    1 root     root         65948 Jan 29 23:36 uloop.so
-rwxr-xr-x    1 root     root         65848 Jan 29 23:36 core.so

Why is that? I could imagine that for some reason a library is always a multiple of exact 64KiB, due to blocksizes or whatever. But that is not the case here. BTW, this is mips_24kc. But I looked on aarch64_cortex-a53, and there I see the same pattern.

1 Like

Interesting observation.
Also weird, that they are not exactly 64 kB, but near that.

I imagine it's due to something like the musl static library being included in each of the library items. I would have thought with some linker trimming there would be a bit more variation, but perhaps there is some reason that the trimming isn't dropping that much out...
It might be worth checking what kind of symbols are in each library to see if there is a common set of unneeded musl functions being carried around.

The plot thickens. I failed to look at the link table, because objdump on my platform couldn't handle mips code.
But I tried binwalk to show the entropy of 3 random libraries:




And indeed, at the places where the entropy is zero, the file only contains zeros!

I suppose this is a linker artifact, or something like that.

1 Like

There seems to be some target-based variation:

The "near 64 kB multiples" .so size seems true for 64-bit aarch64, but not for 32-bit ARM. Both examples below are up-to-date 1-2 days old main/master builds with the default compilation settings (and pretty similar package selection).

mediatek/filogic MT6000 (Flint2):

root@router6000:~# cat /etc/openwrt_release
DISTRIB_ID='OpenWrt'
DISTRIB_RELEASE='SNAPSHOT'
DISTRIB_REVISION='r33165-2256cfac68'
DISTRIB_TARGET='mediatek/filogic'
DISTRIB_ARCH='aarch64_cortex-a53'
DISTRIB_DESCRIPTION='OpenWrt SNAPSHOT r33165-2256cfac68'
DISTRIB_TAINTS='no-all busybox'

root@router6000:~# uname -a
Linux router6000 6.12.71 #0 SMP Fri Feb 20 19:44:26 2026 aarch64 GNU/Linux

root@router6000:~# ls -lR /usr | grep ".so$" | head -n 20
lrwxrwxrwx    1 root     root            17 Feb 20 21:44 ldd -> ../../lib/libc.so
-rwxr-xr-x    1 root     root        131075 Feb 20 21:44 libelf-0.192.so
lrwxrwxrwx    1 root     root            15 Feb 20 21:44 libelf.so.1 -> libelf-0.192.so
-rwxr-xr-x    1 root     root        138842 Feb 20 21:44 libiptext.so
-rwxr-xr-x    1 root     root         65890 Feb 20 21:44 libiptext4.so
-rwxr-xr-x    1 root     root         65890 Feb 20 21:44 libiptext6.so
-rwxr-xr-x    1 root     root         65539 Feb 20 21:44 libiptext_arpt.so
-rwxr-xr-x    1 root     root         65690 Feb 20 21:44 libiptext_ebt.so
lrwxrwxrwx    1 root     root            11 Feb 20 21:44 libpng.so -> libpng16.so
-rw-r--r--    1 root     root         65539 Feb 20 21:44 libuclient.so
-rw-r--r--    1 root     root         65585 Feb 20 21:44 libudebug.so
-rw-r--r--    1 root     root         65674 Feb 20 21:44 libunet.so
-rwxr-xr-x    1 root     root         65618 Feb 20 21:44 uhttpd_ubus.so
-rwxr-xr-x    1 root     root         65546 Feb 20 21:44 conntrack.so
-rwxr-xr-x    1 root     root         65594 Feb 20 21:44 cpu.so
-rwxr-xr-x    1 root     root         65539 Feb 20 21:44 cpufreq.so
-rwxr-xr-x    1 root     root         65539 Feb 20 21:44 exec.so
-rwxr-xr-x    1 root     root         65570 Feb 20 21:44 interface.so
-rwxr-xr-x    1 root     root         65554 Feb 20 21:44 iwinfo.so
-rwxr-xr-x    1 root     root         65546 Feb 20 21:44 load.so

ipq806x R7800:


root@router78:/usr# cat /etc/openwrt_release
DISTRIB_ID='OpenWrt'
DISTRIB_RELEASE='SNAPSHOT'
DISTRIB_REVISION='r33181-8e33c40c7f'
DISTRIB_TARGET='ipq806x/generic'
DISTRIB_ARCH='arm_cortex-a15_neon-vfpv4'
DISTRIB_DESCRIPTION='OpenWrt SNAPSHOT r33181-8e33c40c7f'
DISTRIB_TAINTS='no-all busybox'

root@router78:/usr#  uname -a
Linux router78 6.12.71 #0 SMP Sat Feb 21 18:24:45 2026 armv7l GNU/Linux

root@router78:/usr# ls -lR /usr | grep ".so$" | head -n 20
lrwxrwxrwx    1 root     root            17 Feb 21 20:24 ldd -> ../../lib/libc.so
-rwxr-xr-x    1 root     root         77827 Feb 21 20:24 libelf-0.192.so
lrwxrwxrwx    1 root     root            15 Feb 21 20:24 libelf.so.1 -> libelf-0.192.so
-rwxr-xr-x    1 root     root         69570 Feb 21 20:24 libiptext.so
-rwxr-xr-x    1 root     root          8374 Feb 21 20:24 libiptext4.so
-rwxr-xr-x    1 root     root          8374 Feb 21 20:24 libiptext6.so
-rwxr-xr-x    1 root     root          4098 Feb 21 20:24 libiptext_arpt.so
-rwxr-xr-x    1 root     root          8270 Feb 21 20:24 libiptext_ebt.so
-rw-r--r--    1 root     root         20489 Feb 21 20:24 libuclient.so
-rw-r--r--    1 root     root          8217 Feb 21 20:24 libudebug.so
-rw-r--r--    1 root     root         36938 Feb 21 20:24 libunet.so
-rwxr-xr-x    1 root     root         16426 Feb 21 20:24 uhttpd_ubus.so
-rwxr-xr-x    1 root     root          4102 Feb 21 20:24 conntrack.so
-rwxr-xr-x    1 root     root          8222 Feb 21 20:24 cpu.so
-rwxr-xr-x    1 root     root          8194 Feb 21 20:24 cpufreq.so
-rwxr-xr-x    1 root     root          4102 Feb 21 20:24 dhcpleases.so
-rwxr-xr-x    1 root     root          4098 Feb 21 20:24 entropy.so
-rwxr-xr-x    1 root     root         49154 Feb 21 20:24 exec.so
-rwxr-xr-x    1 root     root          8210 Feb 21 20:24 interface.so
-rwxr-xr-x    1 root     root          8202 Feb 21 20:24 iwinfo.so

My guess would be that linker wants to place something at the next data page boundary, meaning either 64 kB (65536) for 64bit and 4 kB (4096) for 32bit.

OpenWrt aligns ELF PT_LOAD segments

for aarch64:

for armv7:

1 Like

I cannot see how that can be true. The data in squashfs is highly compressed, so a 64KiB library segment will take much less than an erase-block. And I doubt an compressed page can be mmaped directly to ram, although it might be possible, with some abstraction layer.

It is not about flash erase blocks, which typically are 64 or 128 kB even in the older routers.

It is likely about memory addressing architecture, memory "page size".
4 kB is typical for some processors, but apparently 64 kB is used by the gcc linker for 64bit Arm targets.
The curious part is that in kernel options, the 4 kB page size is set also for 64 bit Arm targets. So, I wonder if there is some kind of config error for GCC linker.
(@Mijzelf mentioned mips_24kc which is 32bit architecture, so it is maybe not just aarch64 targets)

From my research it appears to be related to upstream changes in how executables and library files are padded in relation to support for Full RELRO. Padding changed from using commonpagesize to using maxpagesize.

Changing the padding is from the fixes for this bug: 28824 – relro security issues


Some noted this last year: Unexpected growth of files between v19 and v24 - For Developers - OpenWrt Forum

Article on RELRO: Hardening ELF binaries using Relocation Read-Only (RELRO)

Using checksec on the library files and executables that are padded to past 64k boundaies does show Full RELRO. Went back to 19.07.9 and checked a file there - it has the small size but checksec still shows Full RELRO. (checked rrdtool.so).

Some discussion of the effect in a binutils bug here: 30612 – maxpagesize alignment after relro segment takes up space

2 Likes

Thanks for those those binutils bug report links. Sounds like a likely explanation.

If we aren’t building kernels with 64k page size, we can limit the linker max page size to 4k. -Wl,-z,max-page-size=0x1000

is this a reasonable solution for the “problem”? has this change be further discussed by the dev team somewhere? is there already a (non accepted) pr?

Not discussed so far, to my knowledge. I haven't seen actual developers in this thread or in Why packages became so huge since version 24

This sounds like something that the gcc & binutils experts might take a look into.
Not quite sure who, but @hauke , @robimarko and @PolynomialDivision have been active regarding gcc and binutils.

Some other downstream users of gcc & binutils do build with > 4k page sizes even on 32 bit architectures usually as a way to support > 16TB block devices. An example would be some NAS distributions. Capping max page size caused binaries to fail to load on those distributions. Can see mention of it if you follow enough links to related bugs and the discussions in the bug reports linked in posts in this thread.

For each OpenWrt arch we could double check all targets only build using 4k pages and if that is the case force max page size to 4k for that architecture. The binaries - both executables and libraries would not longer have the extra padding.

Misc. link on the 16TB block device limit: Invisible 16 TiB block device limit - Installing and Using OpenWrt - OpenWrt Forum

Well, the generic config defines 4 kB pages, so binutils linker should maybe just check that option and if so, then keep the maxpagesize at 4 kB for ELF programs:

But there are many options related to that. Example about ARM64 and PPC:

So, having a foolproof way for downstream will likely be hard. But keeping OpenWrt coherent should be possible, but I am not sure if there is an elegant solution. I have no binutils expertise, so no idea what would be the optimal way to set the option.

And we haven't yet tested if that really is the solution.

It is total BS, 64kB mprotect is of meaning only for Android and aarch64 that can boot with 4k or 64k pages with same binaries. It is a total amok run to protect 7 extra pages on eg MIPS which will never bolt on anything else than 4k pages.

1 Like

Do we actually boot with large(r) pages ever?

From MIPS TLB guide

The Page Mask register allows TLB entries to map pages larger than 4K.
... blah blah blah ... up to
256MBs

Go for it

Thanks for bringing this up again. I noticed it some time ago and ignored it.

This change introduced the bigger padding: https://sourceware.org/git/?p=binutils-gdb.git;a=commitdiff;h=9833b7757d246f22db4eb24b8e5db7eb5e05b6d9
It was added in binutils 2.39 in OpenWrt 23.05.

I created this PR to revert the relevant changes:

9 Likes