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

14,474

Solution 1

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.

Solution 2

Look in /proc/$pid/maps. The address ranges are over 32-bit addresses (8 hexadecimal digits) or 64-bit addresses (16 hexadecimal digits). This works for any kind of executable, no matter what format. You can only get information about processes running as the same user (unless you're root).

if ! [ -e /proc/$pid/maps ]; then
  echo No such process
elif grep -q '^........[^-]' /proc/$pid/maps; then
  echo 64-bit
elif grep -q . /proc/$pid/maps; then
  echo 32-bit
else
  echo Insufficient permissions
fi

If you have no permission to access this file, then I think the only way is to try to analyze the executable. (While you can always read /proc/$pid/stat, none of the fields that are shown for processes running as different users reveal the process's bit size.) You can make good guess of the process's executable with ps -o comm=, and looking that up in the PATH — but beware that the process may have been launched with a different PATH, or may have rewritten its argv[0]. You can then analyze the executable — if you're willing to assume ELF, look at the 5th byte.

Share:
14,474

Related videos on Youtube

Flexo
Author by

Flexo

(Your about me is currently blank. )

Updated on September 18, 2022

Comments

  • Flexo
    Flexo almost 2 years

    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?

  • goldilocks
    goldilocks over 10 years
    Hmmm, I wonder if the ELF header is in /proc/[pid]/auxv: "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" (man proc).
  • Simon Gates
    Simon Gates over 10 years
    The header itself isn't. I just hded one and it lacked the magic number. There may be some relevant information there, but I think it'd be subject to more frequent changes than the ELF header itself. It was also introduced in 2.6.0, so it's not quite as ubiquitous as /proc/PID/exe. But it does have the architecture information. I'll update my answer.
  • Netch
    Netch over 10 years
    I've tested your recipe and it failed. OpenSuSE 12.2, x86-64, kernel 3.4.63-2.44-default, /bin/bash. The /proc/$pid/maps lines for the binary and the first heap are written in 32-bit style, but all others are in 64-bit style. Likely they are printed using "%08x", but anyway this recipe shall be adjusted.
  • Simon Gates
    Simon Gates over 10 years
    I'm getting a mixture of 8, 12 and 16-nybble values on all the boxes I tried it with. Without checking the source, my guess is the kernel adjusts the padding to the lowest multiple of 16-bits greater than the address range for each line printed, so you'd have to find the longest sequence of hex characters, then check.
  • Simon Gates
    Simon Gates over 10 years
    BUT, since the vsyscall map is always the highest, you could get away with just changing head to tail — which, sadly, won't work because proc doesn't implement seek(2), so it'll have to be something uglier, like awk /proc/self/maps -- 'END { print substr($1, 0, 9); }'
  • Flexo
    Flexo over 10 years
    auxv turned out to be trickier than I'd hoped - sizeof(unsigned long) is 8 on 64 bit or 4 on 32 bit which means that to correctly interpret it directly you have to know if the process is 64 bit or 32 bit to begin with!
  • Simon Gates
    Simon Gates over 10 years
    You're absolutely right. That's pretty annoying. Quick heuristic: if bytes 16x+y (4≤y≤7) are all zero in the file, you're looking at a 64-bit executable. This is a kludge: I'm assuming a little endian machine, and that all the auxv key codes fit a 32-bit unsigned long, so the most significant 32-bits on a 64-bit box would be zero.