file 5.36 says it clearly
file 5.36 actually prints it clearly if the executable is PIE or not. For example, a PIE executable shows as:
main.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, not stripped
and a non-PIE one as:
main.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
The feature was introduced in 5.33 but it did just a simple
chmod +x check. Before that it just printed
shared object for PIE.
In 5.34, it was meant to start checking the more specialized
DF_1_PIE ELF metadata, but due to a bug in the implementation it actually broke things and showed GCC PIE executables as
I have interpreted
file source code, including the bug, and exactly which bytes of the ELF format it checks in excruciating detail at: https://stackoverflow.com/questions/34519521/why-does-gcc-create-a-shared-object-instead-of-an-executable-binary-according-to/55704865#55704865
A quick summary of file 5.36 behavior is:
Elf32_Ehdr.e_type == ET_EXEC
- else if
Elf32_Ehdr.e_type == ET_DYN
DT_FLAGS_1 dynamic section entry is present
DF_1_PIE is set in
- if file is executable by user, group or others
GDB run the executable twice and see ASLR
One very direct thing that you can do is to run the executable twice through GDB and see if the address changes across runs due to ASLR.
I have explained how to do that in detail at: https://stackoverflow.com/questions/2463150/what-is-the-fpie-option-for-position-independent-executables-in-gcc-and-ld/51308031#51308031
While this is not necessarily the most practical solution and not possible if you don't trust the executable, it is fun and it does the ultimate check that we really care about, which is if the Linux kernel / dynamic loader changes the executable location or not.