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
Mags Guru | Sciencx (2021-05-02T15:50:12+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/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.