Linux – Determine if a specific process is 32- or 64-Bit

64bitelflinuxproc

Given a 2.6.x or newer Linux kernel and existing userland that is capable of running both ELF32 and ELF64 binaries (i.e. well past How do I know that my CPU supports 64bit operating systems under Linux?) how can I determine if a given process (by PID) is running in 32- or 64-bit mode?

The naive solution would be to run:

file -L /proc/pid/exe | grep -o 'ELF ..-bit [LM]SB'

but is that information exposed directly in /proc without relying on libmagic?

Best Answer

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:

  1. If the first 5 bytes are 0x7f, "ELF", 1: it's a 32 bit ELF binary.
  2. If the first 5 bytes are 0x7f, "ELF", 2: it's a 64 bit ELF binary.
  3. 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.

Related Question