If you want to limit yourself to ELF detection, you can read the ELF header of /proc/$PID/exe
yourself. It's quite trivial: if the 5th byte in the file is 1, it's a 32-bit binary. If it's 2, it's 64-bit. For added sanity checking:
- If the first 5 bytes are
0x7f, "ELF", 1
: it's a 32 bit ELF binary.
- If the first 5 bytes are
0x7f, "ELF", 2
: it's a 64 bit ELF binary.
- Otherwise: it's inconclusive.
You could also use objdump
, but that takes away your libmagic
dependency and replaces it with a libelf
one.
Another way: you can also parse the /proc/$PID/auxv
file. According to proc(5)
:
This contains the contents of the ELF interpreter information
passed to the process at exec time. The format is one
unsigned long ID plus one unsigned long value for each entry.
The last entry contains two zeros.
The meanings of the unsigned long
keys are in /usr/include/linux/auxvec.h
. You want AT_PLATFORM
, which is 0x00000f
. Don't quote me on that, but it appears the value should be interpreted as a char *
to get the string description of the platform.
You may find this StackOverflow question useful.
Yet another way: you can instruct the dynamic linker (man ld
) to dump information about the executable. It prints out to standard output the decoded AUXV structure. Warning: this is a hack, but it works.
LD_SHOW_AUXV=1 ldd /proc/$SOME_PID/exe | grep AT_PLATFORM | tail -1
This will show something like:
AT_PLATFORM: x86_64
I tried it on a 32-bit binary and got i686
instead.
How this works: LD_SHOW_AUXV=1
instructs the Dynamic Linker to dump the decoded AUXV structure before running the executable. Unless you really like to make your life interesting, you want to avoid actually running said executable. One way to load and dynamically link it without actually calling its main()
function is to run ldd(1)
on it. The downside: LD_SHOW_AUXV
is enabled by the shell, so you'll get dumps of the AUXV structures for: the subshell, ldd
, and your target binary. So we grep
for AT_PLATFORM, but only keep the last line.
Parsing auxv: if you parse the auxv
structure yourself (not relying on the dynamic loader), then there's a bit of a conundrum: the auxv
structure follows the rule of the process it describes, so sizeof(unsigned long)
will be 4 for 32-bit processes and 8 for 64-bit processes. We can make this work for us. In order for this to work on 32-bit systems, all key codes must be 0xffffffff
or less. On a 64-bit system, the most significant 32 bits will be zero. Intel machines are little endians, so these 32 bits follow the least significant ones in memory.
As such, all you need to do is:
1. Read 16 bytes from the `auxv` file.
2. Is this the end of the file?
3. Then it's a 64-bit process.
4. Done.
5. Is buf[4], buf[5], buf[6] or buf[7] non-zero?
6. Then it's a 32-bit process.
7. Done.
8. Go to 1.
Parsing the maps file: this was suggested by Gilles, but didn't quite work. Here's a modified version that does. It relies on reading the /proc/$PID/maps
file. If the file lists 64-bit addresses, the process is 64 bits. Otherwise, it's 32 bits. The problem lies in that the kernel will simplify the output by stripping leading zeroes from hex addresses in groups of 4, so the length hack can't quite work. awk
to the rescue:
if ! [ -e /proc/$pid/maps ]; then
echo "No such process"
else
case $(awk </proc/$pid/maps -- 'END { print substr($1, 0, 9); }') in
*-) echo "32 bit process";;
*[0-9A-Fa-f]) echo "64 bit process";;
*) echo "Insufficient permissions.";;
esac
fi
This works by checking the starting address of the last memory map of the process. They're listed like 12345678-deadbeef
. So, if the process is a 32-bit one, that address will be eight hex digits long, and the ninth will be a hyphen. If it's a 64-bit one, the highest address will be longer than that. The ninth character will be a hex digit.
Be aware: all but the first and last methods need Linux kernel 2.6.0 or newer, since the auxv
file wasn't there before.