Why is /usr/bin/test 4Kb smaller than /usr/bin/[?

A Reddit user named mathisweirdaf shared some interesting observations:

$ ls -lh /usr/bin/{test,[}
-rwxr-xr-x 1 root root 59K Sep 5 2019 ‘/usr/bin/[‘
-rwxr-xr-x 1 root root 55K Sep 5 2019 /usr/bin/test

[ and test should be aliases of e…


This content originally appeared on DEV Community and was authored by Mags Guru

A Reddit user named mathisweirdaf shared some interesting observations:

 $ ls -lh /usr/bin/{test,[}
-rwxr-xr-x 1 root root 59K  Sep  5  2019 '/usr/bin/['
-rwxr-xr-x 1 root root 55K  Sep  5  2019  /usr/bin/test

[ and test should be aliases of each other, and yet there is a 4Kb difference between the GNU coreutils that execute them. Why?

First of all, for all those who were wondering: yes, there is /usr/bin/[. On this topic I have a separate article, but I will explain briefly:
When you write if [ -e /etc/passwd ]; then . this parenthesis does not act as a shell syntax, but just a standard command with a fancy name. It is usually built into the shell, but can sometimes be implemented through /usr/bin/[. This explains much of its mysterious behavior, such as why it is sensitive to spaces: [1=2] turns out to be no more valid than ls-l/tmp.

Nevertheless, where does the difference in size come from? You can compare the objdump output to see where the data fits. Here is an excerpt from objdump -h /usr/bin/[:

              size                                          offset
15 .text         00006e82  0000000000002640  0000000000002640  00002640  2**4
16 .fini         0000000d  00000000000094c4  00000000000094c4  000094c4  2**2
17 .rodata       00001e4c  000000000000a000  000000000000a000  0000a000  2**5

But objdump -h /usr/bin/test:

15 .text         000068a2  0000000000002640  0000000000002640  00002640  2**4
16 .fini         0000000d  0000000000008ee4  0000000000008ee4  00008ee4  2**2
17 .rodata       00001aec  0000000000009000  0000000000009000  00009000  2**5

Here we see that the .text segment (the compiled executable code) is 1504 bytes larger, while the .rodata (constant values and strings) is 864 bytes larger.

The bottom line is that the increased size of the .text segment forces it to move from 8000 to 9000, crossing the page size boundary of 0x1000 (4096) and therefore shifting all other segments by 4096 bytes. It is this difference in size that we observe.

The only nominal difference between [ and test is that [ requires ] as a final argument. Testing this would require a minimal amount of code, so why are those ~1500 bytes used after all?

Since it's hard to review compiled binaries, I put together a copy of coreutils and compared the list of functions in each:

$ diff -u <(nm -S --defined-only src/[ | cut -d ' ' -f 2-) <(nm -S --defined-only src/test | cut -d ' ' -f 2-)
--- /dev/fd/63      2021-02-02 20:21:35.337942508 -0800
+++ /dev/fd/62      2021-02-02 20:21:35.341942491 -0800
@@ -37,7 +37,6 @@
 D __dso_handle
 d _DYNAMIC
 D _edata
-0000000000000099 T emit_bug_reporting_address
 B _end
 0000000000000004 D exit_failure
 0000000000000008 b file_name
@@ -63,7 +62,7 @@
 0000000000000022 T locale_charset
 0000000000000014 T __lstat
 0000000000000014 t lstat
-0000000000000188 T main
+00000000000000d1 T main
 000000000000000b T make_timespec
 0000000000000004 d nslots
 0000000000000022 t one_argument
@@ -142,16 +141,10 @@
 0000000000000032 T umaxtostr
 0000000000000013 t unary_advance
 00000000000004e5 t unary_operator
-00000000000003d2 T usage
+0000000000000428 T usage
 0000000000000d2d T vasnprintf
 0000000000000013 T verror
 00000000000000ae T verror_at_line
-0000000000000008 D Version
-00000000000000ab T version_etc
-0000000000000018 T version_etc_ar
-000000000000042b T version_etc_arn
-000000000000002f R version_etc_copyright
-000000000000007a T version_etc_va
 000000000000001c r wide_null_string.2840
 0000000000000078 T x2nrealloc
 000000000000000e T x2realloc

The main contributors are the version_etc* functions. What do they do?
Let's take a look:

/* The three functions below display the --version information the
   standard way [...]

These are the 260 lines of expanded, internalized, conditional data formatting methods that make up the --version output. All together they take up about bc << "ibase=16; 7A+2F+42B+18+AB+8+99" = 1592 bytes.

What does it mean? It's simple. The extra 4Kb goes to this:

$ /usr/bin/[ --version
[ (GNU coreutils) 8.30
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Kevin Braunsdorf and Matthew Bradburn.

[ --version is missing the final ], so the call is invalid and the result is determined by the implementation. GNU quietly allows version information to be output.

Meanwhile, /usr/bin/test --version turns out to be a valid call and POSIX dictates that it should return success when the first parameter (--version) is a nonempty string.

This difference is even mentioned in the documentation:

Note: [ is responsible for --help and --version options, while test is not.
test treats each of them simply as a non-empty string.

The puzzle is solved!


This content originally appeared on DEV Community and was authored by Mags Guru


Print Share Comment Cite Upload Translate
APA
Mags Guru | Sciencx (2022-10-02T12:19:10+00:00) » Why is /usr/bin/test 4Kb smaller than /usr/bin/[?. Retrieved from https://www.scien.cx/2021/05/02/why-is-usr-bin-test-4kb-smaller-than-usr-bin/.
MLA
" » Why is /usr/bin/test 4Kb smaller than /usr/bin/[?." Mags Guru | Sciencx - Sunday May 2, 2021, https://www.scien.cx/2021/05/02/why-is-usr-bin-test-4kb-smaller-than-usr-bin/
HARVARD
Mags Guru | Sciencx Sunday May 2, 2021 » Why is /usr/bin/test 4Kb smaller than /usr/bin/[?., viewed 2022-10-02T12:19:10+00:00,<https://www.scien.cx/2021/05/02/why-is-usr-bin-test-4kb-smaller-than-usr-bin/>
VANCOUVER
Mags Guru | Sciencx - » Why is /usr/bin/test 4Kb smaller than /usr/bin/[?. [Internet]. [Accessed 2022-10-02T12:19:10+00:00]. Available from: https://www.scien.cx/2021/05/02/why-is-usr-bin-test-4kb-smaller-than-usr-bin/
CHICAGO
" » Why is /usr/bin/test 4Kb smaller than /usr/bin/[?." Mags Guru | Sciencx - Accessed 2022-10-02T12:19:10+00:00. https://www.scien.cx/2021/05/02/why-is-usr-bin-test-4kb-smaller-than-usr-bin/
IEEE
" » Why is /usr/bin/test 4Kb smaller than /usr/bin/[?." Mags Guru | Sciencx [Online]. Available: https://www.scien.cx/2021/05/02/why-is-usr-bin-test-4kb-smaller-than-usr-bin/. [Accessed: 2022-10-02T12:19:10+00:00]
rf:citation
» Why is /usr/bin/test 4Kb smaller than /usr/bin/[? | Mags Guru | Sciencx | https://www.scien.cx/2021/05/02/why-is-usr-bin-test-4kb-smaller-than-usr-bin/ | 2022-10-02T12:19:10+00:00
https://github.com/addpipe/simple-recorderjs-demo